哈夫曼编码原理及其在HTTP2中的使用

2,470 阅读3分钟

背景

说到哈夫曼树大家应该都不陌生,它是一颗根据叶子节点权重进行构造的树,它能够使得树的带权路径长度最短。

而哈夫曼编码就是基于哈夫曼树,这是一个经典的压缩算法,可以根据权重给某个值分配一个01串,用这个较短的01串表达这个较长的值,权重越高的值的01串会越短,从而提高压缩率;

同时哈夫曼编码也是前缀不重复的,也就是不会有某个编码是另外一个编码的前缀,这样我们就能够把编码后的字符连续的存放,不需要其他额外的信息也能解码。

比如对于字符(a,b,c)对应的编码是:

字符编码
h110
e111
l10
o0

可以看到上面的编码都不是其他编码的前缀,我们考虑对hello这个单词进行编码:11011110100,则解码过程就是从左读取,如果发现表里有对应的编码,则找到对应的字符,然后继续下一个字符解码:

当前编码值字符
1
11
110h
1
11
111e
1
10l
1
10l
0o

算法原理

定义

  1. 路径长度:从根节点到叶子节点的长度,也就是编码长度。
  2. 节点权重:一般使用字符出现的概率。
  3. 带权路径长度:路径长度*节点权重。
  4. 树的带权路径长度:整棵树每个叶子节点带权路径长度之和。
  5. 节点编码:往左节点走为0,往右节点走为1,到节点经过的路径就是节点的编码。

树构建过程

  1. 从树的集合S中,找到权重最小的两棵树A和B(只有单个节点也是一棵树);
  2. 产生一个新节点C;
  3. 把A和B的权重加起来作为新节点C的权重,把A和B作为节点C的左右子节点;
  4. 节点C加入集合S;
  5. 重复上面过程直到集合只有一棵树。

比如对于hello这个单词,我们可以知道每个字符出现的次数:

字符出现次数
h1
e1
l2
o1

把这个出现次数作为每个字符的权重:

未构造.png

①选节点h和e(因为权重最小),生成新节点(绿色节点),新节点权重为h+e:

构造1.png

②选节点o和绿色节点(因为权重最小),生成新节点(蓝色节点),新节点权重为o+绿色节点:

构造2.png

③选节点l和蓝色节点(因为权重最小),生成新节点(红色节点),新节点权重为l+蓝色节点:

构造3.png

编码

根据上面的树,按照往左走0,往右1,可以得到每个字符的编码:

字符编码
h110
e111
l0
o10

根据上表则hello的编码为:1101110010,按照二进制为10位,也就是只需要两个字节就可以存储。比直接用hello的ASCII编码需要5个字节少3个字节。

HTTP2的应用

了解HTTP2的同学肯定知道HTTP2是一个二进制协议,为了减小传输体积使用了头部压缩算法HPACK,算法有三个组成部分:静态表编码、动态表编码和哈夫曼编码。

为了使用哈夫曼编码,HPACK统计了大量HTTP头部,根据字符出现频率将ASCII编码为哈夫曼编码:

字符编码二进制长度
'0'000005
'1'000015
'2'000105
'3'0110016
'A'1000016
'B'10111017
'C'10111107
'D'10111117

通过减小出现频率高的字符的编码长度,从而减小整个HTTP头部的大小,提高传输效率。