1.Stream使用介绍
WritableStream[event]
"close","drain","error","finish","pipe","unpipe"
可写流中的"drain"事件触发时机:如果调用stream.write(chunk)方法返回false,流将在适当的时机触发"drain"事件。这时才可以继续想流中写入数据。
//向可写流中写入数据一百万次
function writeMillionTimes(writer,data,encoding,callback){
let i =1000000;
write();
function write(){
var ok = true;
//只要100万次没有执行完成,或者可以吸入是true的状态,就一直执行这个循环
do{
i--;
if(i===0){
//最后一次写入数据,需要调用回调函数
writer.write(data,encoding,callback);
}else{
//检查是否可以继续写入,这里不要传递callback。因为还没有结束
ok = writer.write(data,encoding);
}
}while(i>0 && ok);
//执行到这里来了,要么是执行结束了,要么是被流量太大给吓到了
if(i >0){
//这里就是提前停下的处理路径
//"drain"事件触发后才可以继续写入
writer.once("drain",write);//当触发"drain"事件后,写入空了,可以继续写入数据了。
}
}
}
ReadableStream[event]
"close","data","error","end","error","readable"
可读流中的"data"事件触发时机:[流将数据传递给消费者时触发:1.切换到flowing模式时触发[.pipe(),.resume()];2.调用reabale.read()方法时触发]
可读流中的"end"事件触发时机:流中再米有数据可供消费是触发
可读流中的"readable"事件触发时机:流中有数据可供读取时触发[读取到达流的尾部时,也会触发"readble"事件]
可读流中的"close"事件触发时机:流或其底层资源关闭后触发,触发"close"后将不会再触发任何事件
主要的方法介绍:
readable.pause();将会使flowing模式的流停止触发"data"事件。退出flowing模式,任何可用的数据将会保存在内部缓存中
const readble = getReadableStreamSomehow();
readable.on("data",(chunk)=>{
console.log(`Received ${chunk.length} bytes of data`);
readable.pause();
console.log("There will be no additional data for 1 second");
setTimeout(()=>{
console.log("Now data will start flowing again");
readable.resume();
},1000);
});
readable.pipe(destination[,options]);将可写流自动切换到flowing模式,数据流有自动限速的功能。就是把可读的数据流,写到,可写的数据流中去。
const readable = getReadableStreamSomehow();
const writable = getWritableStreamSomehow("file.txt");
//readable中的数据传给了file.txt文件
readable.pipe(writable);
默认情况下,源数据流触发end时,目标数据流也会触发end。我们可以在options中配置end为false
readable.read([size]); 根据size选项返回数据内容。如果没有size选项将会返回所有的内容。
总体介绍:Streams can be readable, writable, or both. All streams are instances of EventEmitter.
引入模块:const stream = require("stream");
-Readable可读流
-Writable可写流
-Duplex可读写的流
-Transform在读写过程中可以修改和变换数据的流
①所有创建的流对象只能操作strings和Buffer对象
-可写流通过反复调用writeable.write(chunk)方法将数据放到缓存中。
当内部可写缓存总大小<highWaterMark的时候,调用返回的是true
当内部缓存的大小达到或超过highWaterMark时,调用返回的是false
-stream的关键目标是,限制缓存数据的大小,匹配读写速度。
②可写流的举例Writable
-HTTP requests on the client
-HTTP responses on the server
-fs write streams
-zlib streams
-crypto streams
-TCP sockets
-child process stdin
-process.stdout,process.stderr
所有的可写流基本使用模式如下:
const myStream = getWriteStreamSomehow();
myStream.write("some data");
myStream.write("some more data");
myStream.end("done writing data");
③class stream.Writable
-"close" event将在底层资源关闭后触发
-"drain" event如果调用stream.write(chunk)返回false时触发
-"error" event当写入数据出错或者使用管道出错时触发。此时回调函数仅接收一个Error对象,此时需要自己手动关闭流
-"finish" event在stream.end()后触发事件[缓冲区的数据传给了底层系统后]
-"pipe" event可读流上调用stream.pipe()方法,并在目标流向中添加当前可写流时触发
const writer = getWriteableStreamSomehow();
const reader = getReadableStreamSomehow();
writer.on("pipe",(src)=>{
console.error("something is piping into the writer");
assert.equal(src,reader);
});
write.on("unpipe",(src)=>{
console.error("something is unpiping into the writer");
assert(src,reader);
});
reader.pipe(writer);
reader.unpipe(writer);
-writable.end();表明接下来没有数据要被写入Writable。如果有回调函数,将作为"finish"事件的回调函数
注意,在调用stream.end()方法后,再调用stream.write()方法将会导致出错
-writable.write(chunk);返回的是boolean。向流中写入数据,数据处理完后再调用callback
④可读流的举例Readable
-HTTP responses on the client
-HTTP request on the server
-fs read streams
-zib streams
-crypto streams
-TCP sockets
-child process stdout and stderr
-process.stdin
可读流有两种模式:
-flowing模式,可读流自动从系统底层读取数据
-paused模式,需要显示调用stream.read()从流中读取数据片段
两种模式的相互转换
-paused->flowing模式,"data"event,stream.resume(),stream.pipe()
-flowing->paused模式,stream.pause(),stream.unpipe()
⑤class stream.Readble
-"close" event 底层资源关闭后触发
-"data" event
-"end" event 流中没有可以消费的数据时触发
-"error" event监听错误
-"readable" event流中有数据可供读取时触发
const readable = getReadableStreamSomehow();
readable.on("data",(chunk)=>{
console.log(`Received ${chunk.length} bytes of data`);
});
readable.on("end",()=>{
console.log("There will be no more data");
});
-readable.pipe(destination[,options]);将一个Writable流绑定到readable上。
const readable = getReadleStreamSomehow();
const writable = fs.createWriteStream("file.txt");
//readable中的所有数据都传给"file.txt"
readable.pipe(writable);
可以在单个可读流上绑定多个可写流,进行链式管道操作
const r = fs.createReadStream("file.txt");
cosnt z = zlib.createGzip();
const w = fs.createWriteStream("file.txt.gz");
r.pipe(z).pipe(w);
值得注意的是,源可读流触发"end"事件时,目标流也会调用stream.end()方法。可在option中指定end为false
-readable.read([size]) size选项指定了需要读取多少数据,不指定size将会返回所有内容
const readable = getReadableStreamSomehow();
readable.on("readable",()=>{
var chunk;
while(null !==(chunk = readable.read())){
console.log(`Received ${chunk.length} bytes of data`);
}
});
readble.setEncoding(encoding);//设置可读流的编码
2.FS的使用介绍
fs.ReadStream[继承自Readable Stream]
"open" event,当可读流文件被打开的时候
"close" event,文件描述符被关闭的时候,fs.close()被调用
readStream.bytesRead,属性,已经读取的字节数
readStream.path,正在读取流的路径,是fs.createReadStream()的第一个参数,可能是Buffer或者string
fs.WriteStram[继承自WriteStream]
"open" event,当可写流被打开的时候触发,参数是fd文件描述符
"close" event,文件描述符被关闭的时候,fs.close()被调用
writeStream.bytesWritten,已写入的字节数
writeStream.path,正在写入流的路径,fs.createWriteStream()的第一个参数,可能是Buffer或者string
fs.createReadStream(path[,options]);返回一个新建的readStream对象,可读流的highWaterMark是64kb
options{
flags:"r",
encoding:null,
fd:null,//文件描述符被指定的时候,将会忽略path指定的内容,不会触发"open"事件
mode:0o666,
autoClose:true,//如果为false文件描述符不会被关闭,即使发生error
start:10,//如果不指定,从当前文件位置按照顺序读取
end:19
}
fs.createWriteStream(path[,options]);返回一个新建的WriteStream对象
options{
flags:"w",//修改一个文件而不是覆盖他,需要使用r+
defaultEncoding:"utf8",
fd:null,//同createReadStream
mode:0o666,
autoClose:true
}
fs.open(path,flags[,mode],callback);异步的打开文件
callback(err,fd);
"r" 读, 不存在报错
"r+" 读写,不存在报错
"rs+" 同步读写打开文件
"w" 写, 不存在就创建,存在则覆盖
"w+" 读写,不存在就创建,存在则覆盖
"wx" 写, 不存在报错
"wx+" 读写,不存在报错
"a" 追加,不存在就创建
"ax" 追加,不存在报错
"a+" 读写追加,不存在就创建
"ax+" 读写追加,不存在报错
总结,+读写的;x不存在就报错
fs.read(fd,buffer,offset,length,position,callback);从fd指定的文件中读取数据到buffer中
buffer 被写入的buffer
offset Buffer中开始写入的偏移量
length 指定要读取的字节数
position从文件中开始读取的位置
fs.write(fd,buffer,offset,length[,position],callback);把buffer中的内容写到fd中去
fs.readFile(file[,options],callback);异步的读取一个文件的全部内容
//如果编码未指定,返回的原始buffer
fs.readFile("/etc/password",(err,data)=>{
if(err) throw err;
console.log(data);
})
fs.writeFile(file,data[,options],callback);异步的写入数据到文件,如果文件已经存在则覆盖
fs.writeFile("message.txt","hello Node.js","utf8",(err)=>{
if(err) throw err;
console.log("It\'s saved!");
})
3.Buffer使用介绍
Buffer类的实例类似于整数数组,注意:Buffer大小是固定的[不能调整大小],且在V8堆外分配物理内存
const buf1 = Buffer.alloc(10);//创建一个长度为10,且填充为0的Buffer
const buf2 = Buffer.alloc(10,1);//创建一个长度为10,且用0x1填充的Buffer
const buf3 = Buffer.allocUnsafe(10);//创建一个长度为10的Buffer。速度快但是buf种可能有旧数据
const buf4 = Buffer.from([1,2,3]);//创建一个包含[0x1,0x2,0x3]的Buffer
const buf5 = Buffer.from("test");//创建一个包含ASCII字节数组[0x74,...]的Buffer
const buf6 = Buffer.from("test","utf8");//创建一个包含UTF8的字节数组
Buffer支持的编码
"ascii"
"utf8"
"utf16le"
"ucs2"
"base64"
"latin1"
"binary"
"hex"
Buffer实例可以通过for ..of语法进行遍历
const buf = Buffer.from([1,2,3]);
for(var b of buf){
console.log(b);//输出1,2,3
}
Buffer.alloc(size[,fill[,endcoding]]);//size指新建Buffer大小,fill默认填充0,encoding默认编码utf8
注意:size必须小于buffer.kMaxLength的值,否则将会抛出RangeError错误。如果size小于或等于0,将创建一个长度为0的Buffer
如果指定了fill,name会自动调用buf.fill(fill);初始化分配的Buffer
Buffer.allocUnsafe(size);//分配一个没有用0填充的Buffer
Buffer.byteLength(string[,encoding]);//string为要计算长度的值,encoding默认为utf8,返回string包含的字节数
Buffer.compare(buf1,buf2);//比较buf1和buf2,通常用于BUffer实例数组的排序
const buf1 = Buffer.from("1234");
const buf2 = Buffer.from("0123");
cosnt arr = [buf1,buf2];
console.log(arr.sort(Buffer.compare)); //输出[ <Buffer 30 31 32 33>, <Buffer 31 32 33 34> ]
Buffer.concat(list[,totalLength]);//返回一个合并后的Buffer
const buf1 = Buffer.alloc(10);
const buf2 = Buffer.alloc(14);
const buf3 = Buffer.alloc(18);
const totalLength = buf1.length +buf2.length +buf3.length;
const bufA = Buffer.concat([buf1,buf2,buf3],totalLength);
Buffer.from(array);通过数组创建Buffer
buf[index] 通过索引获取buf中的内容
buf.toJSON();返回buf的JOSN格式,当字符串化一个Buffer实例时,JSON.stringify()会隐式地调用该函数
buf.values();返回一个包含buf值的迭代器,当Buffer使用for..of时会自动调用该函数
cosnt buf = Buffer.from("buffer");
for(var value of buf.values()){
console.log(value);
}
for(var value of buf){
console.log(value);
}
buf.write(string[,offset[,length]][,endcoding]);//把string写入到buf中去,length为写入到buf中的string长度。
4.Events使用介绍
某些对象[触发器]会周期性地触发命名事件来调用函数对象[监听器]
const EventEmiter = require("events");
class MyEmitter extends EventEmiter{};
const myEmitter = new MyEmitter();
myEmitter.on("abc",(a,b)=>{
console.log("发生了一个事件");
console.log(a,b,this);//这里的this并不是指向EventEmitter的实例,this的打印内容为{}
});
myEmitter.emit("abc",10,20);
const EventEmiter = require("events");
class MyEmitter extends EventEmiter{};
const myEmitter = new MyEmitter();
myEmitter.on("abc",function(a,b){
console.log("发生了一个事件");
console.log(a,b,this);//这里的this是指向MyEmitter的实例的,打印MyEmitter{...}
});
myEmitter.emit("abc",10,20);
如果EventEmiter中没有对"error"事件进行监听,如果特殊情况触发"error"将会导致大一堆栈,且退出node.js进程
为了防止上述错误产生,可以在process对象的UNcaughtException事件上注册监听器
const EventEmiter = require("events");
class MyEmitter extends EventEmiter{};
const myEmitter = new MyEmitter();
process.on("uncaughtException",(err)=>{
console.log("有错误");
})
myEmitter.emit("error",new Error("Whoops!"));//打印有错误
最佳的做法还是,始终未error事件注册监听器
const EventEmiter = require("events");
class MyEmitter extends EventEmiter{};
const myEmitter = new MyEmitter();
myEmitter.on('error', (err) => {
console.log('有错误');
});
myEmitter.emit('error', new Error('whoops!'));// 打印: 有错误
5.有个例子可以看下
var fs = require('fs'),
path = require('path'),
out = process.stdout;
var filePath = './a.txt';
var readStream = fs.createReadStream(filePath);
var writeStream = fs.createWriteStream('./b.txt');
var stat = fs.statSync(filePath);
var totalSize = stat.size;
var passedLength = 0;
var lastSize = 0;
var startTime = Date.now();
readStream.on('data', function(chunk) {
passedLength += chunk.length;
if (writeStream.write(chunk) === false) {
readStream.pause();
}
});
readStream.on('end', function() {
writeStream.end();
});
writeStream.on('drain', function() {
readStream.resume();
});
setTimeout(function show() {
var percent = Math.ceil((passedLength / totalSize) * 100);
var size = Math.ceil(passedLength / 1000000);
var diff = size - lastSize;
lastSize = size;
console.log('已完成' + size + 'MB, ' + percent + '%, 速度:' + diff * 2 + 'MB/s');
if (passedLength < totalSize) {
setTimeout(show, 500);
} else {
var endTime = Date.now();
console.log();
console.log('共用时:' + (endTime - startTime) / 1000 + '秒。');
}
}, 500);