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 文件末尾开始的,我们以解压上图的 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
【ZIP 的流式解压技术 zip是什么文件】我们根据文档对 Local File Header 的描述,画出其二进制文件中的排列:
ZIP 的流式解压技术 zip是什么文件

文章插图
其中的关键信息为:
ZIP 的流式解压技术 zip是什么文件

文章插图
元数据签名是一个 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 操作进行加密 。其解密方式也是一样的 。
这种方式也支持流式解密 。
ZIP 的流式解压技术 zip是什么文件

文章插图
两种常用的加密方式都支持流式解密,那么加解密需要的关键信息,在 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 的相关信息就存放在这里 。其关键信息如下:
ZIP 的流式解压技术 zip是什么文件

文章插图
原来压缩算法被藏到了 Extra Data 中 。那么盐值被存放在哪里了?答案是存放在 File Data 的头尾 。
综上,我们找到解密所需的所有关键信息,整个流式解密解压的所有技术点都被我们探索完 。剩下的便是按原理实现,以及细节的打磨 。
三 总结
说了那么多,流式解压究竟有什么价值呢?
由于流式解压实现了边下载边解压,将整个操作的时长从下载 + 解压缩变成了约等于纯下载的时长,直接抹掉了解压的耗时 。在 39.1 MB 大小的 ZIP 包下载解压测试中,耗时从 9.08 秒降低至 4.17 秒,有将近 100% 的提速!同时,你可以不必等待整个 ZIP 下载解压完,而是在解压完一小部分数据的时候,就直接展示 UI 。用户侧看起来就好像一瞬间就解压完了 。
因此,流式解压可以应用在许多时间敏感的操作里,也可以用来优化基于 ZIP 文件的相关业务 。例如基于 ZIP 的全局换肤加速、基于 ZIP 的 Web 资源缓存加载的加速等等 。前言中的优酷绘本秒开就是基于这一技术实现 。

    推荐阅读