节点: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
事件 ,这表明缓冲区已被排空。
下面是编写代码的一种方法,它利用write
和drain
事件的返回值:
'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();
内存使用率在大约4秒后达到80%,垃圾收集器放弃尝试Scavenge
,并被迫使用Mark-sweep
(慢10倍以上) – 请参阅本文以获取更多详细信息。
为了便于比较,下面是@ MikeC代码的相同的可视化,当write
缓冲区变满时,它等待drain
: