HBase实战:通话记录分析

目录

前置条件

需求分析

环境准备

实战代码

before和after代码实现

创建表代码实现

运行并查看结果

添加数据代码实现

运行并查看结果

查询某用户3月份的通话记录

运行并查看结果

删除指定rowkey的某单元格数据

添加单元格数据

删除行操作

查找某用户拨出去的通话记录

客户端请求过滤器

实际代码

运行并查看结果


前置条件

Hadoop 集群高可用搭建: Yarn资源调度器-CSDN博客

HBase集群搭建: HBase搭建-CSDN博客

需求分析

一共模拟10个用户一年的通话记录,每个用户产生1000条通话记录。

数据关键字段:用户手机号,通话时长,对方手机号,日期,通话类型(主叫,被叫)

需求:

1.根据上述关键字段,设计数据库,并在HBase上生成相应数据

2.实现某用户某月份的通话记录的查询

3.实现按照用户电话号码和主被叫类型查询通话记录

字段设计

通话类型的设计: 用0和1表示。0表示主叫,1表示被叫。

rowkey的设计 :用户手机号码+反向时间戳+i+j。
好处:按手机号水平分区,提升按手机号查询的效率;反向时间戳:Long.MAX_VALUE-simpleDateFormat.parse(date).getTime())。用最大值减去当前时间戳,时间越晚,计算结果越小;实现了同手机号下,最新的数据排在最前面

环境准备

1.创建Maven项目CallLogDemo

2.添加依赖

复制代码
    <dependency>
      <groupId>org.apache.hbase</groupId>
      <artifactId>hbase-server</artifactId>
      <version>2.0.5</version>
    </dependency>
    <dependency>
      <groupId>org.apache.hbase</groupId>
      <artifactId>hbase-client</artifactId>
      <version>2.0.5</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>RELEASE</version>
      <scope>compile</scope>
    </dependency>

3.启动Hadoop集群:startha.sh(之前编写的启动脚本)

4.启动HBase集群start-hbase.sh

实战代码

beforeafter****代码实现

@Before和@After是JUnit 4单元测试的核心注释。被@Before注释的方法会在每个 @Test 执行前执行,用于初始化轻量级资源;被@After注释的方法则会在每个 @Test 执行后执行,用于清理轻量级资源(必执行)。

注意:方法必须是 public void,无参数;无论 @Test 方法是否执行成功(甚至抛异常),@After 都会执行,保证资源必释放。

java 复制代码
public class CallLogDemo {
    //命名空间的定义
    private String namespace = "wusen";
    //表名称
    private String tableName = "phone_log";
    //表名称对应的TableName对象
    private TableName tableNameObj;
    //表DDL对象
    private Admin admin;
    //表数据的DML对象
    private Table table;
    //连接对象
    private Connection connection;
    // JUnit 4单元测试注释,每个 @Test 执行前执行,初始化轻量级资源;
    @Before
    public void before(){
        Configuration configuration = HBaseConfiguration.create();
        configuration.set("hbase.zookeeper.quorum","node2,node3,node4");
        try{
           connection = ConnectionFactory.createConnection(configuration);
           admin = connection.getAdmin();
           tableNameObj = TableName.valueOf(namespace+":"+tableName);
           table = connection.getTable(tableNameObj);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // JUnit 4单元测试注释,每个 @Test 执行后执行,清理轻量级资源(必执行);
    @After
    public void after() throws IOException {
        if(table!=null){
            table.close();
        }
        if (admin!=null){
            admin.close();
        }
        if (connection!=null){
            connection.close();
        }
    }
}

创建表代码实现

java 复制代码
//定义列族名
    private String family="info";
    //JUnit 4单元测试注释,标记测试方法,框架自动执行;
    @Test
    public void createTable() throws IOException {
        //1.定义命名空间描述器
        NamespaceDescriptor build = NamespaceDescriptor.create(namespace).build();
        try{
            //2.创建命名空间
            admin.createNamespace(build);
            System.out.println("命名空间:"+namespace+"创建成功");
        } catch (NamespaceExistException nee) {
            //3.如果命名空间存在则抛出异常
            System.out.println("命名空间:"+namespace+"已经存在");
        }
        //4.创建表描述器的Builder对象
        TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(tableNameObj);
        //5.创建列族的描述器对象
        ColumnFamilyDescriptor familyDescriptor = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(family)).build();
        //6.将列族描述器添加到表表描述器的Builder对象中
        tableDescriptorBuilder.setColumnFamily(familyDescriptor);
        //7.将表描述器的Builder对象转化为表描述器对象
        TableDescriptor tableDescriptor = tableDescriptorBuilder.build();
        //8.判断表是否存在
        if (admin.tableExists(tableNameObj)){
            //9.如果存在先禁用后删除
            admin.disableTable(tableNameObj);
            admin.deleteTable(tableNameObj);
        }
        //10.创建表
        admin.createTable(tableDescriptor);
    }

运行并查看结果

点击下面按钮进行运行

结果

查看HBase数据库,也生成了对应的命名空间和表

添加数据代码实现

我们需要模拟用户通话记录,这里的代码用于生成10个用户的在某一年内的通话记录,每个用户产生1000条通话记录。

首先实现手机号随机生成的方法getPhoneNumber()

java 复制代码
    private Random random = new Random();

    /**
     * 生成一个手机号码
     * @param prefix:手机号码的前三位
     * @return 生成的手机号码
     */
    private String getPhoneNumber(String prefix){
        //nextInt(int n) 方法的规则:生成大于等于 0、小于 n的整数(左闭右开);
        //"%08d",把整数转成 8 位字符串,不够 8 位左边补 0,够 8 位直接保留。其中%是占位符的起始标记,d表示类型限定:表示要格式化的参数是整数类型
        //%-08d则是左对齐
        return prefix+String.format("%08d",random.nextInt(99999999));
    }

其次实现随机生成日期时间的方法getDate()

这两个方法在接下来生成随机通话记录的时候会调用

java 复制代码
    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //2050-01-01 0:0:0 - 2050-12-31 23:59:59
    private String getDate(int year){
        Calendar calendar = Calendar.getInstance();
        //设置时间,将 Calendar 对象的时间设置为指定年份的 1 月 1 日 00:00:00
        calendar.set(year,0,1);
        //随机月份
        calendar.add(Calendar.MONTH,random.nextInt(12));
        //随机日
        calendar.add(Calendar.DAY_OF_MONTH,random.nextInt(31));
        //小时随机数
        calendar.add(Calendar.HOUR_OF_DAY,random.nextInt(12));
        //获取时间对象
        Date time = calendar.getTime();
        //将时间转化为格式化的字符串并返回
        return simpleDateFormat.format(time);

    }

接下来实现生成用户数据并将其添加到HBase中的方法insert()

java 复制代码
    /**
     * 生成10个用户的在某一年内的通话记录,每个用户产生1000条通话记录
     *dnum:对方手机号码 type:呼叫类型 0主叫 1表示被叫;length:通话时长;date:时间
     */
    @Test
    public void insert() throws IOException, ParseException {
        //定义一个List<Put>
        List<Put> putList = new ArrayList<Put>();
        //循环10次,模拟10个用户
        for (int i = 0;i < 10;i++){
            //清空putList,防止上次操作的影响
            putList.clear();
            //生成当前用户的手机号码
            String phoneNumber = getPhoneNumber("137");
            System.out.println(phoneNumber);
            //模拟每个用户的1000条数据
            for (int j = 0;j < 1000;j++){
                //cf:length=,cf:dnum=,cf:date=,cf:type= 0表示主叫 1表示被叫
                //生成每一行通话记录的数据
                String dnum  = getPhoneNumber("169");
                int length = random.nextInt(200)+1;
                int type = random.nextInt(2);
                String date = getDate(2050);
                //rowKey设计。现按手机号水平分区,提升按手机号查询的效率;
                //Long.MAX_VALUE-simpleDateFormat.parse(date).getTime()):反向时间戳,。用最大值减去当前时间戳,时间越晚,计算结果越小;实现了同手机号下,最新的数据排在最前面
                String rowKey = phoneNumber+"-"+(Long.MAX_VALUE-simpleDateFormat.parse(date).getTime())+i+j;
                //创建put对象
                Put put = new Put(Bytes.toBytes(rowKey));
                put.addColumn(Bytes.toBytes(family),Bytes.toBytes("dnum"),Bytes.toBytes(dnum));
                put.addColumn(Bytes.toBytes(family),Bytes.toBytes("length"),Bytes.toBytes(length));
                put.addColumn(Bytes.toBytes(family),Bytes.toBytes("type"),Bytes.toBytes(type));
                put.addColumn(Bytes.toBytes(family),Bytes.toBytes("date"),Bytes.toBytes(date));
                //将put对象添加到putList
                putList.add(put);
            }
            //将当前用户的1000条通话记录提交
            table.put(putList);
        }
    }

运行并查看结果

运行insert()方法,控制台输出10个随机生成的手机号码

查看HBase,输出两条数据

**思考:**为什么代码中getDate(2050),实际数据库中却出现了2051年的数据呢?

**答案:**代码中用了calendar.add(),导致可能出现边界值越界

java 复制代码
// 问题1:随机月份时,可能让月份从12(Calendar.DECEMBER)再加N,导致年份+1
calendar.add(Calendar.MONTH, random.nextInt(12));
// 问题2:随机日期时,可能让日期超当月最大天数,进一步触发月份/年份进位
calendar.add(Calendar.DAY_OF_MONTH, random.nextInt(31));

以year=2050为例,代码执行步骤:

  1. calendar.set(2050, 0, 1) → 初始时间是2050 年 1 月 1 日(Calendar 的月份是 0 基:0=1 月,11=12 月);

  2. random.nextInt(12)生成11 → calendar.add(Calendar.MONTH, 11) → 时间变成2050 年 12 月 1 日;

  3. random.nextInt(31)生成30 → calendar.add(Calendar.DAY_OF_MONTH, 30) → 12 月只有 31 天,1+30=31 天,刚好是 12 月 31 日(暂时没问题);但如果random.nextInt(12)生成11,且random.nextInt(31)生成31 → 12 月 1 日 + 31 天 = 12 月 32 日 → Calendar自动进位:12 月 32 日 = 2051 年 1 月 1 日;

  4. 最终格式化后,日期就变成了2051 年,和查询到的结果一致。

查询某用户3月份的通话记录

这里使用insert()方法调用后随机生成的第一个手机号码(13774485941)3月份的通话记录。

java 复制代码
    //查询某用户3月份的通话记录
    @Test
    public void scanData() throws ParseException, IOException {
        //1.定义某用户的手机号码,这里的手机号要用insert()方法中生成的
        String phoneNumber = "13774485941";
        //2.定义startRow 包含
        String startRow = phoneNumber+'-'+(Long.MAX_VALUE-simpleDateFormat.parse("2050-04-01 00:00:00").getTime());
        //3.定义stopRow 包含
        String stopRow = phoneNumber+'-'+(Long.MAX_VALUE-simpleDateFormat.parse("2050-03-01 00:00:00").getTime());
        //4.创建Scan对象
        Scan scan = new Scan();
        //5.设置起始和结束行
        scan.withStartRow(Bytes.toBytes(startRow));
        scan.withStopRow(Bytes.toBytes(stopRow),true);
        //6.执行查询
        ResultScanner resultScanner = table.getScanner(scan);
        //7.解析resultScanner
        for (Result result:resultScanner){
            //8.解析result
            Cell[] cells = result.rawCells();
            String rowInfo = "rowKey:"+Bytes.toString(CellUtil.cloneRow(cells[0]));
            rowInfo += ","+Bytes.toString(CellUtil.cloneQualifier(cells[0]))+":"+Bytes.toString(CellUtil.cloneValue(cells[0]));
            rowInfo += ","+Bytes.toString(CellUtil.cloneQualifier(cells[1]))+":"+Bytes.toString(CellUtil.cloneValue(cells[1]));
            rowInfo += ","+Bytes.toString(CellUtil.cloneQualifier(cells[2]))+":"+Bytes.toInt(CellUtil.cloneValue(cells[2]));
            rowInfo += ","+Bytes.toString(CellUtil.cloneQualifier(cells[3]))+":"+Bytes.toInt(CellUtil.cloneValue(cells[3]));
            //输出
            System.out.println(rowInfo);

        }
    }

运行并查看结果

删除指定rowkey的某单元格数据

删除前

实现代码

java 复制代码
    @Test
    public void deleteCell() throws IOException {
        Delete delete = new Delete(Bytes.toBytes("13774485941-92233695045059548070492"));
        //指定具体的列
        delete.addColumn(Bytes.toBytes(family), Bytes.toBytes("length"));
        //执行删除操作
        table.delete(delete);
    }

删除后

添加单元格数据

java 复制代码
    @Test
    public void insertCell() throws IOException {
        Put put = new Put(Bytes.toBytes("13774485941-92233695045059548070492"));
        put.addColumn(Bytes.toBytes(family), Bytes.toBytes("length"), Bytes.toBytes(99));
        table.put(put);
    }

结果:数据添加成功

删除行操作

java 复制代码
    @Test
    public void deleteRow() throws IOException {
        Delete delete = new Delete(Bytes.toBytes("13774485941-92233695045059548070492"));
        //执行删除操作 
        table.delete(delete);
    }

查找某用户拨出去的通话记录

客户端请求过滤器

结构化过滤器 包含其他的过滤器

sql 复制代码
select dnum,type,length,date from xxx
where rowkey like "13774485941%" and type = 0

rowkey以13774485941开头并且 type=0 (主叫)
FilterList两个条件

  • FilterList.Operator.MUST_PASS_ALL 交集(and操作)
    select * from tb_user where date='' and type='0
    • FilterList.Operator.MUST_PASS_ONE 并集( or 操作)
      select * from tb_user where date='' or type='0'
java 复制代码
/**用于组织两个或多个查询条件之间的关系,参数有如下两个:
* MUST_PASS_ALL:表示and的关系,交集,同时成立
* MUST_PASS_ONE:表示or的关系,并集,有一个成立即可
*/
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);

列值过滤器 type=0
SingleColumnValueFilter:用于判断列值的关系:

  • 相等 CompareFilter.CompareOp.EQUAL , CompareOperator.EQUAL(2.0.0+)
  • 不相等( CompareOp.NOT_EQUAL ),范围(比如: CompareOp.GREATER )
java 复制代码
        /**basic:type=0
         *LESS:小于
         *LESS_OR_EQUAL;小于等于
         *EQUAL 等于
         *NOT_EQUAL:不等于
         *  GREATER_OR_EQUAL大于登录
         * GREATER:大于
         *  NO_OP:no operation
         */
        SingleColumnValueFilter valueFilter = new SingleColumnValueFilter(Bytes.toBytes("basic"), Bytes.toBytes("type"),
                //指定列族和列描述符
                CompareOperator.EQUAL,
                //比较条件hbase2.0.0 开始使用
                Bytes.toBytes(0)//value
        );
        //将列值过滤器添加到filterList中
        filterList.addFilter(valueFilter);

列值比较器
嵌套在其他过滤器中,比如 SingleColumnValueFilter

  • RegexStringComparator:使用正则表达式进行比较
  • SubstringComparator:判断子串是否包含在值中
  • BinaryPrefixComparator:二进制前缀比较器
  • BinaryComparator :二进制比较器

KeyValue Metadata

  • FamilyFilter :过滤列族的,当然最好在 scan 中指定而不是使用该过滤器
  • QualifierFilter :过滤列标识符的过滤器。
  • ColumnPrefixFilter:
    1.ColumnPrefixFilter可用于基于Column(又名Qualifier)名称的前导部分进行过滤。
    2.同一列限定符可用于不同的列族。该过滤器返回所有匹配的列。
java 复制代码
        //列族过滤器,这种处理方式比较麻烦,一般不用
        ByteArrayComparable bytecom = new ByteArrayComparable(Bytes.toBytes("basic")) {
            @Override
            public byte[] toByteArray() {
                return new byte[0];
            }

            @Override
            public int compareTo(byte[] value, int offset, int length) {
                return 0;
            }
        };
        FamilyFilter familyFilter = new FamilyFilter(CompareOperator.EQUAL, bytecom);
        //列族过滤直接使用如下方法,简捷高效
        scan.addFamily(Bytes.toBytes("info"));
        //指定需要的列
        scan.addColumn(Bytes.toBytes("info"), Bytes.toBytes("dnum"));

rowkey****过滤器

  • RowFilter:最好在scan中使用startRow和stopRow,当然也可以使用此过滤器
  • PrefixFilter :匹配具有相同 rowkey 前缀的记录。
java 复制代码
        //rowkey的前缀过滤器
        PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("13774485941"));
        //将prefixFilter添加到过滤器的集合中
        filterList.addFilter(prefixFilter);

实际代码

java 复制代码
    /**
     * 查找某用户(rowkey以13774485941为前缀),并且type= 0所有通话记录
     * 列族:basic
     * 列:dnum,type,date,length
     */
    public void findByFilter(String prefix, int type) throws IOException {
        Scan scan = new Scan();
        //指定查询的列族 select info:* from wusenn:phone_log
        scan.addFamily(Bytes.toBytes(family));
        //指定查询的列
        // select info:dnum,info:type,info:date,info:length from wusen:phone_log
        scan.addColumn(Bytes.toBytes(family), Bytes.toBytes("dnum"));
        scan.addColumn(Bytes.toBytes(family), Bytes.toBytes("type"));
        scan.addColumn(Bytes.toBytes(family), Bytes.toBytes("date"));
        scan.addColumn(Bytes.toBytes(family), Bytes.toBytes("length"));
        //两个查询条件之间的关系 and,必须同时满足才能查询出来
        FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
        //rowkey前缀过滤器
        PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes(prefix));
        filterList.addFilter(prefixFilter);
        //列值过滤器
        SingleColumnValueFilter valueFilter = new SingleColumnValueFilter(
                Bytes.toBytes(family), Bytes.toBytes("type"),
                CompareOperator.EQUAL, Bytes.toBytes(type)
        );
        filterList.addFilter(valueFilter);
        //为scan设置filterList
        scan.setFilter(filterList);
        //执行查询,并返回结果集
        ResultScanner resultScanner = table.getScanner(scan);
        //解析resultScanner
        for (Result result : resultScanner) {
            printMsg(result);
        }
    }

    private void printMsg(Result result) {
        //1.解析result
        Cell[] cells = result.rawCells();
        String rowInfo = "rowkey:" + Bytes.toString(CellUtil.cloneRow(cells[0]));
        rowInfo += "," + Bytes.toString(CellUtil.cloneQualifier(cells[0])) + ":" + Bytes.toString(CellUtil.cloneValue(cells[0]));
        rowInfo += "," + Bytes.toString(CellUtil.cloneQualifier(cells[1])) + ":" + Bytes.toString(CellUtil.cloneValue(cells[1]));
        rowInfo += "," + Bytes.toString(CellUtil.cloneQualifier(cells[2])) + ":" + Bytes.toInt(CellUtil.cloneValue(cells[2]));
        rowInfo += "," + Bytes.toString(CellUtil.cloneQualifier(cells[3])) + ":" + Bytes.toInt(CellUtil.cloneValue(cells[3]));
        //2输出
        System.out.println(rowInfo);
    }

    public static void main(String[] args) throws IOException {
        CallLogDemo callLogDemo = new CallLogDemo();
        callLogDemo.before();
        callLogDemo.findByFilter("13774485941",0);
        callLogDemo.after();
    }

运行并查看结果

运行main方法,得到如下结果

Protocol Bufffer 压缩

问题引入

对HBase存储在HDFS上的文件做解析

在浏览器HDFS文件系统查看文件的存储路径

如果没有文件的话,需要hbase客户端执行命令:flush 'wusen:phone_log'

然后查看该文件,查看命令:hbase hfile -p -f HDFS_PATH(hbase数据文件的hdfs路径)

复制代码
[root@node1 ~]# hbase hfile -p -f /hbase_ha/data/wusen/phone_log/17e4800a2a15c5f366933dc6da872676/info/23b462065936426cb727e0df63d4d973

结果如下:


可以发现相同的 rowkey 、列族名称被重复保存了多次,如何解决呢?

解决办法:Protocol Buffer

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。

这里我们可以利用Protocol Buffer将原本的数据转化为仅info:phone_info 一个列,值是MyPhone 对象的 Protobuf 序列化字节(可选压缩)。

安装Google Protocol Buffer

在网站Releases · protocolbuffers/protobuf上可以下载源代码,然后解压编译安装便可以使用。

我这里使用的是protobuf-java-3.19.1.tar.gzhttps://download.csdn.net/download/m0_62491477/92611135

安装步骤如下:

首先查看自己的环境是英文还是中文:yum grouplist

复制代码
# 如果显示中文,则执行下面命令
[root@node1 ~]# yum groupinstall '开发工具'
# 如果是英文,则执行下面命令
[root@node1 ~]# yum groupinstall 'Development tools'

然后将安装包上传到/opt/apps,并解压到/opt

解压后进入到目录执行下述操作

复制代码
[root@node1	apps]# cd /opt/protobuf-3.19.1
[root@node1	protobuf-3.19.1]# ./configure
[root@node1	protobuf-3.19.1]# make
[root@node1	protobuf-3.19.1]# make install

编写 . proto 文件生成 MyPhone . java 工具类

vim myPhone.proto

复制代码
syntax = "proto3";
package com.wusen.hbase.util;
message Phone {
int32 length=1;
string dnum=2;
int32  type=3;
string  date=4;
}

然后执行下面命令

复制代码
[root@node1 ~]# protoc myPhone.proto --java_out=./

会生成一个java文件

将文件下载到本地桌面,然后添加进原项目里

Maven项目中使用

首先在pom.xml中添加依赖,并刷新Maven,导入依赖

XML 复制代码
       <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.19.1</version>
        </dependency>

插入数据

拷贝CallLogDemo下的insert()方法为insertProtocBuf(),其他代码保持不变,只修改以下代码

java 复制代码
                //创建put对象
                Put put = new Put(Bytes.toBytes(rowKey));
//                put.addColumn(Bytes.toBytes(family), Bytes.toBytes("dnum"), Bytes.toBytes(dnum));
//                put.addColumn(Bytes.toBytes(family), Bytes.toBytes("length"), Bytes.toBytes(length));
//                put.addColumn(Bytes.toBytes(family), Bytes.toBytes("type"), Bytes.toBytes(type));
//                put.addColumn(Bytes.toBytes(family), Bytes.toBytes("date"), Bytes.toBytes(date));
                MyPhone.Phone.Builder builder = MyPhone.Phone.newBuilder();
                builder.setDnum(dnum);
                builder.setLength(length);
                builder.setType(type);
                builder.setDate(date);
                MyPhone.Phone phone = builder.build();
                //由原来的四个字段存储通话记录,改为1个字段存储
                put.addColumn(Bytes.toBytes(family),Bytes.toBytes("detail"),phone.toByteArray());

在HBase客户端上清空原来表的数据

执行insertProtocBuf()方法,然后刷新下表:flush 'wusen:phone_log',查看hdfs上的数据文件

可以发现大小只有1.01MB了,之前有2.84MB

查询数据

拷贝scanData()方法为scanProtocBuf()方法,修改下面其中代码,测试时候的手机号码也需要替换成数据库中存在的手机号码

java 复制代码
 //7.解析resultScanner
        for (Result result : resultScanner) {
            //8.解析result
            Cell[] cells = result.rawCells();
//            String rowInfo = "rowKey:" + Bytes.toString(CellUtil.cloneRow(cells[0]));
//            rowInfo += "," + Bytes.toString(CellUtil.cloneQualifier(cells[0])) + ":" + Bytes.toString(CellUtil.cloneValue(cells[0]));
//            rowInfo += "," + Bytes.toString(CellUtil.cloneQualifier(cells[1])) + ":" + Bytes.toString(CellUtil.cloneValue(cells[1]));
//            rowInfo += "," + Bytes.toString(CellUtil.cloneQualifier(cells[2])) + ":" + Bytes.toInt(CellUtil.cloneValue(cells[2]));
//            rowInfo += "," + Bytes.toString(CellUtil.cloneQualifier(cells[3])) + ":" + Bytes.toInt(CellUtil.cloneValue(cells[3]));
//            System.out.println(rowInfo);
            //获取值
            byte[] phoneInfoBytes = CellUtil.cloneValue(cells[0]);
            //将字节数据中的数据反序列化为MyPhone.Phone对象
            MyPhone.Phone phone = MyPhone.Phone.parseFrom(phoneInfoBytes);
            String rowInfo = "dnum:" + phone.getDnum();
            rowInfo += ",type:" + phone.getType();
            rowInfo += ",date:" + phone.getDate();
            rowInfo += ",length:" + phone.getLength();
            System.out.println(rowInfo);

        }

结果如下

相关推荐
2501_941982052 小时前
从孤岛到闭环:如何将企微 RPA 自动化能力无缝接入业务工作流?
数据库
ALex_zry2 小时前
Redis Cluster 故障转移与高可用实践
数据库·redis·wpf
BD同步2 小时前
双模PCIE总线授时板卡选型指南
大数据·网络·eclipse
Re.不晚2 小时前
Redis入门--基础语法大全
数据库·redis·bootstrap
那我掉的头发算什么2 小时前
【Mybatis】动态SQL与留言板小项目
数据库·spring boot·sql·spring·mybatis·配置
迎仔2 小时前
09-消息队列Kafka介绍:大数据世界的“物流枢纽”
大数据·分布式·kafka
pp起床2 小时前
【苍穹外卖】Day05 Redis快速入门
数据库·redis·缓存
BYSJMG2 小时前
大数据分析案例:基于大数据的肺癌数据分析与可视化系统
java·大数据·vue.js·python·mysql·数据分析·课程设计
晚霞的不甘2 小时前
Flutter for OpenHarmony3D DNA 螺旋可视化:用 Canvas 构建沉浸式分子模型
前端·数据库·经验分享·flutter·3d·前端框架