使用NodeJS transform stream 转换数据

1,240 阅读3分钟

接下来会介绍如何使用和创建自定义转换流来操纵文本、二进制或对象流数据

transform streams 是什么?

Node.js转换流是一种流,它读取输入数据,对其进行处理和操作,然后输出新数据。

你可以通过将一个流传递到另一个流中来链接流,从而创建复杂的处理过程

transform-stream graphic

double-transform-stream graphic

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