GZIP 的压缩原理

Gzip是一种无损压缩工具,以将大块数据变得更小而闻名。

Gzip 压缩的原理是通过去除数据中的冗余信息,使用较短的代码表示频繁出现的字符或字符序列,从而减小数据文件的大小。这种压缩算法通常用于减少存储空间需求和加快网络传输速度。

LZ777

image-20240905003956137

LZ77 的核心思路是如果一个串中有两个重复的串,那么只需要知道第一个串的内容和后面串相对于第一个串起始位置的距离 + 串的长度

比如: ABCDEFGABCDEFH → ABCDEFG(7,6)H。7 指的是往前第 7 个数开始,6 指的是重复串的长度,ABCDEFG(7,6)H 完全可以表示前面的串,并且是没有二义性的。

LZ77 用 滑动窗口(sliding-window compression)来实现这个算法。具体思路是扫描头从串的头部开始扫描串,在扫描头的前面有一个长度为 N 的滑动窗口。如果发现扫描头处的串和窗口里的 最长匹配串 是相同的,则用(两个串之间的距离,串的长度)来代替后一个重复的串,同时还需要添加一个表示是真实串还是替换后的“串”的字节在前面以方便解压(此串需要在 真实串和替换“串” 之前都有存在)。

例如:缓冲区字符(ABCB)在滑动窗口的位移6位置找到AB,成功匹配到短语AB,将ABC编码为(6,2,C)

img

Huffman Coding

Huffman Coding 是大学课本中都会提到的算法。核心思路是通过构造 Huffman Tree 的方式给字符重新编码(核心是避免一个叶子的路径是另外一个叶子路径的前缀),以保证出现频路越高的字符占用的字节越少。

哈夫曼树的应用很广,哈夫曼编码就是其在电讯通信中的应用之一。广泛地用于数据文件压缩的十分有效的编码方法。其压缩率通常在20%~90%之间。

解压:Huffman Coding 之后需要维护一张 Huffman Map 表,来记录重新编码后的字符串,根据这张表,还原原始串也是非常高效的。

路径: 在一棵树中 ,从一个结点到另外一个结点的通路就称为 树的路径 .如图1.1 从根节点root到D节点之间的通路就是二叉树中的一条路径

img

压缩核心之 Deflate

GZIP 的核心是 Deflate,在 RFC 1951 中被标准化,并且在当时作为 LZW 的替代品有了非常广泛的使用。

Deflate 是一个同时使用 LZ77 与 Huffman Coding 的算法.

如果想探究加解密的原理请看:《数据压缩技术原理与范例》

http传输应用

RFC 2016 中 GZIP 已经成为了规定的三种标准HTTP压缩格式之一。目前绝大多数的网站都在使用 GZIP 传输 HTML、CSS、JavaScript 等资源文件。

nginx开启

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 开启
gzip on;

# 压缩等级,1-9。设置多少可以参考:http://serverfault.com/questions/253074/what-is-the-best-nginx-compression-gzip-level
gzip_comp_level 2;

# "MSIE [1-6]\." 比如禁止 IE6 使用 GZIP
gzip_disable regex ...

# 最小压缩文件长度
gzip_min_length 20;

# 使用 GZIP 压缩的最小 HTTP 版本
gzip_http_version 1.1;

# 压缩的文件类型,值是 [MIME type](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types)
gzip_types text/html;

打开浏览器,访问你的网站,看 Chrome 的 Network,点 use larger request row,如果 Size 上有两个不一样大小的体积(如:222KB 和 613KB),则代表 GZIP 已经成功开启。

那浏览器又是如何和服务器配合的呢?

image

浏览器在请求资源的时候再 header 里面带上 accept-encoding: gzip 的参数。Nginx 在接收到 Header 之后,发现如果有这个配置,则发送 GZIP 之后的文件(返回的 header 里也包含相关的说明),如果没有则发送源文件。浏览器根据 response header 来处理要不要针对返回的文件进行解压缩然后展示。

缓存应用

我们在使用缓存中间件或者一些存储层的时候总会遇到存储的数据太庞大的问题。

解决办法:分片、压缩

分片就不多说了。

在java中有原生的GZIPInputStream实现,还可以使用Guava或者hutool去简单实现压缩。

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
String s = "一般来说,gzip 压缩算法的目的是减少数据量,从而节省存储空间或提高传输效率。然而,在某些情况下,gzip 压缩后的文件或数据可能实际上会占据更大的存储空间。这通常涉及到以下几个因素:“"+ "数据的特性“、“压缩级别”、“上下文和字典”和“文件系统开销”。"+
"压缩级别gzip 压缩算法通常允许用户指定压缩级别,这决定了压缩的程度。更高的压缩级别意味着更高的压缩比,但同时也需要更多的计算资源和时间。如果选择了低压缩级别,或者没有根据数据特点选择合适的压缩级别,可能无法实现期望的存储节省。上下文和字典gzip 算法依赖于上下文和字典来压缩数据。如果数据中缺乏足够的重复模式或上下文信息,gzip 可能无法构建有效的字典,从而无法达到良好的压缩效果。文件系统开销在某些文统,文件的存储方式和文件系统的开销可能影响文件的总大小。例如,文件系统中的文件分配单元大小、元数据开销或碎片问题都可能导致实际存储的文件大小超过其内容本身的大小。要减少 gzip 压缩后文件大小增加的可能性,可以考虑以下几点:选择适合的压缩级别,通常建议尝试不同级别以找到最佳平衡。对于已经高度压缩的数据格式,可以考虑使用专门为这些格式设计的其他压缩工具或算法。在压缩之前,对数据进行预处理,例如删除不需要的元数据或空白,以减少需要压缩的数据量。如果存储空间是主要考虑因素,可以使用更高效的压缩算法或工具,或者考虑使用不同的存储格式。如果文件系统开销是问题,可以尝试优化文件系统设置,例如选择合适的簇大小或进行文件系统碎片整理" +
"压缩级别gzip 压缩算法通常允许用户指定压缩级别,这决定了压缩的程度。更高的压缩级别意味着更高的压缩比,但同时也需要更多的计算资源和时间。如果选择了低压缩级别,或者没有根据数据特点选择合适的压缩级别,可能无法实现期望的存储节省。上下文和字典gzip 算法依赖于上下文和字典来压缩数据。如果数据中缺乏足够的重复模式或上下文信息,gzip 可能无法构建有效的字典,从而无法达到良好的压缩效果。文件系统开销在某些文统,文件的存储方式和文件系统的开销可能影响文件的总大小。例如,文件系统中的文件分配单元大小、元数据开销或碎片问题都可能导致实际存储的文件大小超过其内容本身的大小。要减少 gzip 压缩后文件大小增加的可能性,可以考虑以下几点:选择适合的压缩级别,通常建议尝试不同级别以找到最佳平衡。对于已经高度压缩的数据格式,可以考虑使用专门为这些格式设计的其他压缩工具或算法。在压缩之前,对数据进行预处理,例如删除不需要的元数据或空白,以减少需要压缩的数据量。如果存储空间是主要考虑因素,可以使用更高效的压缩算法或工具,或者考虑使用不同的存储格式。如果文件系统开销是问题,可以尝试优化文件系统设置,例如选择合适的簇大小或进行文件系统碎片整;
byte[] bytes = ZipUtil.gzip(s.getBytes());
System.out.println(bytes.length);
System.out.println(s.getBytes().length); //963
byte[] unGzip = ZipUtil.unGzip(bytes);//3379
System.out.println(new String(unGzip));
}

原始字符占3379字节压缩后占963字节,释放了将近70%的空间,

我们简单了解原理后可以知道如果重复字符占比较多那么压缩效率会更大。

参考文档