【问题标题】:What is the most efficient way to read only the first line of a file in Node JS?在 Node JS 中只读取文件第一行的最有效方法是什么?
【发布时间】:2015-02-26 16:32:07
【问题描述】:

假设您有许多长文本文件,您只需要从每个文件的第一行提取数据(无需阅读任何进一步的内容)。 Node JS 中最好的方法是什么?

谢谢!

【问题讨论】:

  • 你能从节点内对文件调用head 命令吗?将文件/操作系统的功能用于它们的优点。 :)
  • 我需要的代码是库的一部分,可以在任何支持 Node JS 的操作系统上使用,所以最好使用 Node 本身来完成。如果我没记错的话 head 在 Windows 上不可用,例如。
  • 您必须知道第一行有多长,但是您可以超调并加载第一行,例如 5kb,然后将 5kb 逐行拆分,只保留第一行。使用 fs.read() 代替 fs.readFile() :nodejs.org/api/…
  • @dandavis,我就是这么想的,但不确定是否有更好的方法。 @TZHX 提到了head 命令,碰巧你知道它是如何内部实现的吗?
  • @dandavis,其实我们说的是 Unix head,看看here

标签: javascript node.js stream filesystems text-files


【解决方案1】:

我知道这并不能完全回答问题,但对于那些正在寻找READABLE简单方法的人来说:

const fs = require('fs').promises;

async function getFirstLine(filePath) {
    const fileContent = await fs.readFile(filePath, 'utf-8');
    return (fileContent.match(/(^.*)/) || [])[1] || '';
} 

注意

  • 当然,这仅适用于文本文件,我假设您在描述中使用了这些文件
  • 处理空文件并将返回一个空字符串
  • 这个正则表达式非常高效,因为它很简单(没有OR 条件`或复杂匹配)并且只读取第一行

【讨论】:

  • 他问的是“最有效的方法”和“没有阅读任何进一步的内容”
  • @harscoet 我认为它对那些前来寻找简单解决方案的人很有用。起初我来这里寻找类似的东西,但找不到。我也会写一个更明确的免责声明:)
【解决方案2】:

几乎有一个用于这种情况的内置模块 - readline。它避免弄乱块等等。代码如下所示:

const fs = require('fs');
const readline = require('readline');

async function getFirstLine(pathToFile) {
  const readable = fs.createReadStream(pathToFile);
  const reader = readline.createInterface({ input: readable });
  const line = await new Promise((resolve) => {
    reader.on('line', (line) => {
      reader.close();
      resolve(line);
    });
  });
  readable.close();
  return line;
}

【讨论】:

  • 这不适用于零长度文件。承诺将永远等待 resolve 调用。
  • 除了 Sake 所说的之外,您甚至在读到一行之前就关闭了输入文件......要么这样,要么您非常幸运并从虚假的竞争条件中受益。
  • @RomainVincent 我在上面测试了 Victor 的函数,它似乎每次都给我第一行(测试了 ~ 10 次)。您能否详细说明竞态条件,以及避免竞态条件的代码应该是什么?
  • @nkhil 这是我自己的错,当前示例没有竞争条件。我一定错过了 await 关键字,这是出乎意料的。只返回承诺并在reader.close() 之后立即调用readable.close() 会更简单。这将避免带来转换时非常繁重的等待逻辑。但这在这一点上是挑剔的。主要问题仍然是,如果没有要读取的行,这将永远挂起。
【解决方案3】:

我最终采用了这个解决方案,这似乎是迄今为止我见过的性能最好的:

var fs = require('fs');
var Q = require('q');

function readFirstLine (path) {
  return Q.promise(function (resolve, reject) {
    var rs = fs.createReadStream(path, {encoding: 'utf8'});
    var acc = '';
    var pos = 0;
    var index;
    rs
      .on('data', function (chunk) {
        index = chunk.indexOf('\n');
        acc += chunk;
        index !== -1 ? rs.close() : pos += chunk.length;
      })
      .on('close', function () {
        resolve(acc.slice(0, pos + index));
      })
      .on('error', function (err) {
        reject(err);
      })
  });
}

为方便起见,我创建了一个 npm 模块,命名为“firstline”。

感谢@dandavis 建议使用String.prototype.slice()

【讨论】:

  • 有意思,每个chunk有多大?
  • 感谢您回来。听起来我需要重新调整我自己的缓冲区大小并更改我的 std 建议......我会提供最高速度(如果这很重要),在关闭事件中使用 acc.slice(0,acc.indexOf("\n")) 而不是拆分整个事件会更快64kb+,甚至以某种方式从数据事件中传递索引(加上 acc 的长度)以提高裸机效率。
  • 谢谢@dandavis!您的建议是有道理的,我将编辑答案以包含您的建议;)
  • 提醒我遇到了一个问题,即下一个data 事件将在rs.close() 之前发生。这在 close 事件之前更改了 index 的值,因此它导致了一个错误,您可能无法获得完整的第一行,我必须使用索引检查包装 on data 函数以确保它仍然未定义。
  • @ClarenceLiu 您能否在 GitHub 上提交有关所有详细信息的问题?谢谢! github.com/pensierinmusica/firstline
【解决方案4】:

请试试这个:

https://github.com/yinrong/node-line-stream-util#get-head-lines

一旦获得标题,它就会取消上游的管道。

【讨论】:

    【解决方案5】:

    //给你;

    var lineReader = require('line-reader');
    var async = require('async');
    
    exports.readManyFiles = function(files) {
        async.map(files, 
            function(file, callback))
                lineReader.open(file, function(reader) {
                  if (reader.hasNextLine()) {
                    reader.nextLine(function(line) {
                      callback(null,line);
                    });
                  }
                });
            },
            function(err, allLines) {
                //do whatever you want to with the lines
            })
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-08
      • 1970-01-01
      • 2014-02-01
      • 1970-01-01
      相关资源
      最近更新 更多