前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >整数编码之 ZigZag

整数编码之 ZigZag

原创
作者头像
Jean
发布2020-04-22 10:02:40
2K0
发布2020-04-22 10:02:40
举报
文章被收录于专栏:Web行业观察Web行业观察

zigzag同原码、2的补码一样,是整数的3大编码之一。

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

1. 补码编码

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

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

补码解决了原码中00存在两种编码的问题:

0=[00000000]原=[10000000]原0=[00000000]原=[10000000]原

补码[10000001]补[10000001]补 表示−127−127;此外,原码中还存在加法错误的问题:

1+(−1)=[00000001]原+[10000001]原=[10000010]原=−21+(−1)=[00000001]原+[10000001]原=[10000010]原=−2

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

1+(−1)=[00000001]补+[11111111]补=[00000000]补=01+(−1)=[00000001]补+[11111111]补=[00000000]补=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=[00000001]−1=[ffffffff]20151103=[01337b3f]1=[00000001]−1=[ffffffff]20151103=[01337b3f]

在《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呢?这涉及到编码唯一可译性的问题,只有当编码为前缀码才能保证可译,即

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

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

代码语言:javascript
复制
h(0) = 0x0 = [00]
h(64) = 0x80 = [80]
h(16384) = 0x8000 = [80 00]

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

代码语言:javascript
复制
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抠出来的):

代码语言:javascript
复制
/** 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存储;比如,

代码语言:javascript
复制
ZigZag(Integer.MAX_VALUE)=[fe ff ff ff 0f]

解码

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

代码语言:javascript
复制
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编码的压缩效率就不理想了。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 补码编码
  • 2. ZigZag
    • 编码
      • 解码
      • ZigZag总结如下:
      相关产品与服务
      文件存储
      文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档