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

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

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

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

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

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

Archive structure
~~~~~~~~~~~~~~~~~
SignatureHeader
[PackedStreams]
[PackedStreamsForHeaders]
[
Header
or
{
Packed Header
HeaderInfo
}
]

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

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

2. 压缩数据.

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

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

它的结构如下:

SignatureHeader
~~~~~~~~~~~~~~~
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个字节存储, 它用一种简单的编码方式,进行变长存储. 在这个文件中也有描述:

REAL_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行:

void 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行:

UInt64 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个字节的, 没有采用压缩.

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

[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[]
CRCs[]
}
}
MainStreamsInfo
{
(Same as in AdditionalStreams)
}
FilesInfo
{
NumFiles
Properties[]
{
ID
Size
Data
}
}
}

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

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

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

欢迎大家访问我的个人独立博客: http://byNeil.com

记得顶啊, 小伙伴们.

7z文件格式及其源码的分析(三)的更多相关文章

  1. 7z文件格式及其源码的分析(四)

    这是7z文件格式及其源码的分析系列的第四篇. 上一篇讲到了7z文件静态结构的尾header部分.这一篇开始,将从7z实际压缩流程开始详细介绍7z文件尾header的详细结构. 一, 第一个概念: co ...

  2. 7z文件格式及其源码的分析

    7z文件格式及其源码的分析 本文是一个系列. 主要是分享我最近一年做7z文件开发的经验. 主要包括7z官方源码的结构分析, 以及7z文件格式的分析. 其中涉及到7z源码结构的各个细节, 以及7z文件格 ...

  3. 7z文件格式及其源码的分析(五)

    这是7z文件格式及其源码的分析系列的第五篇. 上一篇讲到了7z文件压缩流程.最近太忙了,好久没更新,都快忘了写到哪了.:) 这一篇就说说7z文件的尾头的生成方式吧. 上一篇已经讲了尾header的结构 ...

  4. 7z文件格式及其源码的分析(二)

    这是第二篇, 第一篇在这里: 这一篇开始分析7z的源码结构. 一. 准备工作: 1. 源码下载: 可以从官方中文主页下载:http://sparanoid.com/lab/7z/. 为了方便, 这里直 ...

  5. 7z文件格式及其源码

    7z文件格式及其源码的分析(四) 这是7z文件格式及其源码的分析系列的第四篇. 上一篇讲到了7z文件静态结构的尾header部分.这一篇开始,将从7z实际压缩流程开始详细介绍7z文件尾header的详 ...

  6. 7z文件格式及其源码linux/windows编译

    7z文件格式及其源码的分析(二) 一. 准备工作: 1. 源码下载: 可以从官方中文主页下载:http://sparanoid.com/lab/7z/. 为了方便, 这里直接给出下载链接: http: ...

  7. tomcat源码分析(三)一次http请求的旅行-从Socket说起

    p { margin-bottom: 0.25cm; line-height: 120% } tomcat源码分析(三)一次http请求的旅行 在http请求旅行之前,我们先来准备下我们所需要的工具. ...

  8. mapreduce job提交流程源码级分析(三)

    mapreduce job提交流程源码级分析(二)(原创)这篇文章说到了jobSubmitClient.submitJob(jobId, submitJobDir.toString(), jobCop ...

  9. hadoop之hdfs------------------FileSystem及其源码分析

    FileSystem及其源码分析 FileSystem这个抽象类提供了丰富的方法用于对文件系统的操作,包括上传.下载.删除.创建等.这里多说的文件系统通常指的是HDFS(DistributedFile ...

随机推荐

  1. C#如何消除绘制图形缩放时抖动,总结

    一.手动双缓冲 首先定义一个BitmapBitmap backBuffer = new Bitmap(画布宽度, 画布高度);然后获取这个Bitmap的GraphicsGraphics graphic ...

  2. 跟我学算法-tensorflow 实现logistics 回归

    tensorflow每个变量封装了一个程序,需要通过sess.run 进行调用 接下来我们使用一下使用mnist数据,这是一个手写图像的数据,训练集是55000*28*28, 测试集10000* 28 ...

  3. SpringMVC配置过程中出现的问题!

    <c:set var="ctx" value="${pageContext.request.contextPath}" />不起作用,原因是web. ...

  4. UNITY引擎变量调用产生不必要内存分配

    https://unity3d.com/de/learn/tutorials/topics/performance-optimization/optimizing-garbage-collection ...

  5. python---webbrowser模块的使用,用非系统默认浏览器打开

    webbrowser模块常用的方法有: webbrowser.open(url, new=0, autoraise=True) 在系统的默认浏览器中访问url地址,如果new=0,url会在同一个浏览 ...

  6. Hello Build To Win!我们是奶牛小分队!——记第一次团队作业

    Hello Build To Win!我们是奶牛小分队! section 1 组建团队 1.  队员介绍: 姓名 学号 风格 擅长技术 编程兴趣 希望软工角色 吴建瑜 2016012024 努力学习, ...

  7. Eclipse创建javaWeb项目工程

    首先,想作为一个较为标准的程序员,应该把自己的开发工具eclipse设置成与公司中大多数程序员的设置一样,比如说工作的字符编码为UTF-8,字体大小等等. 在刚下载好的eclipse中刚进去是没有To ...

  8. 【LA3415 训练指南】保守的老师 【二分图最大独立集,最小割】

    题意 Frank是一个思想有些保守的高中老师.有一次,他需要带一些学生出去旅行,但又怕其中一些学生在旅行中萌生爱意.为了降低这种事情发生的概率,他决定确保带出去的任意两个学生至少要满足下面四条中的一条 ...

  9. freemaker 课程

    品优购电商系统开发 第12章 网页静态化解决方案-Freemarker 传智播客.黑马程序员 1.网页静态化技术Freemarker 1.1为什么要使用网页静态化技术 网页静态化解决方案在实际开发中运 ...

  10. word 标题序号

    设置标题的序号. 1>设置二级标题的序号  步骤一: 步骤二: 步骤三: 步骤四: 2>设置三级标题的序号 程序员的基础教程:菜鸟程序员