基于Node.js实现压缩和解压缩的方法


Posted in Javascript onFebruary 13, 2018

压缩格式

zip 和 gzip 是两种我们最常见到的压缩格式,当然,gzip 在 Windows 下很少有人接触。

tar 是一种归档格式,它默认不会压缩,需要结合 gzip 来将最终的 tar 文件以 gzip 格式压缩成为一个 tar.gz 文件,通常我们会缩写为 tgz。

为什么没有提到 rar?因为它是专利保护的算法,你可以免费获得解压工具,而压缩工具是需要付费的。所以我们一般应用场景下,很少会提供 rar 压缩文件。

本文将分别介绍 gzip,tar,tgz 和 zip 的压缩和解压缩在 Node.js 下如何实现。

未压缩文件库

本文所使用的未压缩文件库来自于 urllib ,需要先 clone 它下来到指定目录。

git clone https://github.com/node-modules/urllib.git nodejs-compressing-demo

gzip

在 Linux 的世界,每个工具的职责会很纯粹,非常单一,如 gzip,它只会对文件进行压缩,至于文件夹如何打包压缩,跟它没关系,那是 tar 要去负责的事情。

gzip 命令行压缩一个文件

例如我们要将 nodejs-compressing-demo/lib/urllib.js 文件进行 gzip 压缩,会得到一个 urllib.js.gz 文件,源文件会被删除。

$ ls -l nodejs-compressing-demo/lib/urllib.js
-rw-r--r-- 1 a a 31318 Feb 12 11:27 nodejs-compressing-demo/lib/urllib.js

$ gzip nodejs-compressing-demo/lib/urllib.js

$ ls -l nodejs-compressing-demo/lib/urllib.js.gz
-rw-r--r-- 1 a a 8909 Feb 12 11:27 nodejs-compressing-demo/lib/urllib.js.gz

# 还原压缩文件
$ gunzip nodejs-compressing-demo/lib/urllib.js.gz

文件大小从 31318 字节减少到 8909 字节,超过 3.5 倍的压缩效果。

还可以通过 pipe 方式,结合 cat 命令,将文件压缩并保存为任意文件:

$ ls -l nodejs-compressing-demo/README.md
-rw-r--r-- 1 a a 13747 Feb 12 11:27 nodejs-compressing-demo/README.md

$ cat nodejs-compressing-demo/README.md | gzip > README.md.gz

$ ls -l README.md.gz
-rw-r--r-- 1 a a 4903 Feb 12 11:50 README.md.gz

Node.js 实现 gzip

当然,我们不会真的从零开始实现一个 gzip 算法和工具,在 Node.js 的世界,早已有人为你准备好这些基础库,我们只需要开箱即用。

本文将会使用 compressing 模块,实现所有压缩和解压缩代码。

为什么会选择 compressing?因为它有足够充分的代码质量和单元测试保证,处于活跃的维护状态,API 非常友好,而且还支持流式接口。

Promise 接口

const compressing = require('compressing');

// 选择 gzip 格式,然后调用 compressFile 方法
compressing.gzip.compressFile('nodejs-compressing-demo/lib/urllib.js', 'nodejs-compressing-demo/lib/urllib.js.gz')
 .then(() => {
  console.log('success');
 })
 .catch(err => {
  console.error(err);
 });

// 解压缩是反响过程,接口都统一为 uncompress
compressing.gzip.uncompress('nodejs-compressing-demo/lib/urllib.js.gz', 'nodejs-compressing-demo/lib/urllib.js2')
 .then(() => {
  console.log('success');
 })
 .catch(err => {
  console.error(err);
 });

结合 async/await 的编程模型,代码写起来就是一个普通的异步 io 操作。

const compressing = require('compressing');

async function main() {
 try {
  await compressing.gzip.compressFile('nodejs-compressing-demo/lib/urllib.js',
   'nodejs-compressing-demo/lib/urllib.js.gz');
  console.log('success');
 } catch (err) {
  console.error(err);
 }

 // 解压缩
 try {
  await compressing.gzip.uncompress('nodejs-compressing-demo/lib/urllib.js.gz',
   'nodejs-compressing-demo/lib/urllib.js2');
  console.log('success');
 } catch (err) {
  console.error(err);
 }
}

main();

Stream 接口

需要特别注意的是,使用 Stream 模式编程,需要处理每个 stream 的 error 事件,并且要手动销毁所有 stream 。

fs.createReadStream('nodejs-compressing-demo/lib/urllib.js')
 .on('error', handleError)
 .pipe(new compressing.gzip.FileStream()) // It's a transform stream
 .on('error', handleError)
 .pipe(fs.createWriteStream('nodejs-compressing-demo/lib/urllib.js.gz2'))
 .on('error', handleError);

// 解压缩,就是 pipe 的方向倒转过来
fs.createReadStream('nodejs-compressing-demo/lib/urllib.js.gz2')
 .on('error', handleError)
 .pipe(new compressing.gzip.UncompressStream()) // It's a transform stream
 .on('error', handleError)
 .pipe(fs.createWriteStream('nodejs-compressing-demo/lib/urllib.js3'))
 .on('error', handleError);

根据官方的Backpressuring in Streams 推荐,我们应该使用 pump 模块来配合 Stream 模式编程,由 pump 来完成这些 Stream 的清理工作。

const pump = require('pump');

const source = fs.createReadStream('nodejs-compressing-demo/lib/urllib.js');
const target = fs.createWriteStream('nodejs-compressing-demo/lib/urllib.js.gz2');

pump(source, new compressing.gzip.FileStream(), target, err => {
 if (err) {
  console.error(err);
 } else {
  console.log('success');
 }
});

// 解压缩
pump(fs.createReadStream('nodejs-compressing-demo/lib/urllib.js.gz2'),
  new compressing.gzip.FileStream(),
  fs.createWriteStream('nodejs-compressing-demo/lib/urllib.js3'),
  err => {
 if (err) {
  console.error(err);
 } else {
  console.log('success');
 }
});

Stream 接口的优势

Stream 接口看起来比 Promise 接口复杂多了,为何还会有这种应用场景呢?

其实在 HTTP 服务领域,Stream 模型会有更大的优势,因为 HTTP 请求本身就是一个 Request Stream,如要将一个上传文件以 gzip 压缩返回,使用 Stream 接口不需要将上传文件保存到本地磁盘,而是直接消费这个文件流。

使用 egg 文件上传的示例代码 ,我们稍微改造一下,就能实现 gzip 压缩然后返回。

const pump = require('pump');

class UploadFormController extends Controller {
 // ... other codes

 async upload() {
  const stream = await this.ctx.getFileStream();
  // 直接将压缩流赋值给 ctx.body,实现边压缩边返回的流式响应
  this.ctx.body = pump(stream, new compressing.gzip.FileStream());
 }
}

tar | gzip > tgz

gzip 章节可以提前知道,tar 是负责对文件夹进行打包:package:的。

例如要对 nodejs-compressing-dem o 整个文件夹打包成一个文件发送给别人,可以通过 tar 命令完成。

$ tar -c -f nodejs-compressing-demo.tar nodejs-compressing-demo/

$ ls -l nodejs-compressing-demo.tar
-rw-r--r-- 1 a a 206336 Feb 12 14:01 nodejs-compressing-demo.tar

如大家所见,tar 打包出来的文件一般都比较大,因为它是未压缩的,大小跟实际文件夹总大小接近。所以我们都会在打包同时进行压缩。

$ tar -c -z -f nodejs-compressing-demo.tgz nodejs-compressing-demo/

$ ls -l nodejs-compressing-demo.tgz
-rw-r--r-- 1 a a 39808 Feb 12 14:07 nodejs-compressing-demo.tgz

tar 和 tgz 超过 5 倍大小的差异,可以大大减少网络传输带宽。

Node.js 实现 tgz

Promise 接口

先使用 compressing.tar.compressDir(sourceDir, targetFile) 将一个文件夹打包到一个 tar 文件,然后使用上文的 gzip 压缩方式,将 tar 文件压缩为 tgz 文件。

const compressing = require('compressing');

compressing.tar.compressDir('nodejs-compressing-demo', 'nodejs-compressing-demo.tar')
 .then(() => {
  return compressing.gzip.compressFile('nodejs-compressing-demo.tar',
   'nodejs-compressing-demo.tgz');
 });
 .then(() => {
  console.log('success');
 })
 .catch(err => {
  console.error(err);
 });

// 解压缩
compressing.gzip.uncompress('nodejs-compressing-demo.tgz', 'nodejs-compressing-demo.tar')
 .then(() => {
  return compressing.tar.uncompress('nodejs-compressing-demo.tar',
   'nodejs-compressing-demo2');
 });
 .then(() => {
  console.log('success');
 })
 .catch(err => {
  console.error(err);
 });

结合 async/await 的编程模型,代码写起来会更加容易阅读:

const compressing = require('compressing');

async function main() {
 try {
  await compressing.tar.compressDir('nodejs-compressing-demo',
   'nodejs-compressing-demo.tar');
  await compressing.gzip.compressFile('nodejs-compressing-demo.tar',
   'nodejs-compressing-demo.tgz');
  console.log('success');
 } catch (err) {
  console.error(err);
 }
 
 // 解压缩
 try {
  await compressing.gzip.uncompress('nodejs-compressing-demo.tgz',
   'nodejs-compressing-demo.tar');
  await compressing.tar.uncompress('nodejs-compressing-demo.tar',
   'nodejs-compressing-demo2');
  console.log('success');
 } catch (err) {
  console.error(err);
 }
}

main();

Stream 接口

通过 compressing.tar.Stream 类,可以动态添加任意文件、文件夹到一个 tar stream 对象中,非常灵活。

const tarStream = new compressing.tar.Stream();
// dir
tarStream.addEntry('dir/path/to/compress');
// file
tarStream.addEntry('file/path/to/compress');
// buffer
tarStream.addEntry(buffer);
// stream
tarStream.addEntry(stream);

const destStream = fs.createWriteStream('path/to/destination.tgz');
pump(tarStream, new compressing.gzip.FileStream(), destStream, err => {
 if (err) {
  console.error(err);
 } else {
  console.log('success');
 }
});

zip

zip 其实可以看作是 tar + gzip 的「商业化」结合,它让使用者不需要区分是压缩文件还是压缩文件夹,反正用我 zip 就对了。

使用 zip 命令行工具压缩一个文件夹的例子:

$ zip -r nodejs-compressing-demo.zip nodejs-compressing-demo/
 adding: nodejs-compressing-demo/ (stored 0%)
 adding: nodejs-compressing-demo/test/ (stored 0%)
 ...
 adding: nodejs-compressing-demo/.travis.yml (deflated 36%)

$ ls -l nodejs-compressing-demo.*
-rw-r--r-- 1 a a 206336 Feb 12 14:06 nodejs-compressing-demo.tar
-rw-r--r-- 1 a a  39808 Feb 12 14:07 nodejs-compressing-demo.tgz
-rw-r--r-- 1 a a  55484 Feb 12 14:34 nodejs-compressing-demo.zip

通过 tgz 和 zip 文件大小对比,可以看出默认的压缩参数下,gzip 的效果会比 zip 好。

Node.js 实现 zip

实现代码跟 tar 类似,只不过默认是压缩的,不需要再添加 gzip 的过程。

const compressing = require('compressing');

compressing.zip.compressDir('nodejs-compressing-demo', 'nodejs-compressing-demo.zip')
 .then(() => {
  console.log('success');
 })
 .catch(err => {
  console.error(err);
 });

// 解压缩
compressing.zip.uncompress('nodejs-compressing-demo.zip', 'nodejs-compressing-demo3')
 .then(() => {
  console.log('success');
 })
 .catch(err => {
  console.error(err);
 });

总结

基于 Node.js 实现的压缩和解压缩是否比想象中简单?感谢 npm 这个巨人,让我们编程也能拥有命令行工具那样简单的体验。

无论是 Promise 接口,还是 Stream 接口,都有它最合适的场景,你会选择了吗?

到此,你拥有的压缩和解压缩能力,你能够做什么样的服务和功能呢?

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Extjs中ComboBox加载并赋初值的实现方法
Mar 22 Javascript
javascript控制swfObject应用介绍
Nov 29 Javascript
ECMAScript 5中的属性描述符详解
Mar 02 Javascript
基于javascript如何传递特殊字符
Nov 30 Javascript
JavaScript实现图片懒加载(Lazyload)
Nov 28 Javascript
jQuery中过滤器的基本用法示例
Oct 11 jQuery
使用express搭建一个简单的查询服务器的方法
Feb 09 Javascript
vue解决一个方法同时发送多个请求的问题
Sep 25 Javascript
js 获取本周、上周、本月、上月、本季度、上季度的开始结束日期
Feb 01 Javascript
vscode调试node.js的实现方法
Mar 22 Javascript
vue商城中商品“筛选器”功能的实现代码
Jul 01 Javascript
vue打包通过image-webpack-loader插件对图片压缩优化操作
Nov 12 Javascript
Vue打包后出现一些map文件的解决方法
Feb 13 #Javascript
nginx部署访问vue-cli搭建的项目的方法
Feb 12 #Javascript
vue2.0实现前端星星评分功能组件实例代码
Feb 12 #Javascript
nginx+vue.js实现前后端分离的示例代码
Feb 12 #Javascript
Vue+webpack项目基础配置教程
Feb 12 #Javascript
详解vue-admin和后端(flask)分离结合的例子
Feb 12 #Javascript
jquery+css3实现熊猫tv导航代码分享
Feb 12 #jQuery
You might like
php 三维饼图的实现代码
2008/09/28 PHP
PHP使用静态方法的几个注意事项
2014/09/16 PHP
PHP中session跨子域的三种实现方法
2016/07/25 PHP
thinkphp中AJAX返回ajaxReturn()方法分析
2016/12/06 PHP
PHP智能识别收货地址信息实例
2019/01/05 PHP
Thinkphp 框架扩展之驱动扩展实例分析
2020/04/27 PHP
一个用js实现控制台控件的代码
2007/09/04 Javascript
javascript form 验证函数 弹出对话框形式
2009/06/23 Javascript
jQuery中replaceAll()方法用法实例
2015/01/16 Javascript
iscroll.js的上拉下拉刷新时无法回弹的解决方法
2016/02/18 Javascript
AngularJS 基础ng-class-even指令用法
2016/08/01 Javascript
js实现各种复制到剪贴板的方法(分享)
2016/10/27 Javascript
vue.js实现请求数据的方法示例
2017/02/07 Javascript
js 转义字符及URI编码详解
2017/02/28 Javascript
vue.js指令和组件详细介绍及实例
2017/04/06 Javascript
关于vue.extend和vue.component的区别浅析
2017/08/16 Javascript
详解vue-cli官方脚手架配置
2018/07/20 Javascript
微信小程序实现图片选择并预览功能
2019/07/25 Javascript
小谈angular ng deploy的实现
2020/04/07 Javascript
Python 字符串操作实现代码(截取/替换/查找/分割)
2013/06/08 Python
Python采用Django制作简易的知乎日报API
2016/08/03 Python
django上传图片并生成缩略图方法示例
2017/12/11 Python
对python程序内存泄漏调试的记录
2018/06/11 Python
浅谈Django2.0 加xadmin踩的坑
2019/11/15 Python
Django-xadmin+rule对象级权限的实现方式
2020/03/30 Python
Keras 实现加载预训练模型并冻结网络的层
2020/06/15 Python
Python DES加密实现原理及实例解析
2020/07/17 Python
Python制作运行进度条的实现效果(代码运行不无聊)
2021/02/24 Python
HTML5的结构和语义(3):语义性的块级元素
2008/10/17 HTML / CSS
英国奢侈品网站:MatchesFashion
2016/12/16 全球购物
Steiff台湾官网:德国金耳釦泰迪熊
2019/12/26 全球购物
校园摄影活动策划方案
2014/02/05 职场文书
超市商业计划书
2014/05/04 职场文书
产品销售计划书
2014/05/04 职场文书
酒店管理专业毕业生自我鉴定
2014/09/29 职场文书
公司与个人合作协议书
2016/03/19 职场文书