位图作为一种简高效使用内存的数据结构,在很多场合都能够用最省的内存表达大量的数据。我对位图最早的印象来自于《编程珠玑》中用位图结构来存储电话号码。感叹其简单、方便。本质上,位图是一个存储单个位的数组,每一个位表示一个数组元素。例如如果我们需要标记100万用户的在线状态,则可以将每个用户对应到一个位,只需要100万个位(约0.125M内存)的位图就可以表示了。如下图所示,在线的用户标记为绿色。其存储则可表示为 {01010011 0011…}。
[img]http://dl.iteye.com/upload/attachment/467881/f13b26bb-7295-3dcb-b465-d52430ef3a01.png[/img]
但是对于某些比较稀疏的位图,其存储效率也会有一些问题。如果100万用户平均只有100个人在线,那另外的999,900个位就浪费掉了。例如在第1位有一个用户,第1,000,000位有个用户,需要1M个位来保存。既浪费了存储空间,也带来大量无用的运算。在前不久我们的应用中就有这种问题。未经压缩的位图保存到数据库中,结果大量的IO操作对数据库的性能造成较大影响。
因为稀疏的位图比较多,比较直接地就想去掉中间的这些空白位,于是就想这样表示:存储第一个位的值(0或1),接着将接下来的所有值相同的位的数目保存为一个整数,再保存接下来另的一类连续位的数目,依此类推。如:{0000 0000 1000 0000 0000 1100 0001 0000 …… } 保存为 0,7,1,11,2,5…。 如果是一百万个位,只有第一个和最后一个是1{10000 …. …. 1},则保存为{1,999998,1}只需要三个整数就可以了!(注:实际上,早有人完整地将这种方法实现了,参考D-Gap Compression)
于是对比较稀疏的位图应用这种方法,由于大部分位图都是比较稀疏,90%以上的位图的体积最终缩小了10倍。在测试过程中,发现最终存储的是大量的小整数(小于1000)。这些小整数其实可以用更短的类型来表示,于是应用变长编码来处理。其原理是每一个字节的高位做标识,其余7位存储值。1标识从这个字节开始是一个新的整数,0表示是上一个字节的延续。则区间[1, 2^7 )中的整数占用一个字节,[2^7, 2^14)中的整数占用两个字节,依次类推。
最终,90%以上的位图体积压缩了30倍以上。
[img]http://dl.iteye.com/upload/attachment/467881/f13b26bb-7295-3dcb-b465-d52430ef3a01.png[/img]
但是对于某些比较稀疏的位图,其存储效率也会有一些问题。如果100万用户平均只有100个人在线,那另外的999,900个位就浪费掉了。例如在第1位有一个用户,第1,000,000位有个用户,需要1M个位来保存。既浪费了存储空间,也带来大量无用的运算。在前不久我们的应用中就有这种问题。未经压缩的位图保存到数据库中,结果大量的IO操作对数据库的性能造成较大影响。
因为稀疏的位图比较多,比较直接地就想去掉中间的这些空白位,于是就想这样表示:存储第一个位的值(0或1),接着将接下来的所有值相同的位的数目保存为一个整数,再保存接下来另的一类连续位的数目,依此类推。如:{0000 0000 1000 0000 0000 1100 0001 0000 …… } 保存为 0,7,1,11,2,5…。 如果是一百万个位,只有第一个和最后一个是1{10000 …. …. 1},则保存为{1,999998,1}只需要三个整数就可以了!(注:实际上,早有人完整地将这种方法实现了,参考D-Gap Compression)
于是对比较稀疏的位图应用这种方法,由于大部分位图都是比较稀疏,90%以上的位图的体积最终缩小了10倍。在测试过程中,发现最终存储的是大量的小整数(小于1000)。这些小整数其实可以用更短的类型来表示,于是应用变长编码来处理。其原理是每一个字节的高位做标识,其余7位存储值。1标识从这个字节开始是一个新的整数,0表示是上一个字节的延续。则区间[1, 2^7 )中的整数占用一个字节,[2^7, 2^14)中的整数占用两个字节,依次类推。
最终,90%以上的位图体积压缩了30倍以上。