7z文件格式及其源碼的分析(三)

上一篇在這裏.  這是7z文件格式分析的第三篇, 相信有了前兩篇的準備,你已經瞭解了7z源碼的大體結構, 以及如何簡單調試7z的源碼了. 不少同窗是否是火燒眉毛想要拔去7z的神祕外衣,看看究竟了. 好, 這就帶大家一探乾坤. 本文開始,咱們詳細介紹7z的文件存儲結構.html

要了解7z的結構,  固然最好從官方的說明開始, 儘管這個說明很是簡略, 但它的確是我入門時的救命稻草.算法

打開源碼的 "DOC" 目錄.  這裏面就是官方全部的文檔了. 其中只有二個文檔跟結構相關:ui

1. 7zFormat.txt,   這是咱們的主角, 裏面介紹了7z文件的大致結構.編碼

2. Methods.txt,  這裏面介紹了7z壓縮算法id的編碼規則, 之後會用到.設計

 

咱們從7zFormat.txt文件開始.調試

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

 

上面就是7z文件的整體結構了.  我來稍微解釋一下.  上面的代碼中, 從波浪線日後開始算.    7z的文件結構基本上分爲三部分:code

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

2. 壓縮數據.htm

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

 

一, 前文件頭就是上圖中的 "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

 

記得頂啊, 小夥伴們.

相關文章
相關標籤/搜索