7z文件格式及其源码的分析(三)
上一篇在這里. ?這是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 HeaderHeaderInfo} ]?
上面就是7z文件的總體結構了. ?我來稍微解釋一下. ?上面的代碼中, 從波浪線往后開始算. ?? 7z的文件結構基本上分為三部分:
1. 前文件頭(就是最前面的header).
2. 壓縮數據.
3. 尾文件頭(就是放在文件末尾的header).
?
一, 前文件頭就是上圖中的 "SignatureHeader". ?它是32個字節定長的. ? ?前文件頭其實記錄的信息很少, 它的主要目的是記錄尾文件頭的位置, 壓縮的主要結構都是存在尾文件頭中.
它的結構如下:
SignatureHeader ~~~~~~~~~~~~~~~BYTE kSignature[6] = {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C}; ArchiveVersion{BYTE Major; // now = 0BYTE Minor; // now = 2}; UINT32 StartHeaderCRC; StartHeader{REAL_UINT64 NextHeaderOffsetREAL_UINT64 NextHeaderSizeUINT32 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)) + y110xxxxx BYTE y[2] : ( xxxxx << (8 * 2)) + y...1111110x BYTE y[6] : ( x << (8 * 6)) + y11111110 BYTE y[7] : y11111111 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
?
記得頂啊, 小伙伴們.
?
?
?
?
轉載于:https://www.cnblogs.com/shuidao/p/3293583.html
總結
以上是生活随笔為你收集整理的7z文件格式及其源码的分析(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 案例:京东秒杀
- 下一篇: React源码分析(二)= Reac初次