node:文件流式操作

node:文件流式操作 为什么需要流式操作传统的文件操作比如fs.readFile读取文件会先准备一块足够大的地方一次性读入文件当文件体积小的时候尚可但对于单文件操作来说就需要流式操作。流式操作不是一次行读入所有数据而是想传送带一样一块一块地传输。它可以分为可读流(ReadableStream)与写入流(WriteableStrea)也可以看作数据的生产者与消费者。可读流可读流就像一个中转站它一边读取文件一边将它运输到目的地。它有两种模式流动模式数据自动从底层系统读取并通过事件尽快提供给应用程序像是打开水龙头水一下子流出来暂停模式消费者必须显示调用strea.read()方法从流中读取数据像是抽水泵你按一下它出一下水常见的可读流有fs.createReadStream()读取本地文件http.IncomingMessage服务器收到的req对象process.stdin标准的输入zlib压缩流可读流的操作事件监听操作constfsrequire(fs);constreadStreamfs.createReadStream(./圣墟.txt,{encoding:utf-8});readStream.on(data,(chunk){console.log(读取到了数据长度为,chunk.length,内容);});readStream.on(end,(){console.log(读取完成);});readStream.on(error,(err){console.log(读取错误,err);});我们读取小说这个文件从输出可以看出是分块读取的可读流有个关键参数HighWaterWork决定了流在停止读取底层资源之前能在内部缓冲区里缓存多少数据这设计到了背压机制nodejs为了防止消费者消费慢生成者生产快而导致内存溢出的保护机制通常默认是64KB。可写流可写流是数据的目的地它接受来自可读流的数据块然后将其写入底层设备如硬盘、网络 Socket、控制台等。常见可写流有fs.createWriteStream()文件写入http.ServerResponseweb开发中的res对象把数据发回给浏览器process.stdout标准输出zlib.createGzip()压缩流crypto.createHash()加密流fs.createWriteStream的操作举例来说constfsrequire(fs);constreadStreamfs.createReadStream(./圣墟.txt,{encoding:utf-8});constwriteStreamfs.createWriteStream(./copy.txt,{encoding:utf-8});readStream.on(data,(chunk){// console.log(读取到了数据长度为, chunk.length, 内容);writeStream.write(chunk);});writeStream.on(finish,(){console.log(所有内容写入到copy.txt文件);});可写流有两个关键状态:write()的返回值如果返回true说明缓冲区还没满你可以继续疯狂写入。如果返回false说明缓冲区满了超过了highWaterMark你应该停下来等待drain事件。drain事件当缓冲区的数据被成功处理掉、可以重新接收新数据时会触发这个事件。这是处理大批量数据而不撑爆内存的关键pipe pipelinepipe是node中非常重要的一个api它像一个水管连接可读流和可写流并且能自动处理背压防止数据撑爆内存。pipe连接可读流与可写流在使用时只需要可读流调用pipe方法传入一个可写流即可例如constfsrequire(fs);constreadStreamfs.createReadStream(./圣墟.txt,{encoding:utf-8});constwriteStreamfs.createWriteStream(./copy.txt,{encoding:utf-8});readStream.pipe(writeStream);而且pipe返回的是流你可以在中间加个过滤器比如读文件-Gzip压缩-传输给浏览器constzlibrequire(zlib);// ... 在 server 回调中res.writeHead(200,{Content-Encoding:gzip});fs.createReadStream(index.html).pipe(zlib.createGzip())// 中间加个压缩层.pipe(res);// 最后给响应pipe的优势在于.pipe()是处理 I/O 的最高效方式因为它只占用极小的内存Buffer 块。它连接了生产者和消费者。在 Web 场景中它是实现文件下载、视频串流、日志读取的最佳实践。对于pipe也有缺点如果中间某个流错了它不会自动销毁其他流容易导致内存泄漏。Node.js 官方现在推荐使用stream.pipeline它能更安全地处理错误用法如下const{pipeline}require(stream);pipeline(fs.createReadStream(large-file.txt),zlib.createGzip(),res,(err){if(err){console.error(传输过程中出错:,err);res.end();}else{console.log(传输圆满完成);}});pipe应用实例将文件流导向HTTP响应consthttprequire(http);constfsrequire(fs);constpathrequire(path);constserverhttp.createServer((req,res){constfilePathpath.join(__dirname,movie.mp4);// 假设是一个很大的视频文件// 1. 获取文件状态以便设置正确的响应头可选但专业fs.stat(filePath,(err,stats){if(err){res.writeHead(404);returnres.end(File not found);}// 2. 设置 Headerres.writeHead(200,{Content-Type:video/mp4,Content-Length:stats.size});// 3. 核心创建可读流并 pipe 到 res (可写流)constreadStreamfs.createReadStream(filePath);// 这一行代码完成了所有工作读取、传输、错误处理关联readStream.pipe(res);// 4. 监听结束可选readStream.on(end,(){console.log(文件传输完成);});});});server.listen(3000,()console.log(Server running at :3000));