1.1. 什么是HBASE
1.1.1. 概念特性
HBASE是一个数据库----可以提供数据的实时随机读写。HBASE与mysql、oralce、db2、sqlserver等关系型数据库不同,它是一个NoSQL数据库(非关系型数据库)
- Hbase的表模型与关系型数据库的表模型不同:
- Hbase的表没有固定的字段定义;
- Hbase的表中每行存储的都是一些key-value对
- Hbase的表中有列族的划分,用户可以指定将哪些kv插入哪个列族
- Hbase的表在物理存储上,是按照列族来分割的,不同列族的数据一定存储在不同的文件中
- Hbase的表中的每一行都固定有一个行键,而且每一行的行键在表中不能重复
- Hbase中的数据,包含行键,包含key,包含value,都是byte[ ]类型,hbase不负责为用户维护数据类型
- HBASE对事务的支持很差
HBASE相比于其他nosql数据库(mongodb、redis、cassendra、hazelcast)的特点:
Hbase的表数据存储在HDFS文件系统中
从而,hbase具备如下特性:存储容量可以线性扩展; 数据存储的安全性可靠性极高!
1.2 安装HBASE
HBASE是一个分布式系统。其中有一个管理角色:HMaster(一般2台,一台active,一台backup);其他的数据节点角色: HRegionServer(很多台,看数据容量)
1.2.1. 安装准备:
首先,要有一个HDFS集群,并正常运行; regionserver应该跟hdfs中的datanode在一起
其次,还需要一个zookeeper集群,并正常运行
然后,安装HBASE
角色分配如下:
Hdp01: namenode datanode regionserver hmaster zookeeper
Hdp02: datanode regionserver zookeeper
Hdp03: datanode regionserver zookeeper
1.2.2. 安装步骤:
1.安装zookeeper
2.安装hbase
解压hbase安装包
修改hbase-env.sh
export JAVA_HOME=/root/apps/jdk1.7.0_67 export HBASE_MANAGES_ZK=false
修改hbase-site.xml
<configuration> <!-- 指定hbase在HDFS上存储的路径 --> <property> <name>hbase.rootdir</name> <value>hdfs://hdp01:9000/hbase</value> </property> <!-- 指定hbase是分布式的 --> <property> <name>hbase.cluster.distributed</name> <value>true</value> </property> <!-- 指定zk的地址,多个用“,”分割 --> <property> <name>hbase.zookeeper.quorum</name> <value>hdp01:2181,hdp02:2181,hdp03:2181</value> </property> </configuration>
修改 regionservers
hdp01
hdp02
hdp03
3. 启动hbase集群:
bin/start-hbase.sh 启动完后,还可以在集群中找任意一台机器启动一个备用的master
bin/hbase-daemon.sh start master 新启的这个master会处于backup状态
4. 启动hbase的命令行客户端
bin/hbase shell
Hbase> list // 查看表
Hbase> status // 查看集群状态
Hbase> version // 查看集群版本
1.3. HBASE表模型
hbase的表模型跟mysql之类的关系型数据库的表模型差别巨大
hbase的表模型中有:行的概念;但没有字段的概念
行中存的都是key-value对,每行中的key-value对中的key可以是各种各样,每行中的key-value对的数量也可以是各种各样
1.3.1. hbase表模型的要点:
1、一个表,有表名
2、一个表可以分为多个列族(不同列族的数据会存储在不同文件中)
3、表中的每一行有一个“行键rowkey”,而且行键在表中不能重复
4、表中的每一对kv数据称作一个cell
5、hbase可以对数据存储多个历史版本(历史版本数量可配置)
6、整张表由于数据量过大,会被横向切分成若干个region(用rowkey范围标识),不同region的数据也存储在不同文件中
7、hbase会对插入的数据按顺序存储:
要点一:首先会按行键排序
要点二:同一行里面的kv会按列族排序,再按k排序
1.3.2. hbase的表中能存储什么数据类型
hbase中只支持byte[]。此处的byte[] 包括了: rowkey,key,value,列族名,表名
1.3.3. HBASE表的物理存储结构
1.4. 3/ hbase命令行客户端操作
1.4.0.1. 建表:
create 't_user_info','base_info','extra_info'
表名 列族名 列族名
1.4.0.2. 插入数据:
hbase(main):011:0> put 't_user_info','001','base_info:username','zhangsan' 0 row(s) in 0.2420 seconds hbase(main):012:0> put 't_user_info','001','base_info:age','18' 0 row(s) in 0.0140 seconds hbase(main):013:0> put 't_user_info','001','base_info:sex','female' 0 row(s) in 0.0070 seconds hbase(main):014:0> put 't_user_info','001','extra_info:career','it' 0 row(s) in 0.0090 seconds hbase(main):015:0> put 't_user_info','002','extra_info:career','actoress' 0 row(s) in 0.0090 seconds hbase(main):016:0> put 't_user_info','002','base_info:username','liuyifei' 0 row(s) in 0.0060 seconds
1.4.0.3. 查询数据方式一:scan 扫描
hbase(main):017:0> scan 't_user_info' ROW COLUMN+CELL 001 column=base_info:age, timestamp=1496567924507, value=18 001 column=base_info:sex, timestamp=1496567934669, value=female 001 column=base_info:username, timestamp=1496567889554, value=zhangsan 001 column=extra_info:career, timestamp=1496567963992, value=it 002 column=base_info:username, timestamp=1496568034187, value=liuyifei 002 column=extra_info:career, timestamp=1496568008631, value=actoress 2 row(s) in 0.0420 seconds
1.4.0.4. 查询数据方式二:get 单行数据
hbase(main):020:0> get 't_user_info','001' COLUMN CELL base_info:age timestamp=1496568160192, value=19 base_info:sex timestamp=1496567934669, value=female base_info:username timestamp=1496567889554, value=zhangsan extra_info:career timestamp=1496567963992, value=it 4 row(s) in 0.0770 seconds
1.4.0.5. 删除一个kv数据
hbase(main):021:0> delete 't_user_info','001','base_info:sex' 0 row(s) in 0.0390 seconds
删除整行数据:
hbase(main):024:0> deleteall 't_user_info','001' 0 row(s) in 0.0090 seconds hbase(main):025:0> get 't_user_info','001' COLUMN CELL 0 row(s) in 0.0110 seconds
1.4.0.6. 删除整个表:
hbase(main):028:0> disable 't_user_info' 0 row(s) in 2.3640 seconds hbase(main):029:0> drop 't_user_info' 0 row(s) in 1.2950 seconds hbase(main):030:0> list TABLE 0 row(s) in 0.0130 seconds => []
1.5. 4/ Hbase重要特性--排序特性(行键)
插入到hbase中去的数据,hbase会自动排序存储:
排序规则: 首先看行键,然后看列族名,然后看列(key)名; 按字典顺序
Hbase的这个特性跟查询效率有极大的关系
比如:一张用来存储用户信息的表,有名字,户籍,年龄,职业....等信息
然后,在业务系统中经常需要:
查询某个省的所有用户
经常需要查询某个省的指定姓的所有用户
思路:如果能将相同省的用户在hbase的存储文件中连续存储,并且能将相同省中相同姓的用户连续存储,那么,上述两个查询需求的效率就会提高!!!
做法:将查询条件拼到rowkey内
1.6. 5/ HBASE客户端API操作
1.6.1. DDL操作
1、创建一个连接
Connection conn = ConnectionFactory.createConnection(conf);
2、拿到一个DDL操作器:表管理器admin
Admin admin = conn.getAdmin();
3、用表管理器的api去建表、删表、修改表定义
admin.createTable(HTableDescriptor descriptor);
1.7. 6 HBASE运行原理
1.7.1. 组件结构图
1.7.2. MASTER职责
- 管理HRegionServer,实现其负载均衡。
- 管理和分配HRegion,比如在HRegion split时分配新的HRegion;在HRegionServer退出时迁移其负责的HRegion到其他HRegionServer上。
- Admin职能 创建、删除、修改Table的定义。实现DDL操作(namespace和table的增删改,column familiy的增删改等)。
- 管理namespace和table的元数据(实际存储在HDFS上)。
- 权限控制(ACL)。
- 监控集群中所有HRegionServer的状态(通过Heartbeat和监听ZooKeeper中的状态)。
1.7.3. REGION SERVER职责
- 管理自己所负责的region数据的读写。
- 读写HDFS,管理Table中的数据。
- Client直接通过HRegionServer读写数据(从HMaster中获取元数据,找到RowKey所在的HRegion/HRegionServer后)。
1.7.4. Zookeeper集群所起作用
- 存放整个HBase集群的元数据以及集群的状态信息。
- 实现HMaster主从节点的failover。
注: HMaster通过监听ZooKeeper中的Ephemeral节点(默认:/hbase/rs/*)来监控HRegionServer的加入和宕机。
在第一个HMaster连接到ZooKeeper时会创建Ephemeral节点(默认:/hbasae/master)来表示Active的HMaster,其后加进来的HMaster则监听该Ephemeral节点
如果当前Active的HMaster宕机,则该节点消失,因而其他HMaster得到通知,而将自身转换成Active的HMaster,在变为Active的HMaster之前,它会在/hbase/masters/下创建自己的Ephemeral节点。
1.7.5. HBASE读写数据流程
1、在HBase 0.96以前,HBase有两个特殊的Table:-ROOT-和.META. 用来记录用户表的rowkey范围所在的的regionserver服务器;
因而客户端读写数据时需要通过3次寻址请求来对数据所在的regionserver进行定位,效率低下;
2、而在HBase 0.96以后去掉了-ROOT- Table,只剩下这个特殊的目录表叫做Meta Table(hbase:meta),它存储了集群中所有用户HRegion的位置信息,而ZooKeeper的节点中(/hbase/meta-region-server)存储的则直接是这个Meta Table的位置,并且这个Meta Table如以前的-ROOT- Table一样是不可split的。这样,客户端在第一次访问用户Table的流程就变成了:
① 从ZooKeeper(/hbase/meta-region-server)中获取hbase:meta的位置(HRegionServer的位置),缓存该位置信息。
② 从HRegionServer中查询用户Table对应请求的RowKey所在的HRegionServer,缓存该位置信息。
③ 从查询到HRegionServer中读取Row。
注:客户会缓存这些位置信息,然而第二步它只是缓存当前RowKey对应的HRegion的位置,因而如果下一个要查的RowKey不在同一个HRegion中,则需要继续查询hbase:meta所在的HRegion,然而随着时间的推移,客户端缓存的位置信息越来越多,以至于不需要再次查找hbase:meta Table的信息,除非某个HRegion因为宕机或Split被移动,此时需要重新查询并且更新缓存。
1.7.6. hbase:meta表
hbase:meta表存储了所有用户HRegion的位置信息:
Rowkey:tableName,regionStartKey,regionId,replicaId等;
info列族:这个列族包含三个列,他们分别是:
info:regioninfo列:regionId,tableName,startKey,endKey,offline,split,replicaId;
info:server列:HRegionServer对应的server:port;
info:serverstartcode列:HRegionServer的启动时间戳。
1.7.7. REGION SERVER内部机制
- WAL即Write Ahead Log,在早期版本中称为HLog,它是HDFS上的一个文件,如其名字所表示的,所有写操作都会先保证将数据写入这个Log文件后,才会真正更新MemStore,最后写入HFile中。WAL文件存储在/hbase/WALs/${HRegionServer_Name}的目录中
- BlockCache是一个读缓存,即“引用局部性”原理(也应用于CPU,分空间局部性和时间局部性,空间局部性是指CPU在某一时刻需要某个数据,那么有很大的概率在一下时刻它需要的数据在其附近;时间局部性是指某个数据在被访问过一次后,它有很大的概率在不久的将来会被再次的访问),将数据预读取到内存中,以提升读的性能。
- HRegion是一个Table中的一个Region在一个HRegionServer中的表达。一个Table可以有一个或多个Region,他们可以在一个相同的HRegionServer上,也可以分布在不同的HRegionServer上,一个HRegionServer可以有多个HRegion,他们分别属于不同的Table。HRegion由多个Store(HStore)构成,每个HStore对应了一个Table在这个HRegion中的一个Column Family,即每个Column Family就是一个集中的存储单元,因而最好将具有相近IO特性的Column存储在一个Column Family,以实现高效读取(数据局部性原理,可以提高缓存的命中率)。HStore是HBase中存储的核心,它实现了读写HDFS功能,一个HStore由一个MemStore 和0个或多个StoreFile组成。
- MemStore是一个写缓存(In Memory Sorted Buffer),所有数据的写在完成WAL日志写后,会 写入MemStore中,由MemStore根据一定的算法将数据Flush到地层HDFS文件中(HFile),通常每个HRegion中的每个 Column Family有一个自己的MemStore。
- HFile(StoreFile) 用于存储HBase的数据(Cell/KeyValue)。在HFile中的数据是按RowKey、Column Family、Column排序,对相同的Cell(即这三个值都一样),则按timestamp倒序排列。
- FLUSH详述
① 每一次Put/Delete请求都是先写入到MemStore中,当MemStore满后会Flush成一个新的StoreFile(底层实现是HFile),即一个HStore(Column Family)可以有0个或多个StoreFile(HFile)。
② 当一个HRegion中的所有MemStore的大小总和超过了hbase.hregion.memstore.flush.size的大小,默认128MB。此时当前的HRegion中所有的MemStore会Flush到HDFS中。
③ 当全局MemStore的大小超过了hbase.regionserver.global.memstore.upperLimit的大小,默认40%的内存使用量。此时当前HRegionServer中所有HRegion中的MemStore都会Flush到HDFS中,Flush顺序是MemStore大小的倒序(一个HRegion中所有MemStore总和作为该HRegion的MemStore的大小还是选取最大的MemStore作为参考?有待考证),直到总体的MemStore使用量低于hbase.regionserver.global.memstore.lowerLimit,默认38%的内存使用量。
④ 当前HRegionServer中WAL的大小超过了
hbase.regionserver.hlog.blocksize * hbase.regionserver.max.logs的数量,当前HRegionServer中所有HRegion中的MemStore都会Flush到HDFS中,
Flush使用时间顺序,最早的MemStore先Flush直到WAL的数量少于
hbase.regionserver.hlog.blocksize * hbase.regionserver.max.logs这里说这两个相乘的默认大小是2GB,查代码,hbase.regionserver.max.logs默认值是32,而hbase.regionserver.hlog.blocksize默认是32MB。但不管怎么样,因为这个大小超过限制引起的Flush不是一件好事,可能引起长时间的延迟
Java API接口程序
1 import java.io.IOException; 2 3 import org.apache.hadoop.conf.Configuration; 4 import org.apache.hadoop.hbase.HBaseConfiguration; 5 import org.apache.hadoop.hbase.HColumnDescriptor; 6 import org.apache.hadoop.hbase.HTableDescriptor; 7 import org.apache.hadoop.hbase.TableName; 8 import org.apache.hadoop.hbase.client.Admin; 9 import org.apache.hadoop.hbase.client.Connection; 10 import org.apache.hadoop.hbase.client.ConnectionFactory; 11 import org.apache.hadoop.hbase.regionserver.BloomType; 12 import org.junit.Before; 13 import org.junit.Test; 14 15 public class HbaseClientDemo { 16 Connection conn = null; 17 18 @Before 19 public void getConn() throws Exception{ 20 // 构建一个连接对象 21 Configuration conf = HBaseConfiguration.create(); // 会自动加载hbase-site.xml 22 conf.set("hbase.zookeeper.quorum", "hdp2-master:2181,hdp2-slaver1:2181,hdp2-slaver2:2181"); 23 24 conn = ConnectionFactory.createConnection(conf); 25 } 26 27 28 /** 29 * DDL 30 * @throws Exception 31 */ 32 @Test 33 public void testCreateTable() throws Exception{ 34 35 // 从连接中构造一个DDL操作器 36 Admin admin = conn.getAdmin(); 37 38 // 创建一个表定义描述对象 39 HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.valueOf("user_info")); 40 41 // 创建列族定义描述对象 42 HColumnDescriptor hColumnDescriptor_1 = new HColumnDescriptor("base_info"); 43 hColumnDescriptor_1.setMaxVersions(3); // 设置该列族中存储数据的最大版本数,默认是1 44 45 HColumnDescriptor hColumnDescriptor_2 = new HColumnDescriptor("extra_info"); 46 47 // 将列族定义信息对象放入表定义对象中 48 hTableDescriptor.addFamily(hColumnDescriptor_1); 49 hTableDescriptor.addFamily(hColumnDescriptor_2); 50 51 52 // 用ddl操作器对象:admin 来建表 53 admin.createTable(hTableDescriptor); 54 55 // 关闭连接 56 admin.close(); 57 conn.close(); 58 59 } 60 61 62 /** 63 * 删除表 64 * @throws Exception 65 */ 66 @Test 67 public void testDropTable() throws Exception{ 68 69 Admin admin = conn.getAdmin(); 70 71 // 停用表 72 admin.disableTable(TableName.valueOf("user_info")); 73 // 删除表 74 admin.deleteTable(TableName.valueOf("user_info")); 75 76 77 admin.close(); 78 conn.close(); 79 } 80 81 // 修改表定义--添加一个列族 82 @Test 83 public void testAlterTable() throws Exception{ 84 85 Admin admin = conn.getAdmin(); 86 87 // 取出旧的表定义信息 88 HTableDescriptor tableDescriptor = admin.getTableDescriptor(TableName.valueOf("user_info")); 89 90 91 // 新构造一个列族定义 92 HColumnDescriptor hColumnDescriptor = new HColumnDescriptor("other_info"); 93 hColumnDescriptor.setBloomFilterType(BloomType.ROWCOL); // 设置该列族的布隆过滤器类型 94 95 // 将列族定义添加到表定义对象中 96 tableDescriptor.addFamily(hColumnDescriptor); 97 98 99 // 将修改过的表定义交给admin去提交 100 admin.modifyTable(TableName.valueOf("user_info"), tableDescriptor); 101 102 103 admin.close(); 104 conn.close(); 105 106 } 107 }
1 import java.util.ArrayList; 2 import java.util.Iterator; 3 4 import org.apache.hadoop.conf.Configuration; 5 import org.apache.hadoop.hbase.Cell; 6 import org.apache.hadoop.hbase.CellScanner; 7 import org.apache.hadoop.hbase.HBaseConfiguration; 8 import org.apache.hadoop.hbase.TableName; 9 import org.apache.hadoop.hbase.client.Connection; 10 import org.apache.hadoop.hbase.client.ConnectionFactory; 11 import org.apache.hadoop.hbase.client.Delete; 12 import org.apache.hadoop.hbase.client.Get; 13 import org.apache.hadoop.hbase.client.Put; 14 import org.apache.hadoop.hbase.client.Result; 15 import org.apache.hadoop.hbase.client.ResultScanner; 16 import org.apache.hadoop.hbase.client.Scan; 17 import org.apache.hadoop.hbase.client.Table; 18 import org.apache.hadoop.hbase.util.Bytes; 19 import org.junit.Before; 20 import org.junit.Test; 21 22 public class HbaseClientDML { 23 Connection conn = null; 24 25 @Before 26 public void getConn() throws Exception{ 27 // 构建一个连接对象 28 Configuration conf = HBaseConfiguration.create(); // 会自动加载hbase-site.xml 29 conf.set("hbase.zookeeper.quorum", "hdp2-master:2181,hdp2-slaver1:2181,hdp2-slaver2:2181"); 30 31 conn = ConnectionFactory.createConnection(conf); 32 } 33 34 35 /** 36 * 增 37 * 改:put来覆盖 38 * @throws Exception 39 */ 40 @Test 41 public void testPut() throws Exception{ 42 43 // 获取一个操作指定表的table对象,进行DML操作 44 Table table = conn.getTable(TableName.valueOf("user_info")); 45 46 // 构造要插入的数据为一个Put类型(一个put对象只能对应一个rowkey)的对象 47 Put put = new Put(Bytes.toBytes("001")); 48 put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("张三")); 49 put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes("18")); 50 put.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("北京")); 51 52 53 Put put2 = new Put(Bytes.toBytes("002")); 54 put2.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("李四")); 55 put2.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes("28")); 56 put2.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("上海")); 57 58 59 ArrayList<Put> puts = new ArrayList<>(); 60 puts.add(put); 61 puts.add(put2); 62 63 64 // 插进去 65 table.put(puts); 66 67 table.close(); 68 conn.close(); 69 70 } 71 72 73 /** 74 * 循环插入大量数据 75 * @throws Exception 76 */ 77 @Test 78 public void testManyPuts() throws Exception{ 79 80 Table table = conn.getTable(TableName.valueOf("user_info")); 81 ArrayList<Put> puts = new ArrayList<>(); 82 83 for(int i=0;i<100000;i++){ 84 Put put = new Put(Bytes.toBytes(""+i)); 85 put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("张三"+i)); 86 put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes((18+i)+"")); 87 put.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("北京")); 88 89 puts.add(put); 90 } 91 92 table.put(puts); 93 94 } 95 96 /** 97 * 删 98 * @throws Exception 99 */ 100 @Test 101 public void testDelete() throws Exception{ 102 Table table = conn.getTable(TableName.valueOf("user_info")); 103 104 // 构造一个对象封装要删除的数据信息 105 Delete delete1 = new Delete(Bytes.toBytes("001")); 106 107 Delete delete2 = new Delete(Bytes.toBytes("002")); 108 delete2.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr")); 109 110 ArrayList<Delete> dels = new ArrayList<>(); 111 dels.add(delete1); 112 dels.add(delete2); 113 114 table.delete(dels); 115 116 117 table.close(); 118 conn.close(); 119 } 120 121 /** 122 * 查 123 * @throws Exception 124 */ 125 @Test 126 public void testGet() throws Exception{ 127 128 Table table = conn.getTable(TableName.valueOf("user_info")); 129 130 Get get = new Get("002".getBytes()); 131 132 Result result = table.get(get); 133 134 // 从结果中取用户指定的某个key的value 135 byte[] value = result.getValue("base_info".getBytes(), "age".getBytes()); 136 System.out.println(new String(value)); 137 138 System.out.println("-------------------------"); 139 140 // 遍历整行结果中的所有kv单元格 141 CellScanner cellScanner = result.cellScanner(); 142 while(cellScanner.advance()){ 143 Cell cell = cellScanner.current(); 144 145 byte[] rowArray = cell.getRowArray(); //本kv所属的行键的字节数组 146 byte[] familyArray = cell.getFamilyArray(); //列族名的字节数组 147 byte[] qualifierArray = cell.getQualifierArray(); //列名的字节数据 148 byte[] valueArray = cell.getValueArray(); // value的字节数组 149 150 System.out.println("行键: "+new String(rowArray,cell.getRowOffset(),cell.getRowLength())); 151 System.out.println("列族名: "+new String(familyArray,cell.getFamilyOffset(),cell.getFamilyLength())); 152 System.out.println("列名: "+new String(qualifierArray,cell.getQualifierOffset(),cell.getQualifierLength())); 153 System.out.println("value: "+new String(valueArray,cell.getValueOffset(),cell.getValueLength())); 154 155 } 156 157 table.close(); 158 conn.close(); 159 160 } 161 162 163 /** 164 * 按行键范围查询数据 165 * @throws Exception 166 */ 167 @Test 168 public void testScan() throws Exception{ 169 170 Table table = conn.getTable(TableName.valueOf("user_info")); 171 172 // 包含起始行键,不包含结束行键,但是如果真的想查询出末尾的那个行键,那么,可以在末尾行键上拼接一个不可见的字节( 00) 173 Scan scan = new Scan("10".getBytes(), "10000 01".getBytes()); 174 175 ResultScanner scanner = table.getScanner(scan); 176 177 Iterator<Result> iterator = scanner.iterator(); 178 179 while(iterator.hasNext()){ 180 181 Result result = iterator.next(); 182 // 遍历整行结果中的所有kv单元格 183 CellScanner cellScanner = result.cellScanner(); 184 while(cellScanner.advance()){ 185 Cell cell = cellScanner.current(); 186 187 byte[] rowArray = cell.getRowArray(); //本kv所属的行键的字节数组 188 byte[] familyArray = cell.getFamilyArray(); //列族名的字节数组 189 byte[] qualifierArray = cell.getQualifierArray(); //列名的字节数据 190 byte[] valueArray = cell.getValueArray(); // value的字节数组 191 192 System.out.println("行键: "+new String(rowArray,cell.getRowOffset(),cell.getRowLength())); 193 System.out.println("列族名: "+new String(familyArray,cell.getFamilyOffset(),cell.getFamilyLength())); 194 System.out.println("列名: "+new String(qualifierArray,cell.getQualifierOffset(),cell.getQualifierLength())); 195 System.out.println("value: "+new String(valueArray,cell.getValueOffset(),cell.getValueLength())); 196 } 197 System.out.println("----------------------"); 198 } 199 } 200 201 @Test 202 public void test(){ 203 String a = "000"; 204 String b = "000 "; 205 206 System.out.println(a); 207 System.out.println(b); 208 209 210 byte[] bytes = a.getBytes(); 211 byte[] bytes2 = b.getBytes(); 212 213 System.out.println(""); 214 215 } 216 }