Table of Contents
1.简介
在2017年,我有机会从事一个旨在提高数据压缩性能的项目。在此过程中,我研究了zlib库及其实现的deflate压缩算法。在这里,我想与那些也希望对zlib有更好理解的人分享我的研究。
1.1 什么是zlib
zlib是一个免费的开源软件库,用于无损数据压缩 和 减压。它是由Jean-loup Gailly(压缩)和Mark Adler(解压缩)用C语言编写的。zlib的第一个版本于1995年5月发布。Jean-loupGailly和Mark Adler也为gzip(GNU zip)。在后台,gzip使用zlib库。
迄今为止,zlib主要由Mark Adler维护,其最新更新和版本均可在GitHub上找到。Mark Adler还积极参与Stack Overflow,以回答有关zlib和gzip的技术问题。
许多软件应用程序和库都使用zlib。如果引起注意,您几乎可以在操作系统,Internet服务,流服务,文档编辑器等中的任何地方找到zlib。这是这些应用程序的不完整列表。
zlib规范于1996年5月获得正式的Internet RFC(征求意见)状态。
2.压缩算法
的 压缩 zlib中使用的算法是 放气方法。deflate方法将输入数据编码为压缩数据。的减压 zlib中使用的算法是 膨胀 方法,这是一种解码过程,需要使用压缩的位流进行解压缩并正确生成原始的全尺寸数据或文件。
在本文档中,我将重点介绍zlib的压缩部分以及zlib对zlib的实现。 放气算法。
我会用这样的话 “数据字节”, “数据字节”, “数据符号”, “数据流”, “位流”指示要压缩的数据。在下面的部分中,这些词具有相同的含义并且可以互换。
2.1 放气
2.2 LZ77
LZ77是基于字典的无损压缩算法。它也被称为LZ1。
基于字典的算法的基本思想是,通过引用该序列的先前出现来替换数据中特定字节序列的出现。
基于字典的压缩算法有两种主要类型:LZ77和LZ78。这两个算法以两个创建者Jakob Ziv和Abraham Lempel命名。LZ77(Lempel-Ziv77)和LZ78(Lempel-Ziv78)分别发表于1977年和1978年。
LZ77压缩算法通过使用 滑动窗口 查找重复的数据序列,并使用称为a的一对数字对每个重复的序列进行编码 长距离对。
2.2.1 滑动窗口
滑动窗口用于检查输入数据序列,并维护用作字典的历史数据。换句话说,字典是先前出现和编码的数据的一部分。
滑动窗由两部分组成: 搜索缓冲区和 前瞻缓冲区。搜索缓冲区包含字典-最近编码的数据,而前瞻缓冲区包含要编码的输入数据序列的下一部分。下图给出了一个滑动窗口的示例。
滑动窗的尺寸是影响压缩性能的关键因素之一。如果滑动窗口太小,则压缩器可能会发现较少的重复数据序列,结果,压缩文件的大小将更大。如果滑动窗口太大,则压缩器可能需要花费更长的时间来查找重复的数据序列,因此压缩速度将变慢。
实际上,滑动窗口的大小通常可以从几KB到MB,例如4 KB,32 KB,1 MB或4 MB。
2.2.2 长距离对
长度-距离对指示下一个 长度 字符与字符完全相同 距离 原始数据流中后面的字符。
在LZ77算法中,压缩器通过搜索缓冲区进行搜索,直到找到与超前缓冲区中的第一个字符匹配为止。搜索缓冲区中可能存在多个匹配项,并且压缩程序将找到长度最长的一个匹配项。当。。。的时候最长的匹配 找到后,压缩器将其编码为三元组 (D,L,C) 哪里:
- D =搜索光标到超前缓冲区起点的距离
- L =最长匹配的长度
- C =超前缓冲区中最长匹配的下一个字符
在三元组中添加第三个元素C的原因是为了处理在搜索缓冲区中找不到匹配项的情况。在这种情况下,D和L的值均为0,并且C是当前预读缓冲区中的第一个字符。
下图显示了LZ77如何找到最长匹配项并为给定字符串编码重复字符“axrrmaxrbaxssr”
的示例。
实际上,压缩器可以根据自己的实现来优化编码输出,并选择除 (D,L,C) 三胞胎。
2.3 霍夫曼编码
霍夫曼编码是一种统计压缩方法。它使用以下代码编码数据符号(例如字符)可变长度代码,代码长度基于相应符号的频率。
霍夫曼编码以及其他可变长度编码方法具有两个属性:
- 对更加频发发生的数据符号的代码比低频发生的数据符号 更短。
- 每个代码可以是 唯一解码。这要求代码是前缀码,表示一个符号的任何代码都不是其他符号的代码前缀。例如,如果将code
“0”
用作symbol“A”
,则code“01”
不能用于symbol“B”
,因为code“0”
是code“01”
的前缀。前缀属性可确保在解码时确定符号边界在哪里没有歧义。
霍夫曼编码有两个步骤:
- 从原始数据符号构建霍夫曼树。霍夫曼树是一种二叉树结构。
- 遍历霍夫曼树并将代码分配给数据符号。
霍夫曼码可以是 固定(静态) 要么 动态。两者都用deflate方法。
可以通过检查大量数据集并找到典型的代码长度来创建固定的霍夫曼代码。使用固定霍夫曼编码时,所有输入数据符号均使用相同的编码。
动态霍夫曼码是通过将输入数据分为多个块,并为每个数据块生成代码来生成的。
3. zlib的实现
zlib的实现是实用且高效的。在过去的20年中,人们进行了许多尝试来提高压缩应用程序的性能,但是似乎我们只能通过使用除放气(和放气),采用并行处理或改进CPU级别指令以外的算法来获得更好的性能。因此,zlib(在其GitHub存储库中指出)相当庞大而精致的压缩库。
在以下各节中,我们将介绍zlib用于实现deflate压缩算法的一些详细技术。
3.1 压缩等级
zlib具有10个压缩级别(0-9)。不同级别的压缩性能不同压缩率 和 速度。级别0表示不压缩,zlib将输出原始数据。1级是最快的,但压缩率较低。级别9提供最高的压缩率,但压缩速度较慢。zlib使用的默认压缩级别为6。
在引擎盖下,压缩级别会在放气过程中更改放气策略和参数。更多细节将在以下各节中讨论。
3.2 滑动窗口
在zlib中,滑动窗口的默认大小为64KB。滑动窗分为两部分,分别对应搜索缓冲区 和 前瞻缓冲区,每个部分为32KB。输入字节被读入窗口的后半部分,然后移至前半部分以保持至少32KB的字典。该组织确保始终以块大小(8KB)的倍数执行IO。此外,在较旧的平台MSDOS上64KB的限制非常有用。
以下代码段显示了如何初始化滑动窗口。宏MAX_WBITS
确定滑动窗口的大小。它是可配置的,默认值为15,这将导致32KB的搜索缓冲区和64KB的滑动窗口。
define MAX_WBITS 15 /* 32K LZ77 window */
s->w_bits = windowBits;
s->w_size = 1 << s->w_bits;
s->w_mask = s->w_size - 1;
s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte));
当超前缓冲区不足时,数据将被复制到滑动窗口中。此过程在函数内部实现fill_window
。
local void fill_window(s)
deflate_state *s;
{
...
}
3.3 寻找最长的匹配
zlib用于在搜索缓冲区中找到最长匹配项的技术很简单,而且对于大多数输入文件来说,它是最快的:使用字符串匹配算法查找可能的匹配项,然后尝试所有可能的匹配项并选择最长的匹配项。
小字符串的匹配算法的灵感来自 Rabin-Karp算法。该算法的关键特征是,在字符串字典中的插入非常简单,因此速度很快,并且完全避免了删除。插入在每个输入字符处执行,而字符串匹配仅在上一个匹配结束时执行。因此,最好花更多时间进行匹配,以允许非常快速的字符串插入并避免删除。当发现较小的匹配项时,将使用蛮力方法来查找较长的字符串。
因此,总而言之,找到最长匹配项的过程包括两个主要部分:
- 在滑动窗口中,对于超前缓冲区中的每个数据符号,使用基于Rabin-Karp算法的方法来查找 可能的匹配在搜索缓冲区中,并记录匹配开始位置。可能有多个可能的匹配项。
- 对于每个可能的匹配,从匹配开始位置开始,检查以下每个数据符号以找到 当前最长的匹配。搜索在步骤1中找到的所有可能的匹配项,直到找到最长的匹配,或找到长度超过预定义的匹配项 最长匹配限制。
3.3.1 匹配长度限制
zlib将MIN_MATCH
和MAX_MATCH
定义为最低 和 最大值 匹配搜索的长度。
#define MIN_MATCH 3
#define MAX_MATCH 258
MIN_MATCH
设置为3。最小匹配长度等于3的原因很明显:小于3的匹配将无助于减小编码数据的大小,因为编码数据符号的长度将相同或更长。
除非您更改相关代码(例如,多次调用UPDATE_HASH
函数),否则无法更改MIN_MATCH
值。
将MAX_MATCH
设置为258这个数字是来自一个事实,即一个长的距离对,这是LZ77编码的数据符号的输出,可以在最258个字节代表。长度至少需要一位,距离至少需要一位,因此输入两位可以发出258个字节。
zlib中的MAX_MATCH
值可以更改,但是更改可能会影响压缩性能。同样在zlib中,还有一些由condition控制的逻辑MAX_MATCH == 258
。启用这些代码后,使用现代编译器时,可以提高压缩性能。
#if (defined(UNALIGNED_OK) && MAX_MATCH == 258)
/* This code assumes sizeof(unsigned short) == 2. Do not use
* UNALIGNED_OK if your compiler uses a different size.
*/
if (*(ushf*)(match+best_len-1) != scan_end ||
*(ushf*)match != scan_start) continue;
...
3.3.2 Rabin-Karp算法
Rabin-Karp算法是由Richard M. Karp和Michael O. Rabin创建的字符串搜索算法。它使用散列来查找文本中一组模式字符串中的任何一个。例如,给定文字“AABAACAADAABAABA”
和图案“AABA”
,我们可以使用Rabin-Karp算法来找出模式在索引中的文本存在0
,9
,12
。
以下伪代码描述了Rabin-Karp算法的工作原理。
# p is a pattern, its length is m
# t is text, its length is n
# the algorithm searches for pattern p in text t
Compute hash_p (for pattern p)
Compute hash_t (for the first substring of t with m length)
for i = 0 to n - m:
if hash_p == hash_t:
Match t[i . . . i+m-1] with p, if matched return 1
else:
Update hash_t for t[i+1 . . . i+m] using rolling hash
End
的 平均 和 最佳情况下的运行时间Rabin-Karp算法的O表示O(n + m),其中n是文本的长度,m是图案的长度。但是它最坏的情况下时间是O(nm)。当模式和文本的所有字符与文本的所有子字符串的哈希值与模式的哈希值匹配时,会发生Rabin-Karp算法的最坏情况。例如text = “AAAAAAA”
和pattern = “AAA”
。
Rabin-Karp算法性能的关键是通过使用以下命令来有效计算文本的连续子字符串的哈希值: 滚动哈希技术。滚动哈希的好处是,它只需执行恒定数量的操作即可计算前一个子字符串的下一个子字符串的哈希值,而不必重新哈希整个子字符串。
3.3.3 哈希链
如前所述,Rabin-Karp算法检查子字符串的哈希值,以查找文本中的匹配项。为了在存储最新数据符号的搜索缓冲区中找到匹配项,zlib使用了哈希链组织保留每3个字节(或由MIN_MATCH
定义的其他值)的哈希值的记录。
zlib中的此哈希链通过使用两个数组来实现:prev[]
和head[]
。两个数组都将位置存储在滑动窗口中。该head[]
数组存储哈希链的头部,该prev[]
数组存储并链接具有相同哈希索引的字符串的位置。下图显示了哈希链如何工作的示例。
在此示例中,
HashValue
函数及其结果仅是示例,并不准确。
prev[]
的大小限制为滑动窗口的一半。因为prev[]
维护的链接仅适用于搜索缓冲区中的数据,并且默认情况下仅最后32K字符串。在索引prev[]
阵列是窗口索引模32K。
以下代码段显示了zlib如何实现哈希链组织。哈希大小随memLevel
为每个压缩级别配置的参数而变化。
s->hash_bits = (uInt)memLevel + 7;
s->hash_size = 1 << s->hash_bits;
s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos));
s->head = (Posf *) ZALLOC(strm, s->hash_size, sizeof(Pos));
#define UPDATE_HASH(s,h,c) (h = (((h)<<s->hash_shift) ^ (c)) & s->hash_mask)
#define INSERT_STRING(s, str, match_head) \
(UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \
match_head = s->prev[(str) & s->w_mask] = s->head[s->ins_h], \
s->head[s->ins_h] = (Pos)(str))
#endif
#define CLEAR_HASH(s) \
s->head[s->hash_size-1] = NIL; \
zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head));
在中
CLEAR_HASH
,数组head[]
被清除。数组prev[]
是动态清除的,不是这里清除的。
3.3.4 自适应搜索限制
在哈希链中搜索最长匹配项时,zlib会限制 链长 以提高搜索效率。搜索限制是通过以下方式设置的:
- 预定义
max_chain_length
值。对于不同的压缩级别,此值是不同的。 - 在搜索过程中,如果找到匹配项且其长度不小于预定义的
good_match
长度,则搜索长度将缩短为new_search_chain_length = search_chain_length / 4
搜索极限值在 配置表:
local const config configuration_table[10] = {
/* good lazy nice max_chain_length */
/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */
/* 1 */ {4, 4, 8, 4, deflate_fast}, /* max speed, no lazy matches */
/* 2 */ {4, 5, 16, 8, deflate_fast},
/* 3 */ {4, 6, 32, 32, deflate_fast},
/* 4 */ {4, 4, 16, 16, deflate_slow}, /* lazy matches */
/* 5 */ {8, 16, 32, 32, deflate_slow},
/* 6 */ {8, 16, 128, 128, deflate_slow},
/* 7 */ {8, 32, 128, 256, deflate_slow},
/* 8 */ {32, 128, 258, 1024, deflate_slow},
/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* max compression */
在上面的代码片段中:
0
-9
是压缩级别。good
,lazy
,nice
是一个很好的匹配,水上匹配和一个漂亮的匹配的长度值。max_chain_length
是zlib搜索的最大链长。
3.4 霍夫曼编码
Zib既实现 静态(固定)霍夫曼编码 和 动态霍夫曼编码。对于LZ77编码数据的每个块,zlib使用静态霍夫曼编码和动态霍夫曼编码来计算该块中的位数,然后选择方法 产生 较小的数量数据的。如果使用两种方法使位数相等,则zlib选择静态霍夫曼编码,因为解码过程更快。
整个数据流可以包含静态和动态霍夫曼编码数据的混合。在每个块的放气流头中发送霍夫曼码。
总而言之,zlib中的霍夫曼编码过程包括以下步骤:
- 在LZ77编码过程中,收集 数据字节统计 并生成直方图
- 对于每个数据块,构建一个 动态霍夫曼树 使用收集的统计信息。
- 使用动态霍夫曼树和静态霍夫曼树计算并比较编码的块长度,并确定是否值得使用 动态或静态树 用于编码阶段。
- 对数据块执行动态或静态霍夫曼编码。
3.5 I / O缓冲区
压缩中的一个重要概念是 数据块。 压缩数据格式由块组成,这些块的头取决于块数据。因此,deflate的输出一次到达一个块,直到第一个块完成之前,什么都没有写(zlib或gzip标头除外)。考虑到数据块在deflate过程中如何传输,zlib实现了几个缓冲区 存储数据块并控制I / O性能。
3.5.1 输入缓冲器
开始压缩之前,zlib 积累数据 在一个 输入缓冲区,当输入缓冲区已满时开始压缩。默认输入缓冲区大小为8KB。
之所以具有输入缓冲区,是因为zlib不会生成任何输出数据,直到在其中生成16K数据符号为止。 文字缓冲区(文字缓冲区的默认大小为16K,有关文字缓冲区的详细信息,请参见下一部分)。因此,以非常短的数据长度开始压缩没有任何好处。使用输入缓冲区可提高I / O效率。
可以使用gzbuffer
功能更改默认的输入缓冲区大小。
gzbuffer(file, size)
{
...
state->want = size;
...
}
3.5.2 文字缓冲
文字缓冲区存储的数据符号由LZ77编码。符号可以是编码为文字的单个字节,也可以是长度-距离对,它可以对前32K未压缩数据中某处最多258个字节的副本进行编码。默认的文字缓冲区大小为16K,因此未压缩数据的累积范围为16K至4MB(对于高度可压缩的数据)。
一旦 文字缓冲区已满,zlib决定为霍夫曼编码构造哪种块,然后执行此操作,创建标头,该标头针对动态块描述该块中的霍夫曼代码,然后为该块创建编码符号。或者,它会创建一个存储块或静态块,无论结果是最少的位数。只有这样,压缩数据才能用于输出。
默认文字缓冲区大小由marco配置DEF_MEM_LEVEL
。在zlib的代码中,DEF_MEM_LEVEL = 8,
所有压缩级别都相同。因此,所有压缩级别都具有相同的16K文字缓冲区大小。
3.5.3 输出缓冲器
为了输出压缩数据,zlib使用两个缓冲区: 暂挂缓冲区, 和 输出缓冲区。数据流如下图所示:
初始化后,zlib将创建一个暂挂缓冲区(默认大小为36K)和一个输出缓冲区(默认大小为8K)。首先输出数据累积在未决缓冲区中,然后得到 复制到输出缓冲区,最后将其写入输出的压缩zip或gz文件中。
数据从函数flush_pending
中的待处理缓冲区复制到输出缓冲区。当文字缓冲区已满时,即表示已处理数据块时,将调用此函数。在某些其他情况下,当需要刷新块时也会调用它。复制到输出缓冲区的数据长度受输出缓冲区中的可用空间限制。
当。。。的时候 输出缓冲区已满,或何时 冲洗信号 发出后,zlib将输出缓冲区写入zip或gz文件。
zlib使用计数器pending_out
和avail_out
记录未决缓冲区和输出缓冲区中有多少字节可用。计数器值0
表示缓冲区已满。
相关代码段:
flush_pending() {
len = s->pending;
if (len > strm->avail_out) len = strm->avail_out;
if (len == 0) return;
zmemcpy(strm->next_out, s->pending_out, len);
}
gzcomp() {
if (strm->avail_out == 0 || (flush != Z_NO_FLUSH &&
(flush != Z_FINISH || ret == Z_STREAM_END))) {
have = (unsigned)(strm->next_out - state->x.next);
if (have && ((got = write(state->fd, state->x.next, have)) < 0 || (unsigned)got != have)) {
gz_error(state, Z_ERRNO, zstrerror());
return -1;
}
if (strm->avail_out == 0) {
strm->avail_out = state->size;
strm->next_out = state->out;
}
}
}
4.优化zlib
多家公司对通过优化zlib的实施来提高压缩性能感兴趣。以下是一些摘要最近的相关作品。
4.1 英特尔:zlib-new
参考:英特尔:英特尔架构处理器上的高性能ZLIB压缩(pdf)
优化的重点在于改进的散列,在LZ77进程中搜索子串的最长前缀匹配以及Huffman代码流。
改进的哈希:对于压缩级别1到5,哈希元素为四元组(至少匹配4个字节)。对于6到9的压缩级别,请使用zlib的原始哈希元素作为三元组(至少匹配3个字节)。
添加两个其他策略:级别1为DEFLATE_quick,级别4至6为DEFLATE_medium。
- DEFLATE_quick:将哈希链搜索限制为第一个条目。
- DEFLATE_medium:旨在在zlib的压缩率之间取得平衡 DEFLATE_slow 策略以及zlib的性能 DEFLATE_fast战略。找到匹配项后,它将跳过匹配长度的转发,并支持延迟匹配评估的变体。当在位置
p
和长度l上找到匹配项时,在位置p + l + 1
检查匹配项。如果找到新的匹配项,则从positionp + l + 1
向后扫描,以确定是否可以改善第二个匹配项。
哈希表移动速度更快:利用SSE(Intel©)一次对8个条目(16字节)进行哈希移位。
更快的CRC计算:利用PCLMULQDQ(Intel©)指令以更改的算法一次处理64字节的输入。
减少循环展开:消除了现代处理器上Adler32和CRC32计算中过多的循环展开。对于Adler32,将展开系数从16减小到8。对于CRC32,将展开系数从8减小到4。
4.2 IBM:快速放气
优化的重点是通过使用LZ4的重复消除过程和改进的霍夫曼编码过程来提高zlib放气过程的速度。
用LZ4替换LZ77更快地消除重复:观察到的提速是中等的,可能由于缓存使用率的差异而未达到LZ4的性能。还观察到的压缩比几乎不受影响。
强制使用静态霍夫曼树:达到较快的速度,但可能对压缩率产生负面影响。
半静态霍夫曼编码:使用静态树压缩中间数据的第一个块(LZ77的输出),并收集该第一个块的统计信息,以构建用于编码后续块的霍夫曼树。第一个块的大小是配置,默认大小是8KB。其他块的大小也是可配置的,默认值为128KB。添加的另一个步骤是在构建霍夫曼树之前确定初始块的统计信息,以确定是否值得使用动态树。这种方法既可以实现更快的速度,又可以实现更好的压缩率。
4.3 脸书:Zstandard
参考:Facebook Zstandard(zstd)设计介绍
Zstandard旨在与现代硬件一起扩展,并压缩得更快,更小,从而可以对各种数据类型进行通用压缩。
通过结合几种最新创新并针对现代硬件来改进zlib。
增加视窗大小 到1MB-8MB(推荐)。
压缩使用 有限状态熵 基于ANS(非对称数字系统)的系统,以提高性能并减少延迟。
用 repcode建模 有效压缩结构化数据
用一个 无分支设计风格 减少CPU分支预测器的开销。
在解压缩中,将数据分成多个并行流,并使用新一代的霍夫曼解码器 并行解码多个符号一个核心。这利用了现代CPU的能力,即每个周期可以发出多个指令。
4.4 Google:Zopfli,Brotli
参考:使用Zopfli的数据压缩(pdf),Brotli,Deflate,Zopfli,LZMA,LZHAM和Bzip2压缩算法的比较(pdf)
Google在2013年推出了 佐普利,与deflate格式兼容。Zopfli给较小的压缩数据大小 比gzip(小3.7-8.3%),但它具有 压缩速度较慢 比gzip 9级高。Zopfli库只能压缩,不能解压缩。
布罗特利 尝试实施 新的压缩格式,并且比放气更有效。它的速度与放气相似,但压缩更紧密。
Brotli压缩格式在RFC 7932中定义。该算法结合了LZ77算法的现代变体,霍夫曼编码和二阶上下文建模的组合。它还使用包含超过13K个单词的静态字典。静态字典有助于压缩短文件。
4.5 苹果:LZFSE
4.6 CloudFlare:zlib
参考:CloudFlare与癌症抗争:开放源代码带来的意想不到的好处
CloudFlare对zlib的改进包括:
- 用 uint64_t 替换16位类型。
- 使用 改进的哈希函数iSCSI CRC32。此功能作为Intel处理器上的硬件指令实现。它具有非常快的性能和更好的碰撞性能。
- 搜索至少匹配的项 4字节。
- 使用SIMD指令 窗户滚动。
- 使用Intel的硬件无携带乘法指令PCLMULQDQ CRC32校验和。
- 优化的最长匹配功能。这是库中对性能要求最高的功能。
尚未包含在已发布的zlib fork中的其他实验:
- 使用zlib(哈希链)中使用的链表的改进版本
基准测试结果
- 测试数据集是四个标准语料库数据,包括ASCII文本,位图图像,数字和源代码文件。
- 速度:通常,CloudFlare的zlib比zlib和Intel的zlib快2至9级,尤其是6至9级(2倍-7.5倍)。
- 压缩率:在所有级别上与zlib非常相似,并且在级别1上优于Intel的zlib。
4.7 CloudFlare:预设字典
参考:CloudFlare:使用预设的deflate字典改善压缩
优化旨在提高HTML文件的压缩性能(降低25%)(主要是 短文)。
将最小匹配长度更改为 4字节。
用一个 预设字典 因此在输入的开头有用于搜索匹配项的反向参考。
- 通过对一组要压缩的文件执行伪LZ77来创建预设字典,并在每个输入文件的前16Kb中找到DEFLATE不会压缩的字符串。然后对各个字符串进行频率计数,并根据其长度和频率对其进行评分。得分最高的字符串将保存到词典文件中。
- 使用Go程序制作这样的deflate字典:GitHub上的独裁者
参考