7z文件格式及其源码的分析(六)-完结篇
2018-08-16 16:05
337 查看
7z文件格式及其源码的分析(六)-完结篇
上一篇在这里 7z文件格式及其源码的分析(五)
这一篇主要介绍7z的流式压缩和解压原理.
对于流式处理来讲, 主要问题是要处理好文件头的问题. 因为是流式的, 所以,不可能走回头路. 也就不可能说压缩完了之后回到开头来修改头的信息.
7z文件的文件头其实是分为两部分的, 这两部分文件头在前面的几篇分析中都有介绍:
-
头Header
, 就是写在文件前面的 Header. 这个头比较小, 主要记录了文件的版本信息, 以及尾header的位置偏移和校验和等等. 其主要作用是文件类型识别和查找到尾Header -
尾Header
, 就是写在文件末尾的 Header. 这里记录了 7z 文件的全部压缩信息, 解压主要就靠这里面的信息了.
7z 常见的压缩过程是: 先把
头Header的一些位置保留, 然后压缩数据. 压缩完之后,
尾Header写完之后, 再把
尾Header的大小,偏移,校验和写回到
头Header中去.
在流式环境下, 最后一步回头写
头Header的过程显然不可能完成. 因为已经流过了.
那 7z 是怎么处理这个问题的呢? 7z 支持流式压缩吗?
7z 的文档里,并没有明确的回答这个问题. 我们还是让它的代码回答吧.
我们找到 7z 的解压代码,
7zIn.cpp文件. 找到
HRESULT CInArchive::ReadDatabase2()函数. 大概在
1136行.
这个函数就是在读取
头Header信息, 并查找
尾Header位置.
我们截取它开头的一部分代码片段:
[code]HRESULT CInArchive::ReadDatabase2( DECL_EXTERNAL_CODECS_LOC_VARS CArchiveDatabaseEx &db #ifndef _NO_CRYPTO , ICryptoGetTextPassword *getTextPassword, bool &passwordIsDefined #endif ) { db.Clear(); db.ArchiveInfo.StartPosition = _arhiveBeginStreamPosition; db.ArchiveInfo.Version.Major = _header[6]; db.ArchiveInfo.Version.Minor = _header[7]; if (db.ArchiveInfo.Version.Major != kMajorVersion) ThrowUnsupportedVersion(); UInt32 crcFromArchive = Get32(_header + 8); UInt64 nextHeaderOffset = Get64(_header + 0xC); UInt64 nextHeaderSize = Get64(_header + 0x14); UInt32 nextHeaderCRC = Get32(_header + 0x1C); UInt32 crc = CrcCalc(_header + 0xC, 20); #ifdef FORMAT_7Z_RECOVERY if (crcFromArchive == 0 && nextHeaderOffset == 0 && nextHeaderSize == 0 && nextHeaderCRC == 0) { UInt64 cur, cur2; RINOK(_stream->Seek(0, STREAM_SEEK_CUR, &cur)); const int kCheckSize = 500; Byte buf[kCheckSize]; RINOK(_stream->Seek(0, STREAM_SEEK_END, &cur2)); int checkSize = kCheckSize; if (cur2 - cur < kCheckSize) checkSize = (int)(cur2 - cur); RINOK(_stream->Seek(-checkSize, STREAM_SEEK_END, &cur2)); RINOK(ReadStream_FALSE(_stream, buf, (size_t)checkSize)); int i; for (i = (int)checkSize - 2; i >= 0; i--) if (buf[i] == 0x17 && buf[i + 1] == 0x6 || buf[i] == 0x01 && buf[i + 1] == 0x04) break; if (i < 0) return S_FALSE; nextHeaderSize = checkSize - i; nextHeaderOffset = cur2 - cur + i; nextHeaderCRC = CrcCalc(buf + i, (size_t)nextHeaderSize); RINOK(_stream->Seek(cur, STREAM_SEEK_SET, NULL)); } else #endif { if (crc != crcFromArchive) ThrowIncorrect(); } db.ArchiveInfo.StartPositionAfterHeader = _arhiveBeginStreamPosition + kHeaderSize; (...) }
可以看到, 下面这几句是在从
头Header上读取
尾Header信息.
[code] UInt32 crcFromArchive = Get32(_header + 8); UInt64 nextHeaderOffset = Get64(_header + 0xC); UInt64 nextHeaderSize = Get64(_header + 0x14); UInt32 nextHeaderCRC = Get32(_header + 0x1C); UInt32 crc = CrcCalc(_header + 0xC, 20);
紧接着, 后面又有一句判断:
[code]if (crcFromArchive == 0 && nextHeaderOffset == 0 && nextHeaderSize == 0 && nextHeaderCRC == 0)
如果这几个都读不到怎么办.
看代码, 接下来, 它会从文件末尾开始, 倒着向前查找特征字符:
[code]if (buf[i] == 0x17 && buf[i + 1] == 0x6 || buf[i] == 0x01 && buf[i + 1] == 0x04)
这就是在找
0x17 0x06或者
0x01 0x04这两个特征串.
代码已经很明确了:
如果找到这其中之一, 就算是找到 尾Header
了.
为什么能这么做呢, 这两个串代表什么呢?
通过查看7z的文档(事实上, 前面讲的
尾Header的结构的时候也能发现)知道.
0x17
是代表压缩Header的标记kEncodedHeader
. 后面的0x06
是 packstream 的标记kPackInfo
.0x01
是代表普通Header的标记kHeader
. 而0x04
是 mainstream 的标记kMainStreamsInfo
.
这正是 7z 的两种
尾Header的格式.
到这里, 可以总结一下了:
7z 流式压缩
压缩时可以把头Header中的 偏移量和 crc 和等 留空.
在解压时, 如果检测到这些留空了, 则会从文件末尾开始查找尾Header特征串.
这种方法可以一定程度实现 流式压缩. 虽然 7z 的文件结构本身,限制它不适合流式压缩的.
欢迎大家讨论交流: Neil 的博客
From Blog by Neil, post 7z文件格式及其源码的分析(六)-完结篇
原文来自 Blog by Neil, post 7z文件格式及其源码的分析(六)-完结篇 转载请注明出处。本站保留一切权力
阅读更多相关文章推荐
- 7z文件格式及其源码的分析(四)
- 7z文件格式及其源码的分析(三)
- 7z文件格式及其源码的分析
- 7z文件格式及其源码的分析(五)
- 7z文件格式及其源码的分析
- 7z文件格式及其源码的分析(二)
- 7z文件格式及其源码
- ffmpeg源码分析--7.关于mpeg文件格式3之audio的track
- FLV文件格式分析(附源码)
- Wireshark的Pcap文件格式分析及解析源码(转)
- 传奇源码分析-客户端(WindHorn简述和传奇文件格式分析)
- HDFS源码分析之FSImage文件内容(一)总体格式
- MP4文件格式分析及分割实现(附源码)
- Wireshark的Pcap文件格式分析及解析源码【转】
- HDFS源码分析之FSImage文件内容(一)总体格式
- GCC Coverage代码分析-.gcda/.gcno文件及其格式分析
- leveldb源码分析之sst文件格式
- 传奇源码分析-客户端(WindHorn简述和传奇文件格式分析)
- lib文件格式分析,以及从lib文件提取obj的思路和源码
- lucene4.5源码分析系列:lucene默认索引的文件格式-总述