在Node.js中,读取文件有两种方式,一种是使用fs.readFile,另一种是使用fs.createReadStream来读取。fs.readFile是每个Node.js用户最熟悉的,它简单易懂,易于使用。但它的缺点是会先把所有的数据读到内存中。一旦遇到大文件,这种方式读取效率很低。fs模块中几种文件读写方式的区别使用异步方式使用同步方式将文件完全读入缓冲区readFilereadFileSync将文件部分读入缓冲区readreadSync将数据完全写入文件writeFilewriteFileSync将写入部分写入缓冲区中的内容当文件writewriteSync使用readFile或readFileSync方法读取文件内容时,Nodejs首先将文件内容完全读入缓存区,然后再从缓存区读取内容。当使用writeFile方法或者writeFileSync方法写入文件内容时,Nodejs首先完成将文件内容读入缓冲区,然后一次性将缓冲区的内容写入文件。也就是说,当readFile方法或readFileSync方法读取文件内容或使用writeFIle或writeFileSync方法写入文件内容时,nodejs将文件视为一个整体。为其分配一个缓存区,一次性将文件内容读入缓存区。在此期间,nodejs将无法进行任何其他操作。而fs.createReadStream通过Stream来读取数据,它将文件(数据)分成小块,然后触发一些特定的事件。我们可以监听这些事件,编写具体的处理函数。与上面的方法相比,这种方法不太好用,但是效率很高。其实Stream在Node.js中不仅仅用于文件处理,在其他地方也能看到它的身影,比如process.stdin/stdout、http、tcpsockets、zlib、crypto等。特性基于事件的通信,可以通过管道连接流的类型ReadableStreamReadableStreamWriteableStreamWriteableStreamDuplexStream双向数据流,可以同时读写TransformStream变换流,可读,可写,可变换)数据object模式通过NodeAPI创建的流只能对string或buffer对象进行操作。但实际上,流的实现可以基于其他Javascript类型(null除外,它在流中有特殊含义)。这样流就处于“对象模式(objectMode)”。创建流对象时,可以通过提供objectMode参数来生成对象模式流。尝试将现有流转换为对象模式是不安全的。BufferNode.js中stream的buffer,开头是以C语言复制文件的代码为模板讨论的。(抛开异步的区别),数据是从src读取到buf,并没有直接写入dest,而是先放在一个比较大的buffer中,等待写入(消费)到dest中。即借助缓冲区,可以将读写的过程分开。Readable和Writable流都会在内部存储数据缓冲区。可以分别通过writable._writableState.getBuffer()和readable._readableState.buffer访问缓冲区。缓冲区的大小在构造流时由highWaterMark标志指定。对于objectMode的流,这个标志表示可以容纳的对象数量。当可读实例调用stream.push()方法时,数据将被推入缓冲区。如果数据没有被消费,调用stream.read()方法读取的话,数据会一直留在buffer队列中。当缓冲区中的数据达到highWaterMark指定的阈值时,可读流将停止从底层绘制数据,直到成功消费当前缓冲的报告。可写流在可写实例上连续调用writable.write(chunk)时,会将数据写入可写流的缓冲区。如果当前buffer的缓冲数据量低于highWaterMark设置的值,调用writable.write()方法会返回true(表示数据已经写入buffer),否则当buffered数据量达到阈值时,数据无法写入buffer。write方法将返回false,直到触发drain事件才能调用write。//将数据写入提供的可写流100万次。//注意背压。functionwriteOneMillionTimes(writer,data,编码,回调){让我=1000000;写();函数写入(){varok=true;做{我--;if(i===0){//最后一次!writer.write(数据,编码,回调);}else{//看看我们是否应该继续,或者等待//不要传递回调,因为我们还没有完成。ok=writer.write(数据,编码);}}while(i>0&&ok);if(i>0){//必须提前停止!//一旦耗尽就再写一些writer.once('drain',write);}}}Duplex和TransformDuplexstream和Transformstream是同时可读可写的,它们内部会维护两个buffer,分别对应读和写,这样可以让双方同时独立操作,维护一个高效的数据流动。比如net.Socket是一个Duplex流,Readable端允许从socket中获取和消费数据,Writable端最后允许将数据写入套接字。数据写入的速度很可能与消费的速度不同,所以两端能够独立操作和缓冲是非常重要的。pipestream的.pipe()附加了一个可写流到可读流上,同时将可写流切换为流模式,将所有数据推送到可写流中。在管道传输数据的过程中,objectMode是传递引用,non-objectMode是复制一份数据传递下去。pipe方法的主要目的是将数据流缓冲到一个可接受的水平,这样不同速度的数据源之间的差异就不会导致内存被占满。pipe的实现可以参考DavidCai的通过源码分析Node.jspipe的实现下面是一个pipe的简单例子:'usestrict'import{createReadStream,createWriteStream}from'fs'cre??ateReadStream('/path/to/a/big/file').pipe(createWriteStream('/path/to/the/dest'))我们可以将pipe方法的主要功能分解为:从源可读流中不断获取指定长度的数据并将获取的数据写入目标可写流。平衡读取和写入速度,以防止在读取速度大大超过写入速度时出现大量延迟数据。事件可读数据流的事件可读在数据流出时触发数据。对于那些没有显式暂停的数据流,添加数据事件监听函数,将数据流切换为流动态,尽快对外提供数据。end当数据被读取时触发。注意不要和writeableStream.end()混淆,writeableStream没有end事件,只有.end()方法close在数据源关闭时触发错误,在数据源关闭时触发可写数据流的事件读取数据时发生errordrainwritable.write(chunk)返回false后,所有缓存写入完成,可重写时触发。调用.end方法时,会释放所有缓存的数据,类似于可读数据流中的end事件,表示写入过程结束。当pipetarget作为unpipetarget时触发unpipetriggererrortriggercopyvideocode写数据时出错varfs=require('fs')varreadStream=fs.createReadStream('264.mp4')varwriteStream=fs.createWriteStream('The.Big.Bang.Theory.S10E07.mp4')readStream.on('data',function(chunk){if(writeStream.write(chunk)===false){console.log('仍然缓存');readStream.pause()}})readStream.on('end',function(chunk){writeStream.end()})writeStream.on('drain',function(){console.log('数据流失');readStream.resume()})varhttp=require('http')varfs=require('fs')http.createServer(function(req,res){//fs.readFile('./buffer/logo.png',function(err,res){//if(err){//res.end('文件不存在')//}//else{//res.writeHeader(200,{'Context-Type':'text/html'})//res.end(data)//}//})fs.createReadStream('../buffer/logo.png').pipe(res)}).listen(8090)在线爬取获取图片可以这样:varhttp=require('http')varfs=require('fs')varrequest=require('request')http.createServer(function(req,res){//fs.readFile('./buffer/logo.png',function(err,res){//if(err){//res.end('文件不存在')//}//else{//res.writeHeader(200,{'Context-Type':'text/html'})//res.end(data)//}//})//fs.createReadStream('../buffer/logo.png').pipe(res)request('https://www.baidu.com/img/bd_logo1.png').pipe(res)}).listen(8090)然后安装请求模块npminstallrequest和pipe(),我们可以重构上面复制视频的代码varfs=require('fs')fs.createReadStream('264.mp4').pipe(fs.createWriteStream('The.Big.Bang.Theory.S10E07.mp4'))总结:可读流它负责读取web数据,将数据缓存到内部Buffer数组中可写流负责消费数据,从可读流中获取数据,然后对获取到的chunk数据块进行处理。至于如何处理,则要看可写流write()方法说明如下:newReadable()varwriteStream=newWritable()readStream.push('I')readStream.push('Love')readStream.push('jxdxsw.com\n')readStream.push(null)writeStream._write=function(块,编码,回调){console.log(chunk.toString());callback()}readStream.pipe(writeStream)varstream=require('stream')varutil=require('util')functionReadStream(){stream.Readable.call(this)}util.inherits(ReadStream,流。可读)ReadStream.prototype._read=function(){this.push('I')this.push('Love')this.push('jxdxsw.com\n')this.push(null)}functionWriteStream(){stream.Writable.call(this)this._cached=newBuffer('')}util.inherits(WriteStream,stream.Writable)WriteStream.prototype._write=function(chunk,encode,callback){console.log(chunk.toString());callback()}functionTransformStream(){stream.Transform.call(this)}util.inherits(TransformStream,stream.Transform)TransformStream.prototype._transform=function(chunk,encode,callback){this.push(chunk)回调()}TransformStream.prototype._flush=function(callback){this.push('天哪!')callback()}varrs=newReadStream()varws=newWriteStream()varts=newTransformStreamrs.pipe(ts).pipe(ws)拓展module.exports=(stream,throwError)=>{returnnewPromise((resolve,reject)=>{if(stream._readableState&&stream._readableState.ended){returnresolve();}if(!stream.readable||stream.destroyed){returnresolve();}stream.resume();functioncleanup(){stream.removeListener('end',onEnd);stream.removeListener('close',onEnd);stream.removeListener('close',onError);}functiononEnd(){cleanup();解决();}functiononError(err){cleanup();//默认不抛出错误if(throwError){reject(err);}else{解决();}}stream.on('end',onEnd);stream.on('关闭',onEnd);stream.on('错误',onError);});};上面代码是封装stream-wormhole实现的,更多用法可以看这里egg-multipartcommonnpmmodulethoughvarthrough=require('through');vartr=through(写,结束);functionwrite(buf){this.queue(buf.toString().toUpperCase())}functionend(){}process.stdin.pipe(tr).pipe(process.stdout);参考Node.js:fs.readFile/writeFile和fs.createReadStream/writeStream区分利用StreamAPI攻击Node.js基础(二)https://github.com/ElemeFE/no...
