1、buffer合並
// 合並分片
function mergeChunks (fileName, chunks, callback) { console.log('chunks:' + chunks) let chunkPaths = chunks.map(function (name) { return path.join(process.env.IMAGESDIR, name) }) // 采用Buffer方式合並
const readStream = function (chunkArray, cb) { let buffers = [] chunkPaths.forEach(function (path) { let buffer = fs.readFileSync(path) buffers.push(buffer) }) let concatBuffer = Buffer.concat(buffers) let concatFilePath = path.join(process.env.IMAGESDIR, fileName) fs.writeFileSync(concatFilePath, concatBuffer) chunkPaths.forEach(function (path) { fs.unlinkSync(path) }) cb() } readStream(chunkPaths, callback) }
buffer方式合並是一種常見的文件合並方式,方法是將各個分片文件分別用fs.readFile()方式讀取,然后通過Buffer.concat()進行合並。
這種方法簡單易理解,但有個最大的缺點,就是你讀取的文件有多大,合並的過程占用的內存就有多大,因為我們相當於把這個大文件的全部內容都一次性載入到內存中了,這是非常低效的。同時,Node默認的緩沖區大小的上限是2GB,一旦我們上傳的大文件超出2GB,那使用這種方法就會失敗。雖然可以通過修改緩沖區大小上限的方法來規避這個問題,但是鑒於這種合並方式極吃內存,我不建議您這么做。
那么,有更好的方式嗎?那是當然,下面介紹一種stream合並方式。
2、sream流合並
// 合並分片
function mergeChunks (fileName, chunks, callback) { console.log('chunks:' + chunks) let chunkPaths = chunks.map(function (name) { return path.join(process.env.IMAGESDIR, name) }) // 采用Stream方式合並
let targetStream = fs.createWriteStream(path.join(process.env.IMAGESDIR, fileName)) const readStream = function (chunkArray, cb) { let path = chunkArray.shift() let originStream = fs.createReadStream(path) originStream.pipe(targetStream, {end: false}) originStream.on('end', function () { // 刪除文件
fs.unlinkSync(path) if (chunkArray.length > 0) { readStream(chunkArray, callback) } else { cb() } }) } readStream(chunkPaths, callback) }
為什么說流更好呢?流到底是什么呢?
流是數據的集合 —— 就像數組或字符串一樣。區別在於流中的數據可能不會立刻就全部可用,並且你無需一次性的把這些數據全部放入內存。這使得流在操作大量數據或是數據從外部來源逐段發送過來的時候變得非常有用。
換句話說,當你使用buffer方式來處理一個2GB的文件,占用的內存可能是2GB以上,而當你使用流來處理這個文件,可能只會占用幾十個M。這就是我們為什么選擇流的原因所在。
在Node.js中,有4種基本類型的流,分別是可讀流,可寫流,雙向流以及變換流。
- 可讀流是對一個可以讀取數據的源的抽象。fs.createReadStream 方法是一個可讀流的例子。
- 可寫流是對一個可以寫入數據的目標的抽象。fs.createWriteStream 方法是一個可寫流的例子。
- 雙向流既是可讀的,又是可寫的。TCP socket 就屬於這種。
- 變換流是一種特殊的雙向流,它會基於寫入的數據生成可供讀取的數據。
所有的流都是EventEmitter的實例。它們發出可用於讀取或寫入數據的事件。然而,我們可以利用pipe方法以一種更簡單的方式使用流中的數據。
在上面那段代碼中,我們首先通過fs.createWriteStream()創建了一個可寫流,用來存放最終合並的文件。然后使用fs.createReadStream()分別讀取各個分片后的文件,再通過pipe()方式將讀取的數據像倒水一樣“倒”到可寫流中,到監控到一杯水倒完后,馬上接着倒下一杯,直到全部倒完為止。此時,全部文件合並完畢。
3、追加文件方式合並
追加文件方式合並指的是使用fs.appendFile()的方式來進行合並。
fs.appendFile()的作用是異步地追加數據到一個文件,如果文件不存在則創建文件。data可以是一個字符串或buffer。
使用這種方法也可以將文件合並,性能強過buffer合並方式,但不及流合並方式。
三種方式各有各的特點,但是在大文件合並上,我推薦使用流方式合並,流合並占內存最少,效率最高,是處理大文件的最佳選擇。