在分析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. ASP.NET跨平台实践:无需安装Mono的Jexus“独立版”

    在Linux上运行ASP.NET网站或WebApi的传统步骤是,先安装libgdiplus,再安装mono,然后安装Jexus.在这个过程中,虽然安装Jexus是挺简便的一件事,但是安装mono就相对 ...

  2. .NET不可变集合已经正式发布

    微软基础类库(Base Class Library)团队已经完成了.NET不可变集合的正式版本,但不包括ImmutableArray.与其一起发布的还包括针对其它不可变对象类型的设计指南. 如果你需要 ...

  3. Java NIO4:Socket通道

    Socket通道 上文讲述了通道.文件通道,这篇文章来讲述一下Socket通道,Socket通道与文件通道有着不一样的特征,分三点说: 1.NIO的Socket通道类可以运行于非阻塞模式并且是可选择的 ...

  4. Linux的tree命令

    发现Linux下有个很好的命令,tree,能把目录以树的形式列出来,还支持很强大的参数. 但默认情况下是不带的,得自己去安装,先到这里下载它的代码:http://mama.indstate.edu/u ...

  5. 用chrome来映射查找样式对应的sass

    较新版本的sass(3.3+)支持source-map功能,可以配合谷歌浏览器或者livestyle来映射查找对应的样式. 要生成source-map可以在grunt中使用 grunt-contrib ...

  6. Azure SQL Database (21) 将整张表都迁移到Azure Stretch Database里

    <Windows Azure Platform 系列文章目录>  Azure SQL Database (19) Stretch Database 概览      Azure SQL Da ...

  7. Content-Type List

    Content-Type List Description of Data Content Typical Filename Extensions MIME type/subtype       Te ...

  8. 一个美术需求引发的Custom Inspector

    需求 Editor模式下,在运行或者非运行状态下,能够按照指定的变化率来自动改变material中属性数值. 需求分析 如何在Editor模式下获得一个游戏对象及其组件,尤其是在非运行状态下?我们知道 ...

  9. Sql Server系列:日期和时间函数

    1. 获取系统当前日期函数GETDATE() GETDATE()函数用于返回当前数据库系统的日期和时间,返回值的类型为datetime. SELECT GETDATE() 2. 返回UTC日期的函数G ...

  10. IOS开发资料汇总

    1 IOS账号注册.程序发布流程 1)http://jamesli.cn/blog/?p=955 2)http://jamesli.cn/blog/?p=966 3)http://jamesli.cn ...