详细解读7z文件格式及其源码的分析(三)

简介: 详细解读7z文件格式及其源码的分析(三)

上一篇在这里. 这是7z文件格式分析的第三篇, 相信有了前两篇的准备,你已经了解了7z源码的大致结构, 以及如何简单调试7z的源码了. 很多同学是不是迫不及待想要拔去7z的神秘外衣,看看究竟了. 好, 这就带你们一探乾坤. 本文开始,我们详细介绍7z的文件存储结构.

要了解7z的结构, 当然最好从官方的说明开始, 尽管这个说明非常简略, 但它的确是我入门时的救命稻草.

打开源码的 "DOC" 目录. 这里面就是官方所有的文档了. 其中只有二个文档跟结构相关:

1. 7zFormat.txt, 这是我们的主角, 里面介绍了7z文件的大体结构.

2. Methods.//代码效果参考:http://www.zidongmutanji.com/bxxx/274308.html

txt, 这里面介绍了7z压缩算法id的编码规则, 以后会用到.

我们从7zFormat.txt文件开始.

?12345678910111213Archive structure~~~~~ SignatureHeader【PackedStreams】【PackedStreamsForHeaders】【 Header or { Packed Header HeaderInfo }】

上面就是7z文件的总体结构了. 我来稍微解释一下. 上面的代码中, 从波浪线往后开始算. 7z的文件结构基本上分为三部分:

1. 前文件头(就是最前面的header).

2. 压缩数据.

3. 尾文件头(就是放在文件末尾的header).

一, 前文件头就是上图中的 "SignatureHeader". 它是32个字节定长的. 前文件头其实记录的信息很少, 它的主要目的是记录尾文件头的位置, 压缩的主要结构都是存在尾文件头中.

它的结构如下:

?123456789101112131415SignatureHeader~~~ BYTE kSignature【6】 = {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C};ArchiveVersion { BYTE Major; // now = 0 BYTE Minor; // now = 2 };UINT32 StartHeaderCRC;StartHeader { REAL_UINT64 NextHeaderOffset REAL_UINT64 NextHeaderSize UINT32 NextHeaderCRC }

先是固定的6个字节的值, 前两个字节的值是字母 '7' 和'z' 的ascii值. 后面四个字节是固定的: 0xbc, 0xaf, 0x27, 0x1c

然后是两个字节的版本号, 注意主版本号在前面, 次版本号在后面. 目前的版本号是: 0.2, 注意这是7z文件格式的版本号, 不是7z软件的版本号.

然后是四个字节的 UINT32 的值, (注意, 7z的所有数据都是采用小端在前的存储, 所以要注意这四个字节的实际存储顺序是低位字节在前面, 高位字节在后. 后面的所有数据都是这种结构, 所以以后就不再强调了. ) . 这4个字节的值是做什么的呢? 先抛开这四个字节本身, 前文件头的32个字节中, 已经用去了 6 + 2 +4 =12 个, 还剩下20个字节. 对了, 这四个字节就是剩下的20个字节的CRC校验值. 具体的CRC算法源码, 在源码中的 "C" 文件夹下的 '7zCrc.c' 和 '7zCrc.h'.

最后这20个字节要一起介绍了. 先是8个字节的UINT64的值, 它记录的是尾文件头(上图中的NextHeader)与前文件头的距离, 这个距离是不算前面这32个字节头的, 也就是抛开前面32个字节开始计数的(解压器通过读取这个值,然后从第33个字节开始直接跳过这个距离, 就可以找到尾文件头了). 然后是8个字节的值, 记录了尾文件头的大小(解压的时候, 通过这个值就能读出尾文件头的长度了). 最后还有4个字节的值, 它也是一个Crc校验值, 是整个尾文件头的校验值.

这里需要注意的是, 上图中用的是 "REAL_UINT64" 这个表达方式, 它的意思就是我们通常理解的占8个字节的UInT64的值(当然是小端存储的啦). 这里用了"real", 真. 那是不是还有"假"的InT64呢. 答案是肯定的. 7z为了兼容压缩大文件(大于4G),这个问题曾一度是zip文件的噩梦, 早期的zip只能压缩小于4G的文件, 并且压缩后的总文件大小也不能超过4G, 后来专门做了标准升级. 好了扯远了. 7z一早设计就考虑到了大文件的问题, 所以很多地方都必须用int64来表达, 这样也会带来一个问题, 就是绝大多数case下, 都不可能超过4G(试问一下,你平时有多少压缩文件超过4G 呢), 所以呢, 就会造成8个字节的int64根本用不上, 多余的字节浪费了. 尤其在小文件压缩的时候, 很影响压缩比. 所以呢, 7z采取了一种巧妙的方法. 就是int64并不是都用8个字节存储, 它用一种简单的编码方式,进行变长存储. 在这个文件中也有描述:

?123456789101112REAL_UINT64 means real UINT64.UINT64 means real UINT64 encoded with the following scheme:Size of encoding sequence depends from first byte: First_Byte Extra_Bytes Value (binary) 0xxxxxxx : ( xxxxxxx ) 10xxxxxx BYTE y【1】 : ( xxxxxx [ (8 1)) + y 110xxxxx BYTE y【2】 : ( xxxxx [ (8 2)) + y ... 1111110x BYTE y【6】 : ( x [ (8 6)) + y 11111110 BYTE y【7】 : y 11111111 BYTE y【8】 : y

上面就是编码方式: 就是根据第一个字节的内容来判断后面还有多少个字节.

如果第一个字节的最高位是 0, 那后面就没有字节了. 范围在 0-127.

如果第一个字节的最高两位是 1, 0, 表示它后面还有一个字节. 读取方式是: ( xxxxxx [ (8 1)) + y

依次类推, 不再详细介绍了.

它的写入方法在: \CPP\7zip\Archive\7z\7zOut.cpp 文件的 第204行:

?12345678910111213141516171819202122void COutArchive::WriteNumber(UInt64 value){ Byte firstByte = 0; Byte mask = 0x80; int i; for (i = 0; i < 8; i++) { if (value < ((UInt64(1) [ ( 7 (i + 1))))) { firstByte |= Byte(value ] (8 i)); break; } firstByte |= mask; mask ]= 1; } WriteByte(firstByte); for (;i > 0; i--) { WriteByte((Byte)value); value ]= 8; }}

它的读取方法在: 7zIn.cpp 的第210行:

?12345678910111213141516171819202122UInt64 CInByte2::ReadNumber(){ if (_pos >= _size) ThrowEndOfData(); Byte firstByte = _buffer【_pos++】; Byte mask = 0x80; UInt64 value = 0; for (int i = 0; i < 8; i++) { if ((firstByte & mask) == 0) { UInt64 highPart = firstByte & (mask - 1); value += (highPart [ (i 8)); return value; } if (_pos >= _size) ThrowEndOfData(); value |= ((UInt64)_buffer【_pos++】 [ (8 i)); mask ]= 1; } return value;}

这里贴出来给大家参考一下. 其实, 后面提到的Uint64如果没有特别说明是8个字节, 那它都是采用这种压缩方式存储的. 但是注意UInt32 无论何时都是占4个字节的, 没有采用压缩.

二, 第二部分比较简单, 它会比较大, 简单的说, 它就是文件压缩后的压缩数据存放地点. 结构如下:

?12【PackedStreams】【PackedStreamsForHeaders】

简单的说, 7z会把文件压缩成若干个"Pack", 就是包的意思, 这里就是按顺序存储这些pack的. 每个pack的位置和大小信息都会记录在尾header中, 解压的时候就会从这里读出pack,然后解压出来. 这里都是简单的排布压缩后的数据, 所以没有多少细节需要介绍的.

三, 真正复杂的主角出场了, 尾文件头, 就是7z中所谓的 nextHeader.

Header structure

~~~~

{

ArchiveProperties

AdditionalStreams

{

PackInfo

{

PackPos

NumPackStreams

Sizes【NumPackStreams】

CRCs【NumPackStreams】

}

CodersInfo

{

NumFolders

Folders【NumFolders】

{

NumCoders

CodersInfo【NumCoders】

{

ID

NumInStreams;

NumOutStreams;

PropertiesSize

Properties【PropertiesSize】

}

NumBindPairs

BindPairsInfo【NumBindPairs】

{

InIndex;

OutIndex;

}

PackedIndices

}

UnPackSize【Folders】【Folders.NumOutstreams】

CRCs【NumFolders】

}

SubStreamsInfo

{

NumUnPackStreamsInFolders【NumFolders】;

UnPackSizes【】

//代码效果参考:http://www.zidongmutanji.com/zsjx/216399.html

CRCs【】

}

}

MainStreamsInfo

{

(Same as in AdditionalStreams)

}

FilesInfo

{

NumFiles

Properties【】

{

ID

Size

Data

}

}

}

尾header的结构非常复杂, 里面有很多压缩概念, 如若没有理解压缩过程, 单独的纯字节层面介绍是没有意义的.

我们下一篇开始介绍详细的7z压缩流程, 介绍7z是如何把一系列的文件, 压缩成一个大文件的, 怎样利用压缩算放, 怎样排布文件结构. 同时我们再一边来介绍这个尾header的结构.

希望大家多多支持, 给我动力写下去.

欢迎大家访问我的个人独立博客:

记得顶啊, 小伙伴们.

来自 Neil的博客

相关文章
|
12月前
|
存储 JSON 搜索推荐
ABAP 报表中如何以二进制方式上传本地文件试读版
ABAP 报表中如何以二进制方式上传本地文件试读版
|
4月前
|
JavaScript 前端开发
nodejs实现解析chm文件列表,无需转换为PDF文件格式,在线预览chm文件以及目录,不依赖任何网页端插件
nodejs实现解析chm文件列表,无需转换为PDF文件格式,在线预览chm文件以及目录,不依赖任何网页端插件
|
缓存 API 定位技术
.tpk格式文件简介
1、.tpk格式的文件是什么?       tpk是ArcGIS10.1推出的一种新的数据文件类型,主要是用于将切片文件打包形成离线地图包,tpk可以在ArcGIS Runtime或者ArcGIS for Android/iOS中作为切片底图被加载。
3455 0
|
21天前
|
Shell Python
pdf压缩程序
【9月更文挑战第07天】
37 5
|
4月前
|
数据安全/隐私保护 Python Windows
Python办公自动化【Word转换PDF、PDF读取内容、PDF合并文件、PDF拆分文件、PDF加密文件、PPT基本操作-增加幻灯片、增加内容】(六)-全面详解(学习总结---从入门到深化)
Python办公自动化【Word转换PDF、PDF读取内容、PDF合并文件、PDF拆分文件、PDF加密文件、PPT基本操作-增加幻灯片、增加内容】(六)-全面详解(学习总结---从入门到深化)
100 0
|
C++
FreeCAD 导出STL格式文件格式
FreeCAD 导出STL格式文件格式
175 0
|
编解码 安全 Unix
数据导入与预处理-第4章-数据获取python读取pdf文档
数据导入与预处理-第4章-数据获取Python读取PDF文档 1 PDF简介 1.1 pdf是什么 2 Python操作PDF 2.1 pdfplumber库
数据导入与预处理-第4章-数据获取python读取pdf文档
|
存储 安全 算法
Android逆向笔记 —— DEX 文件格式解析
Android逆向笔记 —— DEX 文件格式解析
Android逆向笔记 —— DEX 文件格式解析
|
Python
如何使用Python将几十个PDF文件合并成一个PDF?其实只需要这四步
  假定你有一个很无聊的任务,需要将几十个PDF文件合并成一个PDF文件。每一个文件都有一个封面作为第一页,但你不希望合并后的文件中重复出现这些封面。即使有许多免费的程序可以合并PDF,很多也只是简单的将文件合并在一起。让我们来写一个Python程序,定制需要合并到PDF中的页面。   总的来说,该程序需要完成:   找到当前工作目录中所有PDF文件。按文件名排序,这样就能有序地添加这些PDF。除了第一页之外,将每个PDF的所有页面写入输出的文件。   从实现的角度来看,代码需要完成下列任务:   调用os.listdir(),找到当前工作目录中的所有文件,去除掉非PDF文件。调用Py
505 0