【问题标题】:Node.js: Count the number of lines in a fileNode.js:计算文件中的行数
【发布时间】:2012-09-09 07:16:39
【问题描述】:

我有大文本文件,范围在 30MB10GB 之间。如何使用Node.js 计算文件中的行数?

我有这些限制:

  • 不需要将整个文件写入内存
  • 执行任务不需要子进程

【问题讨论】:

  • wc -l file ...
  • “使用 NodeJS”——这个要求背后有什么真正的技术原因吗?
  • 我确信wc 会比任何“本机”nodejs 解决方案更快
  • 你可以数行数——stackoverflow.com/questions/6156501/…
  • @zerkms 您使用的是哪种 shell 脚本语言?

标签: javascript node.js


【解决方案1】:

我发现的最佳解决方案是使用 Promise、async 和 await。这也是一个如何等待履行承诺的例子:

#!/usr/bin/env node
const fs = require('fs');
const readline = require('readline');
function main() {
    function doRead() {
        return new Promise(resolve => {
            var inf = readline.createInterface({
                input: fs.createReadStream('async.js'),
                crlfDelay: Infinity
            });
            var count = 0;
            inf.on('line', (line) => {
                console.log(count + ' ' + line);
                count += 1;
            });
            inf.on('close', () => resolve(count));
        });
    }
    async function showRead() {
        var x = await doRead();
        console.log('line count: ' + x);
    }
    showRead();
}
main();

【讨论】:

  • 说可以将异步函数变成同步函数是不正确的。您的顶级主函数需要为async,以便它可以在showRead() 上调用await。您得到明显确认的唯一原因是因为 NodeJs 事件循环正在等待 IO 阶段完成,并且在此之前程序不会终止。如果您在showRead() 正下方添加一条日志语句,它将立即执行
  • 正确。这更像是一个如何使用await 来等待承诺履行的例子。我选词不当。我会解决的。
【解决方案2】:

如果你使用 Node 8 及以上版本,你可以使用这种 async/await 模式

const util = require('util');
const exec = util.promisify(require('child_process').exec);

async function fileLineCount({ fileLocation }) {
  const { stdout } = await exec(`cat ${fileLocation} | wc -l`);
  return parseInt(stdout);
};

// Usage

async someFunction() {
  const lineCount = await fileLineCount({ fileLocation: 'some/file.json' });
}

【讨论】:

    【解决方案3】:

    我们可以使用indexOf 让VM 找到换行符:

    function countFileLines(filePath){
      return new Promise((resolve, reject) => {
      let lineCount = 0;
      fs.createReadStream(filePath)
        .on("data", (buffer) => {
          let idx = -1;
          lineCount--; // Because the loop will run once for idx=-1
          do {
            idx = buffer.indexOf(10, idx+1);
            lineCount++;
          } while (idx !== -1);
        }).on("end", () => {
          resolve(lineCount);
        }).on("error", reject);
      });
    };
    

    这个解决方案的作用是它使用.indexOf 找到第一个换行符的位置。它递增lineCount,然后找到下一个位置。 .indexOf 的第二个参数告诉从哪里开始寻找换行符。这样我们就跳过了大块的缓冲区。对于每个换行符,while 循环将运行一次,加一。

    我们让 Node 运行时为我们进行搜索,这是在较低级别上实现的,应该更快。

    在我的系统上,这大约是在大文件 (111 MB) 的缓冲区长度上运行 for 循环的两倍。

    【讨论】:

    • 与此处显示的其他解决方案相比,这是最好的解决方案!
    • 这个答案真是太棒了。应该在上面。计算一个 200MB 文件的行数只用了 200 毫秒。
    • 我可以很高兴地确认,这种方法也适用于非常大的文件 (16GB)。
    • 如果有人能从let idx = -1 行开始解释这是如何工作的,那就太好了。谢谢!
    【解决方案4】:
    var fs=require('fs');
    filename=process.argv[2];
    var data=fs.readFileSync(filename);
    var res=data.toString().split('\n').length;
    console.log(res-1);`
    

    【讨论】:

    • 虽然这段代码 sn-p 可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。也请尽量不要用解释性的 cmets 挤满你的代码,这会降低代码和解释的可读性!
    • 此解决方案需要将文件加载到内存中。我建议不要这样做。使用wc 的答案不是因为wc 已针对流式传输文件进行了优化。
    • 与一年前发布相同内容的Alan Viars 相比,这个答案也没有增加任何有价值的东西。
    • 该问题明确指出文件范围从 30MB 到 10GB。此解决方案在处理之前将整个文件读入内存。这可能会导致代码崩溃,因为 JavaScript 会耗尽内存
    【解决方案5】:

    你也可以使用 indexOf():

    var index = -1;
    var count = 0;
    while ((index = chunk.indexOf(10, index + 1)) > -1) count++;
    

    【讨论】:

      【解决方案6】:

      这是另一种没有太多嵌套的方法。

      var fs = require('fs');
      filePath = process.argv[2];
      fileBuffer =  fs.readFileSync(filePath);
      to_string = fileBuffer.toString();
      split_lines = to_string.split("\n");
      console.log(split_lines.length-1);
      

      【讨论】:

      • 对于一个 10gb 的文件,这至少可以说不是很高效。
      • 这个简单好用,但只适用于小文件!如果文件是 10 GB 的文件,则脚本会死。
      【解决方案7】:

      由于 iojs 1.5.0 有 Buffer#indexOf() 方法,用它来比较 Andrey Sidorov 的回答:

      ubuntu@server:~$ wc logs
        7342500  27548750 427155000 logs
      ubuntu@server:~$ time wc -l logs 
      7342500 logs
      
      real    0m0.180s
      user    0m0.088s
      sys 0m0.084s
      ubuntu@server:~$ nvm use node
      Now using node v0.12.1
      ubuntu@server:~$ time node countlines.js logs 
      7342500
      
      real    0m2.559s
      user    0m2.200s
      sys 0m0.340s
      ubuntu@server:~$ nvm use iojs
      Now using node iojs-v1.6.2
      ubuntu@server:~$ time iojs countlines2.js logs 
      7342500
      
      real    0m1.363s
      user    0m0.920s
      sys 0m0.424s
      ubuntu@server:~$ cat countlines.js 
      var i;
      var count = 0;
      require('fs').createReadStream(process.argv[2])
        .on('data', function(chunk) {
          for (i=0; i < chunk.length; ++i)
            if (chunk[i] == 10) count++;
        })
        .on('end', function() {
          console.log(count);
        });
      ubuntu@server:~$ cat countlines2.js 
      var i;
      var count = 0;
      require('fs').createReadStream(process.argv[2])
        .on('data', function(chunk) {
          var index = -1;
          while((index = chunk.indexOf(10, index + 1)) > -1) count++
        })
        .on('end', function() {
          console.log(count);
        });
      ubuntu@server:~$ 
      

      【讨论】:

        【解决方案8】:

        不使用 wc 的解决方案:

        var i;
        var count = 0;
        require('fs').createReadStream(process.argv[2])
          .on('data', function(chunk) {
            for (i=0; i < chunk.length; ++i)
              if (chunk[i] == 10) count++;
          })
          .on('end', function() {
            console.log(count);
          });
        

        速度较慢,但​​并没有你期望的那么多 - 140M+ 文件需要 0.6 秒,包括 node.js 加载和启动时间

        >time node countlines.js video.mp4 
        619643
        
        real    0m0.614s
        user    0m0.489s
        sys 0m0.132s
        
        >time wc -l video.mp4 
        619643 video.mp4
        real    0m0.133s
        user    0m0.108s
        sys 0m0.024s
        
        >wc -c video.mp4
        144681406  video.mp4
        

        【讨论】:

        • 您的基准测试不是很有说服力,因为您在 not 结构化为行的文件上运行它,因此不代表 OP 的文件类型想处理。 if (chunk[i] == 10) count++; 行在分析文本文件期间执行的频率要远远高于分析二进制视频文件期间。
        • 我没有 100mb 的文本文件 :) 即使在类似的 100mb 文本文件但有 10 倍的换行符的情况下,我也不希望有任何区别 - 这是相同的线性搜索迭代每个字节每个缓冲区块
        • 请原谅我的清白,但是“chunk[i] == 10”是什么意思?我猜如果块等于 10 它是一个新行,但为什么要与数字 10 比较?
        • 10 是“换行”字符的 ascii 代码。为了更好的可读性,您可以在前面几行 const LINE_FEED = '\n'.charCodeAt(0) 然后 if (chunk[i] == LINE_FEED) count++
        • 你的实现差了一个。例如,如果你的文件有 2 行,那么它只有 1 个newline,所以你的脚本会记录1
        【解决方案9】:

        您可以按照 cmets 的建议使用 wc 进行此操作

        var exec = require('child_process').exec;
        
        exec('wc /path/to/file', function (error, results) {
            console.log(results);
        });
        

        【讨论】:

        • wc 是一个特定于 bash 的命令,例如在 windows 环境中可能不起作用
        • wc -l 只计算行数
        • wc -l path/to/file 将给出行数以及文件名。要仅获取行数,请使用 wc -l &lt; path/to/file
        • 如果你喜欢这样,试试sed -n '$=' /path/to/file它只会返回行数,你可以在上面应用函数parseInt来获取你的号码。
        • parseInt(execSync('wc -l &lt; /path/to/file').toString().trim())
        猜你喜欢
        • 1970-01-01
        • 2011-03-29
        • 2012-09-24
        • 2019-02-06
        • 1970-01-01
        • 2011-11-17
        • 2016-10-28
        • 1970-01-01
        相关资源
        最近更新 更多