Zstandard 檔案格式
Zstandard 壓縮的資料,來自 Meta 的快速現代壓縮格式。
- MIME 類型
- application/zstd
- 副檔名
- .zst
- 分類
- 壓縮與封存
Magic number(檔案簽章)
| 0x00000000 | 28B52FFD | (·/· | Zstandard magic |
關於 Zstandard 格式
Zstandard 以高出許多的速度達到 gzip 等級的壓縮率,壓縮等級也能細調,現在已被套件管理器與檔案系統廣泛採用。它的小端序 magic number 是 28 B5 2F FD。一個 .zst 檔案是一串 frame,而每個 frame 以一個位元組的描述子開頭,由它的位元決定後面那些欄位到底存不存在 — 所以跟標頭固定十個位元組的 gzip 不同,Zstandard 的標頭長度在六到十八個位元組之間,第五個位元組之後沒有任何位移是固定的。要用手讀懂它,就得先把那個描述子解出來,沒有捷徑。
檔頭結構,一個位元組一個位元組看
| 位移 | 長度 | 欄位 | 意義 |
|---|---|---|---|
| 0 | 4 | Magic number | 磁碟上是 28 B5 2F FD。規格把它寫成 0xFD2FB528,因為 Zstandard 裡每個多位元組值都是小端序,所以數字和位元組順序看起來剛好相反。 |
| 4 | 1 | Frame header descriptor | 其他一切都取決於這個位元組。位元 7-6 給出 frame 內容大小欄位的寬度,位元 5 是單一分段旗標,位元 4 沒用到,位元 3 是保留的、必須是 0,位元 2 打開結尾的內容檢查碼,位元 1-0 給出字典 id 的寬度。命令列工具常見的值是 A2 和 A4。 |
| 5 | 1 | Window descriptor | 只有在描述子位元 5 的單一分段旗標為 0 時才存在。它編碼的是解碼器必須緩衝的視窗大小。旗標被設起來時 — 也就是整份輸入塞得進一個分段的時候 — 這個位元組根本不存在,內容大小直接從這裡開始。把這個條件判斷寫錯,就是經典的 zstd 剖析 bug。 |
| variable | 0-4 | Dictionary id | 零、一、二或四個位元組,由描述子的低兩個位元決定。非零值代表這個 frame 是對著一份共用字典壓縮的,沒有那份字典就解不開,位元組再完好都一樣。 |
| variable | 0-8 | Frame content size | 解壓縮後的長度,小端序,佔零、一、二、四或八個位元組。200000 位元組的輸入存成 40 0D 03 00,48 位元組的輸入則是單一個位元組 30。當編碼器是串流處理、事先不知道長度時,這個欄位完全不存在。 |
| variable | 3 | Block header | 每個區塊開頭的一個 24 位元小端序欄位:位元 0 標記這是最後一個區塊,位元 1-2 給出類型(0 raw、1 RLE、2 compressed、3 保留),位元 3-23 是大小。壓不動的輸入會產生 raw 區塊 — 00 00 10 是一個 131072 位元組的 raw 區塊 — 而 6D 00 00 是一個 13 位元組的最終壓縮區塊。 |
| -4 | 4 | Content checksum | 對原始資料算的 XXH64 的低 32 位元,只有在 frame 標頭描述子的位元 2 被設起來時才存在。它預設是開的,而關掉檢查寫出來的檔案,就直接以最後一個區塊的最後一個位元組結尾。 |
怎麼跟長得像的格式分開
| 容易搞混的 | 怎麼分 |
|---|---|
| skippable frames | 一個 .zst 不見得要以 28 B5 2F FD 開頭。可跳過的 frame 用的 magic number 是 0x184D2A50 到 0x184D2A5F,以小端序存成 50 2A 4D 18 到 5F 2A 4D 18,後面接四個位元組的小端序大小;解碼器會直接跳過它們,而應用程式拿它們來嵌入自己的中介資料。所以一個堅持位移 0 必須是壓縮 frame magic 的偵測器,會拒絕掉完全合法的檔案。 |
| XZ | 同一批工作上的直接對手,也是多數「我該用哪一個」搜尋背後的那組比較。FD 37 7A 58 5A 00 對上 28 B5 2F FD。最高設定下 xz 仍然在壓縮率上贏;Zstandard 在兩端的速度上大幅領先,而且在等級 19 時壓縮率也守得住。 |
| GZIP | 1F 8B 對上 28 B5 2F FD。Zstandard 當初就是明確衝著「用好幾倍的速度做到 gzip 的壓縮率」設計的,所以兩者佔同一個生態位、被原地互換 — 而一個 .zst 就是這樣在建置腳本裡被取名成 .gz 的。 |
| ZIP | ZIP 的壓縮方法 93 就是 Zstandard,所以這個 magic 可能出現在 ZIP 項目裡面,而不是位移 0。少數幾個寫入端之外對它的支援很薄,所以這種封存檔常常在那些讀得懂其他每一種 ZIP 的工具裡打不開。 |
| TAR | 一個 .tar.zst 是套件酬載的常見形狀。frame 裡沒有任何東西能把它跟「壓縮過的單一檔案」分開 — 解壓縮之後去看輸出的位移 257 有沒有 'ustar'。 |
常見的壞法
- 被截斷、但長度還是宣告出來了。如果標頭裡本來就有 frame 內容大小,工具會報出完整的預期輸出大小,然後在中途以非預期的 frame 結束失敗 — 所以在資料斷掉的那一刻之前,數字看起來全是對的。
- 寫入時關掉了檢查碼的 frame。結尾那四個位元組就是不存在,所以沒有東西驗證輸出;這時 raw 區塊裡的損壞會產出錯誤的位元組而且完全不報錯,因為 raw 區塊是逐位元組原樣複製的,沒有熵編碼可以脫軌。
- 字典不見了。當字典 id 欄位非零時,沒有那份一模一樣的字典就解不開這個 frame,即使磁碟上每個位元組都完好無缺。它失敗時報的是字典不符、而不是損壞錯誤,於是大家跑去找一個根本不存在的損壞。
- 串接起來的 frame 被當成一個來讀。把 .zst 檔案接起來是合法的,標準工具會依序把所有 frame 解出來;一個在第一個 frame 的最後一個區塊之後就停手的讀取端,會靜靜地把結果截短,而中間夾著可跳過的 frame 會讓情況更糟。
- 文字模式傳輸。標頭活了下來、宣告的內容大小也讀得對,而解碼在第一個壓縮區塊裡就失敗 — 所以這個檔案一路被認成合法的 Zstandard,直到你真的要用它為止。
這個格式現在還在用嗎
Zstandard 已經成為近幾年通用壓縮的預設選擇,而且還在擴散。Arch 和 Fedora 的套件、Debian 套件的酬載、btrfs 與 ZFS 的透明壓縮、容器映像層、Kafka 和 RocksDB 的內部,以及 Linux 壓自己 initramfs 映像檔用的方式,全部建立在它上面,而且它是 RFC 8878 這份公開的網際網路標準,不是某個專案的私有格式。理由不是任何單一數字,而是它取捨曲線的形狀:在預設等級下,它的壓縮率大致追平 gzip,速度卻快好幾倍,而解壓縮又快、又幾乎不受等級影響,所以選更高的等級只讓寫入端付出代價,讀取端永遠不用。它還支援字典,這是其他常見格式都沒有的,對「大量相似的小酬載」這種情況是質變。它仍然會輸的地方,是典藏用途下的極限壓縮率 — 那裡 xz 和 LZMA 還是領先 — 以及長尾相容性,那裡 gzip 無人能敵。
在這個站上打開 Zstandard 檔
這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。
相關格式
容易和 Zstandard 搞混的格式,可以比對它們的開頭位元組。