再谈数据压缩

来源:互联网 发布:c语言小写变大写 编辑:程序博客网 时间:2024/04/19 09:43

这里想再谈谈在存储导航数据中,用到的一些实用的压缩技巧。

数据存储的时候,我们总是会按照一定的单元来存储。类似于一个一个的block,读取的时候,也是以block为单位进行读取。

我们假设,1000条某种记录存储为一个block,对每条记录赋一个id(从0开始计数),我们可以通过id就可以读取到对应的记录信息。如何以最小的size来存储这1000条记录?

1,如果记录是定长,比如每条记录为5个字节。因为是定长,我们不需要任何额外的信息来定位记录,只要通过记录的id,乘以记录的长度5,就可以知道对应的记录的offset。这样在存储的时候,我们只需要顺序的把每条记录依次写入block就可以。大小=1000*5,也就是5,000个字节。

2,如果记录是变长的。在导航中,其实很多属性只是部分记录有,很多记录完全和这些属性无关。这时候我们一般用变长来存储记录,就是有多少属性就存多少属性,没有就不存。我们同样做个假设,在这1000条记录中,有80%的记录只需要5个字节来存储,其他20%的记录需要10个字节来存储。

    2.1  如果我们还是打算用定长来存储,这样的话,每条记录必须用最长的size来存储。这个例子就是每条记录需要用10个字节来存储。一共需要:10*1000=10,000个字节。


    2.2  如果用变长来存储,我们就需要每条记录的起始位置offset(这个例子中offset可以用2个字节来存储,2个字节可以包含64k的地址范围,对于一般的应用完全足够)。我们可以在block的开头把每条记录的offset先存起来,即用一个数组把所有offset都记下来,再后面顺序把每条记录的具体内容用变长存储起来。当获取具体的那条记录时候,通过id在offset的数组取到对应记录的offset,从这个offset表明的地址开始读取记录,到这条记录的offset和下一条记录的offset之差就是当前记录的长度(为了统一操作,offset数组的长度应该是记录数+1,这样上下2个offset之差就是记录的长度,最后一条记录也是如此)。这种方式需要用到的字节数为:1001*2 + 5*800 + 10*200 = 8001个字节,比第一种方法节省了2000个字节。


    2.3 我们对每条记录多了一个2字节的offset,如果我们能够把offset的字节压倒1个字节。但是1个字节是无法包含大于256的地址空间。不过我们可以把这个字节当成end标志来表示记录结束。就像通过‘\0’来表示字符串的结束一样。我们从开头开始读取block的字节流,如果遇到一个字节为0,我们就知道当前记录的信息结束了,开始下一条记录。这样我们需要的字节数为:1000*1+ 5*800 + 10*200 = 7000个字节。不过这种方式有个毛病,就是不能够随机访问记录。每次访问一条记录,我们总是需要从第一条记录开始顺序判断是否抵到所要的那条记录。为了解决这个问题,我们可以在内存中通过一次扫描,在内存中生成所需要的offset的数组,以后读取就可以随机访问了。

 

   2.4 上面有个方式是用2个字节作为offset来实现随机访问。其实我们可以用更少的字节来存储offset。当我们用2个字节记录第0个offset的时候,下一个offset其实用1个字节就可以了,因为下一个offset和上一个offset之差不会超过256,所以我们可以只用1个字节来来存储下一个offset。同理下下个offset也可以用1个字节来存储offset。当然这样推理是有限度的,直达差值大于256的时候,就不可以用1个字节来存储了,这时候我们再用2个字节来存储一个全新的offset。现在问题就转变为,我们如何把握啥时候存个全新的2字节的offset,啥时候存储1个字节的offset。

       比较好的处理就是用2的幂的个数来确定。比如每4个offset一组,第一个offset用2个字节,后面的3个offset就用1个字节存储和第一个offset的差值(这里存的差值都是和第一offset的差值,而不是上一个offset的差值,这样做的目的是为了在还原offset的时候减少些加法运算,否则你需要不断的自前往后叠加所有的offset才知道当前的offset。)。当然我们也可以8个offset为一组,只要我们能够保证这一组offset的总共的数据size必须在256个字节以内就可以。

              (4个offset为一组)2字节 offset1字节 delta-offset1字节 delta-offset1字节 delta-offset2字节 offset1字节 delta-offset。。。。。。。。      这个例子中,最长的记录为10个字节,也就是16个offset为一组,最大的size范围为160个字节,小于256.我们可以采用16个offset为一组来存储。所需要的字节数大约为:63*17 + 5*800 + 10*200 = 7061个字节。


3,上面提到过offset一般用2个字节存储,这样可以覆盖的地址范围为64k。有时候我们一个block本身就会设置的很大,毕竟现在是大数据时代,64k一点都不大。如果block的size大于64k,我们就只能用3个字节来存储offset了,这无形中也就增加了size。其实通过一些技巧,我们可以让2个字节的offset突破64k的限制。

比如,如果我们知道每条记录的大小都是2的整数倍。这时候offset的最后一个bit是可以不存的,这样2个字节的offset就可以索引到128k的地址空间(如果大部分的记录都是2的整数倍,只有非常少的记录不是,也可以用这个方法,那就是那些不是偶数字节的记录自动补一个字节成偶数。)。还有,在上面这个例子中,我们知道记录的size最小是5字节,那么我们的offset可以是把所有5字节都扣除后的offset,这样也会使地址空间变大。