在 Web 中解压大型 ZIP 并保持目录结构
本文最后更新于:2025年4月25日 晚上
背景
最初是在 reddit 上看到有人在寻找可以解压 zip 文件的 Firefox 插件 [1],好奇为什么还有这种需求,发现作者使用的是环境受限的电脑,无法自由的安装本地程序。于是吾辈便去检查了现有的在线解压工具,结果却发现排名前 5 的解压工具都没有完全支持下面两点
- 解压大型 ZIP 文件,例如数十 G 的 ZIP
- 解压目录时保持目录结构
下面的视频展示了当前一些在线工具的表现
实际上,只有 ezyZip 有点接近,但它也不支持解压 ZIP 中的特定目录。
实现
在简单思考之后,吾辈考虑尝试使用时下流行的 Vibe Coding 来创建一个 Web 工具来满足这个需求。首先检查 zip 相关的 npm 包,吾辈之前经常使用的是 jszip,但这次检查时发现它的不能处理大型 ZIP 文件 [2]。所以找到了更快的 fflate,但遗憾的是,它完全不支持加密解密功能,但作者在 issue 中推荐了 zip.js [3]。
流式解压
官网给出的例子非常简单,也非常简洁明了。如果是解压文件并触发下载,只需要结合使用 BlobWriter/file-saver 即可。
1 |
|
这段代码出现了一个有趣之处:BlobWriter
,它是如何保存解压后的超大型文件的?毕竟数据总要在某个地方,blob 似乎都在内存中,而且也只允许流式读取而不能流式写入。检查一下 GitHub 上的源代码 [4]。
1 |
|
是的,这里的关键在于 Response
,它允许接受某种 ReadableStream [5] 类型的参数,而 ReadableStream 并不保存数据到内存,它只是一个可以不断拉取数据的流。
例如下面手动创建了一个 ReadableStream,它生成一个从零开始自增的无限流,但如果没有消费,它只会产生第一条数据。
1 |
|
如果消费 100 次,它就会生成 100 个值。
1 |
|
而在 zip.js 解压时,通过 firstEntry.getData(blobWriter)
将解压单个文件产生的二进制流写入到了 Response 并转换为 Blob 了。但是,难道 await new Response().blob()
不会将数据全部加载到内存中吗?
是的,一般认为 Blob 保存的数据都在内存中,但当 Blob 过大时,它会透明的转移到磁盘中 [6],至少在 Chromium 官方文档中是如此声称的,JavaScript 规范并未明确指定浏览器要如何实现。有人在 Stack Overflow 上提到 Blob 只是指向数据的指针,并不保存真实的数据 [7],这句话确实非常正确,而且有趣。顺便一提,可以访问 chrome://blob-internals/ 查看浏览器中所有的 Blob 对象。
解压目录
解压目录主要麻烦的是一次写入多个目录和文件到本地,而这需要利用浏览器中较新的 File System API [8],目前为止,它在浏览器中的兼容性还不错 [9],所以这里利用它来解压 ZIP 中的目录并写入本地。无论如何,只要做好降级处理,使用这个新 API 是可行的。
首先,可以通过拖拽 API 或者 input File 来获取一个目录的 FileSystemDirectoryHandle 句柄。一旦拿到它,就可以访问这个目录下所有的文件,并且可以创建子目录和写入文件(支持流式写入)。假设我们有一个要写入的文件列表,可以轻松使用下面的方法写入到选择的目录中。
1 |
|
局限性
尽管 File System API 已经可以胜任普通的文件操作,但它仍然有一些局限性,包括
- 用户可以选择的目录是有限制的,例如,无法直接选择 ~/Desktop 或 ~/Downlaod 目录,因为这被认为是有风险的 [10]。
- 无法保存一些特定后缀名的文件,例如
*.cfg
或者以~
结尾的文件,同样被认为有风险 [11]
总结
这是一个很早之前就有人做过的事情,但直到现在仍然可以发现一些有趣的东西。尤其是 Blob 的部分,之前从未真正去了解过它的存储方式。
基于本文探讨的技术,吾辈最终实现了一个名为 MyUnzip 的在线解压工具,欢迎访问 https://myunzip.com 试用并提出反馈。
- Is there a Firefox addon that can extract a zip file to a selected directory and maintain the directory structure? ↩
- https://github.com/Stuk/jszip/issues/912 ↩
- https://github.com/101arrowz/fflate/issues/90#issuecomment-929490126 ↩
- https://github.com/gildas-lormeau/zip.js/blob/601dedd3251676587123fd35228447682c9b02c9/lib/core/io.js#L207-L228 ↩
- https://developer.mozilla.org/docs/Web/API/ReadableStream ↩
- https://source.chromium.org/chromium/chromium/src/+/main:storage/browser/blob/README.md ↩
- https://stackoverflow.com/questions/38239361/where-is-blob-binary-data-stored ↩
- https://developer.mozilla.org/docs/Web/API/File_System_API ↩
- https://developer.mozilla.org/docs/Web/API/File_System_API#browser_compatibility ↩
- https://github.com/WICG/file-system-access/issues/381 ↩
- https://issues.chromium.org/issues/380857453 ↩