一、HBase定义

1.1 HBase定义

HBase 是一种分布式、可扩展、支持海量数据存储的 NoSQL 数据库非结构化数据存储的数据库,基于列的模式存储。利用Hadoop HDFS作为其文件存储系统,写入性能很强,读取性能较差。利用Hadoop MapReduce来处理HBase中的海量数据,利用Zookeeper作为协同服务。

关系型数据库存储数据是以表格的形式存储,非关系型数据库是以<k,v>进行存储。通过<k,v>进行存储,当在用key读取value的时候,效率更高,速度更快,使用起来更灵活。

划重点:
HBase是基于列存储的,主要用来存储非结构化和半结构化的松散数据,适合大数据的实时查询;
利用 HDFS作为其文件存储系统;
利用 MapReduce来处理 HBase中的海量数据;
利用 Zookeeper作为其分布式协同服务;

HDFS只支持追加写,不支持随机写,不太适合做大量的随机读应用

HBASE却特别适合随机的读写。

1.2 HBase数据模型

用户将数据行存储在带标签的表中,数据行具有可排序的键和任意数量的列,该表存储稀疏。

HBase关于数据的关键在于:稀疏、分布式、多维、排序的映射map。其中映射map指代非关系型数据库的key-value结构。

释义:
稀疏:对比关系型数据库,关系型数据库是一个表格,表格在底层存储的时候要求比较高,每一行每一列都需要预留对应的存储空间。当数据量比较大、列比较多时,存在大量的空值,使用关系型数据库就会造成存储空间的浪费。
使用非关系型数据库就会节省对应的空间。如果某一行某一列为空,可以不存数据,在空间的使用上就会比较节省。
分布式:海量数据保存在多台机器上。
持久化:将内存中的对象存储在数据库中,或者存储在磁盘文件中。
排序:无序的数据当想要查找时,需要遍历整张表。排序的数据可以按照特定算法进行读取。如二分法查找。
映射:由行键、列键、时间戳作为key,映射中的每个值都是一个未解释(经过序列化,无法用UTF-8编码)的字节数组。

1.2.1 基础模型




hbase 存储文档 hbase存储数据_Powered by 金山文档


  • Name Space

命名空间,类似于关系型数据库的 DatabBase 概念,每个命名空间下有多个表。HBase 有两个自带的命名空间,分别是 hbase 和 default,hbase 中存放的是 HBase 内置的表, default 表是用户默认使用的命名空间。

  • Table

类似于关系型数据库的表概念。不同的是,HBase 定义表时只需要声明列族即可,不需要声明具体的列。这意味着,往 HBase 写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase 能够轻松应对字段变更的场景。

  • Row

HBase 表中的每行数据都由一个 RowKey 和多个 Column(列)组成,数据是按照 RowKey 的字典顺序存储的,并且查询数据时只能根据 RowKey 进行检索,所以 RowKey 的设计十分重要。

HBase框架不能写SQL,只能根据RowKey去读取数据。

  • Column

HBase 中的每个列都由 Column Family(列族)和 Column Qualifier(列限定符)进行限定,例如 info:name,info:age。建表时,只需指明列族,而列限定符无需预先定义 。

  • Time Stamp

用于标识数据的不同版本(version),每条数据写入时,如果不指定时间戳,系统会 自动为其加上该字段,其值为写入 HBase 的时间。

  • Cell

由{rowkey, column Family:column Qualifier, time Stamp} 唯一确定的单元。cell 中的数据是没有类型的,全部是字节码形式存贮。

1.2.2 模型结构拆解

HBase数十亿行、数百万列的数据需要进行拆分,才能够进行相应的存储。在对其进行拆分的时候,首先按照行,横向进行切分,切分完的结构就是Region(区域)。Region在拆分完之后,会有对应RowKey的范围,每个Region的RowKey范围不一样,相互之间不交叉。

Region用于实现分布式结构。单独的一张表只能存在一个地方,拆分成Region之后可以放到不同的节点上去。

竖向进行切分,切分出来的单位就是store。竖向的切分以列族为单位纵向切分。纵向切分为store用于底层存储到不同的文件夹中,便于文件存储。

1.2.3 物理存储结构


hbase 存储文档 hbase存储数据_hbase_02


先切分Region(为了划分不同的节点),再切分store(为了拆分文件夹)。一个文件底层是以KV进行编写。key的信息包括行号RowKey、列(列族Colume Family、列名/列限定符)、时间戳、type。

HBase是以HDFS作为存储基础的数据库,而HDFS上的数据是无法进行修改的,只能删除、重写或追加写。那么HBase如何在不能修改数据的基础上实现改数据的功能呢?于是有了时间戳。读取数据的时候有两个时间版本,新的版本会覆盖旧的版本,就会被认为数据已经修改。

1.3 HBase 架构


hbase 存储文档 hbase存储数据_Powered by 金山文档_03


  • Master

主要进程,Master 是所有 Region Server 的管理者,其实现类为 HMaster(服务器上有个HMaster进程)。

功能:负责通过ZK监控RegionServer进程状态,同时是所有元数据变化的接口。内部启动监控执行region的故障转移和拆分的线程。

  • RegionServer

主要进程,具体实现类为HRegionServer,部署在datanode上。

功能:主要负责数据cell的处理。同时在执行区域的拆分和合并的时候由RegionServer来实际执行。

  • Zookeeper

HBase 通过 Zookeeper 来做 Master 的高可用、RegionServer 的监控、元数据的入口以及集群配置的维护等工作。

  • HDFS

HDFS 为 HBase 提供最终的底层数据存储服务,同时为 HBase 提供高可用的支持。

  • StoreFile/HFile

一个表对应一个文件夹,在一张表中,会切分成多个Region(可以在定义表的时候进行切分),再根据列族进行切分成一个个的store。


hbase 存储文档 hbase存储数据_大数据_04


二、HBase基本Shell操作

-- 启动关闭相关
-- 1.启动hbase
bin/start-hbase.sh
-- 2.查看启动情况,
-- 单机版只会有 HMaster 进程
jps
-- 3.关闭hbase
bin/stop-hbase.sh

基础命令:

-- 基础命令
-- 1.进入 HBase 客户端命令行
bin/hbase shell
-- 2.查看帮助命令
help
-- 3.查看当前数据库中有哪些表
list

表空间namespace相关操作:

-- 表空间
-- 1.创建namespace
create_namespace 'nametest'  
-- 2.删除namespace
drop_namespace 'nametest'  
-- 3.查看namespace
describe_namespace 'nametest'  
-- 4.列出所有namespace
list_namespace  
-- 5.在namespace下创建表
create 'nametest:testtable', 'fm1'  
-- 6.查看namespace下的表
list_namespace_tables 'nametest'

表table相关操作:

增删改查

创建表,表中有两个列族 baseinfo, schoolinfo
create 'bigdata:student','baseinfo','schoolinfo'

查看指定表全名空间中的表
list_namespace_tables 'bigdata'

查看表描述
desc/describe 'bigdata:student'

禁用/启用
disable 'bigdata:student'
enable 'bigdata:student'

查看是否启用/禁用
is_disabled 'bigdata:student'
true                                   
is_enabled 'bigdata:student'
false 

删除表  注意,首先要将删除的表设置为禁用状态才可以删除,否则会报错
drop 'bigdata:student'

新增列族
alter 'bigdata:student','teacherinfo'

删除列族
alter 'bigdata:student',{NAME=>'teacherinfo',METHOD=>'delete'}


删除指定列族下的指定列
delete 'bigdata:student','rowkey3','baseinfo:age'
删除指定行
deleteall 'bigdata:student','rowkey3'

增加数据
put 'bigdata:student','rowkey1','baseinfo:name','tom'
put 'bigdata:student','rowkey1','baseinfo:birthday','1999-01-01'
put 'bigdata:student','rowkey1','baseinfo:age','24'
put 'bigdata:student','rowkey1','schoolinfo:name','bdqn'
put 'bigdata:student','rowkey1','schoolinfo:address','jsxueyuan'

put 'bigdata:student','rowkey2','baseinfo:name','jerry'
put 'bigdata:student','rowkey2','baseinfo:birthday','2003-07-02'
put 'bigdata:student','rowkey2','baseinfo:age','20'
put 'bigdata:student','rowkey2','schoolinfo:name','njzb'
put 'bigdata:student','rowkey2','schoolinfo:address','wending'


put 'bigdata:student','rowkey3','baseinfo:name','mands'
put 'bigdata:student','rowkey3','baseinfo:birthday','2022-05-21'
put 'bigdata:student','rowkey3','baseinfo:age','1'
put 'bigdata:student','rowkey3','schoolinfo:name','yey'
put 'bigdata:student','rowkey3','schoolinfo:address','bj'


put 'bigdata:student','rowkey4','baseinfo:name','roboot'
put 'bigdata:student','rowkey4','baseinfo:birthday','2018-12-31'
put 'bigdata:student','rowkey4','baseinfo:age','5'
put 'bigdata:student','rowkey4','schoolinfo:name','xiaoxue'
put 'bigdata:student','rowkey4','schoolinfo:address','shanghai'

版本号设置/查看历史版本号

查看指定列中不同版本的数据
get 'bigdata:student','rowkey2',{COLUMN=>'baseinfo:name','VERSIONS'=>3}

更改列族存储版本的限制
alter 'bigdata:student',{NAME=>'baseinfo',VERSIONS=>3}

条件查询

根据条件查询
get 'bigdata:student','rowkey1'
get 'bigdata:student','rowkey2','baseinfo'
get 'bigdata:student','rowkey2','schoolinfo'
get 'bigdata:student','rowkey2','baseinfo:name'
get 'bigdata:student','rowkey3',{COLUMN=>'baseinfo:name'}

全表扫描

全表扫描
scan 'bigdata:student'
全表扫描指定列族
scan 'bigdata:student', COLUMN=>'baseinfo'
全表扫描指定列族指定列
scan 'bigdata:student', COLUMN=>'baseinfo:birthday'

扫描指定起始行至结束行(不包含)
scan 'bigdata:student',{COLUMNS=>'baseinfo:name', STARTROW=>'rowkey1', STOPROW=>'rowkey4'}

scan 'bigdata:student',{COLUMNS=>'baseinfo:name', STARTROW=>'rowkey1', STOPROW=>'rowkey4', LIMIT=>3}
scan 'bigdata:student',{COLUMNS=>'baseinfo:name', STARTROW=>'rowkey1', STOPROW=>'rowkey4', LIMIT=>3,VERSIONS=>3}

值包含nt
scan 'bigdata:student',FILTER=>"ValueFilter(=,'substring:bdqn')"

值=24
scan 'bigdata:student',FILTER=>"ValueFilter(=,'binary:24')"

列以birth开头的
scan 'bigdata:student',FILTER=>"ColumnPrefixFilter('birth')"

以birth开头,且 值中包含2022
scan 'bigdata:student', FILTER=>"ColumnPrefixFilter('birth') AND ValueFilter(=,'substring:2022')"

以birth开头,且 值中包含2022 或者 1999

三、HBase API

3.1 环境准备

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>2.3.5</version>
</dependency>
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-server</artifactId>
    <version>2.3.5</version>
</dependency>

3.2 创建连接

根据官方 API 介绍,HBase 的客户端连接由 ConnectionFactory 类来创建,用户使用完成之后需要手动关闭连接。同时连接是一个重量级的,推荐一个进程使用一个连接,对 HBase的命令通过连接中的两个属性 Admin 和 Table

3.2.1 单线程创建连接

// 1. 创建配置对象
    Configuration conf = new Configuration();

// 2. 添加配置参数
    conf.set("hbase.zookeeper.quorum","hadoop102,hadoop103,hadoop104");

// 3. 创建 hbase 的连接
// 默认使用 同步连接
   Connection connection = ConnectionFactory.createConnection(conf);
// 可以使用异步连接(不推荐使用)
// 主要影响后续的 DML 操作
    CompletableFuture<AsyncConnection> asyncConnection = ConnectionFactory.createAsyncConnection(conf);

// 4. 使用连接
    System.out.println(connection);

// 5. 关闭连接
    connection.close();

3.2.2 多线程创建连接

// 声明一个静态属性
public static Connection connection = null;
static{
   // 1. 创建连接
   // 默认使用 同步连接
    try {
        // 使用读取本地文件的形式添加参数
        connection = ConnectionFactory.createConnection();
    } catch (IOException e){
        e.printStackTrace();
    }
}

public static void closeConnection() throws IOException {
    // 判断连接是否为null
    if (connection != null){
     connection.close();
    }
}

在 resources 文件夹中创建配置文件 hbase-site.xml,添加以下内容:

<configuration> 
  <property>
     <name>hbase.zookeeper.quorum</name>
     <value>hadoop102,hadoop103,hadoop104</value>
  </property>
</configuration>

3.3 DDL

创建HBaseDDL类,添加静态方法即可作为工具类。

public class HBaseDDL {

    // 添加静态属性 connection 指向单例连接
    public static Connection connection = HBaseConnection.connection;

}

3.3.1 创建命名空间

// 创建命名空间
public static void createNamespace(String namespace) throws IOException{
    // 1. 获取admin
    Admin admin = connection.getAdmin();

    // 2. 调用方法创建命名空间
    // 代码相对shell更加底层,所以shell能够实现的功能,代码一定能够实现
    // 所以需要填写完整的命名空间描述

    // 2.1 创建命名空间描述建造者
    NamespaceDescription.Builder builder = NamespaceDescription.create(namespace);

    // 2.2 给命名空间添加需求
    builder.addConfiguration("user","xsqone");

    // 2.3 使用builder构造出对应 的添加完参数的对象,完成创建
    // 创建命名空间出现的问题都属于方法本身的问题,不应该抛出
    try {
        admin.createNamespace(builder.build());
    } catch (IOException e) {
        System.out.println("命令空间已经存在");
        e.printStackTrace();
    }

    // 3. 关闭admin
    admin.close();
}

3.3.2 判断表格是否存在

/**
 * 创建命名空间
 * @param namespace 命名空间名称
*/
public static boolean isTableExists(String namespace,String tableName) throws IOException {
    // 1. 获取 admin
    Admin admin = connection.getAdmin();

    // 2. 使用方法判断表格是否存在
    boolean b = false;
    try {
        b = admin.tableExists(TableName.valueOf(namespace, tableName));
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 3. 关闭 admin
    admin.close();

    // 3. 返回结果
    return b;
}

3.3.3 创建表

/**
 * 创建表格
 * @param namespace 命名空间名称
 * @param tableName 表格名称
 * @param columnFamilies 列族名称 可以有多个
*/
public static void createTable(String namespace, String tableName, String... columnFamilies){

    // 判断是否有至少一个列族
    if (columnFamilies.length == 0 ){
        System.out.println("创建表格至少有一个列族");
        return;
    }

    // 1. 获取admin
    Admin admin = connection.getAdmin();

    // 2. 调用方法创建表格
    // 2.1 创建表格描述的建造者
   TableDescriptorBuilder tableDescriptorBuilder =  
        TableDescriptorBuilder.newBuilder(TableName.valueOf(namespace, tableName));

    // 2.2 添加参数
    for (String columnFamily : columnFamilies) {
        // 2.3 创建列族描述的建造者
        ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = 
            ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily));

        // 2.4 对应当前的列族添加参数
        // 添加版本参数
        columnFamilyDescriptorBuilder.setMaxVersions(3);

        // 2.5 创建添加完参数的列族描述
        tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptorBuilder.build());
    }

    // 2.3 创建对应的表格描述
    try {
        admin.createTable(tableDescriptorBuilder.build());
    } catch (IOException e) {
        System.out.println("表格已经存在");
        e.printStackTrace();
    }

    // 3. 关闭admin
    admin.close();

}

3.3.4 修改表

/**
 * 修改表格中一个列族的版本
 * @param namespace 命名空间名称
 * @param tableName 表格名称
 * @param columnFamily 列族名称
 * @param version 版本
 */
public static void modifyTable(String namespace ,String tableName,String columnFamily,int version) throws IOException {

    // 判断表格是否存在
    if (!isTableExists(namespace,tableName)){
        System.out.println("表格不存在无法修改");
        return;
    }

    // 1. 获取admin
    Admin admin = connection.getAdmin();

    // 2. 调用方法修改表格
    // 2.0 获取之前的表格描述
    TableDescriptor descriptor = admin.getDescriptor(TableName.valueOf(namespace, tableName));

    // 2.1 创建一个表格描述建造者
    // 如果使用填写 tableName 的方法 相当于创建了一个新的表格描述建造者 没有之前的信息
    // 如果想要修改之前的信息 必须调用方法填写一个旧的表格描述
    TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(descriptor);

    // 2.2 对应建造者进行表格数据的修改
    // 创建列族描述建造者
    ColumnFamilyDescriptor columnFamily1 = descriptor.getColumnFamily(Bytes.toBytes(columnFamily));

    // 创建列族描述建造者
    // 需要填写旧的列族描述
    ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(columnFamily1);

    // 修改对应的版本    
    columnFamilyDescriptorBuilder.setMaxVersions(version);

    // 此处修改的时候 如果填写的新创建 那么别的参数会初始化
    tableDescriptorBuilder.modifyColumnFamily(columnFamilyDescriptorBuilder.build());

    try {
        admin.modifyTable(tableDescriptorBuilder.build());
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 3. 关闭 admin
    admin.close();
}

3.3.5 删除表

/**
 * 删除表格
 * @param namespace 命名空间名称
 * @param tableName 表格名称
 * @return true 表示删除成功
 */

public static boolean deleteTable(String namespace ,String tableName) throws IOException {
    // 1. 判断表格是否存在
    if (!isTableExists(namespace,tableName)){
        System.out.println("表格不存在,无法删除");
        return false;
    }

    // 2. 获取 admin
    Admin admin = connection.getAdmin();

    // 3. 调用相关的方法删除表格
    try {
        // HBase 删除表格之前 一定要先标记表格为不可以
        TableName tableName1 = TableName.valueOf(namespace, tableName);
        admin.disableTable(tableName1);
        admin.deleteTable(tableName1);
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 4. 关闭 admin
    admin.close();
    return true;
}

3.4 DML

public class HBaseMDL {
    // 静态属性
    public static Connection connection = HBaseConnection.connection;
}

3.4.1 插入数据

/**
 * 插入数据
 * @param namespace 命名空间名称
 * @param tableName 表格名称
 * @param rowKey 主键
 * @param columnFamily 列族名称
 * @param columnName 列名
 * @param value 值
 */
public static void putCell(String namespace,String tableName,String rowKey, String columnFamily,String columnName,String value) throws IOException {
    // 1. 获取 table
    Table table = connection.getTable(TableName.valueOf(namespace, tableName));

    // 2. 调用相关方法插入数据
    // 2.1 创建 put 对象
    Put put = new Put(Bytes.toBytes(rowKey));
    // 2.2. 给 put 对象添加数据
    put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnName),Bytes.toBytes(value));

    // 2.3 将对象写入对应的方法
    try {
        table.put(put);
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 3. 关闭 table
    table.close();
 }

3.4.2 读取数据

/**
 * 读取数据 读取对应的一行中的某一列
 *
 * @param namespace 命名空间名称
 * @param tableName 表格名称
 * @param rowKey 主键
 * @param columnFamily 列族名称
 * @param columnName 列名
 */
public static void getCells(String namespace, String tableName, String rowKey, String columnFamily, String columnName) throws IOException {

    // 1. 获取 table
    Table table = connection.getTable(TableName.valueOf(namespace, tableName));

    // 2. 创建 get 对象
    Get get = new Get(Bytes.toBytes(rowKey));

    // 如果直接调用 get 方法读取数据 此时读一整行数据
    // 如果想读取某一列的数据 需要添加对应的参数
    get.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(columnName));
    // 设置读取数据的版本
    get.readAllVersions();

    try {
    // 读取数据,得到 result 对象
        Result result = table.get(get);
    // 处理数据
        Cell[] cells = result.rawCells();
    // 测试方法: 直接把读取的数据打印到控制台
    // 如果是实际开发 需要再额外写方法 对应处理数据
        for (Cell cell : cells) {
            // cell 存储数据比较底层
            String value = new String(CellUtil.cloneValue(cell));
            System.out.println(value);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 关闭 table
    table.close();
}

3.4.3 扫描数据

/**
 * 扫描数据
 *
 * @param namespace 命名空间
 * @param tableName 表格名称
 * @param startRow 开始的 row 包含的
 * @param stopRow 结束的 row 不包含
 */
public static void scanRows(String namespace, String tableName, String startRow, String stopRow) throws IOException {
    // 1. 获取 table
    Table table = connection.getTable(TableName.valueOf(namespace, tableName));

    // 2. 创建 scan 对象
    Scan scan = new Scan();
    // 如果此时直接调用 会直接扫描整张表

    // 添加参数 来控制扫描的数据
    // 默认包含
    scan.withStartRow(Bytes.toBytes(startRow));
    // 默认不包含
    scan.withStopRow(Bytes.toBytes(stopRow));

    try {
        // 读取多行数据 获得 scanner
        ResultScanner scanner = table.getScanner(scan);
        // result 来记录一行数据 cell 数组
        // ResultScanner 来记录多行数据 result 的数组
        for (Result result : scanner) {
            Cell[] cells = result.rawCells();
            for (Cell cell : cells) {
                System.out.print (new 
                    String(CellUtil.cloneRow(cell)) + "-" + new 
                    String(CellUtil.cloneFamily(cell)) + "-" + new 
                    String(CellUtil.cloneQualifier(cell)) + "-" + new 
                    String(CellUtil.cloneValue(cell)) + "\t");
            }
        System.out.println();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 3. 关闭 table
    table.close();
}

3.4.4 带过滤扫描

/**
 * 带过滤的扫描
 *
 * @param namespace 命名空间
 * @param tableName 表格名称
 * @param startRow 开始 row
 * @param stopRow 结束 row
 * @param columnFamily 列族名称
 * @param columnName 列名
 * @param value value 值
* @throws IOException
 */
public static void filterScan(String namespace, String tableName, String startRow, String stopRow, String columnFamily, String columnName, String value) throws IOException {
    // 1. 获取 table
    Table table = connection.getTable(TableName.valueOf(namespace, tableName));

    // 2. 创建 scan 对象
    Scan scan = new Scan();
    // 如果此时直接调用 会直接扫描整张表

    // 添加参数 来控制扫描的数据
    // 默认包含
    scan.withStartRow(Bytes.toBytes(startRow));
    // 默认不包含
    scan.withStopRow(Bytes.toBytes(stopRow));

    // 可以添加多个过滤
    FilterList filterList = new FilterList();
    // 创建过滤器
    // (1) 结果只保留当前列的数据
    ColumnValueFilter columnValueFilter = new ColumnValueFilter(
        // 列族名称
        Bytes.toBytes(columnFamily),
        // 列名
        Bytes.toBytes(columnName),
        // 比较关系
        CompareOperator.EQUAL,
        // 值
        Bytes.toBytes(value)
    );

    // (2) 结果保留整行数据
    // 结果同时会保留没有当前列的数据
    SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(
        // 列族名称
        Bytes.toBytes(columnFamily),
        // 列名
        Bytes.toBytes(columnName),
        // 比较关系
        CompareOperator.EQUAL,
        // 值
        Bytes.toBytes(value)
    );
    // 本身可以添加多个过滤器
    filterList.addFilter(singleColumnValueFilter);

    // 添加过滤
    scan.setFilter(filterList);

    try {
        // 读取多行数据 获得 scanner
        ResultScanner scanner = table.getScanner(scan);
        // result 来记录一行数据 cell 数组
        // ResultScanner 来记录多行数据 result 的数组
        for (Result result : scanner) {
            Cell[] cells = result.rawCells();
            for (Cell cell : cells) {
                System.out.print(new 
                    String(CellUtil.cloneRow(cell)) + "-" + new 
                    String(CellUtil.cloneFamily(cell)) + "-" + new 
                    String(CellUtil.cloneQualifier(cell)) + "-" + new 
                    String(CellUtil.cloneValue(cell)) + "\t");
            }
         System.out.println();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 3. 关闭 table
    table.close();
}

3.4.5 删除数据

/**
* 删除 column 数据
*
* @param nameSpace
* @param tableName
* @param rowKey
* @param family
* @param column
* @throws IOException
*/
public static void deleteColumn(String nameSpace, String tableName, String rowKey, String family, String column) throws IOException {

    // 1.获取 table
    Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));

    // 2.创建 Delete 对象
    Delete delete = new Delete(Bytes.toBytes(rowKey));

    // 3.添加删除信息
    // 3.1 addColumn 删除单个版本
    delete.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(columnName));
    // 3.2 addColumns 删除所有版本
    delete.addColumns(Bytes.toBytes(columnFamily), Bytes.toBytes(columnName));
    // 3.3 删除列族
    delete.addFamily(Bytes.toBytes(family));

    // 3.删除数据
    try {
    table.delete(delete);
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 5.关闭资源
    table.close();
}