軟體壓縮原理:為什麼無損壓縮

computer%20screen%20showing%20data%20visualization...
發表時間:2026 年 10 月 27 日 | 更新日期:2026 年 10 月 27 日 | 編輯:雅寶社區編輯團隊
軟體壓縮原理:為什麼無損壓縮 - 雅寶社區 · 頂客論壇

要真正理解無損壓縮,就必須回到克勞德·香農(Claude Shannon)在 1948 年提出的資訊理論。這套理論不只解釋了壓縮的可能性,也劃出了壓縮的理論天花板。

熵與資訊量

香農提出了一個核心概念:熵(Entropy),用來衡量一個隨機變數的平均不確定性。用最直白的話說:熵就是「平均而言,要描述這個資料來源的輸出,最少需要多少位元」。

舉個例子。假設有一枚公正硬幣,擲出正面的機率是 0.5。你要告訴別人結果,只需要 1 個位元(0 或 1)。但如果這枚硬幣被動過手腳,出現正面的機率是 0.99、反面只有 0.01,那麼平均不確定性就大幅降低了——熵大約只有 0.08 位元。也就是說,理論上平均只需要 0.08 位元就能描述擲幣結果。

香農的熵公式是:

H(X) = − Σ p(x) · log₂ p(x)

其中 p(x) 是每個符號出現的機率。這個公式的直覺是:出現機率越高的符號,攜帶的資訊量越少;出現機率越低的符號,攜帶的資訊量越多。想想看,「明天太陽會升起」這句話幾乎不攜帶資訊,因為它太可預測了;「明天台北會下雪」就攜帶了大量資訊,因為它極端罕見。壓縮演算法的任務,就是讓常見符號用短碼、罕見符號用長碼,使平均碼長逼近熵。

這就是著名的香農源編碼定理(Shannon's Source Coding Theorem):對於一個熵為 H 的資料來源,任何無損編碼的平均碼長不可能低於 H,而存在某種編碼可以任意逼近 H。換句話說,熵就是無損壓縮的理論極限。你不可能把一份資料壓到比它的熵還小,這不是技術問題,而是數學定律。

冗餘資訊:壓縮的真正燃料

既然壓縮的極限是熵,那麼「原始資料大小」與「熵」之間的差距,就是壓縮空間的來源。這個差距稱為冗餘(Redundancy)。冗餘越多,壓縮潛力越大;冗餘越少,資料就越「不可壓縮」。

冗餘大致可以分成幾種類型,理解這幾種類型,就等於理解了所有無損壓縮演算法的分類邏輯:

  • 統計冗餘:某些符號出現的頻率遠高於其他符號。例如英文文章中 E、T、A 出現頻率遠高於 Z、Q、X。用固定長度編碼(例如 ASCII 的 8 位元)就是在浪費位元。
  • 字典冗餘:相同或相似的字串重複出現。例如程式碼中的 function、return、const 會反覆出現,HTML 標籤也是。用「指向先前出現位置的指標」就能省下大量空間。
  • 結構冗餘:資料本身具有可預測的排列規律。例如連續遞增的整數序列、表格中大量空白、圖片的漸層色塊。
  • 上下文冗餘:某個符號的出現高度依賴前面的符號。例如看到「Q」下一個字母幾乎必然是「U」;看到中文「資」下一個字很可能是「料」「訊」「產」。
  • 不同的演算法,就是針對不同類型冗餘設計的「獵手」。理解了這一點,你會發現所有教科書上的演算法都不再是死記硬背的名詞,而是針對特定冗餘結構的最佳化策略。

    三、無損壓縮的核心原理

    接下來,我們把無損壓縮的技術路線拆解成三大類。絕大多數實用演算法,都可以歸類到這三種思路之中,或是它們的組合。

    統計式壓縮:讓常見的符號變短

    統計式壓縮的核心思想極其直觀:出現頻率高的符號,用較短的碼;出現頻率低的符號,用較長的碼。這聽起來簡單,但要做得「可解碼」卻需要精巧的設計。

    關鍵在於前綴碼(Prefix Code)的概念。假設我們把 A 編成 0、B 編成 01,那麼收到 01 時,你無法確定它是「B」還是「A 後面接某個以 1 開頭的符號」。前綴碼要求:任何一個碼字都不能是另一個碼字的前綴。滿足這個條件,解碼器才能從左到右逐位元判斷、即時解碼,不需要回退。

    霍夫曼編碼(Huffman Coding)正是構造最優前綴碼的經典演算法。它的做法是:把所有符號視為節點,依據頻率建立一棵二元樹。反覆取出頻率最低的兩個節點,合併成一個新節點(頻率為兩者之和),放回集合中,直到只剩一個節點為止。最後從根節點出發,往左走記 0、往右走記 1,就得到每個符號的編碼。

    舉個例子。假設有四個符號,頻率分別是 A:50%、B:25%、C:15%、D:10%。固定 2 位元編碼的平均碼長是 2 位元。霍夫曼樹會把頻率最低的 C、D 合併(25%),再與 B(25%)合併(50%),最後與 A 合併。結果 A 得到 1 位元、B 得到 2 位元、C 得到 3 位元、D 得到 3 位元,平均碼長 = 0.5×1 + 0.25×2 + 0.15×3 + 0.1×3 = 1.9 位元,比固定編碼省了 5%。

    霍夫曼編碼的優點是簡單、保證最優(在符號獨立同分布的假設下)、解碼快速。缺點是它只能為整數位元分配碼字,且需要額外儲存編碼表。當符號種類很多、頻率差距很小時,霍夫曼編碼的效益會下降;此外它對「動態變化的頻率」反應較差,因此實務上常見到「自適應霍夫曼」(Adaptive Huffman)與「區塊霍夫曼」等變體。

    字典式壓縮:重複的字串只存一份

    字典式壓縮的思路與統計式完全不同:它不關心單一符號出現多少次,而是關心「相同的字串是否重複出現」。這對於充滿重複片段的資料(原始碼、HTML、日誌檔、資料庫傾印)特別有效。

    最具代表性的演算法是 LZ77(Lempel-Ziv 1977)。它的核心機制是滑動視窗(Sliding Window):壓縮器維護一個「已經處理過的最近 N 個位元組」的視窗,每當讀到新資料時,就在視窗裡尋找「最長匹配」。找到之後,不需要輸出原始字串,只需要輸出一組 (距離, 長度) 的配對——「從我目前位置往前 D 個位元組的地方,複製 L 個位元組過來」。解壓縮時,只要照著這個指令複製,就能完美還原。

    例如字串 ABCABCABC,LZ77 可能壓縮成「字面 ABC」+「(距離 3, 長度 6)」。原本 9 個位元組,壓縮後可能只需要 5~6 個位元組的描述。

    LZ78 換了另一種做法:它不記錄「距離」,而是建立一本顯式字典,每次遇到新字串就加入字典,之後只要輸出字典索引即可。LZW(Lempel-Ziv-Welch)是 LZ78 的改良版,由 Terry Welch 在 1984 年提出,它不需要在輸出中傳遞字典內容,而是讓壓縮與解壓雙方「同步建構」字典,因此省下了字典的傳輸成本。GIF 圖片格式、早期的 UNIX compress 工具都採用 LZW。

    字典式壓縮的優點是對重複性高的資料效果驚人,缺點是記憶體需求較大(尤其視窗越大、字典越大),且對「沒有重複片段」的資料幾乎無效。

    結構式與上下文式壓縮:預測下一個符號

    第三類思路是建立機率模型,預測下一個符號。這類方法通常能達到最高的壓縮比,但計算成本也最高。代表性的技術包括:

  • 算術編碼(Arithmetic Coding):把整段輸入映射成一個 0 到 1 之間的小數區間。每個符號依其機率把當前區間切分成子區間,最後只需輸出任何一個落在最終區間內的數字。算術編碼可以突破霍夫曼編碼「整數位元」的限制,做到接近熵的極限。
  • PPM(Prediction by Partial Matching):用前面的 N 個字元作為上下文,統計下一個字元出現的機率分布,再用算術編碼輸出。上下文越長、預測越準。
  • 上下文混合(Context Mixing):同時使用多個不同長度的上下文模型,動態加權混合它們的預測結果。7-Zip 的 PPMd 與 RAR 的部分模式都屬於此類。
  • ANS(Asymmetric Numeral Systems):近年新興的編碼方式,結合了算術編碼的壓縮效率與霍夫曼編碼的運算速度,被 zstd、LZFSE 等現代壓縮器採用。
  • 四、經典無損壓縮演算法深度解析

    前面我們談了三種原理。現在把它們落實在幾個最具影響力的演算法上,看看理論如何變成每天在你電腦上跑的程式碼。

    霍夫曼編碼:從機率到最優前綴碼

    霍夫曼編碼的偉大之處,在於它證明了「貪心法」可以得到全域最優解。它的演算法步驟如下:

    統計每個符號的出現頻率,建立葉節點。

    把節點放入優先佇列(以頻率為優先)。

    取出頻率最低的兩個節點,建立父節點,頻率為兩者之和。

    把父節點放回佇列。

    重複步驟 3–4,直到只剩一個節點,即為根節點。

    從根到每個葉節點的路徑即為編碼(左 0、右 1)。

    霍夫曼編碼在 1952 年由 David Huffman 提出,至今仍是 DEFLATE、JPEG、MP3 等格式的關鍵組件之一。它的實作簡單、解碼時只需建表即可,因此在硬體實作上也相當友善。不過它有一個實務上的痛點:編碼表本身需要被儲存。對於小檔案,編碼表的開銷可能超過壓縮省下的空間,這也是為什麼很多壓縮工具對小檔案反而會選擇「不壓縮,直接儲存」。

    LZ77 與 DEFLATE:現代壓縮的主力架構

    如果說霍夫曼編碼是壓縮界的古典名車,那 DEFLATE 就是最常見的國民車。DEFLATE 由 Phil Katz 在 1990 年代初期設計,是 ZIP、GZIP、PNG 等格式共同採用的核心演算法。

    DEFLATE 的架構是兩階段組合:

  • 第一階段:LZ77 字典壓縮。在 32KB 的滑動視窗內,把重複出現的字串替換成 (距離, 長度) 的配對,同時輸出字面值(literal)與配對(length-distance pair)。
  • 第二階段:霍夫曼編碼。對第一階段產生的「字面值/長度」符號流與「距離」符號流,分別建立霍夫曼編碼表,進一步壓縮。
  • 這個組合之所以經典,是因為它同時吃到了「字典冗餘」與「統計冗餘」兩種紅利,而兩階段的運算成本又相對可控。DEFLATE 的壓縮等級通常對應到不同的搜尋策略(例如「快速模式」只看最近的匹配、「最大壓縮」會做更徹底的鏈式搜尋),這也是為什麼同一個 ZIP 檔在不同工具下大小會略有差異。

    DEFLATE 的極限也很明確:32KB 視窗對於超大檔案或需要長距離匹配的資料(例如重複間隔很大的一批文件)就顯得不足。因此後續出現了 LZMA(Lempel-Ziv-Markov chain Algorithm)、Zstandard、Brotli 等改良版本,分別在視窗大小、模型精緻度與速度上做出不同取捨。

    LZW:讓字典自己長出來

    LZW 的巧妙之處,在於它不需要在壓縮檔中傳遞字典內容。壓縮器與解壓縮器都從「所有單一字元」的初始字典開始,每當壓縮器輸出一個碼,雙方就同步更新字典。這使得 LZW 的實作非常精簡,也讓它成為早期 GIF 與 TIFF 格式的壓縮方式。

    不過 LZW 有個歷史插曲值得一提:它在 1980 年代被 Unisys 公司主張擁有專利,導致 GIF 格式在 1990 年代末期引發爭議,也間接促使 PNG 格式的誕生(PNG 採用的是 DEFLATE,而非 LZW)。這段歷史告訴我們,演算法不只是技術選擇,有時也是生態與授權的選擇。

    BWT 與後現代壓縮:把相似的字元排在一起

    Burrows-Wheeler Transform(BWT)是一個聽起來很玄、但概念很優雅的技術。它不做壓縮,而是做「重排」:把整段字串的所有循環位移排序,然後取排序後矩陣的最後一列。經過這個轉換後,原本分散在各地的相同字元會被排在一起,形成大量連續重複的字元。接著再搭配 MTF(Move-to-Front)轉換與霍夫曼或算術編碼,就能達到極高的壓縮比。

    bzip2 就是 BWT + MTF + 霍夫曼的經典組合。它的壓縮比通常優於 GZIP,但速度較慢、記憶體需求較高,且不支援隨機存取。這也說明了無損壓縮的一個常見規律:壓縮比越高,通常越需要「看完整段資料」才能做出好決策,因此解壓時也越難做到隨機存取。

    ANS 與現代壓縮:速度與壓縮比的再平衡

    近十年最受注目的技術是 ANS(Asymmetric Numeral Systems),由 Jarek Duda 在 2007 年左右提出。它解決了長久以來的兩難:算術編碼壓縮效率高但速度慢(尤其難以平行化),霍夫曼編碼速度快但壓縮效率稍遜。ANS 用「數字系統」的方式重新詮釋機率編碼,讓它既接近熵極限、又能用查表方式高速運算。

    Zstandard(zstd)與 Apple 的 LZFSE 都採用了 ANS 家族技術,讓「壓縮比接近 LZMA、速度卻接近 DEFLATE」成為可能。這也是為什麼近年在資料庫、日誌系統、容器映像等領域,zstd 快速竄起,取代了許多原本使用 gzip 的場景。

    五、為什麼無損壓縮能「無損」?

    談完演算法,我們回到本文最核心的問題:壓縮後的資料明明長得完全不一樣,為什麼解壓後還能一個位元都不差?

    可逆性是數學保證,不是運氣

    關鍵在於:無損壓縮本質上是一種「雙射(Bijection)」。也就是說,壓縮函數必須存在一個完全對應的反函數,使得「壓縮 → 解壓縮」的複合運算等於恆等運算。只要演算法設計正確,這個性質是數學上被保證的,不是靠運氣,也不是靠「盡量還原」。

    我們回頭看霍夫曼編碼。解碼器拿到位元流後,從根節點開始,讀一個位元就往左或往右走,走到葉節點就輸出一個符號,然後回到根節點繼續。因為霍夫曼碼滿足「前綴自由」性質,所以這個過程永遠不會走錯路。每一個位元流都恰好對應唯一一串符號,不存在歧義。

    再看 LZ77。解壓縮器維護一份「已解出的歷史資料」,遇到字面值就輸出並加入歷史,遇到 (距離, 長度) 就從歷史中複製對應片段。因為壓縮器在產生配對時,保證「距離」指的位置已經存在於歷史中,所以解壓縮器一定能找到對應資料。壓縮與解壓的狀態機是完全同步的,這是可逆性的來源。

    算術編碼則更抽象一些。壓縮器把整段輸入映射到一個極小的實數區間,只需要輸出「足以唯一識別該區間」的位元。解壓縮器從這個數值出發,反推每個符號的機率區間,逐步還原。只要精度足夠,還原就是唯一解。

    校驗與容錯:實務上的最後一道防線

    雖然理論上無損壓縮是完美的,但實務上仍可能出錯:硬碟壞軌、傳輸雜訊、記憶體位元翻轉、甚至壓縮軟體本身的 bug。因此,多數壓縮格式都會加入校驗碼(Checksum),例如 CRC-32(ZIP、GZIP、PNG)、CRC-64(XZ)、XXH64(zstd 可選)等。解壓縮完成後,工具會比對原始資料的校驗值與記錄值是否一致,若不一致就報錯。

    這也解釋了一個使用上的小知識:「壓縮檔損毀」通常不是演算法不無損,而是檔案在儲存或傳輸過程中出了問題。這也是為什麼重要備份通常會建議「壓縮 + 校驗」並行,例如 7-Zip 的「測試壓縮檔」功能、RAR 的恢復記錄(Recovery Record),本質上都是在對抗實體層的不可靠。

    六、常見無損壓縮格式與工具比較

    理解了原理之後,我們來看看實務上的選擇。不同的壓縮格式,其實就是不同演算法組合與工程取捨的產物。

    格式比較表

    格式/演算法

    核心技術

    壓縮比

    壓縮速度

    解壓速度

    典型用途

    ZIP(DEFLATE)

    LZ77 + 霍夫曼

    中等

    通用打包、跨平台傳輸

    GZIP(DEFLATE)

    LZ77 + 霍夫曼

    中等

    網頁傳輸、日誌壓縮

    BZIP2(BWT)

    BWT + MTF + 霍夫曼

    中等

    需要高壓縮比的離線備份

    XZ(LZMA2)

    LZ77 變體 + 區間編碼

    很高

    很慢

    中等

    軟體發行包、長期歸檔

    7z(LZMA / LZMA2)

    改良 LZ77 + 區間編碼

    很高

    中等

    高壓縮比打包、多檔歸檔

    RAR(RAR5)

    LZ + PPM 混合

    很高

    中等

    商業壓縮、恢復記錄

    Zstandard(zstd)

    LZ77 + FSE/ANS

    可調(極快~慢)

    極快

    資料庫、容器、即時壓縮

    Brotli

    LZ77 + 靜態字典 + 霍夫曼

    中等

    網頁資源(CSS/JS/HTML)

    該怎麼選?從場景出發

    場景一:日常檔案傳輸。如果你只是要把一批文件壓成一個檔寄給同事,ZIP 仍然是最安全的選擇——跨平台支援最廣、Windows 與 macOS 雙擊即可解壓。若在意檔案大小,可以改用 7z,但收件人可能需要額外安裝工具。

    場景二:網頁與 API 傳輸。目前主流瀏覽器都支援 Brotli 與 gzip。Brotli 對 HTML、CSS、JS 這類文字資源的壓縮比通常優於 gzip(尤其搭配內建靜態字典),而 zstd 則在 API 的 JSON 傳輸與伺服器間通訊逐漸成為新標準。

    場景三:長期備份與歸檔。若是「一次寫入、極少讀取」的冷資料,XZ 或 7z 的高壓縮比能省下可觀的儲存成本;但若需要頻繁解壓(例如日誌分析),zstd 的高解壓速度更實用。這也是為什麼很多資料庫與日誌系統選擇「zstd 等級 3~9」作為預設,而不是追求極致壓縮比。

    場景四:容器與軟體映像。Docker 映像、套件倉庫等場景,通常會選擇 zstd 或 gzip,理由是「解壓速度」比「壓縮比」更影響部署體驗。一個壓縮比高 5% 但解壓慢 3 倍的格式,在 CI/CD 流程中通常是虧的。

    七、無損壓縮的極限與常見迷思

    講到這裡,我們已經具備了判斷各種壓縮說法的基礎。最後,來破解幾個最常見的誤解。

    迷思一:為什麼不能把所有檔案都壓到很小?

    這可以用鴿籠原理(Pigeonhole Principle)簡單反證。假設存在一種無損壓縮演算法,能把「所有」長度為 N 位元的檔案都壓成少於 N 位元。那麼壓縮後的檔案種類數一定少於原始檔案種類數,根據鴿籠原理,必然有兩個不同的原始檔案被壓成同一個壓縮結果。如此一來,解壓縮時就不可能分辨它們原本是哪一個,矛盾。

    換句話說,「某些檔案能被壓小」必然伴隨著「某些檔案會被壓大」。實務上的壓縮工具因此都會設計「若壓縮後反而變大,則以原始形式儲存」的機制,這就是為什麼你偶爾會看到壓縮檔大小與原始檔幾乎相同(甚至略大)。

    迷思二:已經壓縮過的檔案為什麼壓不動?

    因為它們的熵已經很高、冗餘已經被移除得差不多了。JPEG、MP3、MP4、ZIP、PNG 等格式,本身就是壓縮過的結果,其位元流看起來接近隨機。你再用 ZIP 去壓一個 ZIP 檔,幾乎不會變小——因為已經沒有統計或字典冗餘可以被利用了。

    不過這也不是絕對的。若一個壓縮檔內部包含大量「未壓縮」的內容(例如一個 ZIP 裡面包了許多 BMP 或 WAV),第二次壓縮仍可能略有收益。但這種情況越來越少見,因為現代格式多半已對內容做了最佳化。

    迷思三:壓縮比越高越好?

    絕對不是。壓縮比的提升通常伴隨壓縮時間、解壓時間、記憶體占用的增加。在一些極端設定下(例如 LZMA 搭配超大字典型),壓縮一個檔案可能需要數十分鐘,解壓時也需要數 GB 記憶體。對於需要即時處理的場景(資料庫、串流、容器啟動),這種取捨往往不划算。

    正確的思維是:先確定你的瓶頸是儲存空間還是 CPU/延遲,再選擇對應的演算法與等級。這也是為什麼現代壓縮工具(如 zstd、7-Zip)都提供「等級」參數,讓使用者依照場景權衡。

    迷思四:壓縮炸彈是什麼?

    壓縮炸彈(Zip Bomb)是利用高壓縮比特性製作的惡意檔案。例如一個數十 KB 的壓縮檔,解壓後可能膨脹成數十 GB,導致磁碟耗盡或系統癱瘓。著名的「42.zip」就是經典案例。

    這提醒我們:壓縮比的極端值本身沒有問題,問題在於解壓時缺乏資源限制。現代的解壓工具與雲端服務通常會設定「解壓後大小上限」或「壓縮比異常偵測」來防範這類攻擊。

    八、結語:壓縮是一場機率與結構的對話

    回顧整篇文章,我們從「壓縮就是檔案變小」這個樸素的直覺出發,一路走到資訊理論的熵、霍夫曼的最優前綴碼、LZ77 的滑動視窗、BWT 的重排魔法、ANS 的現代工程。你會發現,無損壓縮之所以能「無損」,從來不是因為它「記住了所有細節」,而是因為它「看懂了資料的規律」,並且用一個可逆的方式把規律編碼起來。

    當你下次按下「壓縮」時,不妨想想這背後發生的事:你的檔案被統計、被建模、被重新編碼,然後在解壓的那一刻,每一個位元都被精確地放回原位。這是一種近乎優雅的工程契約——只要演算法正確、只要資料沒有損壞,它就承諾給你一個「與原本完全相同」的世界。

    而對於雅寶社區的讀者來說,理解這些原理的實際價值,不只是為了考試或面試,而是為了在做技術決策時,能夠說清楚「我為什麼選 zstd 而不是 gzip」、能夠判斷「這個壓縮比數字是不是話術」、也能夠在面對「無損壓縮可以把任何東西變小」這類說法時,微微一笑,然後用鴿籠原理反駁。

    壓縮的極限是熵,而人類對效率的追求沒有極限。這大概就是這項技術最迷人的地方。

    🏠 返回首頁