节点:fs write()不写入内部循环。 为什么不?

我想创build一个写入stream,并写入到我的数据进来。但是,我能够创build该文件,但没有写入它。 最终,这个过程耗尽内存。

我发现的问题是我在循环中调用write()。

这是一个简单的例子:

'use strict' var fs = require('fs'); var wstream = fs.createWriteStream('myOutput.txt'); for (var i = 0; i < 10000000000; i++) { wstream.write(i+'\n'); } console.log('End!') wstream.end(); 

什么都不会写,甚至不是你好。 但为什么? 如何在循环内写入文件?

问题是,你没有给它一个缓冲区的机会。 最终这个缓冲区已经满了,你的内存不足了。

WriteStream.write返回一个布尔值,指示数据是否已成功写入磁盘。 如果数据没有成功写入,则应等待drain事件 ,这表明缓冲区已被排空。

下面是编写代码的一种方法,它利用writedrain事件的返回值:

 'use strict' var fs = require('fs'); var wstream = fs.createWriteStream('myOutput.txt'); function writeToStream(i) { for (; i < 10000000000; i++) { if (!wstream.write(i + '\n')) { // Wait for it to drain then start writing data from where we left off wstream.once('drain', function() { writeToStream(i + 1); }); return; } } console.log('End!') wstream.end(); } writeToStream(0); 

为了补充MikeC的出色答案 ,下面是关于writable.write()的当前文档(v8.4.0)的一些相关细节:

如果返回false则应该停止向stream写入数据的更多尝试,直到发生'drain'事件。

当一个stream没有消耗,调用write()会缓冲chunk ,并返回false 。 一旦所有当前缓冲的块被排空(被操作系统接受), 'drain'事件将被发射。 build议一旦write()返回false ,在发出'drain'事件之前不会写入更多的块。 虽然在不stream失的stream上调用write()是允许的,但是Node.js会caching所有写入的块,直到最大的内存使用量发生,此时它将无条件地中止 。 即使在中止之前,高内存使用率也会导致较差的垃圾收集器性能和较高的RSS (即使不再需要内存,通常也不会将其释放回系统)。

并在溪stream中进行背压 :

在任何情况下,数据缓冲区已经超过了highWaterMark或写入队列正忙, .write()将返回false

当返回一个false值时,背压系统就会启动。

一旦数据缓冲区被清空,一个.drain()事件将被发射并恢复传入的数据stream。

一旦队列完成,背压将允许数据再次发送。 正在使用的内存空间将自行释放,并准备下一批数据。

  +-------------------+ +=================+ | Writable Stream +---------> .write(chunk) | +-------------------+ +=======+=========+ | +------------------v---------+ +-> if (!chunk) | Is this chunk too big? | | emit .end(); | Is the queue busy? | +-> else +-------+----------------+---+ | emit .write(); | | ^ +--v---+ +---v---+ ^-----------------------------------< No | | Yes | +------+ +---v---+ | emit .pause(); +=================+ | ^-----------------------+ return false; <-----+---+ +=================+ | | when queue is empty +============+ | ^-----------------------< Buffering | | | |============| | +> emit .drain(); | ^Buffer^ | | +> emit .resume(); +------------+ | | ^Buffer^ | | +------------+ add chunk to queue | | <---^---------------------< +============+ 

以下是一些可视化(通过使用--max-old-space-size=512运行V8堆内存大小为512MB的脚本)。

此可视化显示了i每10,000个步骤(X轴显示i )的堆内存使用情况 (红色)和增量时间(紫色):

 'use strict' var fs = require('fs'); var wstream = fs.createWriteStream('myOutput.txt'); var latestTime = (new Date()).getTime(); var currentTime; for (var i = 0; i < 10000000000; i++) { wstream.write(i+'\n'); if (i % 10000 === 0) { currentTime = (new Date()).getTime(); console.log([ // Output CSV data for visualisation i, (currentTime - latestTime) / 5, process.memoryUsage().heapUsed / (1024 * 1024) ].join(',')); latestTime = currentTime; } } console.log('End!') wstream.end(); 

慢 - 统计

内存使用接近512MB的最大限制时,脚本运行速度会越来越慢,直到达到限制时才会最终崩溃。


该可视化使用带有--trace_gc v8.setFlagsFromString()来显示每个垃圾回收的当前内存使用情况(红色)和执行时间(紫色)(X轴以秒为单位显示总的运行时间):

 'use strict' var fs = require('fs'); var v8 = require('v8'); var wstream = fs.createWriteStream('myOutput.txt'); v8.setFlagsFromString('--trace_gc'); for (var i = 0; i < 10000000000; i++) { wstream.write(i+'\n'); } console.log('End!') wstream.end(); 

慢 -  GC

内存使用率在大约4秒后达到80%,垃圾收集器放弃尝试Scavenge ,并被迫使用Mark-sweep (慢10倍以上) – 请参阅本文以获取更多详细信息。


为了便于比较,下面是@ MikeC代码的相同的可视化,当write缓冲区变满时,它等待drain

快速统计

快 -  GC