XZ 檔案格式
XZ 壓縮的資料,通常使用 LZMA2 演算法。
- MIME 類型
- application/x-xz
- 副檔名
- .xz
- 分類
- 壓縮與封存
Magic number(檔案簽章)
| 0x00000000 | FD377A585A00 | ·7zXZ· | XZ magic |
關於 XZ 格式
XZ 把 LZMA2 包進一個帶完整性檢查的容器,能得到很高的壓縮率,因此用於軟體發佈與核心映像檔。6 位元組的 magic number 是 FD 37 7A 58 5A 00。跟它取代掉的那些格式不同,xz 是一個正經的容器,不是蓋在串流上的一層標頭:它有串流標頭和一份對應的串流頁尾,中間是一個或多個區塊,每個區塊有自己的標頭與 CRC,檔尾還有一份索引,列出每個區塊壓縮前後的大小。有那份索引,讀取端才能在不解壓縮的情況下驗證檔案、或在裡面 seek — 而這也正是為什麼被截斷的 .xz 根本無從檢查。
檔頭結構,一個位元組一個位元組看
| 位移 | 長度 | 欄位 | 意義 |
|---|---|---|---|
| 0 | 6 | Header magic bytes | FD 37 7A 58 5A 00。第 1 到 4 個位元組讀成 ASCII 就是 '7zXZ',這是它跟 7-Zip 自家格式混淆不清的長期來源;開頭那個 FD 刻意落在 ASCII 之外,而結尾那個 00 則讓檔案在任何文字模式傳輸裡大聲壞掉。 |
| 6 | 2 | Stream flags | 第一個位元組是保留的,必須是 00。第二個位元組的低四個位元指名完整性檢查方式:0 是沒有、1 是 CRC-32、4 是 CRC-64、10 是 SHA-256。標準工具寫出來的檔案讀到的是 00 04,因為預設是 CRC-64。 |
| 8 | 4 | CRC-32 of the stream flags | 小端序,只涵蓋位移 6 那兩個位元組。它存在的目的,是讓讀取端在還沒解出任何東西之前,就能信任自己要套用哪一種檢查方式。 |
| 12 | 1 | Block header size | 編碼過的值:真正的大小是「這個值加一再乘以四」,所以 04 代表二十個位元組的區塊標頭。這裡是 00 有特殊意義 — 代表沒有更多區塊了,索引就從這個位移開始。 |
| 13 | 1 | Block flags | 低兩個位元是濾波器數量減一;位元 6 表示後面有一個壓縮大小欄位,位元 7 表示有未壓縮大小欄位。典型的單一濾波器、兩個大小都在的區塊,讀到的是 C0。 |
| variable | variable | Block sizes and filter flags | 在區塊標頭裡,依序是:選用的壓縮與未壓縮大小,以多位元組的變動長度整數表示,接著是濾波器鏈 — LZMA2 的濾波器 id 是 0x21,帶一個編碼字典大小的屬性位元組 — 然後補零到四的倍數,最後是區塊標頭自己的 CRC-32。 |
| variable | variable | Index | 在最後一個區塊之後:每個區塊一筆記錄,給出它壓縮前後的大小,補齊到四的倍數,後面跟著它自己的 CRC-32。`xz --list` 讀的就是這個,也是為什麼 xz 能在不解壓縮的情況下報出檔案內容。 |
| -12 | 4 | Stream footer CRC-32 | 小端序,涵蓋它後面那六個頁尾位元組。頁尾固定十二個位元組,所以跟 ZIP 的結尾記錄不同,永遠不必倒著掃描去找它。 |
| -8 | 4 | Backward size | 小端序;真正的索引大小是「這個值加一再乘以四」。索引是十二個位元組的檔案,這裡存的是 02 00 00 00。就是它讓索引能從檔尾找到,也因此讓整個格式可以 seek。 |
| -4 | 2 | Stream flags (copy) | 位移 6 和 7 那兩個位元組的逐位元組複本,好讓一個從檔尾開始讀的解碼器,得到跟標頭宣告一樣的檢查方式。兩邊對不上,代表檔案被動過手腳或被接錯了。 |
| -2 | 2 | Footer magic 'YZ' | 59 5A。每個完好的 .xz 都以這兩個位元組結尾;只要它們不是最後兩個位元組,這個檔案就是被截斷了,前面的資料看起來再合法都不改變這件事。 |
怎麼跟長得像的格式分開
| 容易搞混的 | 怎麼分 |
|---|---|
| 7-Zip | 這是這個格式自己招來的混淆。.xz 開頭是 FD 37 7A 58 5A 00,其中 '7z' 坐在位移 1 和 2;真正的 .7z 則是位移 0 就是 37 7A BC AF 27 1C。所以同樣那兩個位元組在兩種格式裡都出現,只差一格,而一個隨手搜尋 '7z' 的做法兩邊都會中。它們是毫無關係的格式:.7z 是帶目錄的多檔案封存檔,.xz 是單一條壓縮串流。 |
| lzma (LZMA_Alone) | 它的前身,也是 .lzma 檔案為什麼那麼難辨識的原因:它根本沒有 magic number。標頭是十三個位元組 — 一個屬性位元組(通常是 5D),接四個位元組的小端序字典大小,再接八個位元組的小端序未壓縮大小,未知時就是重複的 FF。真實檔案讀起來是 5D 00 00 80 00 後面接八個 FF 位元組,這就是它最接近簽章的東西了。 |
| GZIP | 1F 8B 對上 FD 37 7A 58 5A 00,而且結構上比這暗示的差得更遠:gzip 是蓋在串流上的一層標頭、沒有索引,而 xz 在檔尾保有一份區塊索引,因此不必解壓縮就能列出內容與驗證。 |
| Zstandard | 現代的對手。28 B5 2F FD 對上 xz 的 magic。在最高設定下 xz 仍然壓得比較小,但 Zstandard 解壓縮快好幾倍、壓縮也快得多 — 這就是為什麼套件管理器一直在往那邊遷移。 |
| TAR | 一個 .tar.xz 和「用 xz 壓了單一個檔案」是同一種格式。解壓縮之後看結果的位移 257,第一個 512 位元組 tar 標頭裡有沒有 'ustar' — .xz 容器本身沒有任何東西能分辨這兩者。 |
常見的壞法
- 截斷。索引和頁尾都住在檔尾,所以被截斷的 .xz 既列不出內容也驗證不了,即使已經到手的每個區塊都解得出來。工具會回報非預期的輸入結束,而檔尾那兩個位元組的 'YZ',是用肉眼確認最快的方法。
- 區塊本體裡翻掉一個位元。預設的完整性檢查是 CRC-64,所以損壞會被抓到 — 但只在解壓縮之後,因為位移 8 的 CRC-32 和區塊標頭自己的 CRC 都只涵蓋中介資料。資料損壞嚴重時,LZMA2 通常在走到檢查碼之前就先以解碼錯誤失敗了。
- 結尾的 NUL 位元組被剝掉。區塊和串流都是用零位元組補齊到四的倍數的,所以任何會修掉結尾 NUL 的傳輸或建置步驟,都會毀掉頁尾賴以存在的對齊。檔案照樣被認成 xz,然後在最後一刻失敗。
- 串接起來的串流被當成一條來剖析。把 .xz 檔案接起來是合法的,中間還允許選用的零填充,而標準工具會把全部解出來。一個自己手寫、看到第一組頁尾 magic 就停的讀取端,會靜靜地把後面的東西全部丟掉。
- 文字模式傳輸。標頭 magic 裡那個 00 的存在就是要讓這件事立刻失敗,而它也確實做到了 — 但同樣的轉換發生在串流更後面時,標頭是完好的,所以檔案會被正確辨識,只有在解碼走到損壞處時才失敗。
這個格式現在還在用嗎
XZ 是用在「壓一次、被下載很多次,而且下載才是昂貴那一段」的場合。核心的 tarball、發行版套件、原始碼釋出和容器基礎層,十幾年來都倚賴它,因為在高預設等級下,LZMA2 仍然是常見格式裡輸出最小的通用選擇。代價也是真的:壓縮很慢而且極度吃記憶體,而解壓縮的記憶體用量取決於寫入端選的字典大小,所以一個用預設等級 9 做出來的封存檔,開它要大約 64 MB — 這在嵌入式目標上是實打實的問題。這一點加上好幾倍的速度差距,正是 Zstandard 從 2019 年前後開始在套件管理器和檔案系統裡穩定取代它的原因。xz 仍然是第一選擇的地方,是靜態內容那種受頻寬限制的散佈;而任何會被反覆寫入或即時串流的東西,它都是很差的選擇。
在這個站上打開 XZ 檔
這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。
相關格式
容易和 XZ 搞混的格式,可以比對它們的開頭位元組。