Javascript 使用Node.js对大型文件进行排序和区分

Javascript 使用Node.js对大型文件进行排序和区分,javascript,node.js,sorting,diff,node-streams,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(构建在节

我有两个文件,每行有一个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(构建在节点上)。因此,生成子流程并委托给这些工具不是一个选项

“哑”并将每个文件加载到内存中,在换行符上拆分并在生成的数组上调用
.sort()
,效果出人意料地好(虽然速度很快,但占用了相当多的内存…),但发现差异却相当困难

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

使用Node.js加载、排序和区分如此大的文件的有效技术是什么

我不是在寻找完整的解决方案(尽管如此,请放心!),在这个阶段,指针才是真正有用的


谢谢

由于内存中的文件已作为排序数组存在,请签出

这似乎正好适合您的用例:

>>> 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' ]

由于内存中的文件已作为排序数组存在,请签出

这似乎正好适合您的用例:

>>> 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' ]

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

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):

使用“哑”方法加载文件并在换行符上进行拆分速度更快(~1.2秒),但内存开销明显更高(~2倍)


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

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

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):

使用“哑”方法加载文件并在换行符上进行拆分速度更快(~1.2秒),但内存开销明显更高(~2倍)


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

感谢您花时间推荐此工具-非常好=]感谢您花时间推荐此工具-非常好=]