接下来会介绍如何使用和创建自定义转换流来操纵文本、二进制或对象流数据
transform streams 是什么?
Node.js转换流是一种流,它读取输入数据,对其进行处理和操作,然后输出新数据。
你可以通过将一个流传递到另一个流中来链接流,从而创建复杂的处理过程
Node.js内置了多种transform streams
Node.js的核心API中自带了多种转换流:
- zlib - 用于gzip压缩和解压缩
- crypto - 用于加密、解密和计算消息摘要
使用gzip压缩流
要对流进行gzip压缩,只需使用zlib创建一个gzip转换流,并将流通过管道传输。您可以通过向zlib.createGzip(options)工厂传递选项对象来自定义压缩级别和缓冲区。有关详细信息,请参阅gzip选项。
下面的示例读取myfile.txt,即时压缩流数据,并将其写入myfile.txt.gz:
var fs = require('fs');
var zlib = require('zlib');
var gzip = zlib.createGzip();
var rstream = fs.createReadStream('myfile.txt');
var wstream = fs.createWriteStream('myfile.txt.gz');
rstream
.pipe(gzip)
.pipe(wstream)
.on('finish', function () {
console.log('done compressing');
});
解压gzip流
这个例子从前一个例子中读取被压缩的文件 myfile.txt.gz,对其进行解压缩,并将其输出到标准输出
var fs = require('fs');
var zlib = require('zlib');
var gunzip = zlib.createGunzip();
var rstream = fs.createReadStream('myfile.txt.gz');
rstream.pipe(gunzip).pipe(process.stdout);
自定义transform stream
在内置转换流不能满足需求的时候,通常希望对流执行自己的转换,因此Node.js使用Transform抽象类创建自定义转换流非常容易。通过使用一个叫做readable-stream的polyfill npm模块,我们可以使代码适用于较早版本的Node.js,我将在下面进行演示。
要实现此操作,请继承Transform并实现原型方法:
_transform(chunk,enc,cb) - 读取块并推送转换后的数据 _flush(cb)- 如果需要在输入完成后在末尾写入其他数据
创建转换流,将所有文本转换为大写
var stream = require('stream');
var util = require('util');
var Transform = stream.Transform || require('readable-stream').Transform;
function Upper(options) {
if (!(this instanceof Upper)) {
return new Upper(options);
}
Transform.call(this, options);
}
util.inherits(Upper, Transform);
Upper.prototype._transform = function (chunk, enc, cb) {
var upperChunk = chunk.toString().toUpperCase();
this.push(upperChunk);
cb();
};
// try it out
var upper = new Upper();
upper.pipe(process.stdout); // output to stdout
upper.write('hello world\n'); // input line 1
upper.write('another line'); // input line 2
upper.end(); // finish
创建过滤数据的对象流 可以使用 transform 流以非常相似的方式操纵对象流。 以下示例会从一系列对象流中过滤掉敏感属性:
var stream = require('stream');
var util = require('util');
// node v0.10+ use native Transform, else polyfill
var Transform = stream.Transform || require('readable-stream').Transform;
/*
* Filters an object stream properties
*
* @param filterProps array of props to filter
*/
function Filter(filterProps, options) {
// allow use without new
if (!(this instanceof Filter)) {
return new Filter(filterProps, options);
}
// init Transform
if (!options) options = {}; // ensure object
options.objectMode = true; // forcing object mode
Transform.call(this, options);
this.filterProps = filterProps;
}
util.inherits(Filter, Transform);
/* filter each object's sensitive properties */
Filter.prototype._transform = function (obj, enc, cb) {
var self = this;
// determine what keys to keep
var filteredKeys = Object.keys(obj).filter(function (key) {
// only those keys not in this list
return self.filterProps.indexOf(key) === -1;
});
// create clone with only these keys
var filteredObj = filteredKeys.reduce(function (accum, key) {
accum[key] = obj[key];
return accum;
}, {});
// push the filtered obj out
this.push(filteredObj);
cb();
};
// try it out, output to stdout
// filter phone and email from objects
var filter = new Filter(['phone', 'email']);
filter.on('readable', function () {
var obj;
while (null !== (obj = filter.read())) {
console.log(obj);
}
});
// now send some objects to filter through
filter.write({ name: 'Foo', phone: '555-1212', email: 'foo@foo.com', id: 123 });
filter.write({ name: 'Bar', phone: '555-1313', email: 'bar@bar.com', id: 456 });
filter.end(); // finish