Node.js在读取大型逐位文件时会耗尽内存

3

我正在尝试编写一些JS代码,它可以读取文件并将其写入流中。问题是该文件非常大,因此我必须逐位读取。看起来我不应该用光内存,但实际上确实出现了这种情况。以下是代码:

var size = fs.statSync("tmpfile.tmp").size;

var fp = fs.openSync("tmpfile.tmp", "r");

for(var pos = 0; pos < size; pos += 50000){
    var buf = new Buffer(50000),
        len = fs.readSync(fp, buf, 0, 50000, (function(){
            console.log(pos);
            return pos;
        })());

    data_output.write(buf.toString("utf8", 0, len));

    delete buf;
}

data_output.end();

由于某些原因,它达到了264900000,然后抛出。我认为调用会强制将数据写入,然后从内存中丢弃它,但我可能错了。某些原因导致数据留在内存中,我不知道是什么原因。非常感谢任何帮助。


1
"delete buf;" 是无效的,请尝试使用 "buf = null"。 - Raynos
4个回答

3
我遇到了一个非常类似的问题。我正在读取一个非常大的csv文件,有1000万行,并将其写成json格式。在Windows任务管理器中,我发现我的进程使用了超过2GB的内存。最终我想出了一个解决方法,输出流可能比输入流慢,而且输出流缓冲了大量数据。我通过每写入100个输出流时暂停输入流,并等待输出流清空来解决这个问题。这样做可以给输出流足够的时间赶上输入流。我认为这对于本次讨论来说并不重要,但是我使用“readline”逐行处理csv文件。

在此过程中,我还发现,如果我不是将每一行都写入输出流,而是将100行左右连接在一起,然后一起写入,这也可以改善内存情况并使操作更快。

最终,我发现我只需要使用70M的内存就可以完成文件传输(从csv到json)。

以下是我的写入函数代码片段:

var write_counter = 0;
var out_string = "";
function myWrite(inStream, outStream, string, finalWrite) {
    out_string += string;
    write_counter++;
    if ((write_counter === 100) || (finalWrite)) {
        // pause the instream until the outstream clears
        inStream.pause();
        outStream.write(out_string, function () {
            inStream.resume();
        });
        write_counter = 0;
        out_string = "";
    }
}

2

您应该使用管道(pipes),例如:

var fp = fs.createReadStream("tmpfile.tmp");
fp.pipe(data_output);

更多信息请参考:http://nodejs.org/docs/v0.5.10/api/streams.html#stream.pipe 编辑:顺便提一下,你实现中的问题是,通过这种方式分块读取文件后,写入缓冲区不会被刷新,因此在大量写入之前,你将读取整个文件。

1
根据文档data_output.write(...)将返回true如果字符串已被刷新,false如果没有(由于内核缓冲区已满),这是什么类型的流?
此外,我相当确定这不是问题,但是:为什么每次循环迭代都要分配新的Buffer?在循环之前初始化buf不是更有意义吗?

啊,说得对。那是我在调试,试图弄清楚在每次迭代后删除它是否会有影响。实际上这是在将一个大文件发送到远程存储,而且是HTTP协议。 - Ain't Nobody Special
回复:HTTP:那很有道理。读取文件比通过网络发送文件要快得多,而“写入”操作不会被阻塞,直到字节实际发送。 (如果尚未发送,则只会返回false,然后稍后发出“排水”事件。) - ruakh

0
我不知道同步文件函数是如何实现的,但你是否考虑使用异步函数呢?这样更有可能允许垃圾回收和 I/O 刷新发生。所以不是使用 for 循环,而是在上一个读取的回调函数中触发下一个读取。
以下是类似的代码(请注意,根据其他评论,我正在重复使用缓冲区):
var buf = new Buffer(50000),
var pos = 0, bytesRead;  

function readNextChunk () {
    fs.read(fp, buf, 0, 50000, pos,
      function(err, bytesRead){
        if (err) {
          // handle error            
        }
        else {
          data_output.write(buf.toString("utf8", 0, bytesRead));
          pos += bytesRead;
          if (pos<size)
            readNextChunk();
        }
      });
}
readNextChunk();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接