GZIP压缩原理分析(28)——第五章 Deflate算法详解(五19) 动态哈夫曼编码分析(08) LZ77过程(07)

*哈希函数以及哈希值计算初探

前面我们说过哈希值计算的问题,为了对后面的源码分析能够有更深入的了解,这里对哈希值的计算过程做一个初探。我们这里只分析哈希值计算过程,因为小弟本身能力有限,所以不分析哈希函数的原理。

前面我们讲过,压缩是逐字节进行的,放到这里也一样,哈希值的计算也是逐字节进行的。那么问题来了,逐字节计算,那就是说每个字节算一次哈希值,但是前面不是说哈希值是拿三个字符计算的吗,怎么这里又说逐字节了呢?实际上,在开始压缩之前,压缩算法要进行一系列的初始化,初始化的内容包括相关数据结构、映射表、静态哈夫曼树等等,其中一个要初始化的内容就是哈希值。压缩使用的哈希函数有一个功能,源码中的注释是这样说的“all calls to toUPDATE_HASH are made with consecutive input characters, so that a running hashkey can be computed from the previous key instead of complete recalculationeach time.”(文中那个UPDATE_HASH就是哈希函数),意思大概就是:因为压缩逐字节处理(i.e.处理连续的输入字符。我这里意译了),故可以利用上一个哈希值来计算这次的哈希值,不用每次都拿三个字符重新计算本次的哈希值(这里意译了)。

光看注释不够,我们拿下面这个例子来具体说明一下。有如下字符串,

“abcdefghijklmnopq”,

假设这个字符串已经在窗口中,a就是窗口中第一个字符。在初始化的时候,压缩算法就把字符串“ab”的哈希值计算出来了,我们将其记为ins_h0。等到压缩真正开始,a成为先行缓冲区第一个字符,需要计算“abc”的哈希值的时候,只要用ins_h0和c这两个数就可以将字符串“abc”的哈希值计算出来,我们将其记为ins_h1;当计算“bcd”的哈希值时,拿ins_h1和d就可以将其计算出来,我们将其记为ins_h2;当计算“cde”的哈希值时,就拿ins_h2和e就可以将其计算出来,以此类推。

用上一个哈希值来计算这次的哈希值,这就是压缩中哈希值的计算过程。但这有一个问题必须思考:这次的哈希值用上一个哈希值计算出来,那又怎么保证匹配的呢?例如这个字符串

“mnoabczxyuvabczxydefgh”,

字符串“abc”的哈希值由字符串“vab”的哈希值和c计算得出,记为ins_h_a;字符串“abc”的哈希值由字符串“oab”和c计算得出,记为ins_h_b,计算ins_h_a和ins_h_b的参数不完全相同,为什么这两个哈希值却是相同的?会不会有什么问题?

我们先来看一下哈希函数,哈希函数如下,

#define UPDATE_HASH(h,c) (h =(((h)<<H_SHIFT) ^ (c)) & HASH_MASK)

其中,h就是上一个哈希值,c是参与计算的那个字符(三个字符中的最后一个字符。放到上面的例子中,就是三个字符中最右面那个字符),HASH_MASK 在一般情境下是十进制的32767,即二进制的“0111 1111 1111 1111”, H_SHIFT 在一般情境下是5。这些变量中最关键的就是H_SHIFT!源码中的注释是这样描述它的作用的:“…the oldest byte nolonger takes part in the hash key…”,大概意思就是说有了这个,就可以把哈希值中那些比较陈旧的部分干掉,陈旧的部分就是指当前三个字符之外的那些旧的字符对于当前哈希值的“影响”。比如用“vab”的哈希值算“abc”的哈希值时,“vab”哈希值中“ab”以外的部分就都被干掉了。

我们用这个哈希函数手动计算一遍哈希值,以下面这个字符串为例

“abcd”,

用两个流程来计算,第一个流程,计算“abc”的哈希值,再用这个哈希值计算“bcd”的哈希值;第二个流程,直接计算“bcd”的哈希值。如果这两个流程的计算结果相同,基本可以说明我们的担心是多余的。但是注意,这只是个验证,是个实验,绝不是证明,希望哪位仁兄能给出充分的数学证明,小弟在此先谢过了。

如下表所示,

字符

十进制ASCII码

二进制码

a

97

0110 0001

b

98

0110 0010

c

99

0110 0011

d

100

0110 0100

哈希函数为(h =(((h)<<H_SHIFT) ^ (c)) & HASH_MASK),初始化的哈希值是0,H_SHIFT是5,HASH_MASK的二进制值是0111 1111 1111 1111,即十进制的32767。

我们先看第一个流程,a的哈希值是

ins_h_a = (((0)<<5) ^ (97))& 32767),

即,

                                   0000  0000(0左移五位还是0)

^ 0110  0001

       0110  0001,

该值与HASH_MASK按位与操作之后还是二进制的0110 0001,所以“a”的哈希值就是二进制的“0110 0001”,ins_h_a = 0110 0001

拿ins_h_a来计算“ab”的哈希值。所以“ab”的哈希值就是

ins_h_ab = (((ins_h_a)<<5)^ (98)) & 32767),

即,

0110 0001 << 5 = 0110 00010000 0,

   01100001 0000 0

^ 00000011 0001 0

      01100010 0001 0,

该值与该值与HASH_MASK按位与操作之后还是二进制的0110 0010 0001 0,所以“ab”的哈希值就是二进制的“0110 0010 0001 0”,ins_h_ab = 0110 0010 0001 0

拿ins_h_ab来计算“abc”的哈希值。所以“abc”的哈希值就是

ins_h_abc =(((ins_h_ab)<<5) ^ (99)) & 32767),

即,

0110 0010 0001 0 << 5 =0110 0010 0001 0000 00,

   0110 0010 0001 0000 00

^ 0000 0000 0001 1000 11

   0110 0010 0000 1000 11

与HASH_MASK按位与,就是

     0110 0010 0000 1000 11

&  0001 1111 1111 1111 11

         0000 0010 0000 1000 11,

所以“abc”的哈希值就是二进制的“0000 0010 0000 1000 11”,ins_h_abc = 0000 0010 0000 1000 11

拿ins_h_abc来计算“bcd”的哈希值。所以“bcd”的哈希值就是

ins_h_bcd =(((ins_h_abc)<<5) ^ (100)) & 32767),

即,

0000 0010 0000 1000 11<< 5= 0000 0010 0000 1000 1100 000,

    0000 0010 0000 1000 1100 000

^  0000 0000 0000 0000 1100 100

    0000 0010 0000 1000 0000 100

与HASH_MASK按位与,就是

  0000 0010 0000 1000 0000 100

&0000 0000 1111 1111 1111 111

      0000 0000 0000 1000 0000 100,

所以“bcd”的哈希值就是二进制的“0000 0000 0000 1000 0000 100”,ins_h_bcd =0000 0000 0000 1000 0000 100

第一个流程结束,各哈希值如下,

ins_h_a     = 0110 0001

ins_h_ab  =01100010 0001 0

ins_h_abc = 00000010 0000 1000 11

ins_h_bcd = 00000000 0000 1000 0000 100

现在看第二个流程。Ins_h的初始值仍然是0,但是现在我们从b开始计算“bcd”的哈希值。b的哈希值是

ins_h_b = (((0)<<5) ^ (98))& 32767),

即,

                                   0000  0000(0左移五位还是0)

^ 0110  0010

       0110  0010,

该值与HASH_MASK按位与操作之后还是二进制的0110 0010,所以“b”的哈希值就是二进制的“0110 0010”,ins_h_b = 0110 0010

拿ins_h_b来计算“bc”的哈希值。所以“bc”的哈希值就是

ins_h_bc = (((ins_h_b)<<5)^ (99)) & 32767),

即,

0110 0010 << 5 = 0110 00100000 0,

   0110  0010  0000  0

^ 0000  0011  0001  1

       0110  0001  0001  1,

该值与该值与HASH_MASK按位与操作之后还是二进制的0110 0001 0001 1,所以“bc”的哈希值就是二进制的“0110 0001 0001 1”,ins_h_bc = 0110 0001 0001 1

拿ins_h_bc来计算“bcd”的哈希值。所以“bcd”的哈希值就是

ins_h_bcd’ = (((ins_h_bc)<<5)^ (100)) & 32767),

即,

0110 0001 0001 1<< 5 = 01100001 0001 1000 00,

   0110 0001 0001 1000 00

^ 0000 0000 0001 1001 00

   0110 0001 0000 0001 00

与HASH_MASK按位与,就是

    0110 0001 0000 0001 00

&  0001 1111 1111 1111 11

        0001 0001 0000 0001 00,

所以“bcd”的哈希值就是二进制的“0001 0001 0000 0001 00”,ins_h_bcd’ =0001 0001 0000 0001 00

第二个流程结束,各哈希值如下,

ins_h_b      =01100010

ins_h_bc     =01100001 0001 1

ins_h_bcd’= 00010001 0000 0001 00

ins_h_bcd= 0000 0000 0000 1000 0000 100而ins_h_bcd’ = 0001 00010000 0001 00,去掉高位的0,两者都是1000 0000 100,说明我们之前的担心基本是多余的(再次强调,上面的计算过程并不是证明,只是个实验)。在计算哈希值的过程中,使用上一个哈希值来计算本次的哈希值确实没问题。其实从上面的实验过程我们可以大致感觉到,移位和那个掩码操作应该是干掉陈旧字节对哈希值的影响的关键操作,当然,这个只是我目前的猜测,欢迎纠正。

至此,哈希值的分析基本结束,压缩使用的哈希值的原理就是这样,后面分析源码时要结合这部分内容理解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值