为避免内存问题,您需要使用streams 处理文件 - 简单来说,增量。您无需将整个文件加载到内存中,而是读取每个行,它会得到相应的处理,然后在符合Garbage Collection 的条件后立即进行处理。
在 Node 中,您可以结合使用 CSV stream parser 将二进制内容流式传输为 CSV 行和 through2,这是一个流实用程序,可让您控制 流溪流;在这种情况下暂停它以允许将行保存在数据库中。
过程
流程如下:
- 您获取数据流
- 您通过 CSV 解析器对其进行管道传输
- 你通过一个 through2 管道它
- 您将每一行保存在数据库中
- 保存完成后,请致电
cb() 继续下一项。
我不熟悉multer,但这里有一个使用来自文件的流的示例。
const fs = require('fs')
const csv = require('csv-stream')
const through2 = require('through2')
const stream = fs.createReadStream('foo.csv')
.pipe(csv.createStream({
endLine : '\n',
columns : ['Year', 'Make', 'Model'],
escapeChar : '"',
enclosedChar : '"'
}))
.pipe(through2({ objectMode: true }, (row, enc, cb) => {
// - `row` holds the first row of the CSV,
// as: `{ Year: '1997', Make: 'Ford', Model: 'E350' }`
// - The stream won't process the *next* item unless you call the callback
// `cb` on it.
// - This allows us to save the row in our database/microservice and when
// we're done, we call `cb()` to move on to the *next* row.
saveIntoDatabase(row).then(() => {
cb(null, true)
})
.catch(err => {
cb(err, null)
})
}))
.on('data', data => {
console.log('saved a row')
})
.on('end', () => {
console.log('end')
})
.on('error', err => {
console.error(err)
})
// Mock function that emulates saving the row into a database,
// asynchronously in ~500 ms
const saveIntoDatabase = row =>
new Promise((resolve, reject) =>
setTimeout(() => resolve(), 500))
foo.csv CSV 的例子是这样的:
1997,Ford,E350
2000,Mercury,Cougar
1998,Ford,Focus
2005,Jaguar,XKR
1991,Yugo,LLS
2006,Mercedes,SLK
2009,Porsche,Boxter
2001,Dodge,Viper
为什么?
这种方法避免了在内存中加载整个 CSV。一旦处理了row,它就会超出范围/变得无法访问,因此它有资格进行垃圾收集。这就是使这种方法如此高效的原因。理论上,这允许您处理无限大小的文件。阅读Streams Handbook 了解有关流的更多信息。
一些提示
- 您可能希望在每个循环中保存/处理多于 1 行(在相同大小的 块中)。在这种情况下,将一些
rows 推入一个数组,处理/保存整个数组(块),然后调用 cb 以继续下一个块 - 重复该过程。
- 流会发出您可以监听的事件。
end/error 事件对于响应操作是成功还是失败特别有用。
- Express 默认使用流 - 我几乎可以肯定您根本不需要
multer。