1. 程式人生 > 實用技巧 >ZIP 也能邊下載邊解壓?流式解壓技術揭祕!

ZIP 也能邊下載邊解壓?流式解壓技術揭祕!

對於一個 ZIP 檔案,由於標準的解壓方式總是從讀取檔案的末尾開始的,因此必須下載完整個 ZIP 解壓後才能訪問。當用戶通過網路訪問 ZIP 檔案時,下載解壓所帶來的耗時將大大降低使用者體驗。那麼能不能邊下載邊解壓呢?阿里巴巴文娛技術 喻遠將介紹 ZIP 流式解壓的原理和技術實現路徑。

開啟網路上的 ZIP 檔案需要幾步?下載,解壓,拿到所有檔案。面對一個 ZIP,能不能「邊下邊播」、「按需下載」?

今年 6 月,優酷繪本技術團隊開發出新的解壓方式——ZIP 流式解壓技術,併成功應用在優酷繪本秒開專案中,30M+ 繪本平均載入時長只需 0.91s,載入耗時比傳統的解壓方式降低了 88.3%,讓使用者的閱讀體驗直線提升。實際對比效果如下:

優化前

優化後

本文將介紹 ZIP流式解壓的原理和技術實現路徑,希望為大家帶來啟發,將 ZIP 流式解壓技術更多的應用到業務中。

一 什麼是ZIP

ZIP 是一種檔案格式,定義瞭如何將多個檔案、資料塊組織在一起形成一個完整的檔案。例如我們常見的 .apk,.ipa,.sketch,都是ZIP檔案。通常程式是這樣建立 ZIP 檔案的:

  • 壓縮單個檔案形成單檔案資料塊;

  • 在資料塊前後新增檔案描述資訊;

  • 對每個待壓縮的檔案重複以上步驟後,拼接所有資料形成更大的資料塊;

  • 提取所有檔案描述資訊,生成一份「檔案目錄」,附在最後一個數據塊的尾部。

我們將檔案前部描述資訊稱為 Local File Header,檔案後部描述資訊稱為 Data Descriptor, 被壓縮的檔案本身稱為 File Data,將最後的檔案目錄稱為 Central Directory。以上所有合在一起,就是一個標準的 ZIP 檔案。如下圖:

ZIP 檔案格式

一個標準的解壓方式總是從讀取 ZIP 檔案末尾開始的,我們以解壓上圖的 File Data 1 為例:

  • 首先在 ZIP 檔案末尾找到 Central Directory 資料塊;

  • 在 Central Directory 資料塊中找到 File Header 1;

  • 從 File Header 1 中讀取 Local File Header 1 的偏移量和 File Data 1 的相關資訊;

  • 根據偏移量找到 Local File Header 1;

  • 讀取 Local File Header 1;

  • 解密 File Data 1(如果需要);

  • 解壓 File Data 1;

  • 讀取 Data Descriptor 1;

  • 使用 File Header 1 中儲存的 CRC-32 做校驗步驟 7 中計算的 CRC-32,以確保解壓後的資料完整性。

標準解壓方式存在的不足

可以發現,標準的解壓強依賴尾部的 Central Directory。當 ZIP 檔案儲存在 cdn 上時,哪怕我們只想訪問其中的一個檔案,也必須下載整個 ZIP 解壓後才可訪問。假如 ZIP 檔案有 100 MB,但是我們只需要訪問其中的某一個 10 KB 的檔案,那麼下載整個 ZIP 將是對流量的巨大浪費。

二 優酷技術方案:ZIP流式解壓

我們的一個初步的想法是能不能邊下載邊解壓?

要實現這點,首先需要改變解壓方式,使其不能再依賴尾部的 Central Directory。

根據 ZIP 檔案格式標準可知,除了 Central Directory,每個 File Data 頭部的 Loca File Header 部分也包含了該檔案的相關資訊。

假如 Local File Header 中包含了充分的資訊,我們也許可以基於 Local File Header 去解壓檔案資料,其解壓流程就可以變為:

  • 從頭開始,搜尋到 Local File Header 1;

  • 讀取 Local File Header 1;

  • 解密 File Data 1(如果需要);

  • 解壓 File Data 1;

  • 讀取 Data Descriptor 1;

  • CRC32 的校驗。

那麼 Local File Header 裡到底儲存了什麼?是否滿足解密解壓所需?

瞭解 Local File Header

我們根據文件對 Local File Header 的描述,畫出其二進位制檔案中的排列:

Local File Header 資料結構

其中的關鍵資訊為:

Signature

元資料簽名

Compress Method

壓縮演算法

Compressed Size

壓縮後文件大小

Uncompressed Size

壓縮前檔案大小

CRC-32

檔案的迴圈冗餘校驗值

File name

檔名

元資料簽名是一個 Magic Number,用來標記接下來資料是什麼內容。例如 Local File Header 的簽名是 0x04034b50,用 char 表示也就是 { 'P', 'K', '3', '4' }。當讀取到對應資料簽名時,則意味著接下來的資料結構符合對應元資料的定義,需要使用對應規則解析。

Compress Method 指明資料塊用何種演算法壓縮,解壓需要使用對應的演算法。

Compressed Size 和 UnCompressed Size可以幫助確定檔案的結尾地址和 Data Descriptor 的偏移量。這兩個 Size 也是檔案解密時 HMAC 計算的關鍵。

有了 Magic Number 作為元資料簽名,我們只需要逐位元組遍歷去匹配這個 Number,就可以找到 Loca File Header,而不再需要依賴尾部的定位資訊。而且 Local File Header 中儲存的元資料足夠我們決定解壓演算法、計算大小、校驗 CRC-32 了。

還有一個問題是,解壓縮演算法是否支援流式解壓縮?是否有特定的上下文依賴?通過了解壓縮演算法的原理[1],我們知道,所有的壓縮演算法都是支援從頭部開始流式解壓的。

而下載方面,檔案是以從頭到尾連續的方式下載,這又天然地和和從頭解壓的方式配合,便可以初步實現邊下邊解!

加密 ZIP 檔案的問題

一切都相當順利,直到遇到了加密後的 ZIP 檔案。加密後的 ZIP 檔案的 Local File Header 中的關鍵資訊除了簽名和檔名以外,其他資訊都被隱去,需要去 Central Directory 中讀取。

再一次,我們回到了依賴 Central Directory 的狀態。

在失去如此多關鍵資訊的情況下能否繼續做到流式解壓?我們需要先挖掘一下 ZIP 的加密方式。

ZIP 的加密方式

ZIP 檔案支援多種加密方式,最常見的是 Traditional PKWARE Encryption 和 AES Encryption 。

Traditional PKWARE Encryption 是 ZIP 自定義的一種基於密碼的對稱加密方式,每個位元組的加密僅和密碼有關,加密前後的資料長度不變。這種不依賴上下文的加密方式可以實現我們需要的流式解密。

AES 加密採用的是 CTR 模式。CTR 模式將明文分組,並生成一個計數器。使用金鑰對計數器進行加密生成二進位制位元組流。利用這個位元組流和明文進行 XOR 操作進行加密。其解密方式也是一樣的。

這種方式也支援流式解密。

兩種常用的加密方式都支援流式解密,那麼加解密需要的關鍵資訊,在 Local File Header 中是否有儲存就成了能否流式解密的關鍵。

流式解密的關鍵資訊

無論是 Traditional PKWARE Encryption 還是 AES Encryption,在解密時都需要一些除密碼之外的關鍵資訊,例如鹽值,加密演算法的強度等。此外,在 AES 加密的 ZIP 檔案中, Local File Header 中的 Compress Method 欄位被抹去,這樣我們便無法知曉壓縮演算法,因此無法解壓。

至此,問題集中為:

  • Local File Header 中是否有足夠的加密所需資訊。

  • 加密的 ZIP 檔案,是否能在除 Central Directory 以外的位置找到 Compress Method 欄位。

Local File Header 中加密相關的資訊

ZIP 格式的設計者在設計 ZIP 檔案格式的初期就提供了檔案拓展能力,一些額外的拓展資料可以存放在 Local File Header 的 Extra Field 中。ZIP AES 加密說明書[2]告訴我們 AES 的相關資訊就存放在這裡。其關鍵資訊如下:

Signature

Extra Data 簽名(0x9901)

AES Encryption strength

AES 加密強度(128或192或256)

Actual compress Method

真正的壓縮演算法

原來壓縮演算法被藏到了 Extra Data 中。那麼鹽值被存放在哪裡了?答案是存放在 File Data 的頭尾。

綜上,我們找到解密所需的所有關鍵資訊,整個流式解密解壓的所有技術點都被我們探索完。剩下的便是按原理實現,以及細節的打磨。

三 總結

說了那麼多,流式解壓究竟有什麼價值呢?

由於流式解壓實現了邊下載邊解壓,將整個操作的時長從下載 + 解壓縮變成了約等於純下載的時長,直接抹掉了解壓的耗時。在 39.1 MB 大小的 ZIP 包下載解壓測試中,耗時從 9.08 秒降低至 4.17 秒,有將近 100% 的提速!

同時,你可以不必等待整個 ZIP 下載解壓完,而是在解壓完一小部分資料的時候,就直接展示 UI。使用者側看起來就好像一瞬間就解壓完了。

因此,流式解壓可以應用在許多時間敏感的操作裡,也可以用來優化基於 ZIP 檔案的相關業務。例如基於 ZIP 的全域性換膚加速、基於 ZIP 的 Web 資源快取載入的加速等等。前言中的優酷繪本秒開就是基於這一技術實現。

參考

[1]https://houbb.github.io/2018/11/09/althgorim-compress-althgorim-12-zip-02

[2]AES Encryption Information: Encryption Specification AE-1 and AE-2

https://www.winzip.com/win/en/aes_info.html

[3]ZIP File Format Specification

https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.2.1.TXT

[4]AES Coding Tips for Developers

https://www.winzip.com/win/en/aes_tips.html

揭開資料壓縮的神祕面紗

程式設計師學習壓縮演算法的起點

本書的主題是資料壓縮,也就是用最緊湊的方式來表示資料。本書先講解了5類資料壓縮演算法,即變長編碼、統計壓縮、字典編碼、上下文模型和多上下文模型,然後介紹了夏農的資訊理論,以及怎樣通過各種方法來突破熵,如統計編碼、自適應統計編碼、字典轉換、上下文資料轉換、資料建模等。

本書還討論了資料壓縮中的一些要點,如多媒體資料壓縮和通用壓縮,並介紹了有損資料壓縮。本書最後說明了資料壓縮與你、你的公司以及未來的技術是如何相互關聯的。

圖靈官方小店

享受正版低價折扣