您的位置:首页 > 其它

7z文件格式及其源码的分析(六)-完结篇

2018-08-16 16:05 337 查看

7z文件格式及其源码的分析(六)-完结篇

2条回复

上一篇在这里 7z文件格式及其源码的分析(五)

这一篇主要介绍7z的流式压缩和解压原理.

对于流式处理来讲, 主要问题是要处理好文件头的问题. 因为是流式的, 所以,不可能走回头路. 也就不可能说压缩完了之后回到开头来修改头的信息.

7z文件的文件头其实是分为两部分的, 这两部分文件头在前面的几篇分析中都有介绍:

  1. 头Header
    , 就是写在文件前面的 Header. 这个头比较小, 主要记录了文件的版本信息, 以及尾header的位置偏移和校验和等等. 其主要作用是文件类型识别和查找到尾Header

  2. 尾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
 的结构的时候也能发现)知道.

  1. 0x17
     是代表压缩Header的标记 
    kEncodedHeader
    . 后面的 
    0x06
     是 packstream 的标记 
    kPackInfo
    .
  2. 0x01
     是代表普通Header的标记 
    kHeader
    . 而 
    0x04
     是 mainstream 的标记 
    kMainStreamsInfo
    .

这正是 7z 的两种 

尾Header
 的格式.

到这里, 可以总结一下了:

7z 流式压缩
压缩时可以把 

头Header
 中的 偏移量和 crc 和等 留空.
在解压时, 如果检测到这些留空了, 则会从文件末尾开始查找 
尾Header
 特征串.

这种方法可以一定程度实现 流式压缩. 虽然 7z 的文件结构本身,限制它不适合流式压缩的.

欢迎大家讨论交流: Neil 的博客

byNeil
byNeil.com

From Blog by Neilpost 7z文件格式及其源码的分析(六)-完结篇

原文来自 Blog by Neilpost 7z文件格式及其源码的分析(六)-完结篇 转载请注明出处。本站保留一切权力

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: