基于Node.js實(shí)現(xiàn)壓縮和解壓縮的方法
壓縮格式
zip 和 gzip 是兩種我們最常見(jiàn)到的壓縮格式,當(dāng)然,gzip 在 Windows 下很少有人接觸。
tar 是一種歸檔格式,它默認(rèn)不會(huì)壓縮,需要結(jié)合 gzip 來(lái)將最終的 tar 文件以 gzip 格式壓縮成為一個(gè) tar.gz 文件,通常我們會(huì)縮寫(xiě)為 tgz。
為什么沒(méi)有提到 rar?因?yàn)樗菍@Wo(hù)的算法,你可以免費(fèi)獲得解壓工具,而壓縮工具是需要付費(fèi)的。所以我們一般應(yīng)用場(chǎng)景下,很少會(huì)提供 rar 壓縮文件。
本文將分別介紹 gzip,tar,tgz 和 zip 的壓縮和解壓縮在 Node.js 下如何實(shí)現(xiàn)。
未壓縮文件庫(kù)
本文所使用的未壓縮文件庫(kù)來(lái)自于 urllib ,需要先 clone 它下來(lái)到指定目錄。
gzip
在 Linux 的世界,每個(gè)工具的職責(zé)會(huì)很純粹,非常單一,如 gzip,它只會(huì)對(duì)文件進(jìn)行壓縮,至于文件夾如何打包壓縮,跟它沒(méi)關(guān)系,那是 tar 要去負(fù)責(zé)的事情。
gzip 命令行壓縮一個(gè)文件
例如我們要將 nodejs-compressing-demo/lib/urllib.js 文件進(jìn)行 gzip 壓縮,會(huì)得到一個(gè) urllib.js.gz 文件,源文件會(huì)被刪除。
$ 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 字節(jié)減少到 8909 字節(jié),超過(guò) 3.5 倍的壓縮效果。
還可以通過(guò) pipe 方式,結(jié)合 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 實(shí)現(xiàn) gzip
當(dāng)然,我們不會(huì)真的從零開(kāi)始實(shí)現(xiàn)一個(gè) gzip 算法和工具,在 Node.js 的世界,早已有人為你準(zhǔn)備好這些基礎(chǔ)庫(kù),我們只需要開(kāi)箱即用。
本文將會(huì)使用 compressing 模塊,實(shí)現(xiàn)所有壓縮和解壓縮代碼。
為什么會(huì)選擇 compressing?因?yàn)樗凶銐虺浞值拇a質(zhì)量和單元測(cè)試保證,處于活躍的維護(hù)狀態(tài),API 非常友好,而且還支持流式接口。
Promise 接口
const compressing = require('compressing');
// 選擇 gzip 格式,然后調(diào)用 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);
});
// 解壓縮是反響過(guò)程,接口都統(tǒng)一為 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);
});
結(jié)合 async/await 的編程模型,代碼寫(xiě)起來(lái)就是一個(gè)普通的異步 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 模式編程,需要處理每個(gè) stream 的 error 事件,并且要手動(dòng)銷(xiāo)毀所有 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 的方向倒轉(zhuǎn)過(guò)來(lái)
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);
根據(jù)官方的Backpressuring in Streams 推薦,我們應(yīng)該使用 pump 模塊來(lái)配合 Stream 模式編程,由 pump 來(lái)完成這些 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 接口的優(yōu)勢(shì)
Stream 接口看起來(lái)比 Promise 接口復(fù)雜多了,為何還會(huì)有這種應(yīng)用場(chǎng)景呢?
其實(shí)在 HTTP 服務(wù)領(lǐng)域,Stream 模型會(huì)有更大的優(yōu)勢(shì),因?yàn)?HTTP 請(qǐng)求本身就是一個(gè) Request Stream,如要將一個(gè)上傳文件以 gzip 壓縮返回,使用 Stream 接口不需要將上傳文件保存到本地磁盤(pán),而是直接消費(fèi)這個(gè)文件流。
使用 egg 文件上傳的示例代碼 ,我們稍微改造一下,就能實(shí)現(xiàn) gzip 壓縮然后返回。
const pump = require('pump');
class UploadFormController extends Controller {
// ... other codes
async upload() {
const stream = await this.ctx.getFileStream();
// 直接將壓縮流賦值給 ctx.body,實(shí)現(xiàn)邊壓縮邊返回的流式響應(yīng)
this.ctx.body = pump(stream, new compressing.gzip.FileStream());
}
}
tar | gzip > tgz
gzip 章節(jié)可以提前知道,tar 是負(fù)責(zé)對(duì)文件夾進(jìn)行打包:package:的。
例如要對(duì) nodejs-compressing-dem o 整個(gè)文件夾打包成一個(gè)文件發(fā)送給別人,可以通過(guò) 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
如大家所見(jiàn),tar 打包出來(lái)的文件一般都比較大,因?yàn)樗俏磯嚎s的,大小跟實(shí)際文件夾總大小接近。所以我們都會(huì)在打包同時(shí)進(jìn)行壓縮。
$ 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 超過(guò) 5 倍大小的差異,可以大大減少網(wǎng)絡(luò)傳輸帶寬。
Node.js 實(shí)現(xiàn) tgz
Promise 接口
先使用 compressing.tar.compressDir(sourceDir, targetFile) 將一個(gè)文件夾打包到一個(gè) 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);
});
結(jié)合 async/await 的編程模型,代碼寫(xiě)起來(lái)會(huì)更加容易閱讀:
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 接口
通過(guò) compressing.tar.Stream 類(lèi),可以動(dòng)態(tài)添加任意文件、文件夾到一個(gè) tar stream 對(duì)象中,非常靈活。
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 其實(shí)可以看作是 tar + gzip 的「商業(yè)化」結(jié)合,它讓使用者不需要區(qū)分是壓縮文件還是壓縮文件夾,反正用我 zip 就對(duì)了。
使用 zip 命令行工具壓縮一個(gè)文件夾的例子:
$ 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
通過(guò) tgz 和 zip 文件大小對(duì)比,可以看出默認(rèn)的壓縮參數(shù)下,gzip 的效果會(huì)比 zip 好。
Node.js 實(shí)現(xiàn) zip
實(shí)現(xiàn)代碼跟 tar 類(lèi)似,只不過(guò)默認(rèn)是壓縮的,不需要再添加 gzip 的過(guò)程。
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);
});
總結(jié)
基于 Node.js 實(shí)現(xiàn)的壓縮和解壓縮是否比想象中簡(jiǎn)單?感謝 npm 這個(gè)巨人,讓我們編程也能擁有命令行工具那樣簡(jiǎn)單的體驗(yàn)。
無(wú)論是 Promise 接口,還是 Stream 接口,都有它最合適的場(chǎng)景,你會(huì)選擇了嗎?
到此,你擁有的壓縮和解壓縮能力,你能夠做什么樣的服務(wù)和功能呢?
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Node.js 多進(jìn)程處理CPU密集任務(wù)的實(shí)現(xiàn)
這篇文章主要介紹了Node.js 多進(jìn)程處理CPU密集任務(wù)的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05
使用nodeJS中的fs模塊對(duì)文件及目錄進(jìn)行讀寫(xiě),刪除,追加,等操作詳解
nodeJS中fs模塊對(duì)系統(tǒng)文件及目錄進(jìn)行讀寫(xiě)操作,本文將詳細(xì)介紹nodejs中的文件操作模塊fs的使用方法2020-02-02
node作為中間服務(wù)層如何發(fā)送請(qǐng)求(發(fā)送請(qǐng)求的實(shí)現(xiàn)方法詳解)
node作為中間服務(wù)層如何發(fā)送請(qǐng)求?下面小編就為大家分享一下發(fā)送請(qǐng)求的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助2018-01-01
詳解使用Nodejs內(nèi)置加密模塊實(shí)現(xiàn)對(duì)等加密與解密
這篇文章主要介紹了使用Nodejs內(nèi)置加密模塊實(shí)現(xiàn)對(duì)等加密與解密,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05
node.js實(shí)現(xiàn)微信JS-API封裝接口的示例代碼
這篇文章主要介紹了node.js實(shí)現(xiàn)微信JS-API封裝接口的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09
node.js基于socket.io快速實(shí)現(xiàn)一個(gè)實(shí)時(shí)通訊應(yīng)用
這篇文章主要介紹了node.js基于socket.io快速實(shí)現(xiàn)一個(gè)實(shí)時(shí)通訊應(yīng)用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
nodejs express搭建服務(wù)并熱更新文件過(guò)程詳解
這篇文章主要為大家介紹了nodejs express搭建服務(wù)并熱更新文件過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
Nodejs Stream 數(shù)據(jù)流使用手冊(cè)
這篇文章主要介紹了Nodejs Stream 數(shù)據(jù)流使用手冊(cè)的相關(guān)資料,感興趣的小伙伴一起學(xué)習(xí)吧2016-04-04
安裝node.js和npm的一些常見(jiàn)報(bào)錯(cuò)
NVM(Node?Version?Manager)是一個(gè)用于在同一機(jī)器上同時(shí)安裝并管理多個(gè)Node.js版本的工具,這篇文章主要給大家介紹了關(guān)于安裝node.js和npm的一些常見(jiàn)報(bào)錯(cuò),需要的朋友可以參考下2023-06-06

