【问题标题】:Sorting and diffing large files with Node.js使用 Node.js 对大文件进行排序和比较
【发布时间】:2016-11-27 03:11:28
【问题描述】:

我有两个文件,每行都有一个 UUID。每个文件都有几十万行(它们是从数据库转储生成的)。需要对这些文件进行分类并找到差异(添加/删除)。使用一些 *nix 工具很容易做到这一点,只需要几秒钟:

$ sort file-a.txt > file-a-sorted.txt
$ sort file-b.txt > file-b-sorted.txt
$ diff file-a-sorted.txt file-b-sorted.txt

但是,我想将此功能添加到我们拥有的 CLI(基于 Node 构建),该 CLI 旨在供多平台使用。因此,生成子流程并委托给这些工具不是一种选择。

“愚蠢”并将每个文件加载到内存中,在换行符上拆分并在结果数组上调用 .sort() 效果出奇的好(尽管使用了大量内存,但速度很快......)但发现差异证明了更难。

我确信答案在流领域的某个地方,但我缺乏操作它们的经验,所以我不确定从哪里开始。

使用 Node.js 加载、排序和区分此类大文件的有效技术有哪些?

我不是在寻找完整的解决方案(不过,请随意!),在这个阶段,指针会非常有用。

谢谢!

【问题讨论】:

    标签: javascript node.js sorting diff node-streams


    【解决方案1】:

    最后,我们使用了非常简单的集合,与数组不同,即使有数千个条目,它也能保持极高的性能和内存效率。这是我们最初的测试代码:

    const fs = require('fs')
    const readline = require('readline')
    
    const memory = () => process.memoryUsage().rss / 1048576).toFixed(2)
    
    const loadFile = (filename, cb) => {
      // this is more complex that simply calling fs.readFile() but
      // means we do not have to buffer the whole file in memory  
      return new Promise((resolve, reject) => {
        const input = fs.createReadStream(filename)
        const reader = readline.createInterface({ input })
    
        input.on('error', reject)
    
        reader.on('line', cb)
        reader.on('close', resolve)
      })
    }
    
    const start = Date.now()
    
    const uniqueA = new Set()
    const uniqueB = new Set()
    
    // when reading the first file add every line to the set
    const handleA = (line) => {
      uniqueA.add(line)
    }
    
    // this will leave us with unique lines only
    const handleB = (line) => {
      if (uniqueA.has(line)) {
        uniqueA.delete(line)
      } else {
        uniqueB.add(line)
      }
    }
    
    console.log(`Starting memory: ${memory()}mb`)
    
    Promise.resolve()
      .then(() => loadFile('uuids-eu.txt', handleA))
      .then(() => {
        console.log(`${uniqueA.size} items loaded into set`)
        console.log(`Memory: ${memory()}mb`)
      })
      .then(() => loadFile('uuids-us.txt', handleB))
      .then(() => {
        const end = Date.now()
    
        console.log(`Time taken: ${(end - start) / 1000}s`)
        console.log(`Final memory: ${memory()}mb`)
    
        console.log('Differences A:', Array.from(uniqueA))
        console.log('Differences B:', Array.from(uniqueB))
      })
    

    这给了我们这个输出(2011 Macbook Air):

    Starting memory: 19.71mb
    678336 items loaded into set
    Memory: 135.95mb
    Time taken: 1.918s
    Final memory: 167.06mb
    Differences A: [ ... ]
    Differences B: [ ... ]
    

    使用“哑”方法加载文件并在换行符处分割更快(~1.2s),但内存开销明显更高(~2x)。

    我们使用Set 的解决方案还具有可以跳过排序步骤的优势,这也比原始问题中概述的 *nix 工具更快。

    【讨论】:

      【解决方案2】:

      由于您已经将内存中的文件作为排序数组保存,请查看difflib

      这似乎完全适合您的用例:

      >>> difflib.unifiedDiff('one two three four'.split(' '),
      ...                     'zero one tree four'.split(' '), {
      ...                       fromfile: 'Original'
      ...                       tofile: 'Current',
      ...                       fromfiledate: '2005-01-26 23:30:50',
      ...                       tofiledate: '2010-04-02 10:20:52',
      ...                       lineterm: ''
      ...                     })
      [ '--- Original\t2005-01-26 23:30:50',
        '+++ Current\t2010-04-02 10:20:52',
        '@@ -1,4 +1,4 @@',
        '+zero',
        ' one',
        '-two',
        '-three',
        '+tree',
        ' four' ]
      

      【讨论】:

      • 感谢您抽出宝贵时间推荐此工具 - 非常好 =]
      猜你喜欢
      • 2021-12-16
      • 1970-01-01
      • 2013-09-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-22
      • 1970-01-01
      相关资源
      最近更新 更多