nodejs篇 stream模块

前言

在这里插入图片描述

”数据流“(stream)是处理系统缓存的一种方式。操作系统采用数据块(chunk)的方式读取数据,每收到一次数据,就存入缓存。Node应用程序有两种缓存的处理方式,第一种是等到所有数据接收完毕,一次性从缓存读取,这就是传统的读取文件的方式;第二种是采用“数据流”的方式,收到一块数据,就读取一块,即在数据还没有接收完成时,就开始处理它。

第一种方式先将数据全部读入内存,然后处理,优点是符合直觉,流程非常自然,缺点是如果遇到大文件,要花很长时间,才能进入数据处理的步骤。第二种方式每次只读入数据的一小块,像“流水”一样,每当系统读入了一小块数据,就会触发一个事件,发出“新数据块”的信号。应用程序只要监听这个事件,就能掌握数据读取的进展,做出相应处理,这样就提高了程序的性能。

nodejs中,很多IO处理的对象,都部署了Steam接口,比如:

  1. 文件读写
  2. HTTP 请求的读写
  3. TCP 连接
  4. 标准输入输出

而stream的接口我们可以分三类,可读数据流,可写数据流,双向数据流。

可数数据流

Readable

var Readable = require('stream').Readable;
var rs = new Readable();
rs.push('beep ');
rs.push('boop\n');
rs.push(null);

rs.pipe(process.stdout); // beep boop

可读数据流有两种状态,流动态和暂停态。
流动态就是数据开始传送,暂停态就是数据暂停传送。

四种方法可以让暂停态转为流动态

1.添加data事件的监听函数

var fs = require('fs');
var readableStream = fs.createReadStream('url.js');
var data = '';

readableStream.setEncoding('utf8'); // 还处于暂停态

readableStream.on('data', function(chunk) {
    
    
  data+=chunk;
}); // 从暂停态进入流动态,data开始被赋值

readableStream.on('end', function() {
    
    
  console.log(data);
});

2.调用resume方法

var fs = require('fs');
var readable = fs.createReadStream('url.js'); // 暂停态
readable.resume(); // 强制从暂停态进入流动态
readable.on('end', function(chunk) {
    
    
  console.log('数据流到达尾部,未读取任务数据');
});

3.调用pipe方法将数据送往一个可写数据流

var fs = require("fs");
fs.createReadStream("url.js").pipe(process.stdout);

4.显式调用stream.read()

var fs = require('fs');
var readableStream = fs.createReadStream('url.js');
var data = '';
var chunk;

readableStream.setEncoding('utf8');

// readable事件表示系统缓冲之中有可读的数据,使用read方法去读出数据。如果没有数据可读,read方法会返回null。
readableStream.on('readable', function() {
    
    
  while ((chunk=readableStream.read()) !== null) {
    
    
    data += chunk;
  }
});

readableStream.on('end', function() {
    
    
  console.log(data)
});

两种方法可以让流动态转为暂停态

1.不存在pipe方法的目的地时,调用pause方法

var fs = require('fs');
var readableStream = fs.createReadStream('url.js');
readableStream.on('data', function(chunk) {
    
    
    console.log('读取%d字节的数据', chunk.length);
    readableStream.pause(); // 暂停了
    console.log('接下来的1秒内不读取数据');
    setTimeout(function() {
    
    
      console.log('数据恢复读取');
      readableStream.resume(); // 之后重新从暂停状态进入流动态
    }, 1000);
  });

2.存在pipe方法的目的地时,移除所有data事件的监听函数,并且调用unpipe方法,移除所有pipe方法的目的地

var fs = require('fs');
var readableStream = fs.createReadStream('url.js');
readableStream.pipe(process.stdout);
setTimeout(function() {
    
    
  console.log('停止写入url.js');
  readableStream.unpipe(process.stdout); // 移除管道后,无法继续传送数据流,进入暂停态
}, 0);

可读数据流的常用方法和属性

readable 属性
一个可读数据流的readable属性返回一个布尔值。如果数据流是一个可读数的据流,就返回true,否则返回false。

var fs = require('fs');
var readableStream = fs.createReadStream('url.js');
console.log(readableStream.readable) // true

read()
read方法从系统缓存读取并返回数据。如果读不到数据,则返回null。

var fs = require("fs");
var readableStream = fs.createReadStream("url.js");
readableStream.on("readable", function () {
    
    
  var chunk;
  while (null !== (chunk = readableStream.read())) {
    
    
    console.log("got %d bytes of data", chunk.length);
  }
});

_read()
可读数据流的_read方法,可以将数据放入可读数据流,但这个过程会持续,如果不加以阻止,将会死循环。

var Readable = require("stream").Readable;
var rs = Readable();
var count = 0;

rs._read = function () {
    
    
  if (count < 10) {
    
    
    rs.push(count + "dx ");
    count++;
  } else {
    
    
// null时,及默认为结束
    rs.push(null);
  }
};

rs.pipe(process.stdout); // 0dx 1dx 2dx 3dx 4dx 5dx 6dx 7dx 8dx 9dx

setEncoding()
调用该方法,会使得数据流返回指定编码的字符串,而不是缓存之中的二进制对象。比如,调用setEncoding(‘utf8’),数据流会返回UTF-8字符串,调用setEncoding(‘hex’),数据流会返回16进制的字符串。
setEncoding的参数是字符串的编码方法,比如utf8、ascii、base64、buffer、hex等

var Readable = require("stream").Readable;
var rs = Readable();
var count = 0;

rs._read = function () {
    
    
  if (count < 2) {
    
    
    rs.push(count + "dx ");
    count++;
  } else {
    
    
    // null时,及默认为结束
    rs.push(null);
  }
};
// rs.setEncoding('hex')
// rs.pipe(process.stdout); // 3064782031647820

rs.setEncoding('utf8')
rs.pipe(process.stdout); // 0dx 1dx

resume() 和pause()
resume方法会使得“可读数据流”继续释放data事件,即转为流动态。
pause方法使得流动态的数据流,停止释放data事件,转而进入暂停态。任何此时已经可以读到的数据,都将停留在系统缓存。

var fs = require('fs');
var readableStream = fs.createReadStream('url.js');
readableStream.on('data', function(chunk) {
    
    
    console.log('读取%d字节的数据', chunk.length);
    readableStream.pause(); // 暂停了
    console.log('接下来的1秒内不读取数据');
    setTimeout(function() {
    
    
      console.log('数据恢复读取');
      readableStream.resume(); // 之后重新从暂停状态进入流动态
    }, 1000);
  });

isPaused()
该方法返回一个布尔值,表示“可读数据流”被客户端手动暂停(即调用了pause方法)。

var readable = require("stream").Readable()
console.log(readable.isPaused())  // === false
readable.pause()
console.log(readable.isPaused()) // === true
readable.resume()
console.log(readable.isPaused()) // === false

pipe() 和 unpipe()
pipe方法是自动传送数据的机制,就像管道一样。它从“可读数据流”读出所有数据,将其写出指定的目的地。整个过程是自动的。

unpipe方法移除pipe方法指定的数据流目的地。如果没有参数,则移除所有的pipe方法目的地。如果有参数,则移除该参数指定的目的地。如果没有匹配参数的目的地,则不会产生任何效果。

通过下面这种方式可以复制url.js这个文件

var fs = require('fs');
var readableStream = fs.createReadStream('url.js');
var writableStream = fs.createWriteStream('url1.js');
// pipe方法必须在可读数据流上调用,它的参数必须是可写数据流。
readableStream.pipe(writableStream);

var fs = require('fs');
var zlib = require('zlib');

fs.createReadStream('url.js')
  .pipe(zlib.createGunzip())
  .pipe(fs.createWriteStream('url1.js'));

上面代码采用链式写法,先读取文件,然后进行压缩,最后输出。

var fs = require("fs");
var readable = fs.createReadStream("url.js");
var writable = fs.createWriteStream("url1.js");
readable.pipe(writable);
setTimeout(function () {
    
    
  console.log("停止写入url1.js");
  readable.unpipe(writable);
  console.log("手动关闭url1.js的写入数据流");
  writable.end();
}, 1);

可读数据流的事件监听

主要就是 readable data end close error 这五个事件

  1. readable事件在数据流能够向外提供数据时触发。
  2. 那些没有显式暂停的数据流,添加data事件监听函数,会将数据流切换到流动态,尽快向外提供数据。
  3. 无法再读取到数据时,会触发end事件。
  4. 数据源关闭时,close事件被触发。并不是所有的数据流都支持这个事件。
  5. 当读取数据发生错误时,error事件被触发。
    在之前的例子中,其实已经用到过readable data end,接下来验证一下close和error

close

var fs = require("fs");
var readable = fs.createReadStream("url.js");
readable.resume(); // 开始读取数据
readable.on("close", function () {
    
    
  console.log("数据源关闭"); // 当数据源被读取完毕,数据源关闭时触发
});

error

var fs = require("fs");
var readable = fs.createReadStream("url2.js"); // url2不存在,会报错
readable.resume(); // 开始读取数据
readable.on("error", function (error) {
    
    
  console.log(error); // no such file or directory, open 'D:\Codes\Test\url2.js'....
  console.log("url2不存在"); // url2不存在
});

可写数据流

  1. 客户端的http requests
http.request(options, (res) => {
    
    }
  1. 服务器的http responses
var server = http.createServer(function (req, res) {
    
    }
  1. fs write streams
var writableStream = fs.createWriteStream('file2.txt');
  1. zlib streams
zlib.createGzip()
  1. crypto streams
const crypto = require('crypto');
const cipher = crypto.createCipher('aes192', 'a password');

let encrypted = '';
cipher.on('readable', () => {
    
    
  const data = cipher.read();
  if (data)
    encrypted += data.toString('hex');
});
cipher.on('end', () => {
    
    
  console.log(encrypted);
  // Prints: ca981be48e90867604588e75d04feabb63cc007a8f8ad89b10616ed84d815504
});

cipher.write('some clear text data');
cipher.end();
  1. tcp sockets
const net = require('net');
const server = net.createServer((c) => {
    
    
  // 'connection' listener
  console.log('client connected');
  c.on('end', () => {
    
    
    console.log('client disconnected');
  });
  c.write('hello\r\n');
  c.pipe(c);
});
  1. child process stdin
  2. process.stdout, process.stderr
fs.createReadStream("url.js").pipe(process.stdout)

可写数据流的方法和属性

writable 属性
writable属性返回一个布尔值。如果数据流仍然打开,并且可写,就返回true,否则返回false。

var fs = require("fs");
var writableStream = fs.createWriteStream('url1.js');
console.log(writableStream.writable) // true

write()
它接受两个参数,一个是写入的内容,可以是字符串,也可以是一个stream对象(比如可读数据流)或buffer对象(表示二进制数据)
另一个是写入完成后的回调函数,它是可选的,当然第二个参数也可以传入编码方式的字符串。

var fs = require("fs");
var writableStream = fs.createWriteStream("url1.txt");
// writableStream.write("dx ", function () {
    
    
//   console.log("已经写入了dx");
// });
writableStream.write("dx1 ", "ascii");
writableStream.end("yx\n");

cork(),uncork()
cork方法可以强制等待写入的数据进入缓存。
当调用uncork方法或end方法时,缓存的数据就会吐出。

var fs = require("fs");
var stream = fs.createWriteStream("url1.txt");;
stream.cork();
stream.write('some ');
stream.write('data ');
process.nextTick(() => stream.uncork());

setDefaultEncoding()
setDefaultEncoding方法用于将写入的数据编码成新的格式。

var fs = require("fs");
var writableStream = fs.createWriteStream("url1.js");
writableStream.write("dx1 ");
writableStream.setDefaultEncoding("base64url");
writableStream.write("yx"); // yx将会以base64url的方式写入

end()
end方法用于终止“可写数据流”。该方法可以接受三个参数,全部都是可选参数。
第一个参数是最后所要写入的数据,可以是字符串,也可以是stream对象或buffer对象;
第二个参数是写入编码;
第三个参数是一个回调函数,finish事件发生时,会触发这个回调函数。

var fs = require("fs");
var writableStream = fs.createWriteStream("url1.js");
writableStream.write("dx1 ");
writableStream.end("yx1",'ascii',function() {
    
    
    console.log('写入结束了')
});

可写数据流的事件

drain事件
writable.write(chunk)返回false以后,当缓存数据全部写入完成,可以继续写入时,会触发drain事件,表示缓存空了。

var fs = require("fs");
var writableStream = fs.createWriteStream("url1.js");

function writeOneMillionTimes(writer, data, encoding, callback) {
    
    
    var i = 1000000;
    write();
    function write() {
    
    
      var ok = true;
      do {
    
    
        i -= 1;
        if (i === 0) {
    
    
          writer.write(data, encoding, callback);
        } else {
    
    
          ok = writer.write(data, encoding);
        }
      } while (i > 0 && ok);
      if (i > 0) {
    
    
        writer.once('drain',function(){
    
    
            console.log(i)
        });
      }
    }
  }
  writeOneMillionTimes(writableStream,'write1','utf-8',function(){
    
    
    // console.log()
  })

finish事件
调用end方法时,所有缓存的数据释放,触发finish事件。

var fs = require("fs");
var writableStream = fs.createWriteStream("url1.js");
writableStream.write("dx1 ");
writableStream.end("yx1",'ascii',function() {
    
    
    console.log('写入结束了')
});

writableStream.on('finish',function(){
    
    
    console.log('end调用了,完成了')
})

pipe事件 和 unpipe事件
这个很好理解,当有pipe()调用时,触发pipe事件监听,当有unpipe()调用时,触发unpipe事件监听。

var fs = require("fs");
var readable = fs.createReadStream("url.js");
var writable = fs.createWriteStream("url1.js");

writable.on("pipe", function () {
    
    
  console.log("pipe触发了");
});

writable.on("unpipe", function () {
    
    
  console.log("unpipe触发了");
});

readable.pipe(writable);
setTimeout(function () {
    
    
  console.log("停止写入url1.js");
  readable.unpipe(writable);
  console.log("手动关闭url1.js的写入数据流");
  writable.end();
}, 1);

error事件
事件在写入数据出错或者使用管道出错时触发。事件发生时,回调函数仅会接收到一个 Error 参数。

错误处理

需要额外调用一个包 on-finished
命令行安装

npm install on-finished

下面这样做,不仅能解决stream流本身发生错误
还能避免客户端中断下载后 写入的数据流就会收不到close事件,一直处于等待状态,造成内存泄漏的问题。

var onFinished = require("on-finished");
var http = require("http");

http.createServer(function (req, res) {
    
    
  // set the content headers
  var stream = fs.createReadStream("filename.txt");
  stream
    .on("error", onerror)
    .pipe(zlib.createGzip())
    .on("error", onerror)
    .pipe(res);

  function onerror(err) {
    
    
    console.error(err.stack);
    // 彻底关闭这次的stream
    stream.destroy();
  }

  onFinished(res, function () {
    
    
    // make sure the stream is always destroyed
    stream.destroy();
  });
});

nodejs相关其它内容

nodejs commonjs介绍
nodejs fs模块介绍
nodejs path模块介绍
nodejs events模块介绍
nodejs http模块介绍
nodejs net模块介绍
nodejs url模块介绍
nodejs process模块介绍
nodejs buffer模块介绍

猜你喜欢

转载自blog.csdn.net/glorydx/article/details/129165518