如何压缩存储数字

来源:互联网 发布:传智播客python百度云 编辑:程序博客网 时间:2024/05/20 09:24

在存储数据的时候,我们大部分的时候都是在存储数字。如何高效且用最少的字节的来存储这是个有很多技巧的问题。

现总结些经验如下.

一,  定长存储,就是每个值用等长的字节数来存储。这种方法是显而易见的。

       1,确定存储的数的最大值,确定用多少个字节就可以存储。比如存储的数值最大不会超过256,这是用1个字节来存储将是最划算。如果最大值不会超过0xFFFFFF,用3个字节存储最划算。

       2,当用多字节来存储的时候,固定的字节流顺序存储可以解决大端小端对齐不一致问题,强烈提醒不要直接把一个int型写到文件存储,除非你保证一辈子都不关心大端小端对齐问题。比如存储3个字节长度的数值代码:

static  void pack_uint24(UINT8 *addr, UINT32 u24 )
{
    assert(u24 <= 0xFFFFFF);
    addr[0] = (u24 >>  0) & 0xFF;
    addr[1] = (u24 >>  8) & 0xFF;
    addr[2] = (u24 >> 16) & 0xFF;
}

即总是先存储低字节,后存储高字节。解析的时候代码:

static  UINT32 unpack_uint24( const UINT8 addr[3]  )
{
    return ((UINT32)addr[0] <<  0) |
        ((UINT32)addr[1] <<  8) |
        ((UINT32)addr[2] << 16);
}

二, 变长存储,用不固定的字节数来存储数值。这样比固定字节存储节省很多空间。

       1,用1-2个字节存储不大于0x7FFF的数值。通过第一个字节的最高位来确定是1个字节还是2个字节,当最高位为0,表示一个字节存储,当最高位为1,表示2个字节存储。

//  msb       format
// ---------------
//  0*,*  ..... 1 byte  (1 +  7 bits)  表示的数值范围为0x0 ~ 0x7F
//  1*,* ..... 2 bytes (1 + 15 bits) 表示的数值范围为0x0 ~ 0x7FFF

这里面还有个技巧,就是当msb为1的时候,也覆盖了0 ~ 0x7F的数值范围。为了表示更广泛的范围,可以把当msb为1的时候的数值范围修改为:0x80 ~ 0X807F,即当是2个字节表示的时候,得到的数值加上0x80为真实的存储数值。

//  msb       format
// ---------------
//  0*,*  ..... 1 byte  (1 +  7 bits)  表示的数值范围为0x0 ~ 0x7F
//  1*,* ..... 2 bytes (1 + 15 bits) 表示的数值范围为0x80 ~ 0x807F


     2, 用1-3个字节存储不大于0x3FFFFF的数值,由于字节数有3种情况(1个字节,2个字节,3个字节),所以需要预留2bit出来描述字节数。

//
//  msb       format
// ---------------
//  0*  ..... 1 byte  (1 +  7 bits)    表示的数值范围为0x0 ~ 0x7F
//  10* ..... 2 bytes (2 + 14 bits) 表示的数值范围为0x0 ~0x3FFF
//  11* ..... 3 bytes (2 + 22 bits) 表示的数值范围为0x0 ~ 0x3FFFFF
//

同样的有上面的技巧,通过修正2字节和3字节的表示范围,来覆盖大那么一点点的数值范围。


    3,上面总是用最少的bit位来描述字节数,其实也没有必要一定要留出某个bit来描述字节数信息。可以预留出某个数值来表示字节数。比如,

//
//  msb       format
// ---------------
//  0x0000   - 0x00EF   ..... 1 byte      表示的数值范围为0x0 ~ 0xEF
//  0xF000  -  0xFFFF    ..... 2 bytes   表示的数值范围为0x0 ~ 0x0FFF
//

我们把第一个字节的搞4个bit来描述字节数,当高4个字节为0 - E的时候,表示1个字节存储,描述范围为 0 ~ 0xEF,当高4bit为F,后12bit表示真实的数值,表示范围为 0 - 0x0FFF.这种表示方法和第一种表示方法的区别在于,此方法表示的数值范围更小,但是用1个字节表示范围更宽,既牺牲2字节的表示范围来扩展1个字节的表示范围。


        变长存储的效用体现在小数值出现的频率很高,这样用1个字节就可以存储大部分情况,而少部分情况需要2个字节来存储。比如,我们需要存储name表的时候,2个name之间其实有很多重复的字符。每个name都把自己完整的字符串存起了将非常大,通过token来存储就可以起到很好的压缩功能,具体做法:

每一个name由token组成,每一个token由具体的字符组成,其中,每一个token有自己唯一的id来标示,而name就由一系列的token id来组成。如何对token取id,我们把token按出现的次数来排序,出现的次数越多,就排在越前面,他的token id就越小。name中的token id就可以用变长来存储了。这时候,根据token 总数可以确定是用第一种变长还是第二种变长,当token的总数不超过0x0FFF个的时候,第三种变长可以得到更好的压缩率,因为1个字节可以表示更多的小id范围,有个事实就是id越小,出现的频率越高,说明用1个字节的地方更多。


原创粉丝点击