Java中关于protobuf的使用

1、什么是protobuf

 

    protobuf全称Protocol Buffers,谷歌数据交换的一种格式,以二进制格式进行存储,独立于语言,平台无关,可扩展的机制,可序列化。

 

2、为什么要使用protobuffer

 

    protobuffer占用内存少,传输速率比传统的xml和json数据交换格式快,那么快在哪里呢?下面就protobuffer和json数据格式进行比较:

 

2.1 运行环境:pc机 window7 & 64位操作系统 8g内存

 

2.2数据格式准备

 

    ① protobuf定义的数据格式(student.proto)

    

    option java_package = "com.bestjike.protobuf";

    option java_outer_classname = "BestJkProtoBuf";

    message Teacher {

      repeated Student student = 1;

    }

    message  Student {

      optional string name = 1;

      optional int32 age = 2;

      optional string addr = 3;

      optional string qq = 4;

      optional string phone = 5;

      optional string nation = 6;

      optional string idcard = 7;

    }

    

    ②json要定义的数据格式

 

    {"teacher":[{"name":"hello0","age":23,"addr":"上海","qq":"126479327","phone":"15900007030","nation":"汉族","idcard":"621323199310021234"}]}

 

    2.3.测试对比

 

    分别是用100,1000,10000,100000数据进行测试,测试的效果是从空间上和时间去做对比。

 

   2.3.1. protobuf测试过程:

 

    ①cmd窗口输入以下命令生成protobuf代码

 

    D:\item\protobuf\proto>protoc.exe --java_out=./ student.proto

 

    ②测试代码如下

 

        package com.bestjike.protobuf;

        import java.io.File;

        import java.io.FileNotFoundException;

        import java.io.FileOutputStream;

        import java.io.IOException;

        import java.io.OutputStream;

        import java.util.ArrayList;

        import java.util.List;

        public class ProtoBufTest {        

            public static void main(String [] args) throws FileNotFoundException, IOException{

            long start = System.currentTimeMillis();

            int size = 100000;

            List<BestJkProtoBuf.Student> list = new ArrayList<BestJkProtoBuf.Student>();

            for (int i = 0; i < size; i++) {

                BestJkProtoBuf.Student.Builder builder = BestJkProtoBuf.Student.newBuilder();

                builder.setName("hello"+i);

                builder.setAge(23);

                builder.setAddr("上海");

                builder.setQq("126479327");

                builder.setPhone("15900007030");

                builder.setNation("汉族");

                builder.setIdcard("621323199310021234");

                BestJkProtoBuf.Student student = builder.build();

                list.add(student);

            }

            File filePath = new File("D:\\proto"+size);

            if(!filePath.exists()){

                filePath.createNewFile();

            }

            OutputStream stream = new FileOutputStream(filePath);

            BestJkProtoBuf.Teacher.Builder teaBuilder = BestJkProtoBuf.Teacher.newBuilder();

            teaBuilder.addAllStudent(list);

            BestJkProtoBuf.Teacher teacher = teaBuilder.build();

            teacher.writeTo(stream);

            System.out.println("写入文件的时间:"+(System.currentTimeMillis() - start));

            //开始对数据进行解析

            start = System.currentTimeMillis();

            byte [] buffer = teacher.toByteArray();

            teacher = BestJkProtoBuf.Teacher.parseFrom(buffer);

            for (int i = 0; i < teacher.getStudentCount(); i++) {

                System.out.println(teacher.getStudent(i).getName());

            }

            System.out.println("解析时间:"+(System.currentTimeMillis() - start));

        }

    }

 

2.3.2 json测试过程

 

package com.bestjike.protobuf;

import java.io.File;

import java.io.IOException;

import java.io.PrintWriter;

import java.util.ArrayList;

import java.util.List;

import net.sf.json.JSONArray;

import net.sf.json.JSONObject;

public class JsonTest {

    public static void main(String [] args) throws IOException {

        long start = System.currentTimeMillis();

        int size = 100000;

        List<Student> list = new ArrayList<Student>();

        for (int i = 0; i < size; i++) {

            Student student = new Student();

            student.setName("hello"+i);  

            student.setAge(23);

            student.setAddr("上海");

            student.setQq("126479327");

            student.setPhone("15900007030");

            student.setNation("汉族");

            student.setIdcard("621323199310021234");

            list.add(student);

        }

        File filePath = new File("D:\\json"+size);

        if(!filePath.exists()){

            filePath.createNewFile();

        }

        PrintWriter stream = new PrintWriter(filePath);

        Teacher teacher = new Teacher();

        teacher.setStudentList(list);

        String result = Utils.toJson(teacher);

        stream.write(result);

        System.out.println(result);

        stream.close();

        System.out.println("时间:"+(System.currentTimeMillis() - start));

        start = System.currentTimeMillis();

        //解析json

        JSONObject studentList = JSONObject.fromObject(result);

        Object objectTea = studentList.get("teacher");

        JSONArray student = JSONArray.fromObject(objectTea);

        int length  = student.size();

        for (int i = 0; i < length; i++) {

            JSONObject object = JSONObject.fromObject(student.get(i));

            Student stu = (Student) JSONObject.toBean(object, Student.class);

            System.out.println(stu.getName());

        }

        System.out.println("解析时间:"+(System.currentTimeMillis() - start));

    }

}

 

2.3.3测试结果

 

    数据占用空间:

单位KB

100

1000

10000

100000

protobuf

8KB

73KB

732KB

7412KB

json

13KB

128KB

1288KB

12978KB

 

    解析数据时间:

单位毫秒MS

100

1000

10000

100000

protobuf

4MS

28MS

151MS

1111MS

json

115MS

298MS

2762MS

240332MS

 

2.3.4 结果说明

 

    测试次数超过10遍以上,以上的数值属于平均值,只是做了下大略的比较,没有考虑到最优算法,只是最平常的思维,从数据上看,无论是从空间上还是时间上protobuf是优于json的,从程序的编写程度,protobuf并没有很复杂,但是仅仅依靠上面的程序不能说明json怎么样,json解析工具也有多款:JackJson,Gson等,json通用性比较好,这一点上protobuf不能比拟的,只能说protobuf更适合适用存储,适合上面举的例子,但以上例子实际情况下是很少见的。

 

3. protobuf的深入分析

 

    3.1 protobuf消息的定义message

 

      protobuf有自己的消息定义,为.proto文件中的多个message,message中的字段filed都有修饰符,数据类型,字段名,tag以及选项。

     修饰符    数据类型  字段名    tag    选项

     optional int32  age=  1      [default=10];

     

    3.1.1 修饰符

 

    required:必须给定值,同时接受方能解析该字段值。(proto3已抛弃)

    optional:可选值,没有该值不会出现在序列化后的数据中的。

    repeated:和optional类似,只是它类似于Java中的list包含多个元素的值。

 

    3.1.2 数据类型

    

protobuf 数据类型

描述

字节 (N不固定变长)

Java语言映射

bool

布尔类型

1字节

boolean

double

64位浮点数

N

double

float

32为浮点数

N

float

int32

32位整数、

N

int

uint32

无符号32位整数

N

int

int64

64位整数

N

long

uint64

64为无符号整

N

long

sint32

32位整数,处理负数效率更高

N

int

sint64

64位整数 处理负数效率更高

N

int

fixed32

32位无符号整数

4

int

fixed64

64位无符号整数

8

long

sfixed32

32位整数、能以更高的效率处理负数

4

int

sfixed64

64为整数

8

long

string

只能处理 ASCII字符

N

String

bytes

用于处理多字节的语言字符、如中文

N

ByteString

enum

可以包含一个用户自定义的枚举类型uint32

N

enum

message

可以包含一个用户自定义的消息类型

N

class

比如:对枚举类型的字段可以这样

enum Content {

    head = 0;

    body = 1;

    tail = 2;

}

 

3.1.3 字段名

 

protobuf建议带下划线的命名方式:mobile_phone

 

3.1.4 tag值

 

序列化字段值时将会按tag值从小到大进行存储。

 

3.1.5选项

 

比如设置默认值:

optional int32   age =  1   [default=20];

常用选项:java_package,java_outer_classname,optimize_for,packed,deprecated

 

3.2 字段的存储

 

 当字段值被序列化后,数据以二进制的形式进行存储,序列化的代码如下:

 BestJkProtoBuf.Teacher.Builder teaBuilder = BestJkProtoBuf.Teacher.newBuilder();

 BestJkProtoBuf.Teacher teacher = teaBuilder.build();

 byte [] buffer = teacher.toByteArray();

序列化的字段值是以<key1,value1>,<key2,value2>的形式存在

 

3.3 字段值的编码

 

序列化后的值以key+value的形式,这里主要谈到key的编码,序列化的后的数据也有它的数据类型wire type,wire type有六种:

Wire TypeMessage的对应的数据类型
0Varint:int32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bit :fixed64, sfixed64, double
2Length-delimited :string, bytes, messages, packed repeated 
3Start group groups (废弃)
4End group groups (废弃)
532-bit: fixed32, sfixed32, float

key = tag << 3 | wire_type 低3位用来表示wire type,剩下的5位是tag的值。

 

 (1)varint 

 

varint为可变的整数类型,而int类型一般4个字节,下面说下varint的存储数据的原理,举个例子

optional int age  = 1 [default=10];

第一行为key,第二行value值

0

0

0

0

1

0

0

0

0

0

0

0

1

0

10

每一行左边的第一位最高位为标志位,为1时,表示value不止一个字节,为0时,表示value只需要一个字节,长度是由每个字节左边第一位最高位来决定的,不难看出,当tag>16时,key需要2个字节,每一个字节只用到7个位来存储值,相对于int类型的,当值大于2的28方减1为268435455时,需要5个字节来存储,因此当数字比较的时候,varint便不适合了。

同时会发现varint表示负数会很不方便,protobuf提供了sint32和sint64,varint对这两个进行编码,ZigZag先将对应的负数转换成对应的正数,而ZigZag编码规则如下:

0

0

-1

1

1

2

-2

3

2

4

......

.....

比如-3的存储

0

0

0

0

1

0

0

0

0

0

0

0

0

1

0

1

 

(2)64-bit 和 32-bit

 

由于varint对较大的数字存储并不合适,因此64-bit和32-bit编码弥补了varint的缺陷,它们是定长的。比如存储2的28方的值268435456

 

0

0

0

0

1

1

0

1

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

10

0

0

0

 

 

3)Length-delimited 

 

length-delimited编码将value的值的长度也存储着,下面string为例,存储“best”字串时

0

0

0

0

1

0

1

0

0

0

0

0

0

1

0

0

0

1

1

0

0

0

1

0

0

1

1

0

0

1

0

1

0

1

1

1

0

0

1

1

0

1

1

1

0

1

0

0

第一行:key

第二行:value的长度5

第三行:b

第四行:e

第五行:s

第六行:t

 

4.收尾

  

    当以repeated修饰字段时,会涉及多个字段的值,因此设置选项[packed = true],只会存储一个key,否则的话每个值都会有一个key,proto3以上的版本已经优化,不需要考虑。这里不讲解proto编译器根据.proto文件如何生成对应的protobuf代码的,有兴趣的自己可以研究下。当然,这里主要说明protobuf用于java中开发,protobuf产生当然是为了解决某一类问题而产生的,protobuf是由Google开发的,最初protobuf被用来处理索引服务器的请求/响应协议,当时手动编组和解组比较繁琐,涉及到不同的协议。现在protobuf大部分应用在他们的RPC系统和数据存储系统

转载于:https://my.oschina.net/u/3144970/blog/824123

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值