整数压缩编码 ZigZag
在分析Avro源码时,发现Avro为了对int、long类型数据压缩,采用Protocol Buffers的ZigZag编码(Thrift也采用了ZigZag来压缩整数)。
1. 补码编码
为了便于后面的分析,我们先回顾下几个概念:
- 原码:最高位为符号位,剩余位表示绝对值;
 - 反码:除符号位外,对原码剩余位依次取反;
 - 补码:对于正数,补码为其自身;对于负数,除符号位外对原码剩余位依次取反然后+1。
 
补码解决了原码中\(0\)存在两种编码的问题:
\]
补码\([1000 \enspace 0001]_补\) 表示\(-127\);此外,原码中还存在加法错误的问题:
\]
若用补码,则可得到正确结果:
\]
因此,在计算机存储整数时,采用的是补码。此外,整数的补码有一些有趣的性质:
- 左移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 = [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总结如下:
- ZigZag仅从经验出发,认为较小的整数会有较大的概率出现,故设计编码策略:小整数对应的ZigZag码字短,大整数对应的ZigZag码字长。
 - 但是,在特定的场景下,比如,要传输的整数为大整数居多,ZigZag编码的压缩效率就不理想了。
 
整数压缩编码 ZigZag的更多相关文章
- protobuf编码
		
proto2 Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化,适合做数据存储或 RPC 数据交换格式.可用于通讯协议.数据存储等领域的语言无关.平台无 ...
 - protocol buffer 整数序列化
		
http://blog.csdn.net/csfreebird/article/details/7624807 varints用于正整数 (无符号整数) varints 是 一个很不错的技术.将一个整 ...
 - 高效的数据压缩编码方式 Protobuf
		
一. protocol buffers 是什么? Protocol buffers 是一种语言中立,平台无关,可扩展的序列化数据的格式,可用于通信协议,数据存储等. Protocol buffers ...
 - CSP 201612-4 压缩编码 【区间DP+四边形不等式优化】
		
问题描述 试题编号: 201612-4 试题名称: 压缩编码 时间限制: 3.0s 内存限制: 256.0MB 问题描述: 问题描述 给定一段文字,已知单词a1, a2, …, an出现的频率分别t1 ...
 - HNUSTOJ-1520 压缩编码
		
1520: 压缩编码 时间限制: 1 Sec 内存限制: 2 MB提交: 107 解决: 54[提交][状态][讨论版] 题目描述 某工业监控设备不断发回采样数据.每个数据是一个整数(0到1000 ...
 - zigzag压缩算法
		
前文 Base 128 Varints 编码(压缩算法) 介绍了Base 128 Varints这种对数字传输的编码,了解到了这种编码方式是为了最大程度压缩数字的.但是,在前文里,我们只谈论到了正数的 ...
 - C语言  ·  查找整数  ·  基础练习
		
问题描述 给出一个包含n个整数的数列,问整数a在数列中的第一次出现是第几个. 输入格式 第一行包含一个整数n. 第二行包含n个非负整数,为给定的数列,数列中的每个数都不大于10000. 第三行包含一个 ...
 - C语言  ·  整数平均值
		
编写函数,求包含n个元素的整数数组中元素的平均值.要求在函数内部使用指针操纵数组元素,其中n个整数从键盘输入,输出为其平均值. 样例输入: (输入格式说明:5为输入数据的个数,3 4 0 0 2 是以 ...
 - C++整数转字符串的一种方法
		
#include <sstream> //ostringstream, ostringstream::str() ostringstream stream; stream << ...
 
随机推荐
- Swift库运行崩溃
			
报错如下: 解决方法: 退出 Xcode 找到 DerivedData 文件夹 删除 (路径: ~/Library/Developer/Xcode/DerivedData) 删除 com.apple. ...
 - SQL——行值表达式(Row Value Expressions)
			
概述 最近接触了一个新概念——行值表达式,也叫做行值构造器.这是一个很强大的SQL功能,通常我们所操作的SQL表达式都只能针对一行中的单一字段进行操作比较,而行值表达式可以针对一行中的多个字段进行操作 ...
 - windows多线程编程星球(一)
			
以前在学校的时候,多线程这一部分是属于那种充满好奇但是又感觉很难掌握的部分.原因嘛我觉得是这玩意儿和编程语言无关,主要和操作系统的有关,所以这部分内容主要出现在讲原理的操作系统书的某一章,看完原理是懂 ...
 - 实验环境里新创建成功的web application却在浏览器中返回404错误
			
刚刚翻笔记翻到一些刚学SharePoint时候解决的一些很2的初级问题,本来是有些挣扎该不该把它们记录到这个blog里的?因为担心这些很初级的文章会拉低这个blog的逼格,但是我的哥们善意的提醒了我一 ...
 - shell简单用法笔记(一)
			
一.linux中主要用的bash shell:查看linux系统中支持的shell种类可用: vim /etc/shell 执行shel脚步的方式: 1.赋予脚步可执行权限,使用相对或绝对路径调用该脚 ...
 - Python黑帽编程1.2  基于VS Code构建Python开发环境
			
Python黑帽编程1.2 基于VS Code构建Python开发环境 0.1 本系列教程说明 本系列教程,采用的大纲母本为<Understanding Network Hacks Atta ...
 - 一个新人如何学习在大型系统中添加新功能和Debug
			
文章背景: 今年七月份正式入职,公司主营ERP软件,楼主所在的组主要负责二次开发,使用的语言是Java. 什么叫二次开发呢?ERP软件的客户都是企业.而这些企业之间的情况都有所不同,一套标准版本的企业 ...
 - Step by Step 创建一个 Web Service
			
原创地址:http://www.cnblogs.com/jfzhu/p/4022139.html 转载请注明出处 (一)创建Web Service 创建第一个项目,类型选择ASP.NET Empty ...
 - MA均线组合
			
MA5.MA13.MA21.MA34.MA55.MA90.MA120.MA250
 - Android开发学习之路-Handler消息派发机制源码分析
			
注:这里只是说一下sendmessage的一个过程,post就类似的 如果我们需要发送消息,会调用sendMessage方法 public final boolean sendMessage(Mess ...