ByteScope

Zstandard 檔案格式

Zstandard 壓縮的資料,來自 Meta 的快速現代壓縮格式。

MIME 類型
application/zstd
副檔名
.zst
分類
壓縮與封存

Magic number(檔案簽章)

0x0000000028B52FFD(·/·Zstandard magic

關於 Zstandard 格式

Zstandard 以高出許多的速度達到 gzip 等級的壓縮率,壓縮等級也能細調,現在已被套件管理器與檔案系統廣泛採用。它的小端序 magic number 是 28 B5 2F FD。一個 .zst 檔案是一串 frame,而每個 frame 以一個位元組的描述子開頭,由它的位元決定後面那些欄位到底存不存在 — 所以跟標頭固定十個位元組的 gzip 不同,Zstandard 的標頭長度在六到十八個位元組之間,第五個位元組之後沒有任何位移是固定的。要用手讀懂它,就得先把那個描述子解出來,沒有捷徑。

檔頭結構,一個位元組一個位元組看

位移長度欄位意義
04Magic number磁碟上是 28 B5 2F FD。規格把它寫成 0xFD2FB528,因為 Zstandard 裡每個多位元組值都是小端序,所以數字和位元組順序看起來剛好相反。
41Frame header descriptor其他一切都取決於這個位元組。位元 7-6 給出 frame 內容大小欄位的寬度,位元 5 是單一分段旗標,位元 4 沒用到,位元 3 是保留的、必須是 0,位元 2 打開結尾的內容檢查碼,位元 1-0 給出字典 id 的寬度。命令列工具常見的值是 A2 和 A4。
51Window descriptor只有在描述子位元 5 的單一分段旗標為 0 時才存在。它編碼的是解碼器必須緩衝的視窗大小。旗標被設起來時 — 也就是整份輸入塞得進一個分段的時候 — 這個位元組根本不存在,內容大小直接從這裡開始。把這個條件判斷寫錯,就是經典的 zstd 剖析 bug。
variable0-4Dictionary id零、一、二或四個位元組,由描述子的低兩個位元決定。非零值代表這個 frame 是對著一份共用字典壓縮的,沒有那份字典就解不開,位元組再完好都一樣。
variable0-8Frame content size解壓縮後的長度,小端序,佔零、一、二、四或八個位元組。200000 位元組的輸入存成 40 0D 03 00,48 位元組的輸入則是單一個位元組 30。當編碼器是串流處理、事先不知道長度時,這個欄位完全不存在。
variable3Block header每個區塊開頭的一個 24 位元小端序欄位:位元 0 標記這是最後一個區塊,位元 1-2 給出類型(0 raw、1 RLE、2 compressed、3 保留),位元 3-23 是大小。壓不動的輸入會產生 raw 區塊 — 00 00 10 是一個 131072 位元組的 raw 區塊 — 而 6D 00 00 是一個 13 位元組的最終壓縮區塊。
-44Content 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 時壓縮率也守得住。
GZIP1F 8B 對上 28 B5 2F FD。Zstandard 當初就是明確衝著「用好幾倍的速度做到 gzip 的壓縮率」設計的,所以兩者佔同一個生態位、被原地互換 — 而一個 .zst 就是這樣在建置腳本裡被取名成 .gz 的。
ZIPZIP 的壓縮方法 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 搞混的格式,可以比對它們的開頭位元組。

格式開頭位元組副檔名
GZIP1F 8B.gz
XZFD 37 7A 58 5A 00.xz
BZIP242 5A 68.bz2
ZIP50 4B 03 04.zip

常見問題

檔案的 magic number 是什麼?

檔案開頭的前幾個位元組,不管檔名寫什麼,它標明的是真正的格式。PNG 一定以 89 50 4E 47 開頭,ZIP 則是 50 4B 03 04。作業系統、瀏覽器和 file 指令讀的都是這幾個位元組,而不是相信副檔名 — 這就是為什麼把檔案改名並不會改變它實際上是什麼。

我要怎麼自己檢查 magic number?

在 macOS 或 Linux 上,xxd -l 16 檔名 會印出前 16 個位元組;在 Windows 上,PowerShell 的 Format-Hex -Path 檔名 -Count 16。或者把檔案丟進本站的檔案類型辨識工具,它會在本機讀取位元組並告訴你是什麼格式,不會上傳任何東西。

為什麼兩種格式的 magic number 會一樣?

因為其中一種是建立在另一種之上。docx、xlsx、pptx、apk、jar 與 epub 骨子裡都是 ZIP 壓縮檔,所以全都以 ZIP 的簽章開頭;要分辨它們得看裡面的項目。Java class 檔和 macOS 的通用二進位檔都以 CA FE BA BE 開頭,靠緊接其後的欄位區分。

有完全沒有 magic number 的檔案嗎?

有。XML、HTML、JSON、SVG、CSV 這類以文字為基礎的格式沒有固定的位元組簽章,所以改用結構來辨識:開頭的 <?xml 宣告、<svg 元素,或是 { 後面接著帶引號的鍵。這種偵測是合理推測而非確定,因此本參考頁會把這些格式標示為結構偵測。

magic number 相符就代表檔案安全嗎?

不代表。它告訴你的是格式,不是內容是否無害 — 一個貨真價實的 PDF 或 docx 仍然可能夾帶惡意內容。辨識真實類型是有用的第一道檢查,尤其在副檔名和位元組不一致的時候,但它不是惡意軟體掃描。

手上有檔案可能是 Zstandard?用檔案類型辨識工具查出來 — 它在本機讀取 magic number,不上傳任何東西。或者瀏覽所有格式。