在分析Avro源码时,发现Avro为了对int、long类型数据压缩,采用Protocol Buffers的ZigZag编码(Thrift也采用了ZigZag来压缩整数)。

1. 补码编码

为了便于后面的分析,我们先回顾下几个概念:

  • 原码:最高位为符号位,剩余位表示绝对值;
  • 反码:除符号位外,对原码剩余位依次取反;
  • 补码:对于正数,补码为其自身;对于负数,除符号位外对原码剩余位依次取反然后+1。

补码解决了原码中\(0\)存在两种编码的问题:

\[0=[0000 \enspace 0000]_原=[1000 \enspace 0000]_原
\]

补码\([1000 \enspace 0001]_补\) 表示\(-127\);此外,原码中还存在加法错误的问题:

\[1 + (-1) = [0000 \enspace 0001]_原 + [1000 \enspace 0001]_原 = [1000 \enspace 0010]原 = -2
\]

若用补码,则可得到正确结果:

\[ 1 + (-1) = [0000 \enspace 0001]_补 + [1111 \enspace 1111]_补 = [0000 \enspace 0000]_补 = 0
\]

因此,在计算机存储整数时,采用的是补码。此外,整数的补码有一些有趣的性质:

  • 左移1位(n << 1),无论正数还是负数,相当于乘以2;对于正数,若大于Integer.MAX_VALUE/2(1076741823),则会发生溢出,导致左移1位后为负数
  • 右移31位(n >> 31),对于正数,则返回0x00000000;对于负数,则返回0xffffffff

这些性质正好在ZigZag编码中用到了。

2. ZigZag

对于int值1,-1,20151103,均是用4 Bytes来表示:

\[1 = [00 \enspace 00 \enspace 00 \enspace 01] \\
-1 = [ff \enspace ff \enspace ff \enspace ff] \\
20151103 = [01 \enspace 33 \enspace 7b \enspace 3f]
\]

在《Huffman编码》中证明了压缩编码应满足:

高概率的码字字长应不长于低概率的码字字长

一般情况下,使用较多的是小整数,那么较小的整数应使用更少的byte来编码。基于此思想,ZigZag被提出来。

编码

首先,ZigZag按绝对值升序排列,将整数hash成递增的32位bit流,其hash函数为h(n) = (n << 1) ^ (n >> 31);对应地long类型(64位)的hash函数为(n << 1) ^ (n >> 63)。整数的补码(十六进制)与hash函数的对应关系如下:

n hex h(n) ZigZag (hex)
0 00 00 00 00 00 00 00 00 00
-1 ff ff ff ff 00 00 00 01 01
1 00 00 00 01 00 00 00 02 02
-2 ff ff ff fe 00 00 00 03 03
2 00 00 00 02 00 00 00 04 04
... ... ... ...
-64 ff ff ff c0 00 00 00 7f 7f
64 00 00 00 40 00 00 00 80 80 01
... ... ... ...

拿到hash值后,想当然的编码策略:直接去掉hash值的前导0之后的byte作为压缩编码。但是,为什么ZigZag(64)=8001呢?这涉及到编码唯一可译性的问题,只有当编码为前缀码才能保证可译,即

任意一码字均不为其他码字的前缀

我们来看看,如果按上面的策略做压缩编码,则

h(0) = 0x0 = [00]
h(64) = 0x80 = [80]
h(16384) = 0x8000 = [80 00]

那么,当收到字节流[80 00]时,是应解码为两个整数64, 00,还是一个整数16384?因此,为了保证编码的唯一可译性,需要对hash值进行前缀码编码,ZigZag采用了如下策略:

input: int n
output: byte[] buf loop
if 第七位满1或有进位:
n |= 0x80;
取低位的8位作为一个byte写入buf;
n >>>=7(无符号右移7位,在高位插0);
else:
取低位的8位作为一个byte写入buf
end

ZigZag编码的Java实现(从org.apache.avro.io.BinaryData抠出来的):

/** Encode an integer to the byte array at the given position. Will throw
* IndexOutOfBounds if it overflows. Users should ensure that there are at
* least 5 bytes left in the buffer before calling this method.
* @return The number of bytes written to the buffer, between 1 and 5.
*/
public static int encodeInt(int n, byte[] buf, int pos) {
// move sign to low-order bit, and flip others if negative
n = (n << 1) ^ (n >> 31);
int start = pos;
if ((n & ~0x7F) != 0) {
buf[pos++] = (byte)((n | 0x80) & 0xFF);
n >>>= 7;
if (n > 0x7F) {
buf[pos++] = (byte)((n | 0x80) & 0xFF);
n >>>= 7;
if (n > 0x7F) {
buf[pos++] = (byte)((n | 0x80) & 0xFF);
n >>>= 7;
if (n > 0x7F) {
buf[pos++] = (byte)((n | 0x80) & 0xFF);
n >>>= 7;
}
}
}
}
buf[pos++] = (byte) n;
return pos - start;
}

ZigZag是一种变长编码,当整数值较大时,hash值的十六进制的有效位会较长,对应地ZigZag码字会出现需要5 byte存储;比如,

ZigZag(Integer.MAX_VALUE)=[fe ff ff ff 0f]

解码

解码为编码的逆操作,首先,将ZigZag编码还原成hash值,然后用hash函数\(h(n)\)的逆函数\(h^{-1}(n)\) = (n >>> 1) ^ -(n & 1)得到原始的整数值。Java代码实现(在avro源码org.apache.avro.io.BinaryDecoder中)如下:

public static int readInt(byte[] buf, int pos) throws IOException {
int len = 1;
int b = buf[pos] & 0xff;
int n = b & 0x7f;
if (b > 0x7f) {
b = buf[pos + len++] & 0xff;
n ^= (b & 0x7f) << 7;
if (b > 0x7f) {
b = buf[pos + len++] & 0xff;
n ^= (b & 0x7f) << 14;
if (b > 0x7f) {
b = buf[pos + len++] & 0xff;
n ^= (b & 0x7f) << 21;
if (b > 0x7f) {
b = buf[pos + len++] & 0xff;
n ^= (b & 0x7f) << 28;
if (b > 0x7f) {
throw new IOException("Invalid int encoding");
}
}
}
}
}
pos += len;
return (n >>> 1) ^ -(n & 1); // back to two's-complement
}

ZigZag总结如下:

  1. ZigZag仅从经验出发,认为较小的整数会有较大的概率出现,故设计编码策略:小整数对应的ZigZag码字短,大整数对应的ZigZag码字长。
  2. 但是,在特定的场景下,比如,要传输的整数为大整数居多,ZigZag编码的压缩效率就不理想了。

整数压缩编码 ZigZag的更多相关文章

  1. protobuf编码

     proto2 Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化,适合做数据存储或 RPC 数据交换格式.可用于通讯协议.数据存储等领域的语言无关.平台无 ...

  2. protocol buffer 整数序列化

    http://blog.csdn.net/csfreebird/article/details/7624807 varints用于正整数 (无符号整数) varints 是 一个很不错的技术.将一个整 ...

  3. 高效的数据压缩编码方式 Protobuf

    一. protocol buffers 是什么? Protocol buffers 是一种语言中立,平台无关,可扩展的序列化数据的格式,可用于通信协议,数据存储等. Protocol buffers ...

  4. CSP 201612-4 压缩编码 【区间DP+四边形不等式优化】

    问题描述 试题编号: 201612-4 试题名称: 压缩编码 时间限制: 3.0s 内存限制: 256.0MB 问题描述: 问题描述 给定一段文字,已知单词a1, a2, …, an出现的频率分别t1 ...

  5. HNUSTOJ-1520 压缩编码

    1520: 压缩编码 时间限制: 1 Sec  内存限制: 2 MB提交: 107  解决: 54[提交][状态][讨论版] 题目描述 某工业监控设备不断发回采样数据.每个数据是一个整数(0到1000 ...

  6. zigzag压缩算法

    前文 Base 128 Varints 编码(压缩算法) 介绍了Base 128 Varints这种对数字传输的编码,了解到了这种编码方式是为了最大程度压缩数字的.但是,在前文里,我们只谈论到了正数的 ...

  7. C语言 · 查找整数 · 基础练习

    问题描述 给出一个包含n个整数的数列,问整数a在数列中的第一次出现是第几个. 输入格式 第一行包含一个整数n. 第二行包含n个非负整数,为给定的数列,数列中的每个数都不大于10000. 第三行包含一个 ...

  8. C语言 · 整数平均值

    编写函数,求包含n个元素的整数数组中元素的平均值.要求在函数内部使用指针操纵数组元素,其中n个整数从键盘输入,输出为其平均值. 样例输入: (输入格式说明:5为输入数据的个数,3 4 0 0 2 是以 ...

  9. C++整数转字符串的一种方法

    #include <sstream> //ostringstream, ostringstream::str() ostringstream stream; stream << ...

随机推荐

  1. 用c#创建支持多语言的WinForm应用程序

    实现多语言的方法可能有使用资源文件,或者配置xml两种方法吧.没时间研究过多,学习了一下使用资源文件的方法,成功了. 在.net2.0 中,m$ 为我们提供了一种简单方便的方法, 使用资源文件 1.新 ...

  2. c#控制打印机杂项

    因项目中需要用到控制打印机的相关信息,此贴将网络寻找的资料做了些整理 1. C# 如何设置系统的默认打印机 using System.Runtime.InteropServices;   [DllIm ...

  3. 第一章-第十五题(谈谈你对压力的看法,以及怎么和别人合作, 帮助别人,把压力转化为动力,在互相帮助的环境中成长。)--By林培文

    压力是一把双刃剑,有害也有利,重点是如何看待压力,从而做到趋利避害,化压力为动力.对于当前的我来说,压力利大于弊.形成这一想法,我经历了好几个阶段.第一个阶段是小学时代,那会整天就知道玩,也没什么压力 ...

  4. PHP基础知识之对象复制

    对象的复制默认为浅复制 进行深复制的方法为:在类中定义魔法方法__clone(),类的对象复制时,会自动调用 __clone方法,在 __clone方法中可以进行各种复制对象的个性化 class My ...

  5. 通过Javascript得到URL中的参数(query string)

    我们知道,"GET"请求中,通常把参数放在URL后面,比如这样http://www.cnblogs.com/season-huang/index?param=yes&art ...

  6. 眼见为实:.NET类库中的DateTimeOffset用途何在

    在 EnyimMemcachedCore(支持.NET Core的memached客户端)中实现 Microsoft.Extensions.Caching.Distributed.IDistribut ...

  7. MyBatis(跨表查询)

    1.建立数据库表如下: DROP TABLE IF EXISTS t_demo_user; CREATE TABLE IF NOT EXISTS t_demo_user( userid ), user ...

  8. 黑科技:gif二维码

    本篇文章是缘于在微博上看到了一的有意思的东西.由于腾讯与阿里的竞争关系,如果你是一个大V,在微博上发布微信的二维码会被屏蔽掉.于是有人发现了这样一个现象:人眼有视觉暂留效应,手机的摄像头由于捕捉影像的 ...

  9. ASP.NET MVC学前篇之请求流程

    ASP.NET MVC学前篇之请求流程 请求流程描述 对于请求的流程,文章的重点是讲HttpApplication和HttpModule之间的关系,以及一个简单的示例实现.(HttpModule又是M ...

  10. Android EditText悬浮在输入法之上

    Android EditText悬浮在输入法之上 使用 android:windowSoftInputMode="adjustResize" 会让界面整体被顶上去,很多时候我们不需 ...