使用Node.js

时间:2016-11-23 21:40:59

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

我有两个文件,每行都有一个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上),用于多平台使用。因此,产生子流程并委托给这些工具不是一种选择。

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

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

使用Node.js加载,排序和区分大型文件的有效技术是什么?

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

谢谢!

2 个答案:

答案 0 :(得分:0)

由于您已将内存中的文件作为已排序的数组,结帐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' ]

答案 1 :(得分:0)

最后,我们使用了一些非常简单的东西,与数组不同,即使有数千个条目,它仍然具有极高的性能和内存效率。这是我们的初始测试代码:

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: [ ... ]

使用' dumb'加载文件和拆分换行的方法甚至更快(~1.2s),但内存开销明显更高(~2x)。

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