前段时间公司的web服务器需要增加代理的gz解压功能。刚好手里有一些基础库,试着写了gz的解码函数。
开始以为很简单,后面读了不少相关的文档才发觉还是比较复杂的,花了不少时间才理清楚。
本文就对gz文件格式作一个完整的描述,因为复杂的部分主要是deflate(rfc1951)格式包的实现,所以本文也可以作为
deflate格式的参考简要说明。

为了本文理解上的准确性和rfc1951的标准的一致性,
定义如下的一些术语。

1, 符号 指输入数据的最小分隔单位,一般指0-255的字符。
2, 回退距离(backward distance) 指LZ77往后回退的距离大小,大小从1开始。
3, 匹配长度(length) 指LZ77中符号串在和字典窗口中相等的最长大小。
4, 匹配对 指<匹配长度, 回退距离>的lZ77的记录对,LZ77算法中匹配结果要么是一个匹配中要么是一个字符。
5, 编码表 指符号和编码值(可以是固定值,也可以是huffman编码)的一一对应表

首先,deflate文件的压缩原理说明一下:

deflate使用了lz77的字典压缩和huffman压缩两种方式,扩展位编码及长度压缩内容还使用了类似游程编码的处理方法。
lz77压缩通过查找字典窗口字符串使用匹配对或单个字符记录。
然后使用huffman字符对字符或匹配对进行编码。

具体的huffman及lz77编码方法网上有比较多的参考教程。

deflate格式中字典窗口大小固定为32k, 最长匹配大小为258, 最小匹配距离为3。

其它需要的细化的地方我这再作说明。

实现了上述算法之后,便发现huffman压缩后的编码数据,如何保存编码表才是最主要的工作。
rfc1951的deflate实质上是定义了一种LZ77和huffman编码的保存方案。

下面先说明deflate的字符存储方式(这一部分可参考rfc1951相关部分)。

A, 基本的字节存储方案
deflate格式对于编码的保存采用字节为单位,从左到右依次为第0字节,第1字节...。
  对于一个字节的字节内容deflate的存储方案有两种,一种可以称为数值型,另一种称作编码型。
  对于编码型(包括huffman码和固定的编码)采用从低位到高位的填充方式,对于数值型则使用由高位到低位的方式。
  对于小端机器,可认为编码型存储值和内存二进制值相反,数值编码值和内存二进制值一致。
  
B, 块存储方案
  deflate以流块为基本单位,所有的编码后的数据最后以块存储下来,块中的字节则按A中定义方式进行存储。
  每一个块以三位的编码头开始
  其中第一位指示是否为最后一个块(1表示为最后一块),后两位分别为二进制数值(注意是数值)表示块类型
    00表示非压缩块(简写为ncm(noncompression mode))   无数据压缩
    01表示固定编码压缩块(简写为fcm)                  固定编码表的压缩,数据中无需加入编码表
    10表示带有编码表的块(简写为dcm)                  huffman编码表压缩,数据中含有huffman编码表信息

ncm,fcm,dcm格式块头分别如下(其中第一位(图示第三位)1表示是最后一块:

+---+---+---+ 
 | 0    0   0/1  
 +---+---+---+

+---+---+----+ 
 | 0    1   0/1  
 +---+---+---+

+---+---+---+ 
 | 1    0   0/1  
 +---+---+---+

ncm模式(非压缩模式)

一个ncm块块头后紧接使用如下存储格式
  0   1   2    3    4...
 +---+---+---+---+================================+
 |  LEN  | NLEN |...        LEN bytes of literal data...               |
 +---+---+---+---+================================+
   其中len是两字节的数值,nlen是对len按位取反的两字节数值,后面是len长度的数据的直接拷贝。
   这里说明一下,rfc1951规定中起始位置不一定是整数字节,所以对于ncm块如果从非整数位开始,
   完成块头信息后,其它位信息不处理直到下一字节,但实际GNU gzip中需要把后续位置为0.

 fcm模式(固定huffman压缩模式)
  fcm块使用如下编码表存储编码后的数据
  如上所言,LZ77匹配数据要么为一个符号,要么为匹配对。匹配中的最大匹配长度为3-258, 低于3个字节
  则无压缩必要。fcm中把符号和长度一起编码,对于符号0-255直接使用编码符号;对于3-258的长度
  则使用如下编码表,扩展码+扩展位(数值型)可表示对应范围的长度值。这样长度和符号一起编码为0-285的编码。
  其中256表示块的结束。
  
    Extra Extra Extra
    Code Bits Length(s) Code Bits Lengths Code Bits Length(s)
    ---- ---- ------             ---- ---- -------            ---- ---- -------
    257   0    3           267    1   15,16            277    4   67-82
    258   0    4           268    1   17,18            278    4   83-98
    259   0    5           269    2   19-22            279    4   99-114
    260   0    6           270    2   23-26            280    4   115-130
    261   0    7           271    2   27-30            281    5   131-162
    262   0    8           272    2   31-34            282    5   163-194
    263   0    9           273    3   35-42            283    5   195-226
    264   0    10         274    3   43-50            284    5   227-257
    265   1    11,12    275    3   51-58            285    0   258
    266   1    13,14    276    3   59-66
    
    然后对0-285使用如下的固定huffman编码表编码
    Lit Value Bits         Codes
    --------- -      ---        -----
    0 - 143        8      00110000-10111111
    144 - 255    9      110010000-111111111
    256 - 279    7      0000000-0010111
    280 - 287    8      11000000-11000111
    
    回退距离则使用同样方法使用如下的距离编码表编码为0-29的值,然后直接作为5位数值存储。
    
    Extra Extra Extra
    Code Bits Dist Code Bits Dist    Code Bits Distance
    ---- ---- ----         ---- ---- ------       ---- ---- --------
    0     0   1           10    4  33-48       20   9  1025-1536
    1     0   2           11    4  49-64       21   9  1537-2048
    2     0   3           12    5  65-96       22  10 2049-3072
    3     0   4           13    5  97-128     23  10 3073-4096
    4     1   5,6        14    6  129-192   24  11 4097-6144
    5     1   7,8        15    6  193-256   25  11 6145-8192
    6     2   9-12      16    7  257-384   26  12 8193-12288
    7     2   13-16    17    7  385-512   27  12 12289-16384
    8     3   17-24    18    8  513-768   28  13 16385-24576
    9     3   25-32    19    8  769-1024 29  13 24577-32768

一个fcm块块头后紧接如下格式
 +--------+--------- +----------------------+------------+
 |   H/P  |   H/P    |      ...........           | 0000000 |(最后块部分标志256)
 +--------+----- ----+----------------------+------------+

每个存储单元中要么为LZ77符号(不存在的匹配,返回的单个符号)的huffman编码H,其为上述的00110000-10111111、110010000-111111111、0000000-0010111、11000000-11000111中的范围之一,存储顺序在小端机器上和图示表示相反(编码类型)。要么为LZ77匹配对P,其存储格式如下:

+-------+------- +---5--+----------------------------+
 |   H   |    E     |   D    |              ED               |
 +------+--------+-------+---------------- -----------+

其中H为匹配长度huffman编码,E为扩展位存储长度扩展信息(0-5位),然后存储距离0-29的距离5位数值编码D,然后是距离扩展信息数值编码ED(0-13位)。

dcm模式(动态huffman压缩模式)
  dcm模式块同样使用了fcm中的编码表作为第一步,把符号和长度一起变成0-285的编码,距离变为0-29。  
  不同于fcm中不需要存储huffman编码表,dcm中把0-285的编码作为符号使用huffman进行编码,生成一个长度/符号huffman树。
  接着是deflate不同于普通的huffman树的地方,dcm中并没有存储符号和对应的编码,而是把huffman树作了一个变化,
  所谓的huffman正规化/范式化,范式化处理后可以保证huffman按照符号的排序(从小到大)后输出的编码刚好为字典序。

其处理方法可以通过保证右边任意节点深度大于左边所有节点深度来保证或使用rfc1951中的数值方法进行处理。

假设符号按0-285的顺序,存储其对应的编码长度,符号不需要再存储;只需存储对应的长度+符号的总数,便可还原长度符号编码表。同样方法应用于距离编码表。
  此时,便是如何存储这两个长度序列,由于长度存储的是huffman编码的码长度值范围为0-15(后面说明部分介绍如何保证长度范围),
  为了存储这两个固定长度的长度序列,dcm对长度/符号的长度及距离序列分别进行如下编码(类似游程编码):
0 - 15: 表示码长度0-15.
16:  拷贝先前码长度3-6次   后扩展两位(数值)表示3-6范围
17: 重复长度码'0' 3-10次    后扩展三位(数值)表示3-10范围
18: 重复长度码'0' 11-138次   后扩展七位(数值)表示11-138范围

编码后数值范围为[0-18],然后把两个数列拼接一起(记为CS),记录符号(长度值)总数;同样使用长度+符号总数便可还原。
  因此再次使用huffman编码,正规化后huffman树长度范围为0-7(见说明部分长度约束),再经过rfc1951的序列重排(序列为16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15),最后直接使用3位数值存储。
  加上原来的距离序列(记为ds)、长度/符号编码序列(记为LS),块头后紧接如下格式:
           5位             5位                  4位
 +---------------+-----------------+----------------+--------------+---------------+---------------+---------------+
 |LS长度-257  DS长度-1       CS长度-4            CS              LS               DS             压缩数据  |
 +---------------+------------------+---------------+--------------+---------------+---------------+---------------+
 
 注意:
  1,huffman树的正规化实际上在rfc1951中提供一种数值计算的算法,可以使用进行计算。
  2,上面的符号/长度编码序列及距离要求码值长度小于15,最后的序列CS长度要求小于7,实际数据处理会出现大于的情况。
      可以通过剪去大于分支,移到最下层最低频率/权重数据位置,相应位置节点上移。
      实际上这一部分是rfc1951中并没有清晰说明的地方。修改之后的树不再是huffman树,因此不同的实现可能存储结果不同。
  3,huffman中距离编码存在1位也没有的情况(无匹配对),但rfc中要求DS存储值为DS长度-1,因此无法存储值为0情况,
      实际过程中通过设置对应编码为空实现(rfc中说明), 这一部分可能需要处理一下huffman树。
  4,rfc1951中要求ncm中数据最大长度不超过65535(两个字节长度最大值),除此外数据块大小无限制。同时要求数据块
    编码树独立,但解压可以使用前个块中字典,可以通过传入字典窗口保证。
    
完成了deflate包,余下的就是gz格式(rfc1952)的封装,包头很简单
分别为签名(0x1f,0x8b),压缩方法(8),标志位,创建时间,扩展标志,系统类型。

+----+----+----+----+----+----+----+----+----+----+
|ID1|ID2|CM|FLG|       MTIME        |XFL|OS | (more-->)
+----+----+----+----+----+----+----+----+----+----+
标志位
bit 0 FTEXT       文本模式
bit 1 FHCRC       包头CRC32低位检验值
bit 2 FEXTRA      扩展标志
bit 3 FNAME       文件名
bit 4 FCOMMENT    注释信息

后三位保留使用,全部置为0
如果设置标志依次写入扩展信息然后就是deflate数据体

最后附加4位crc32校验和和文件模2^32值
0    1   2    3   4    5    6    7
+---+---+---+---+---+---+---+---+
|     CRC32     |       ISIZE      |
+---+---+---+---+---+---+---+---+

说明:
deflate格式是比较复杂的一个压缩格式,主要难点在以下几个地方。
1,LZ77压缩算法中的最长距离匹配是比较复杂的一部分,zlib使用了Rabin & Karp算法匹配,加入了
  不同策略来平衡速度和压缩比。不同的实现方法下压缩速度和压缩率差异比较大。是数据压缩率和速度差异的根本部分。实现时优先保证这部分的算法效率。
2,huffman树的剪枝(长度范围约束)是比较复杂的一部分,一般数据的超出部分有限,否则处理时间有一定要求。

deflate从使用上过于复杂化了huffman表存储这一部分,因为本身符号数目有限,直接存储要方便的多,
而deflate设计多次使用huffman压缩,同时为了满足位长度要进行修剪,这样树不再是huffman树,可能存储的压缩比也会损失一些。效果存储的最主要部分是lz77的压缩部分,受于限制最长匹配只能258,对于特别长的重复内容压缩效果可能会损失一些。

参考:

https://tools.ietf.org/html/rfc1951

https://tools.ietf.org/html/rfc1952

https://blog.csdn.net/jison_r_wang/article/details/52075870

https://www.cnblogs.com/en-heng/p/4992916.html

deflate及gzip格式说明相关推荐

  1. 文章分享:Gzip 格式和 DEFLATE 压缩算法详解

    Introduction 引言: 当键入 tar -zcf src.tar.gz src 时,就可以将 src 下的所有文件打包成一个 tar.gz 格式的压缩包.这里的 "tar" ...

  2. gzip格式分析与识别

    " 介绍gzip格式,识别gzip压缩的数据流量." 在协议分析过程中,经常会发现gzip压缩的数据,例如在HTTP协议中,在HTTP头中会标示,内容编码为gzip.DEFLATE ...

  3. 【协议分析】Gzip格式与解析

    一.gzip 与 zlib的关系 zlib被设计成一个免费的.通用的.合法的.不受任何专利保护的无损数据压缩库,几乎可以在任何计算机硬件和操作系统上使用.zlib数据格式本身可以跨平台移植.zlib由 ...

  4. android解压缩GZIP格式的网页数据

    简介: 转载请注明来自:http://blog.csdn.net/icyfox_bupt/article/details/9572813 进行安卓的软件开发就少不了和网络上的数据打交道,为了做出让用户 ...

  5. linux上传oracle压缩包,Linux中从oracle官网下载jdk文件不是标准的gzip格式文件问题...

    http://download.oracle.com/otn-pub/java/jdk/8u131-b11/d54c1d3a095b4ff2b6607d096fa80163/jdk-8u131-lin ...

  6. HDFS文件系统内的文件格式转换(zip格式转化成gzip格式)

    这篇主要介绍利用hdfs接口,使用java编程向hdfs写入数据. 一.模仿hadoop fs -put 和 -copyFromLoca命令,实现本地复制文件到hdfs: View Code 二.写一 ...

  7. gzip 与 deflate :gzip算法原理深入分析

    gzip 与 deflate :gzip算法原理深入分析 gzip 与 deflate :gzip算法原理深入分析 - gzip 与 deflate :gzip算法原理深入分析 [复制链接]     ...

  8. java对gzip格式进行压缩与解压缩

    java对gzip格式进行压缩和解压缩 对gzip格式字符串进行解压缩 public static String unzip(String compressedStr){ if(compressedS ...

  9. python读取gzip格式及普通格式网页的方法

    一般情况下,我们读取网页分析去返回内容时是这样子的: #!/usr/bin/python #coding:utf-8 import urllib2 headers = {"User-Agen ...

最新文章

  1. 16. Spring Boot使用Druid(编程注入)【从零开始学Spring Boot】
  2. Spring Webflux: Kotlin DSL [片断]
  3. HM16.0之帧间预测——xCheckRDCostInter()函数
  4. osmnx 应用 可视化两张图异同的点和边
  5. 第三篇——第二部分——第六文 监控SQL Server镜像
  6. 剑与轮回找回服务器,剑与轮回自由之都1服开服时间表_剑与轮回新区开服预告_第一手游网手游开服表...
  7. 开发人员不得不知的MySQL索引和查询优化
  8. substr判断最后一个是不是逗号_考研最后一个月是不是很累?
  9. 如何实现分布式锁?已拿意向书!
  10. VS C++ 控制台----暂停的方法
  11. Android MediaPlayer播放raw资源封装类
  12. MEGA2560 arduino烧录USB 芯片flash以及bootloader记录
  13. 2022年各国程序员薪资排名
  14. 服务器与Tomcat区别
  15. IOS Xcode 开发适配最低IOS版本 适配POD库
  16. keil uvisoin软件打开他人的工程文件上面出现黄色感叹号警告标志,后续编译不能生成.o文件而报错
  17. 使用python绘画蟒蛇
  18. java开发环境搭建(一)
  19. 优盘格式化了怎么恢复里面的数据?
  20. 手机游戏显示不了服务器闪退,手机为什么玩游戏闪退_手机游戏进去就闪退解决方法...

热门文章

  1. 最新阿里、腾讯、华为、字节等大厂的薪资和职级对比,看看你差了多少...
  2. ESLive课件白板介绍
  3. 2022年HarmonyOS/OpenHarmony生态观察
  4. Tic-Tac-Toe人机对弈程序(python实现)
  5. php文件怎么改为mp3,[视频转音频软件]怎么把mp4转换成mp3音乐格式
  6. 《A Deep Generative Framework for Paraphrase Generation》论文笔记--摘要、引言
  7. Node.js开发入门—Express安装与使用
  8. 第三章 同步 Windows程序设计 王艳平版
  9. 想请问一下图上的问题可以吗,拜托了拜托了
  10. 一文跑通腾讯地图demo