有序整数序列的压缩方法

1. 整数的变长编码

utf8编码

utf8是一种变长编码方式,使用1~4个字节表示一个符号,参考 字符编码问题
utf8的编码规则如下表,字母x表示可用比特:

字节数二进制表示有效比特数目可表示的最大编码编码范围(16进制)
10xxxxxxx70x7F0000 0000-0000 007F
2110xxxxx 10xxxxxx110x7FF0000 0080-0000 07FF
31110xxxx 10xxxxxx 10xxxxxx160xFFFF0000 0800-0000 FFFF
411110xxx 10xxxxxx 10xxxxxx 10xxxxxx210x1FFFFF0001 0000-0010 FFFF

这种编码方式冗余度较高,每个字节都可以根据它的高位的若干比特确定它在整个二进制表示中的位置(比如是首个字节还是非首个字节),即使字节流中丢一些字节,也能定位出下个字符的起始字节。
有时候这种冗余是不必要的。

protocolbuf中的整数编码

base-128变长编码:

  • 每个字节使用低7位表示数字,除了最后一个字节,其他字节的最高位都设置为1
  • 采用Little-Endian字节序

例如1的编码是00000001,200(0xC8,110010002)的编码是11001000 00000001(7位一组分为12和10010002,最低有效组在前面)。
其实就是,“0”标识0到0x7F的数字,“10”标识0x80到0x3FFF的数字,“110”标识0x40000到‭0x1FFFFF‬的数字……
这些数字是不是很熟,不就是霍夫曼编码么。
这种编码有效的前提是一个数字有比较大的可能性是小数字,也就是高位有比较多的0,也就只适用于0和正整数。

ZigZag编码:
考虑以上编码方法中是对什么进行编码呢?是整数的内存表示,也就是补码,正数的补码就是它本身,负数的补码中会有很多前导1,用上面的方法会占用多个字节,所以只适用于无符号数。对于有符号数,考虑先转换成无符号数。
最简单的想法,把符号位从最高位移到最低位不就行了吗,除了符号位的其他位,用来表示数字的绝对值,以8位整数为例展示这种想法:

原数字补码编码方式对应无符号数字负数的编码-2
00000 00000000 00000
10000 00010000 00102
-11111 11110000 001130000 0001
20000 00100000 01004
-21111 11100000 010150000 0011

转换的数字缺了个1,强迫症受不了,于是把所有负数的编码减去个2就可以了,然后发现负数的新编码的高7位和补码的低7位正好相反。所以,可以这样描述这种新编码方式:非负数是左移1位,负数是最高位移到最低位,其他位取反,可以统一用一个式子来计算(假定的32位整数):(n << 1) ^ (n >> 31),这就是ZigZag编码了。

2. delta-encoding

比如现在有整数列表[250,265,273,345,423],转化成每个整数相对于前一个整数的增量值,增量列表就是[250,15,8,72,73]。数字变小了更容易压缩,比如使用上节中的编码方法。

前缀压缩

让我联想起在leveldb的data block中用到的前缀压缩。
data block中存储的一系列的entry,一个entry是一个key-value对,key和value都是字符串。
各个entry是以key排序的,所以相邻的两个key很可能有一个公共的前缀,为了节省空间,可以采用前缀压缩。
每个entry由以下字段构成:shared_bytes,unshared_bypes,value_bytes,unshared_key_data,value_data。
举个例子,有以下的key:fantastic,fantasyboy,fantasygirl,压缩方式如下:

shared_bytesunshared_bypesunshared_key_data
09fantastic
64yboy
74girl

frame of reference

ElasticSearch使用名为frame of reference的更精细的压缩方式。
具体方法是:
将所有整数进行增量编码,将增量编码分成很多个block,每个block包含256个整数
计算出存储每个block里面所有整数最多需要多少位,把位数作为头信息放在每个block的前面

下图是来自 Frame of Reference and Roaring Bitmaps 中的一个示例(假设每个block只有3个文件而不是256)。
frame of reference

3. bitmap

不是过分稀疏的整数序列用bitmap来存储是比较合适的。
对于特别稀疏的序列,其实也可以用bitmap,不过要配套使用一些压缩方案,比如基于Run-Length Encoding的WAH (Word Aligned Hybrid Compression Scheme),Concise(Compressed ‘n’ Composable Integer Set)等。
Better bitmap performance with Roaring bitmaps 提出了Roaring Bitmap,表现比WAH,Concise更优秀,github 上有多种实现。
下面根据 高效压缩位图RoaringBitmap的原理与应用 简单描述下RoaringBitmap。

将32位无符号整数按照高16位分桶,即最多可能有2^16=65536个桶,论文内称为container。存储数据时,按照数据的高16位找到container(找不到就会新建一个),再将低16位放入container中。也就是说,一个RoaringBitmap就是很多container的集合。
当桶内数据的数量不大于4096时,采用ArrayContainer来存储,其本质上是一个unsigned short类型的有序数组。当桶内数据的数量大于4096时,会采用BitmapContainer来存储,其本质就是普通位图,用长度固定为1024的unsigned long型数组表示,共有2^16个位,可存储65536个数据。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值