赞
踩
上一篇文章通过一个例子大致了解了protobuf的作用,我曾经打开那个存储对象编码后的文件,里面像是有一团乱码:
这篇文章主要研究protobuf是如何编码的,同时你也能感受到protobuf为什么更快更省带宽。
在开始研究过程之前,必须先要了解Varints,Varints提供了一种办法能让一个或者多个字节代表整型变量,通常在java中一个int需要占用4个字节,即使数字1也需要4个字节,而使用Varints能用更少的字节代表比较小的数字,这样做的目的就是为了减少编码后使用的空间,毕竟整型很常用,使用Varints带来的提高还是很客观的。了解Varints的作用后,下面介绍一下它是怎么做到的。
比如数字149通过Varints编码后变成了
10010101 00000001
这里面有2个字节,在Varints中每个字节的第一个bit都是代表后面还有没有更多的字节,从上面的例子能看出,第一个字节首bit是1,代表后面还有字节,需要继续处理,第二个的首bit是0,代表到此为止后面没有字节了。
还有一个需要注意的是,Varints采用的是 least significant group first, 网上没有找到合适的翻译,其实就是它在表示整型变量时将字节顺序反过来存储,比如上面这个例子:
10010101 00000001 -> 0010101 0000001 //去掉首bit
0010101 0000001 ->00000010010101 //反转顺序
00000010010101换算成10进制就是149
假设现在有一个消息实体定义为如下:
message Message { int32 a = 1; }
上面的Message里只有一个整型变量a, tag为1. 如果现在将一个a为149的Message写入文件,然后从文件中按照字节读出刚刚写入的Message内容:
00001000 10010101 00000001
同样的149和上面介绍的相比,多了一个字节00001000, 这个是什么作用呢?
protobuf 的消息编码都是按照多个key-value对来存储的,既然上面的149是value, 那多出来的字节肯定就是key了,而在protobuf中一个key包含2部分
常用的wire type如下,这个例子中我们的wire type是0
- Type Meaning Used For
- 0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
- 1 64-bit fixed64, sfixed64, double
- 2 Length-delimited string, bytes, embedded messages, packed repeated fields
- 3 Start group groups (deprecated)
- 4 End group groups (deprecated)
- 5 32-bit fixed32, sfixed32, float
而在protobuf中key的计算方式是 key = (field_number << 3) | wire_type, 也就是 1<<3 | 0 即 0001000
你可以理解为key中的后三位就是wire type
准备有两个字段的Message,一个整型一个字符串类型
syntax = "proto3"; package tutorial; option java_package = "com.example.tutorial"; option java_outer_classname = "TestMessage"; message Message { int32 a = 1; string query = 2; }
通过前面文章提到的maven插件生成对象代码TestMessage.java, 新建一个对象并写入文件中:
TestMessage.Message.Builder message = TestMessage.Message.newBuilder(); message.setA(149); message.setQuery("zack"); // Write the new address book back to disk. FileOutputStream output = new FileOutputStream("testmessage.txt"); message.build().writeTo(output); output.close();
然后从上面的文件中读出字节流:
File file = new File(fileName); InputStream in = null; try { in = new FileInputStream(file); int tempbyte; while ((tempbyte = in.read()) != -1) { System.out.print(Integer.toBinaryString(tempbyte)+" "); } in.close(); } catch (IOException e) { e.printStackTrace(); return; }
得出的结果如下:
1000 10010101 1 10010 100 1111010 1100001 1100011 1101011
确实挺长,我们可以一点一点解刨,前3个字节和上面的例子一样1000 10010101 1,就是149的整型变量,那来看看后面的6个字节:
10010 100 1111010 1100001 1100011 1101011
现在回想一下整个过程,你是否发现protobuf在使用尽量少的字节去表达尽量多的含义,包括减少整型变量的空间占用以及在表示变量类型和field_num时只使用少的字节数,同样的消息,编码占用的空间越少,则它传输也就越快。
至此,我们研究了protobuf是如何将一个对象的属性编码成一个字节流的过程,如果你还想知道其他类型的字段是如何编码的,可以参考protobuf的官网,这里就不细讲了,原理都是差不多的。
另附文章中提到的工程文件:
欢迎关注我的个人的博客www.zhijianliu.cn, 虚心求教,有错误还请指正轻拍,谢谢
版权声明:本文出自志健的原创文章,未经博主允许不得转载
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。