此文的目的:

  1、重点理解Hbase的整体工作机制

  2、熟悉编程api,能够用来写程序

1.  什么是HBASE

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.1.1、各种数据库之间的差别比较

Hbase,Hive区别:

Hive数据仓库的理解:

  1、仓库就是存放历史数据存的地方,反复对历史数据进行读操作,统计分析操作,历史数据不需要修改。

  2、Hive严格意义上来讲不能算是数据库;

  Hive与Hbase巨大的区别在于,Hive底层依赖的文件系统HDFS中的数据是用户提交的,没有固定的格式,可以理解成按照分隔符分割的简单文本,而不是精心设计的文件(如Mysql那样精心设计的文件加上mysql中共的软件系统,可以对数据进行随机的访问和修改操作),Hive只能对这些数据进行读取,分析,不能对修改和跟新数据。

  3、mysql也当然具备做为数据仓库的功能和能力,但是数据量太大是,mysql不适合,mysql适于联机事务处理(在线实时交互)。

Hbase

  1、同msyql一样,底层的文件系统的精心设计的,Hbase的底层文件系统也是HDFS。

  2、具有联机事务处理数据库的特性(快速 实时操作数据库,增删改查)。

  3、Hbase本身的特性:

        • 文件系统:HDFS(表可以很大很大)

        • 分布式系统
        • nosql表结构。

1.2  Hbase快速理解

两个基本问题:

  1、怎么存数据

  2、怎么查数据

1.2.1 、Hbase特性与表结构

(以上是Hbase的逻辑结构)

列族:KV分为若干的大类:,如上表所示。

  1、每个列族中的kv数据可以随意存放,key可以不同,没有严格要求,完全有用户决定,当然一般使用情况下,数据是规整的;

  如:下表是可以的,但是为了数据的规整,一般不建议随意为key起名字,最好保持一致。

rowkey base_info
001 name:jj, age:12, sex:mal
002  nick:ls, age:15, xb:male

  2、同一个列祖中的kv的个数也是灵活的,可以省略某些kv

cell:同一个数据可以保存多个值

  1、一个kv就是一个cell

  2、一个key可有有多个版本的值

  3、时间戳作为版本

1.2.2、Hbase整体工作机制示意图

  如下图:

1.2.2.1、存储问题(分散存储)

  按照region划分范围存储(region目录还细分为列族目录,列族目录下才存放具体的文件)

  

1.2.2.2、查询问题(分布式:分任务查询)

  Hbase底层文件系统是HDFS,Hbase中的表最终也会落地HDFS,那么Hbase的一张表可以很大很大,表中的数据不断的增加增加存储也是可以的,但是怎么查询呢?

    

  当请求特别多的时候,一台Hbase服务器(region server)是不行的,Hbase是一个分布式的系统,当有多个Hbase提供服务的时候,某一次客户端的请求具体由那个服务器来处理呢?

  当某一台服务器挂了,谁来接替他的工作,如何接替?

  解决:服务器需要分任务(分布式系统里肯定是要分任务的)

      一台服务器,负责Hbase中某个表的某一个部分

  如何界定部分?

    需要划分范围:按照行健范围

  这样通过分任务之后就是一个分布式系统。不同的regionServer可以并行的去访问hdfs中的数据(表数据)这样还有一个问题,若某一张表中的所有数据都存在同一个HDFS中的文件中,即使是负责同一张表的不同范围regionserver,大量的并行请求也会同时访问同一个hdfs文件,这会造成性能上的瓶颈,所以表中的数据在HDFS中是按照region划分范围存储(region目录还细分为列族目录,列族目录下才存放具体的文件)的这样同一个表的不同region范围的数据落地HDFS中不同的文件中。否则会造成即是分了任务一个dataNode被频繁的访问。

1.2.2.2.1、客户端读写数据是的路由流程:客户端找数据的流程

      问题描述:客户端怎么知道他要访问的某个region在那一台regionserver上呢?

            master是不会保存哪些region在哪些regionserver上的,否则就是有状态的节点了,一旦master挂了,regionserver立刻无法提供服务,而事实不是这样。

            上述信息就是所谓的索引信息,master是不会保存索引信息的,索引信息是保存在系统索引表中的。

  1、索引表当然也存在于hdfs中,且只有一个region;

  2、谁来负责查询索引表

  下图所示,索引表数据的查询由hdp-02机器上的regionserver负责,那么客户端怎样知道meta数据由hdp-02负责

  

  zookeeper上会记录元数据索引表,有哪一台regionserver负责管理。 客户单端,每次访问数据之前,先查询zookeeper。

 

  下图为Zookeeper节点meta-region-server的信息

  

访问流程:

  1、客户端去Zookeeper上查询,负责索引表数据的regionserver;

  2、找该台regionserver服务器,查询出客户端要访问的region数据由哪一台regionserver负责;

  3、客户端找具体的regionserver要数据;

总结:

    1、Hbase表中的数据是存放在hdfs中的。

  2、regionserver只负责逻辑功能,对数据进行增删改查,不存储它负责的region的数据。

  3、一个regionserver可以负责多个表的多个region。

  4、region是regionServer管理数据的基本单元。

    

  1、客户端查找数据不经过master

  2、客观端查找数据一定经过Zookeeper

 

 

Hbase整体工作机制示意图

Hbase集群中有两个角色

  region server

  master

region server负责数据的逻辑处理(增删改查),region server对数据的操作是不经过master。某一个瞬间master挂了,regionserver还是可以正常服务的,但是一定时间之后,万一某一个regionserver挂了,该regionserver负责的任务得不到重新分配,就会出问题。

1.2.2.3、服务器宕机问题(借助Zookeeper实现HA)

master对regionserver的监管,状态协调

  1、所有的状态信息记录在Zookeeper里。

  2、master负责监管region server的状态,知道每一个regionserver负责哪些表的哪些region,不负责帮用户查数据,一旦发现某个region server发生故障,会找另外的一台机器来接替该region server负责的region区域。

  3、master通过Zookeeper来获取regionserver的状态。

  4、master通过Zookeeper监听region server,maste是没有状态的节点,master存在单点故障的风险;通过主备容灾实现HA机制。

master HA

  状态信息记录在Zookeeper里。master是无状态节点,standby 切换为 active状态,查看Zookeeper后,立马知道现在的集群是什么样子。

1.2.3、Hbase工作机制补充—regionserver数据管理

首先在hbase的表中插入一些数据,然后来观察一下hdfs中存的数据,发现hdfs下并没有数据,但是scan明明可以查到数据的,这是怎么回事呢?

scan可以查到数据。而上图hdfs中却没有数据文件。

其实:此时此刻的数据位于内存中。

1.2.3.1、内存缓存热数据

每个region在内存中都对应分配一块缓存空间,memstore,但是memstore毕竟有限,不会将全部的数据都存入到内存中,还是有很大的数据是存在hdfs中的。当数据量很小的时候没有必要写入到hdfs文件中,这就解释了为什么上述hdfs中没有文件数据。

上述用户插入的数据都保存在了内存中,这样速度会比存入hdfs中快很多,但是又不能吧全部数据都存入到内存中,内存中只会保存一些热数据【刚刚被访问过的,刚刚被插入的数据】

如果有人找regionserver查数据是,regionserver内存中没有该数据,就会去hdfs中查找,找到之后作为热数据,然后缓存在内存中,超过一段时间没有人访问就不是热数据了,就不会继续保存在内存中。

2、数据保存在内存中就有风险,万一没有来的落地hdfs,宕机了,内存中的数据会丢失,怎么办?

  解决方案,regionserver一方面在自己内存中写数据,一方面在hdfs中写日志,一旦宕机后,master找来替换机器后,该机器会读取日志信息,还原内存中的数据。

hdfs中查看相应的痕迹

 总结:

  1、热数据存储

  2、日志记录

1.2.3.2、持久化到hdfs

1、当内存中的数据插满时候,数据会持久化到hdfs中

2、当hbase退出时候,数据也会持久化到hdfs中

列族目录下已经有数据文件。

使用hadoop 命令查看具体数据

 

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表的物理结构

3、安装Hbase

HBASE是一个分布式系统

其中有一个管理角色:  HMaster(一般2台,一台active,一台backup)

其他的数据节点角色:  HRegionServer(很多台,看数据容量),最好部署在datanode节点上。

3.1、 安装准备:

首先,要有一个HDFS集群,并正常运行; regionserver应该跟hdfs中的datanode在一起

其次,还需要一个zookeeper集群,并正常运行

然后,安装HBASE

角色分配如下:

Hdp01:  namenode  datanode  regionserver  hmaster  zookeeper

Hdp02:  datanode   regionserver  zookeeper

Hdp03:  datanode   regionserver  zookeeper

不需要yarn集群,不需要跑mapreduce等运算框架的程序,但是mapreduce可以读取操作Hbase中的数据。

3.2、安装步骤

3.2.1、安装Zookeeper

3.2.2、安装Hbase

解压hbase安装包,conf目录下的配置文件如下:

3.2.2.1、hbase-env.sh

  1、java_Home

  2、拒使用自己的zookeeper

修改conf目录下的hbase-env.sh

  1. export JAVA_HOME=/root/apps/jdk1..0_67
  2. export HBASE_MANAGES_ZK=false

Hbase自带一套Zookeeper,这里选择关闭再带的Zookeeper,用我们自己提供的zookeeper。

3.2.2.2、hbase-site.xml

  1、HDFS

  2、指定Hbase为分布式模式,默认是单机模式

  3、Zookeeper地址

  1. <configuration>
  2. <!-- 指定hbase在HDFS上存储的路径 -->
  3. <property>
  4. <name>hbase.rootdir</name>
              <!-- 会在hdfs根目录/下建立一个文件夹,用来放hbase中的数据【Hbase基于HDFS】-->
    <value>hdfs://hdp01:9000/hbase</value>
  5. </property>
  6. <!-- 指定hbase是分布式的 -->
  7. <property>
  8. <name>hbase.cluster.distributed</name>
  9. <value>true</value>
  10. </property>
  11. <!-- 指定zk的地址,多个用“,”分割 -->
  12. <property>
  13. <name>hbase.zookeeper.quorum</name>
  14. <value>hdp01:2181,hdp02:2181,hdp03:2181</value>
  15. </property>
  16. </configuration>

3.2.2.3、regionservers

告知Hbase启动脚本,在哪些机器上启动,regionservers

  1. hdp01
  2. hdp02
  3. hdp03

确保hdfs没有问题

3.3、启动hbase集群

确保Zookeeper正常,确保hdfs正常。

3.3.1、批量启动

使用自动批量启动脚本

  1. bin/start-hbase.sh

改命令会在本机起送master,

启动完后,还可以在集群中找任意一台机器启动一个备用的master

  1. bin/hbase-daemon.sh start master

新启的这个master会处于backup状态

3.3.2、逐个启动

人肉,逐个机器,启动进程。

  1. bin/hbase-daemon.sh start master
  1. bin/hbase-daemon.sh start regionserever

同步服务器时间

写入硬件时钟,否则重启无效。

3.3.3、查看hdfs文件

启动Hbase后会在hbase-site.xml中配置的HDFS路径下,建立对应的文件夹。

hbase文件夹内容如下

数据存放在data文件下,data文件夹里有default库【默认数据库】,库里会存放用户建立的表,hbase是系统的一些数据。

3.3.4、查看Hbase网页端

通过网页端查看Hbase信息,HMaster监听两个端口。一个是内部通信端口16000,一个是外部服务端口16010。

3.3.5、索引表

系统表

记录所有用户表的region位置信息。

索引表,记录索引,哪一个用户表的哪一个region范围在哪一台regionserver上

客户端找数据的时候,先查索引表,确定自己要访问的数据范围在一个regionserver上,然后再去访问该regionserver去拿数据。

3.3.6、查看Zookeeper

Hbase集群中不同角色的信息沟通是通过Zookeeper的,那么必定会在Zookeeper记录一下状态信息。

4、hbase客户端

4.1、命令行客户端

  1. bin/hbase shell
  2. Hbase> list // 查看表
  3. Hbase> status // 查看集群状态
  4. Hbase> version // 查看集群版本

进入命令行客户端,help查看都有哪些命令【命令分为不同的组别 ddl dml tools replication...】。

  1. bin/hbase shell

如果shell命令行无法退格删除字符,则如下操作

语句没有分号

4.1.1、建表

  1. create 't_user_info','base_info','extra_info'
  2.  
  3. 表名 列族名 列族名

 查看建表后的状态

HDFS中的数据

4.1.2、插入数据

put命令

语法:

  1. put 't_user_info','行健','列族:key','value'

  1. hbase(main):011:0> put 't_user_info','','base_info:username','zhangsan'
  2. 0 row(s) in 0.2420 seconds
  3.  
  4. hbase(main):012:0> put 't_user_info','','base_info:age',''
  5. 0 row(s) in 0.0140 seconds
  6.  
  7. hbase(main):013:0> put 't_user_info','','base_info:sex','female'
  8. 0 row(s) in 0.0070 seconds
  9.  
  10. hbase(main):014:0> put 't_user_info','','extra_info:career','it'
  11. 0 row(s) in 0.0090 seconds
  12.  
  13. hbase(main):015:0> put 't_user_info','','extra_info:career','actoress'
  14. 0 row(s) in 0.0090 seconds
  15.  
  16. hbase(main):016:0> put 't_user_info','','base_info:username','liuyifei'
  17. 0 row(s) in 0.0060 seconds

  

4.1.3、查询数据方式一:get 单行查询

语法:

  1. -- 返回该行全部数据
  2. get 't_user_info','行健'
  3.  
  4. -- 返回该行指定列族:key的值
  5. get 't_user_info','行健', '列族:key'

特性:Hbase会对 ' 列族:key ' 进行字典序排序

   timestamp:是key的版本号 

  1. hbase(main):020:0> get 't_user_info',''
  2. COLUMN CELL
  3. base_info:age timestamp=1496568160192, value=19
  4. base_info:sex timestamp=1496567934669, value=female
  5. base_info:username timestamp=1496567889554, value=zhangsan
  6. extra_info:career timestamp=1496567963992, value=it
  7. 4 row(s) in 0.0770 seconds

 

4.1.3、查询数据方式二:scan 扫描

 scan是全表扫描

特性:

  1、先按照行健排序。

  2、同一行健,按照key的字典序排序。

  1. hbase(main):017:0> scan 't_user_info'
  2. ROW COLUMN+CELL
  3. 001 column=base_info:age, timestamp=1496567924507, value=18
  4. 001 column=base_info:sex, timestamp=1496567934669, value=female
  5. 001 column=base_info:username, timestamp=1496567889554, value=zhangsan
  6. 001 column=extra_info:career, timestamp=1496567963992, value=it
  7. 002 column=base_info:username, timestamp=1496568034187, value=liuyifei
  8. 002 column=extra_info:career, timestamp=1496568008631, value=actoress
  9. 2 row(s) in 0.0420 seconds

4.1.4、delete 删除一个kv数据

  1. hbase(main):021:0> delete 't_user_info','','base_info:sex'
  2. 0 row(s) in 0.0390 seconds

4.1.5、deleteall 删除整行数据

  1. hbase(main):024:0> deleteall 't_user_info',''
  2. 0 row(s) in 0.0090 seconds
  3.  
  4. hbase(main):025:0> get 't_user_info',''
  5. COLUMN CELL
  6. 0 row(s) in 0.0110 seconds

4.1.6、删除整个表

  disable

  drop

删除表之前先要停用表。

  1. hbase(main):028:0> disable 't_user_info'
  2. 0 row(s) in 2.3640 seconds
  3.  
  4. hbase(main):029:0> drop 't_user_info'
  5. 0 row(s) in 1.2950 seconds
  6.  
  7. hbase(main):030:0> list
  8. TABLE
  9. 0 row(s) in 0.0130 seconds
  10.  
  11. => []

4.2、客户端api

DDL

  如何描述一个表

  如何创建一个表

    删除一个表

    修改一个表

* 1、构建连接
* 2、从连接中取到一个表DDL操作工具admin
* 3、admin.createTable(表描述对象);
* 4、admin.disableTable(表名);
* 5、admin.deleteTable(表名);
* 6、admin.modifyTable(表名,表描述对象);

DML

4.2.1、创建连接对象

  1. import org.apache.hadoop.conf.Configuration;
  2. import org.apache.hadoop.hbase.HBaseConfiguration;
  3. import org.apache.hadoop.hbase.HColumnDescriptor;
  4. import org.apache.hadoop.hbase.HTableDescriptor;
  5. import org.apache.hadoop.hbase.TableName;
  6. import org.apache.hadoop.hbase.client.Admin;
  7. import org.apache.hadoop.hbase.client.Connection;
  8. import org.apache.hadoop.hbase.client.ConnectionFactory;
  9. import org.apache.hadoop.hbase.regionserver.BloomType;
  10.  
  11. Connection conn = null;
  12.  
  13. @Before
  14. public void getConn() throws Exception{
  15. // new Configuration() 加载的是hadoop的配置文件:core-site.xml hdfs-site.xml,不会加载hbase-site.xml
         // 构建一个连接对象
         // Hbase提供了HbaseConfiguraton 用来加载hbase-site.xml
         Configuration conf = HBaseConfiguration.create(); // 会自动加载hbase-site.xml
         // 客户端查询数据的路由流程可知:客户端需要先链接 Zookeeper 获取索引表
  16. conf.set("hbase.zookeeper.quorum", "hdp-01:2181,hdp-02:2181,hdp-03:2181");

  17.      // 创建链接对象
  18. conn = ConnectionFactory.createConnection(conf);
  19. }

4.2.2、DDL操作

1、创建一个连接

  1. Connection conn = ConnectionFactory.createConnection(conf);

2、拿到一个DDL操作器:表管理器admin

  1. Admin admin = conn.getAdmin();

3、用表管理器的api去建表、删表、修改表定义

  1. admin.createTable(HTableDescriptor descriptor);

4.2.2.1、创建表

  1. @Test
  2. public void testCreateTable() throws Exception{
  3.  
  4. // 从连接中构造一个DDL操作器
  5. Admin admin = conn.getAdmin();
  6.  
  7. // 创建一个表定义描述对象
  8. HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.valueOf("user_info"));
  9.  
  10. // 创建列族定义描述对象
    // 通过列族描述定义对象,可以设置列族的很多重要属性信息
  11. HColumnDescriptor hColumnDescriptor_1 = new HColumnDescriptor("base_info");
  12. hColumnDescriptor_1.setMaxVersions(3); // 设置该列族中存储数据的最大版本数,默认是1
  13.  
  14. HColumnDescriptor hColumnDescriptor_2 = new HColumnDescriptor("extra_info");
  15.  
  16. // 将列族定义信息对象放入表定义对象中
  17. hTableDescriptor.addFamily(hColumnDescriptor_1);
  18. hTableDescriptor.addFamily(hColumnDescriptor_2);
  19.  
  20. // 用ddl操作器对象:admin 来建表
  21. admin.createTable(hTableDescriptor);
  22.  
  23. // 关闭连接
  24. admin.close();
  25. conn.close();
  26.  
  27. }

4.2.2.2、删除表

先停用表 disableTable

然后删除表 deleteTable

  1. @Test
  2. public void testDropTable() throws Exception{
  3.  
  4. Admin admin = conn.getAdmin();
  5.  
  6. // 停用表
  7. admin.disableTable(TableName.valueOf("user_info"));
  8. // 删除表
  9. admin.deleteTable(TableName.valueOf("user_info"));
  10.  
  11. admin.close();
  12. conn.close();
  13. }

4.2.2.3、修改表

  1. // 修改表定义--添加一个列族
  2. @Test
  3. public void testAlterTable() throws Exception{
  4.  
  5. Admin admin = conn.getAdmin();
  6.  
  7. // 取出旧的表定义信息
  8. HTableDescriptor tableDescriptor = admin.getTableDescriptor(TableName.valueOf("user_info"));
  9.  
  10. // 新构造一个列族定义
  11. HColumnDescriptor hColumnDescriptor = new HColumnDescriptor("other_info");
  12. hColumnDescriptor.setBloomFilterType(BloomType.ROWCOL); // 设置该列族的布隆过滤器类型
  13.  
  14. // 将列族定义添加到表定义对象中
  15. tableDescriptor.addFamily(hColumnDescriptor);
  16.  
  17. // 将修改过的表定义交给admin去提交
  18. admin.modifyTable(TableName.valueOf("user_info"), tableDescriptor);
  19.  
  20. admin.close();
  21. conn.close();
  22.  
  23. }

 完整代码

  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", "hdp-01:2181,hdp-02:2181,hdp-03:2181");
  23.  
  24. conn = ConnectionFactory.createConnection(conf);
  25. }
  26.  
  27. /**
  28. * DDL
  29. * @throws Exception
  30. */
  31. @Test
  32. public void testCreateTable() throws Exception{
  33.  
  34. // 从连接中构造一个DDL操作器
  35. Admin admin = conn.getAdmin();
  36.  
  37. // 创建一个表定义描述对象
  38. HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.valueOf("user_info"));
  39.  
  40. // 创建列族定义描述对象
  41. HColumnDescriptor hColumnDescriptor_1 = new HColumnDescriptor("base_info");
  42. hColumnDescriptor_1.setMaxVersions(3); // 设置该列族中存储数据的最大版本数,默认是1
  43.  
  44. HColumnDescriptor hColumnDescriptor_2 = new HColumnDescriptor("extra_info");
  45.  
  46. // 将列族定义信息对象放入表定义对象中
  47. hTableDescriptor.addFamily(hColumnDescriptor_1);
  48. hTableDescriptor.addFamily(hColumnDescriptor_2);
  49.  
  50. // 用ddl操作器对象:admin 来建表
  51. admin.createTable(hTableDescriptor);
  52.  
  53. // 关闭连接
  54. admin.close();
  55. conn.close();
  56.  
  57. }
  58.  
  59. /**
  60. * 删除表
  61. * @throws Exception
  62. */
  63. @Test
  64. public void testDropTable() throws Exception{
  65.  
  66. Admin admin = conn.getAdmin();
  67.  
  68. // 停用表
  69. admin.disableTable(TableName.valueOf("user_info"));
  70. // 删除表
  71. admin.deleteTable(TableName.valueOf("user_info"));
  72.  
  73. admin.close();
  74. conn.close();
  75. }
  76.  
  77. // 修改表定义--添加一个列族
  78. @Test
  79. public void testAlterTable() throws Exception{
  80.  
  81. Admin admin = conn.getAdmin();
  82.  
  83. // 取出旧的表定义信息
  84. HTableDescriptor tableDescriptor = admin.getTableDescriptor(TableName.valueOf("user_info"));
  85.  
  86. // 新构造一个列族定义
  87. HColumnDescriptor hColumnDescriptor = new HColumnDescriptor("other_info");
  88. hColumnDescriptor.setBloomFilterType(BloomType.ROWCOL); // 设置该列族的布隆过滤器类型
  89.  
  90. // 将列族定义添加到表定义对象中
  91. tableDescriptor.addFamily(hColumnDescriptor);
  92.  
  93. // 将修改过的表定义交给admin去提交
  94. admin.modifyTable(TableName.valueOf("user_info"), tableDescriptor);
  95.  
  96. admin.close();
  97. conn.close();
  98.  
  99. }
  100.  
  101. }

4.2.3、布隆过滤器BloomType

假设有一个互联网爬虫程序,不断将网页中的url爬取下来,这里有一个问题,因为链接可能存在回路,会造成程序的死循环,因此需要判断每条url是否已经被爬取过。

最传统的办法就是将url放入数据库中,每次新爬取的url和数据库进行比对,但是当数据量很大是,每一条url都要和全部的数据进行一次比对,显然不可。

后来出现布隆过滤器专门来解决这个问题。

大致思路:

  提供一个64k(或者其他长度,越长精度越大)大小的二进制数组。

  将url通过一个算法(简单理解为hash算法)映射成8个bit,对应在一个64k大小的二进制的8个索引上。

  经过这个算法索引位置全部吻合的连个url有很大概率是同一个url;

  但是索引位置不吻合的两位url一定不是同一个url;

Hbase中的BloomType的应用

  之前说过hbase中表在hdfs中是按照如下目录存放的,有hbase的持久化操作可知,一旦内存中的数据写满或者其他原因,就会序列化内存中的数据到hdfs,而内存中保存的是热数据,这样可能会造成同一个key出现在不同的序列化文件中,而且每个key还有不同的版本,更加大了这个可能性。

    /库名/表名/region/列族/文件1

               .../文件2

               .../文件3

               .../文件4

问题:

  1、不同的文件中可能保存相同的key

  2、文件太多,在查询时挨个比对显然效率太低

解决方式:

  1、可以为每行数据生成已给bloom过滤器记录

  2、或者为每个字段生成一个bloom过滤器记录

这样在找数据的时候,对数据进行结算得到若干比特,然后去比较bloom过滤器,这样会快很多。

4.2.4、DML操作

  1. // 获取一个操作指定表的table对象,进行DML操作
  2. Table table = conn.getTable(TableName.valueOf("user_info"));
  1. import org.apache.hadoop.conf.Configuration;
  2. import org.apache.hadoop.hbase.Cell;
  3. import org.apache.hadoop.hbase.CellScanner;
  4. import org.apache.hadoop.hbase.HBaseConfiguration;
  5. import org.apache.hadoop.hbase.TableName;
  6. import org.apache.hadoop.hbase.client.Connection;
  7. import org.apache.hadoop.hbase.client.ConnectionFactory;
  8. import org.apache.hadoop.hbase.client.Delete;
  9. import org.apache.hadoop.hbase.client.Get;
  10. import org.apache.hadoop.hbase.client.Put;
  11. import org.apache.hadoop.hbase.client.Result;
  12. import org.apache.hadoop.hbase.client.ResultScanner;
  13. import org.apache.hadoop.hbase.client.Scan;
  14. import org.apache.hadoop.hbase.client.Table;
  15. import org.apache.hadoop.hbase.util.Bytes;

4.2.4.1、增加数据

1、Table对象,进行DML操作;

2、数据封装对象put;

3、Table.put(put) | Table.put(List<put>puts);

  1. /**
  2. * 增
  3. * 改:put来覆盖
  4. * @throws Exception
  5. */
  6. @Test
  7. public void testPut() throws Exception{
  8.  
  9. // 获取一个操作指定表的table对象,进行DML操作
  10. Table table = conn.getTable(TableName.valueOf("user_info"));
  11.  
  12. // 构造要插入的数据为一个Put类型(一个put对象只能对应一个rowkey)的对象
  13. Put put = new Put(Bytes.toBytes("001"));
  14. put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("张三"));
  15. put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes("18"));
  16. put.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("北京"));
  17.  
  18. Put put2 = new Put(Bytes.toBytes("002"));
  19. put2.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("李四"));
  20. put2.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes("28"));
  21. put2.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("上海"));
  22.  
  23. ArrayList<Put> puts = new ArrayList<>();
  24. puts.add(put);
  25. puts.add(put2);
  26.  
  27. // 插进去
  28. table.put(puts);
  29.  
  30. table.close();
  31. conn.close();
  32.  
  33. }
  1. /**
  2. * 循环插入大量数据
  3. * @throws Exception
  4. */
  5. @Test
  6. public void testManyPuts() throws Exception{
  7.  
  8. Table table = conn.getTable(TableName.valueOf("user_info"));
  9. ArrayList<Put> puts = new ArrayList<>();
  10.  
  11. for(int i=0;i<100000;i++){
  12. Put put = new Put(Bytes.toBytes(""+i));
  13. put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("张三"+i));
  14. put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes((18+i)+""));
  15. put.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("北京"));
  16.  
  17. puts.add(put);
  18. }
  19.  
  20. table.put(puts);
  21.  
  22. }

4.2.4.2、删除数据

对称结构,插入的时候需要Put对象

删除的时候,需要Delete对象

  1. /**
  2. * 删
  3. * @throws Exception
  4. */
  5. @Test
  6. public void testDelete() throws Exception{
  7. Table table = conn.getTable(TableName.valueOf("user_info"));
  8.  
  9. // 构造一个对象封装要删除的数据信息
         // 全部删除
  10. Delete delete1 = new Delete(Bytes.toBytes("001"));

  11.      // 删除指定的key
  12. Delete delete2 = new Delete(Bytes.toBytes("002"));
         // qualifier为用户意义上的key,hbase中 family+qualifier 为一个key
  13. delete2.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"));
  14.  
  15. ArrayList<Delete> dels = new ArrayList<>();
  16. dels.add(delete1);
  17. dels.add(delete2);
  18.  
  19. table.delete(dels);
  20.  
  21. table.close();
  22. conn.close();
  23. }

4.2.4.3、修改数据

  使用put来覆盖

4.2.4.5、查看数据

qualifier为用户意义上的key,hbase中 family+qualifier 为一个key

对称结构,插入的时候需要Put对象

删除的时候,需要Delete对象

查看单个行键数据,需要Get对象

4.2.4.5.1、取出单行数据

Table.get(Get)

可以取出该行特定 familyName:key 的 value

也可以遍历该行全部的value

  1. /**
  2. * 查
  3. * @throws Exception
  4. */
  5. @Test
  6. public void testGet() throws Exception{
  7.  
  8. Table table = conn.getTable(TableName.valueOf("user_info"));
  9.  
  10. // Get对象 指定行健
         Get get = new Get("002".getBytes());

  11. // 行健为002的全部数据
  12. Result result = table.get(get);
  13.  
  14. // 从结果中取用户指定的某个key的value
  15. byte[] value = result.getValue("base_info".getBytes(), "age".getBytes());
  16. System.out.println(new String(value));
  17.  
  18. System.out.println("-------------------------");
  19.  
  20. // 遍历整行结果中的所有kv单元格
         // 类似迭代器
  21. CellScanner cellScanner = result.cellScanner();
  22. while(cellScanner.advance()){
  23. Cell cell = cellScanner.current();
  24.  
  25. byte[] rowArray = cell.getRowArray(); //本kv所属的行键的字节数组
  26. byte[] familyArray = cell.getFamilyArray(); //列族名的字节数组
  27. byte[] qualifierArray = cell.getQualifierArray(); //列名的字节数据
  28. byte[] valueArray = cell.getValueArray(); // value的字节数组

  29.        // Hbase不仅仅是存储用户数据,同时还会存储很多附加的信息,以上get方法直接将用户数据和附加数据一起返回,若想获取用户信息,需要指定其实偏移量和数据长度 
  30. System.out.println("行键: "+new String(rowArray,cell.getRowOffset(),cell.getRowLength()));
  31. System.out.println("列族名: "+new String(familyArray,cell.getFamilyOffset(),cell.getFamilyLength()));
  32. System.out.println("列名: "+new String(qualifierArray,cell.getQualifierOffset(),cell.getQualifierLength()));
  33. System.out.println("value: "+new String(valueArray,cell.getValueOffset(),cell.getValueLength()));
  34.  
  35. }
  36.  
  37. table.close();
  38. conn.close();
  39.  
  40. }

4.2.4.5.2、批量取出数据

取出多个行健范围的数据,需要Scan对象

Table.get(Get)只能取出一个行健范围的数据;

如何按照行健范围取出数据?

table.getScanner(scan)

拿到一个扫描器

  1. /**
  2. * 按行键范围查询数据
  3. * @throws Exception
  4. */
  5. @Test
  6. public void testScan() throws Exception{
  7.  
  8. Table table = conn.getTable(TableName.valueOf("user_info"));
  9.  
  10. // 包含起始行键,不包含结束行键,但是如果真的想查询出末尾的那个行键,那么,可以在末尾行键上拼接一个不可见的字节(\000
         // Scan scan = new Scan("10".getBytes(), "10000".getBytes());
  1. Scan scan = new Scan("10".getBytes(), "10000\001".getBytes());
  2.  
  3. ResultScanner scanner = table.getScanner(scan);
  4.  
  5. Iterator<Result> iterator = scanner.iterator();
  6.  
  7. while(iterator.hasNext()){
  8. // 拿到一行数据
  9. Result result = iterator.next();
  10. // 遍历整行结果中的所有kv单元格
  11. CellScanner cellScanner = result.cellScanner();
  12. while(cellScanner.advance()){
  13. Cell cell = cellScanner.current();
  14.  
  15. byte[] rowArray = cell.getRowArray(); //本kv所属的行键的字节数组
  16. byte[] familyArray = cell.getFamilyArray(); //列族名的字节数组
  17. byte[] qualifierArray = cell.getQualifierArray(); //列名的字节数据
  18. byte[] valueArray = cell.getValueArray(); // value的字节数组
  19.  
  20. System.out.println("行键: "+new String(rowArray,cell.getRowOffset(),cell.getRowLength()));
  21. System.out.println("列族名: "+new String(familyArray,cell.getFamilyOffset(),cell.getFamilyLength()));
  22. System.out.println("列名: "+new String(qualifierArray,cell.getQualifierOffset(),cell.getQualifierLength()));
  23. System.out.println("value: "+new String(valueArray,cell.getValueOffset(),cell.getValueLength()));
  24. }
  25. System.out.println("----------------------");
  26. }
  27. }

范围查询的细节

道理:

在真正的结尾行健后面,拼接一个数字0的字节

\000是一个字节,全是0

\表示转移,此时后面的0不是数字0,不是字符0

  1. @Test
  2. public void test(){
  3. String a = "000";
  4. String b = "000\0";
  5.  
  6. System.out.println(a);
  7. System.out.println(b);
  8.  
  9. byte[] bytes = a.getBytes();
  10. byte[] bytes2 = b.getBytes();
  11.  
  12. System.out.println("");
  13. }

结果

  1. 000
  2. 000

5、 Hbase重要特性--排序特性(行键)

插入到hbase中去的数据,hbase会自动排序存储:

排序规则:  首先看行键,然后看列族名,然后看列(key)名; 按字典顺序

Hbase的这个特性跟查询效率有极大的关系

比如:一张用来存储用户信息的表,有名字,户籍,年龄,职业....等信息

然后,在业务系统中经常需要:

  查询某个省的所有用户

  经常需要查询某个省的指定姓的所有用户

思路:如果能将相同省的用户在hbase的存储文件中连续存储,并且能将相同省中相同姓的用户连续存储,那么,上述两个查询需求的效率就会提高!!!

做法:将查询条件拼到rowkey内

6、数据类型

Hbase中只有一种数据类型:二进制数组

Hbase内部没有对放入的数据维护类型。

这就要求,将来往Hbase里插入数据是,需要将数据转换成二进制数组,去除数据后还要进行解析。

无论是表名,列族名,key,还是value都是二进制数组。

在命令行客户端是以字符串形式来显示的二进制数组数据。

7、练习

  1. {
  2. "events": "1473367236143\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000027\u0001\n1473367261933\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000028\u0001\n1473367280349\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000029\u0001\n1473367331326\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000030\u0001\n1473367353310\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000031\u0001\n1473367387087\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000032\u0001\n1473367402167\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000033\u0001\n1473367451994\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000034\u0001\n1473367474316\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000035\u0001\n1473367564181\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000036\u0001\n1473367589527\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000037\u0001\n1473367610310\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000038\u0001\n1473367624647\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000039\u0001\n1473368004298\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000040\u0001\n1473368017851\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000041\u0001\n1473369599067\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000042\u0001\n1473369622274\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000043\u0001\n",
  3. "header": {
  4. "cid_sn": "1501004207EE98AA",
  5. "mobile_data_type": "",
  6. "os_ver": "22",
  7. "mac": "1c:xx:xx:xx:xx:xx",
  8. "resolution": "1080x1920",
  9. "commit_time": "1473396818952",
  10. "sdk_ver": "103",
  11. "device_id_type": "mac",
  12. "city": "江门市",
  13. "android_id": "86783xx:xx:xx:xx:xx",
  14. "device_model": "HUAWEI VNS-AL00",
  15. "carrier": "中国xx",
  16. "promotion_channel": "1",
  17. "app_ver_name": "1.4",
  18. "imei": "8678300xx:xx:xx:xx:xx",
  19. "app_ver_code": "401xx:xx:xx:xx:xx",
  20. "pid": "pid",
  21. "net_type": "3",
  22. "device_id": "m.1c:xx:xx:xx:xx:xx",
  23. "app_device_id": "m.1c:xx:xx:xx:xx:xx",
  24. "release_channel": "1009",
  25. "country": "CN",
  26. "time_zone": "28800000",
  27. "os_name": "android",
  28. "manufacture": "OPPO",
  29. "commit_id": "fde7ee2e48494b24bf3599771d7c2a78",
  30. "app_token": "XIAONIU_A",
  31. "account": "none",
  32. "app_id": "com.appid.xiaoniu",
  33. "build_num": "YVF6R163xx:xx:xx:xx:xx",
  34. "language": "zh"
  35. }
  36. }

1/假如公司有一个app,每日会在日志服务器上生成大量的日志(在给的样本数据中)
2/公司有一个需求,经常需要在网页上查看某一段时间范围内的日志数据

3/实现思路:
a、每天的日志数据要入库(读文件、解析、插入hbase)
行键:2017-09-17-10-device_id-commit_time .....
分两个列族:events数据列族; headers数据列族

b、开发一个web系统(页面-表单(填查询条件:日期范围; 日期范围+device_id); 页面--展现日志数据表格)
dao层有两种方式: 方式一,继承hbase的客户端jar包,直接在web项目中查询hbase数据
方式二,自己封装一个查询hbase的后台服务,web项目中就去请求你自己的服务


===================未完(下个笔记)==================

Hbase—学习笔记(一)的更多相关文章

  1. HBase学习笔记之HBase的安装和配置

    HBase学习笔记之HBase的安装和配置 我是为了调研和验证hbase的bulkload功能,才安装hbase,学习hbase的.为了快速的验证bulkload功能,我安装了一个节点的hadoop集 ...

  2. HBASE学习笔记(四)

    这两天把要前几天的知识点回顾一下,接下来我会用自己对知识点的理解来写一些东西 一.知识点回顾 1.hbase集群启动:$>start-hbase.sh ===>hbase-daemon.s ...

  3. HBase学习笔记之BulkLoad

    HBase学习之BulkLoad bulkload的学习以后再写文章. 参考资料: 1.https://blog.csdn.net/shixiaoguo90/article/details/78038 ...

  4. HBase学习笔记之HBase原理和Shell使用

    HBase学习指南之HBase原理和Shell使用 参考资料: 1.https://www.cnblogs.com/nexiyi/p/hbase_shell.html,hbase shell

  5. HBase学习笔记(四)—— 架构模型

    在逻辑上,HBase 的数据模型同关系型数据库很类似,数据存储在一张表中,有行有列. 但从 HBase 的底层物理存储结构(K-V)来看,HBase 更像是一个 multi-dimensional m ...

  6. Hbase学习笔记01

    最近做项目接触到了HDFS.mapreduce以及Hbase,有了实战机会,今天打算将这些知识好好总结下,以备不时之需.首先从Hbase开始吧. Hbase是建立在HDFS上的分布式数据库,下图是Hb ...

  7. HBase学习笔记-高级(一)

    HBase1. hbase.id记录了集群的唯一标识:hbase.version记录了文件格式的版本号2. split和.corrupt目录在日志分裂过程中使用,以便保存一些中间结果和损坏的日志在表目 ...

  8. HBASE学习笔记--API

    HBaseConfiguration HBaseConfiguration是每一个hbase client都会使用到的对象,它代表的是HBase配置信息.它有两种构造方式: public HBaseC ...

  9. HBase学习笔记一

    HBase简介 HBase概念 HBase的原型是谷歌的Bigtable论文 HBase是一个高可靠性.高性能.面向列.可伸缩的分布式存储系统,利用HBase技术可在廉价PC上搭建起大规模结构化存储集 ...

随机推荐

  1. AIX6.1用g++安装Poco-1.6.1-all

    项目终于到了把程序往小型机上部署的阶段了.不得不说AIX真是让人恶心,一个Poco编译弄了我1周时间.网上根本没有相关的资料. 1. AIX下安装gcc/g++:在ftp://ftp.software ...

  2. java对含有中文的字符串进行Unicode编码

    public class MyUtil { public static void main(String[] args) throws Exception { String s = "a中a ...

  3. 2018-2019 20165226 网络对抗 Exp1 PC平台逆向破解

    2018-2019 20165226 网络对抗 Exp1 PC平台逆向破解 目录 一.逆向及Bof基础实践说明 二.直接修改程序机器指令,改变程序执行流程 三.通过构造输入参数,造成BOF攻击,改变程 ...

  4. Program.cs 累积_C#

    using System; using System.Diagnostics; using System.Threading; using System.Windows.Forms; using Ut ...

  5. dubbo 官方参考手册~备案(防止哪天阿里一生气把dubbo给删除了)

          首页  ||  下载  ||  用户指南  ||  开发者指南  ||  管理员指南  ||  培训文档  ||  常见问题解答  ||  发布记录  ||  发展路线  ||  社区 E ...

  6. EC20 MODULE serial com log in passwd

    ec20 module would print debug info via debug uart, and you can log in by user root, the passwd is qu ...

  7. 【C++11新特性】 nullptr关键字

    原文链接:http://blog.csdn.net/xiejingfa/article/details/50478512 熟悉C++的童鞋都知道,为了避免“野指针”(即指针在首次使用之前没有进行初始化 ...

  8. spark 存储管理机制

    累加器 -- Accumulators 广播变量--Broadcast Variables 思考 回顾 存储管理模块架构--从架构上来看 存储管理模块架构--通信层 存储管理模块架构--存储层 存储管 ...

  9. webpack(5)--Resolve

    Resolve webpack在启动后会从配置的入口模块触发找出所有依赖的模块,Resolve配置webpack如何寻找模块对应的文件.webpack内置JavaScript模块化语法解析功能,默认会 ...

  10. LinkedHashMap唯一,存储取出有序

    package cn.itcast_03; import java.util.LinkedHashMap; import java.util.Set; /* * LinkedHashMap:是Map接 ...