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

use*_*603 5 javascript fs node.js

我想创建一个写入流并在我的数据进入时写入它.但是,我能够创建该文件,但没有写入任何内容.最终,该过程耗尽内存.

我发现问题是我在循环中调用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();
Run Code Online (Sandbox Code Playgroud)

什么都没有写,甚至没有你好.但为什么?如何在循环内写入文件?

Tac*_*tex 7

为了补充@MikeC 的出色回答,以下是当前文档 (v8.4.0) 中的一些相关细节writable.write()

如果false返回,则应停止进一步尝试将数据写入流,直到'drain'发出事件。

当流没有排空时,调用write()将缓冲chunk,并返回false。一旦所有当前缓冲的块都被排空(操作系统接受交付),'drain'将发出该事件。建议一旦write()返回false,在'drain'发出事件之前不再写入块。虽然write()允许调用未耗尽的流,但Node.js 将缓冲所有写入的块,直到出现最大内存使用量,此时它将无条件中止。即使在它中止之前,高内存使用量也会导致垃圾收集器性能不佳和高RSS(即使不再需要内存,通常也不会释放回系统)。

在流中进行背压

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

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

清空数据缓冲区后,.drain()将发出一个事件并恢复传入的数据流。

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

               +-------------------+         +=================+
               |  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    |
                        |            <---^---------------------<
                        +============+
Run Code Online (Sandbox Code Playgroud)

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

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

'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();
Run Code Online (Sandbox Code Playgroud)

慢 - 统计

随着内存使用量接近 512MB 的最大限制,脚本运行得越来越慢,直到达到限制时它最终崩溃。


此可视化使用v8.setFlagsFromString()with--trace_gc显示每个垃圾收集的当前内存使用情况(红色)和执行时间(紫色)(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();
Run Code Online (Sandbox Code Playgroud)

慢 - GC

大约 4 秒后内存使用率达到 80%,垃圾收集器放弃尝试Scavenge并被迫使用Mark-sweep(慢了 10 倍以上)——更多细节请参阅这篇文章


为了进行比较,以下是 @MikeC 代码的相同可视化,它drainwrite缓冲区变满时等待:

快速 - 统计

快速 - GC


Mik*_*uck 5

问题是你永远不会给它一个耗尽缓冲区的机会.最终这个缓冲区已满,你的内存不足.

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);
Run Code Online (Sandbox Code Playgroud)