JavaWeb - 简单聊聊 GZIP 的压缩原理与日常应用

简介: JavaWeb - 简单聊聊 GZIP 的压缩原理与日常应用

前言

在基于 HTTP 协议的网络传输中 GZip 经常被使用,Nginx 中也可以使用半行代码开启 GZip。GZip 压缩的原理是什么呢?本篇文章是我在网上阅读了一些文档后做的简单总结。


从 RFC 1952 看起

RFC 1952GZIP file format specification version 4.3。该规范主要定义了 GZip 压缩的在数据格式方面的规范,以方便不同的操作系统、CPU、文件系统等之间进行文件传输交换。下面挑有意思的几个点说,感兴趣的可以阅读 RFC 1952 的原文。


GZIP 的文件格式在设计上其实是可以允许一个文件里有多个压缩数据集(compressed data sets)—— GZIP 压缩后的片段拼接而成的。但就我们大多数应用场景来说,基本上都是一个文件一个压缩数据集,如果是多个文件一起打包的话,也往往是将多个包合并成一个 tar 文件。

每个压缩数据集都是下面的结构:

| ID1 | ID2 | CM | FLG | MTIME(4字节) | XFL | OS | ---> more

|| 之间是 1 byte,都是大端字节(Big Edian)

  • 其中 ID1 和 ID2 分别是 0x1f 和 0x8b,用来标识文件格式是 gzip
  • CM 标识 加密算法,目前 0-7是保留字,8 指的是 deflate 算法
  • FLG 从低地址到高地址分别是 FTEXT、FHCRC、FEXTRA、FNAME、FCOMMENT、reserved、 reserved、reserved,这里每个 bit 被设置了之后有什么意义感兴趣的话可以详细参考 RFC 1952。比较有意思的是 FEXTRA,如果它被设置了表示存在额外的拓展字段。拓展字段的结构如下:
  • | SI1 | SI2 | LEN | ... LEN bytes of subfield data ... |
  • SI1、SI2 是对子域的 ID,由 ASCII 码组成。如果你需要使用的话,可以向他的维护者 Jean-Loup Gailly <gzip@prep.ai.mit.edu> 发邮件申请。目前 Apollo file 就有自己的专属 ID
  • MTIME 指的是源文件最近一次修改时间,存的是 Unix 时间戳
  • XFL 是给压缩算法传的一些参数,用来标识如何解压。defalte 算法中 2 表示使用压缩率最高的算法,4 表示使用压缩速度最快的算法
  • OS 标识压缩程序运行的文件系统,以处理 EOF 等的问题
  • more 后面是根据 FLG 的开启情况决定的,可能会有 循环冗余校验码、源文件长度、附加信息等多种其他信息

压缩核心之 Deflate

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

Deflate 是一个同时使用 LZ77 与 Huffman Coding 的算法,这里简单介绍下这两种算法的大致思路:

  • LZ77

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

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


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

image.png

实际过程中滑动窗口的大小是固定的,匹配的串也有最小长度限制,以方便 标识+两个串之间的距离+串的长度 所占用的字节是固定的 以及 不要约压缩体积越大。更加详细的实现可以参考:Standford Edu. lz77 algorithmLZ77 Compression AlgorithmLZ77压缩算法编码原理详解(结合图片和简单代码)

这里通过这个压缩机制也就能比较容易的解释为啥 CSS BEM 写法 GZIP 压缩之后可以忽略长度以及 JPEG 图片 GZIP 之后可能会变大 的情况了

解压:GZIP 的压缩因为要在窗口里寻找重复串相对来说效率是比较低的(LZ77 还是通过 Hash 等系列方法提高了很多),那解压又是怎么个情况呢?观察压缩后的整个串,每个小串前都有一个标识要标记是原始串还是替换“串”,通过这个标识就能以 O(1)的复杂度直接读完并且替换完替换“串”,整体上效率是非常可观的。

  • Huffman Coding

Huffman Coding 是大学课本中一般都会提到的算法。核心思路是通过构造 Huffman Tree 的方式给字符重新编码(核心是避免一个叶子的路径是另外一个叶子路径的前缀),以保证出现频路越高的字符占用的字节越少。关于 Huffman Tree 的构造这里不再细说,不太清楚的可以参考:Huffman Codingimage.png

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

Deflate 综合使用了 LZ77 和 Huffman Coding 来压缩文件,相对而言又提升了很多。详细可以参考 gzip原理与实现


网站中的使用

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

Nginx 开启

Nginx 的 ngx_http_gzip_module 也提供了开启 GZIP 压缩的方式,有下面的一些常用配置:

#开启gzipon;
#压缩等级,1-9。设置多少可以参考:http://serverfault.com/questions/253074/what-is-the-best-nginx-compression-gzip-levelgzip_comp_level2;
#"MSIE [1-6]\."比如禁止IE6使用GZIPgzip_disableregex ...
#最小压缩文件长度gzip_min_length20;
#使用GZIP压缩的最小HTTP版本gzip_http_version1.1;
#压缩的文件类型,值是 [MIMEtype](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types)gzip_typestext/html;

相关探测

Nginx 上开启 GZIP 之后,理论上会按照 GZIP 配置打开压缩。那如何检测是否开启成功了呢?

image.png

image.png

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

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

image.png

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

 

参考文档

目录
相关文章
|
6天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
18天前
|
存储 Java 关系型数据库
高效连接之道:Java连接池原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。频繁创建和关闭连接会消耗大量资源,导致性能瓶颈。为此,Java连接池技术通过复用连接,实现高效、稳定的数据库连接管理。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接池的基本操作、配置和使用方法,以及在电商应用中的具体应用示例。
37 5
|
8天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
8天前
|
Java
Java之CountDownLatch原理浅析
本文介绍了Java并发工具类`CountDownLatch`的使用方法、原理及其与`Thread.join()`的区别。`CountDownLatch`通过构造函数接收一个整数参数作为计数器,调用`countDown`方法减少计数,`await`方法会阻塞当前线程,直到计数为零。文章还详细解析了其内部机制,包括初始化、`countDown`和`await`方法的工作原理,并给出了一个游戏加载场景的示例代码。
Java之CountDownLatch原理浅析
|
10天前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
15天前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
基于开源框架Spring AI Alibaba快速构建Java应用
|
8天前
|
SQL Java 数据库连接
从理论到实践:Hibernate与JPA在Java项目中的实际应用
本文介绍了Java持久层框架Hibernate和JPA的基本概念及其在具体项目中的应用。通过一个在线书店系统的实例,展示了如何使用@Entity注解定义实体类、通过Spring Data JPA定义仓库接口、在服务层调用方法进行数据库操作,以及使用JPQL编写自定义查询和管理事务。这些技术不仅简化了数据库操作,还显著提升了开发效率。
20 3
|
18天前
|
SQL 监控 Java
技术前沿:Java连接池技术的最新发展与应用
本文探讨了Java连接池技术的最新发展与应用,包括高性能与低延迟、智能化管理和监控、扩展性与兼容性等方面。同时,结合最佳实践,介绍了如何选择合适的连接池库、合理配置参数、使用监控工具及优化数据库操作,为开发者提供了一份详尽的技术指南。
28 7
|
16天前
|
SQL Java 数据库连接
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率。本文介绍了连接池的工作原理、优势及实现方法,并提供了HikariCP的示例代码。
30 3
|
16天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
33 2