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類資料壓縮演算法,即變長編碼、統計壓縮、字典編碼、上下文模型和多上下文模型,然後介紹了夏農的資訊理論,以及怎樣通過各種方法來突破熵,如統計編碼、自適應統計編碼、字典轉換、上下文資料轉換、資料建模等。
本書還討論了資料壓縮中的一些要點,如多媒體資料壓縮和通用壓縮,並介紹了有損資料壓縮。本書最後說明了資料壓縮與你、你的公司以及未來的技術是如何相互關聯的。
圖靈官方小店
享受正版低價折扣