Protoc Buffer 是我们比较常用的序列化框架,Protocol Buffer 序列化后的占空间小,传输高效,可以在不同编程语言以及平台之间传输。今天这篇文章主要介绍 Protocol Buffer 使用 VarInt32 减少序列化后的数据大小。

VarInt32 编码

VarInt32 (vary int 32),即:长度可变的 32 为整型类型。一般来说,int 类型的长度固定为 32 字节。但 VarInt32 类型的数据长度是不固定的,VarInt32 中每个字节的最高位有特殊的含义。如果最高位为 1 代表下一个字节也是该数字的一部分。因此,表示一个整型数字最少用 1 个字节,最多用 5 个字节表示。如果某个系统中大部分数字需要 >= 4 字节才能表示,那其实并不适合用 VarInt32 来编码。下面以一个例子解释 VarInt32 的编码方式:

以 129 为例,它的二进制为 1000 0001 。
由于每个字节最高位用于特殊标记,因此只能有 7 位存储数据。
第一个字节存储最后 7 位 (000 0001),但并没有存下所有的比特,因此最高位置位 1,剩下的部分用后续字节表示。所以,第一个字节为:1000 0001
第二个字节只存储一个比特位即可,因此最高位为 0 ,所以,第二个字节为:0000 0001
这样,我们就不必用 4 字节的整型存储 129 ,可以节省存储空间

在 Protoc buffer 中,每一个 ProtoBuf 对象都有一个方法 public void writeDelimitedTo(final OutputStream output),该方法将 ProtoBuf 对象序列化后的长度以及序列化数据本身写入到输出流 output 中。多个对象调用该方法可以将序列化后的数据写入到同一个输出流。由于每次写入都有长度,所以反序列化时先解析长度,在读取对应长度的字节数据,即可解析出每个对象。该方法中对序列化后长度的编码便使用 VarInt32,因为一个 Protobuf 对象序列化后的长度不会太大,因此使用 VarInt32 编码能够有效的节省存储空间。接下来我们看下 Protoc Buffer 中如何实现 VarInt32 编码,跟进 writeDelimitedTo 方法,可以看到 VarInt32 编码的源码如下:

  /**
* Encode and write a varint. {@code value} is treated as
* unsigned, so it won't be sign-extended if negative.
*/
public void writeRawVarint32(int value) throws IOException {
while (true) {
if ((value & ~0x7F) == 0) {//代表只有低7位有值,因此只需1个字节即可完成编码
writeRawByte(value);
return;
} else {
writeRawByte((value & 0x7F) | 0x80);//代表编码不止一个字节,value & 0x7f 只取低 7 位,与 0x80 进行按位或(|)运算为了将最高位置位 1 ,代表后续字节也是改数字的一部分
value >>>= 7;
}
}
}

该方法对 int 类型的值进行 VarInt32 编码,可以验证最多 5 个字节即可完成编码。

VarInt32 解码

理解了编码后,解码就没什么可说的了。就是从输入字节流中,读取一个字节判断最高位,将真实数据位拼接成最终的数字即可。Hadoop RPC 中使用了 Protoc Buffer 作为数据序列化框架。其中,Hadoop 针对 writeDelimitedTo 方法实现了对 VarInt32 的解码。源码如下:

/**
* Read a variable length integer in the same format that ProtoBufs encodes.
* @param in the input stream to read from
* @return the integer
* @throws IOException if it is malformed or EOF.
*/
public static int readRawVarint32(DataInput in) throws IOException {
byte tmp = in.readByte();
if (tmp >= 0) {// tmp >= 0 代表最高位是 0 ,否则 tmp < 0 代表最高位是 1 ,需要继续往下读
return tmp;
}
int result = tmp & 0x7f;
if ((tmp = in.readByte()) >= 0) {
result |= tmp << 7;
} else {
result |= (tmp & 0x7f) << 7;
if ((tmp = in.readByte()) >= 0) {
result |= tmp << 14;
} else {
result |= (tmp & 0x7f) << 14;
if ((tmp = in.readByte()) >= 0) {
result |= tmp << 21;
} else {
result |= (tmp & 0x7f) << 21;
result |= (tmp = in.readByte()) << 28;
if (tmp < 0) {//我们说 VarInt32 最多 5 个字节表示,当程序执行到这里,tmp < 0,说明,编码格式有问题// Discard upper 32 bits.
for (int i = 0; i < 5; i++) {
if (in.readByte() >= 0) {
return result;
}
}
throw new IOException("Malformed varint");
}
}
}
}
return result;
}

在 Hadoop 源码中并没有使用循环去解码,而是使用多个 if 条件判断,根据 tmp 的正负号来判断最高位是否是 1。如果读取的该数字用了 5 个字节编码,当读到了第 5 个字节,理论上 tmp 应该大于 0 。但是如果 tmp 小于 0 ,说明编码格式有问题。在 Hadoop 源码中程序会继续往下读,最多再向下读 5 个字节且丢掉最高位仍然 < 0 的字节。如果在该过程某个字节最高位为 0 ,便停止读取直接返回。这个处理逻辑在其他框架源码中也有出现。

看完 Hadoop 的源码,我们在看看 Protoc Buffer 自己提供的解析源码:

  /**
* Like {@link #readRawVarint32(InputStream)}, but expects that the caller
* has already read one byte. This allows the caller to determine if EOF
* has been reached before attempting to read.
*/
public static int readRawVarint32(
final int firstByte, final InputStream input) throws IOException {
if ((firstByte & 0x80) == 0) {
return firstByte;
} int result = firstByte & 0x7f;
int offset = 7;
for (; offset < 32; offset += 7) {
final int b = input.read();
if (b == -1) {
throw InvalidProtocolBufferException.truncatedMessage();
}
result |= (b & 0x7f) << offset;
if ((b & 0x80) == 0) {
return result;
}
}
// Keep reading up to 64 bits.
for (; offset < 64; offset += 7) {
final int b = input.read();
if (b == -1) {
throw InvalidProtocolBufferException.truncatedMessage();
}
if ((b & 0x80) == 0) {
return result;
}
}
throw InvalidProtocolBufferException.malformedVarint();
}

可以看到 Protoc Buffer 自己提供的解码方式与 Hadoop 是一样的,包括遇到错误的编码时候的异常处理方式也是一样的。

小结

本篇文章主要介绍了 VarInt32 编解码,VarInt32 表示一个整型数字最少用 1 个字节, 最多用 5 个字节。所以在传输数字大部分都比较小的场景下适合使用。当然,我们也可以用 VarInt64 来表示长整型的数字。 在介绍 VarInt32 的同时我们也看到了 ProtoBuf 和 Hadoop 这样的框架在传输数据的优化上不放过任何一个细节,值得我们学习。

公众号「渡码」

Protoc Buffer 优化传输大小的一个细节的更多相关文章

  1. MySQL 性能优化--优化数据库结构之优化数据大小

    MySQL性能优化--优化数据库结构之优化数据大小   By:授客  QQ:1033553122 尽量减少表占用的磁盘空间.通常,执行查询期间处理表数据时,小表占用更少的内存. 表列 l   尽可能使 ...

  2. BUFFER CACHE之调整buffer cache的大小

    Buffer Cache存放真正数据的缓冲区,shared Pool里面存放的是sql指令(LC中一次编译,多次运行,加快处理性能,cache hit ratio要高),而buffer cache里面 ...

  3. IIS 设置文件传输大小限制

    IIS默认传输文件大小为30M,最大允许传输为2G. 1.通过webconfig配置节点设置 在IIS 6.0 设置如下配置节点: 但是IIS 7.0-8.0还要做添加如下配置节点才能正确,否则还是默 ...

  4. Luogu3163 [CQOI2014]危桥 ---- 网络流 及 一个细节的解释

    Luogu3163 [CQOI2014]危桥 题意 有$n$个点和$m$条边,有些边可以无限次数的走,有些边这辈子只能走两次,给定两个起点和终点$a_1 --> a_2$(起点 --> 终 ...

  5. Httpd服务入门知识-使用mod_deflate模块压缩页面优化传输速度

    Httpd服务入门知识-使用mod_deflate模块压缩页面优化传输速度 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.mod_deflate模块概述 mod_deflate ...

  6. 【JOB】Oracle中JOB的创建方法以及一个细节的探究

    在Oracle中可以使用JOB来实现一些任务的自动化执行,类似于UNIX操作系统crontab命令的功能.简单演示一下,供参考. 1.创建表T,包含一个X字段,定义为日期类型,方便后面的定时任务测试. ...

  7. Protocol Buffer格式传输

    1.简单明了介绍ProtocolBuffer 2. ProtocolBuffer(pb)所做事情其实类似于xml.json,也就是把某种数据结构的信息依照某种格式保存起来.主要用于数据存储.传输等. ...

  8. java 性能优化:35 个小细节,让你提升 java 代码的运行效率

    前言 代码 优化 ,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没 ...

  9. JAVA性能优化:35个小细节让你提升java代码的运行效率

    代码优化,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用,但是, ...

随机推荐

  1. http_load测试初阶

    http_load的标准的两个例子是: 1.         http_load -parallel 5 -fetches 1000 urls.txt 2.         http_load -ra ...

  2. TCP协议是如何保证可靠传输的【经典】

    参考:http://blog.csdn.net/cmm0401/article/details/77878998 从特点上我们已经知道,TCP 是可靠的但传输速度慢 ,UDP 是不可靠的但传输速度快. ...

  3. linux awk(good)

    一个用awk处理字符串的例子: #!/bin/bash source="nokia201703148855" preffixStr=$(echo $source |awk '{pr ...

  4. 在asp.net core中使用cookie认证

    以admin控制器为要认证的控制器举例 1.对控制器设置权限特性 //a 认证命名空间 using Microsoft.AspNetCore.Authorization; using Microsof ...

  5. c#开发移动APP-Xamarin入门扩展剖析

    原文:c#开发移动APP-Xamarin入门扩展剖析 上节将Phoneword应用程序扩展到包含第二个屏幕,该屏幕可以跟踪应用程序的拨打历史 Navigation Xamarin.Form提供了一个内 ...

  6. ASP.NET Core MVC 设计模式 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core MVC 设计模式 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core MVC 设计模式 上一章节中,我们提到 ASP.NET Co ...

  7. bigdata_mac下安装spark_scala

    Java 下载安装Mac对应版本的JDK. Apache-spark $ brew update $ brew info apache-spark $ brew install apache-spar ...

  8. Symbol not found: _lua_objlen

    lua: error loading module 'cjson' from file '/usr/local/lib/lua/5.3/cjson.so': dlopen(/usr/local/lib ...

  9. vagrant up default: Warning: Authentication failure. Retrying...的一些解决办法

    vagrant up default: Warning: Authentication failure. Retrying...的一些解决办法 一般看到这个信息时,虚拟机已经启动成功,可以中断命令后v ...

  10. 漫谈 JVM —— 内存

    JVM 是什么呢?说的直白点就是 Java 代码运行的地方,全称 Java Virtural Machine,Java 虚拟机.有的人就会奇怪了,为什么 Java 程序员需要了解这个东西?毕竟大多数情 ...