对于一个 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类数据压缩算法,即变长编码、统计压缩、字典编码、上下文模型和多上下文模型,然后介绍了香农的信息论,以及怎样通过各种方法来突破熵,如统计编码、自适应统计编码、字典转换、上下文数据转换、数据建模等。

本书还讨论了数据压缩中的一些要点,如多媒体数据压缩和通用压缩,并介绍了有损数据压缩。本书最后说明了数据压缩与你、你的公司以及未来的技术是如何相互关联的。

图灵官方小店

享受正版低价折扣

ZIP 也能边下载边解压?流式解压技术揭秘!相关推荐

  1. java流式上传下载_精讲RestTemplate第6篇-文件上传下载与大文件流式下载

    C++Templates(第2版英文版) 123.24元 (需用券) 去购买 > 本文是精讲RestTemplate第6篇,前篇的blog访问地址如下: 精讲RestTemplate第1篇-在S ...

  2. 基于流式的md5计算-多线程下载工具Lwget介绍

    在数据传输的时候,我们希望实现以下目标: 1. 使用多线程传输,加速下载速度 2. 数据在传输过程中,进行流式md5计算,避免在传输完毕之后校验大文件 3. 支持断点续传 4. 支持http协议和ft ...

  3. 优化问题中什么形式有闭式解

    闭式解也被称为解析解,与数值解对应. 解析解(analytical solution)就是一些严格的公式,给出任意的自变量就可以求出其因变量,也就是问题的解, 他人可以利用这些公式计算各自的问题.所谓 ...

  4. 点燃式、压燃式发动机区别

    点燃式 PI 压燃式 CI 压燃式内燃机吸入的是空气,进行压缩燃烧,压燃式发动机压缩比较高,比较有力量,适合大马力的发动机. 点燃式内燃机吸入的是空气和燃料(常是汽油),点燃式内燃机的优点是速度比较快 ...

  5. Python批量下载CHIRPS气象数据并完成解压裁剪等

    文章目录 前言 一.CHIRPS是什么? 二.实现步骤 1.下载数据 2.解压缩 3.批量裁剪 三.完整代码如下 四.代码结果 前言   最近需要下载气象数据--CHIRPS,借助之前学的批量下载哨兵 ...

  6. Linux zip进行分卷压缩, unzip 进行多卷组合并解压

    一.Linux zip 分卷压缩: 1.先把源文件压缩个成zip zip -r b.zip a.mp4 2.根据情况分卷,比如我这里最大40MB: zip -s 40m b.zip --out c.z ...

  7. springboot Java实现多文件的zip压缩操作 + 通过浏览器下载文件的两种方式

    注只适配utf-8的场景,待完善! 压缩为zip文件 通过java程序输出文件 /*** 功能:压缩多个文件成一个zip文件* @param srcfile:源文件列表* @param zipfile ...

  8. ubunt linux解压gz,ubuntu解压命令

    ubuntu解压命令 .tar 解包:tar xvf FileName.tar 打包:tar cvf FileName.tar DirName (注:tar是打包,不是压缩!) ----------- ...

  9. sysbench 压测 详解

    下载sysbench(mysql官网就有) 解压,进入解压以后的目录 ./autogen.sh ./configure --with-mysql-includes=/usr/local/mysql/i ...

最新文章

  1. 【Vue】宝塔面板服务器配置Vue项目
  2. Drupal7 将到2021年11月结束支持,请注意升级
  3. Binary classification - 聊聊评价指标的那些事儿【实战篇】
  4. java map按照value排序_基础:Java集合需要注意的 5 个问题
  5. 使用dom breakpoint找到修改属性的javascript代码
  6. 使用 C++ 处理 JSON 数据交换格式
  7. Python利用turtle绘制五角星
  8. c语言 数据结构 list、queue、tree抽象数据类型的定义与实现 详尽代码和注释
  9. 大白菜安装服务器linux,通过U盘安装Debian
  10. 计算机初试占比高的学校,复试压力小,初试占比70%及以上的院校汇总!
  11. [Be a Coding Plasterer] Components 1:get Basic Things
  12. 安装caffe-yolov3时报错:ImportError: No module named torch(环境变量设置)
  13. 怎么自学python-如何系统地自学 Python?
  14. ros 机械臂复位_ROS入门学习之八机器人综合应用
  15. ofdm导频信道估计matlab,ofdm系统中基于导频的信道估计算法的性能分析(样例3)...
  16. 如何进行支付流程及安全测试?
  17. 分区 MBR文件系统
  18. 花998购买的拍摄技巧和7天起号培训文档,学了一周的总结。
  19. 【EasyUI篇】TreeGrid树表格组件
  20. MATLAB学习系列--绘制函数曲线

热门文章

  1. python怎么用函数查看变量类型_查看变量类型的Python内置函数是()。
  2. Linux宏定义屏蔽打印信息,宏定义实现调试信息的打印
  3. oracle查询表中的某一行,oracle查看所有表及各表行数?
  4. 舞伴问题数据结构java_Gale-Shapley算法解决舞伴问题过程详解(C++实现)
  5. sip 时序图_时序图怎么看_教你如何看懂时序图 - 什么是时序图_时序图怎么看_教你如何看懂时序图...
  6. Vmo前端数据模型设计
  7. Python学习小结---粗略列表解析
  8. SSH框架搭建问题总结
  9. ViewGroup1——自定义布局
  10. html met详解转