nodejs的fs
模塊並無提供一個copy
的方法,但咱們能夠很容易的實現一個,好比:javascript
var source = fs.readFileSync('/path/to/source', {encoding: 'utf8'}); fs.writeFileSync('/path/to/dest', source);
這種方式是把文件內容所有讀入內存,而後再寫入文件,對於小型的文本文件,這沒有多大問題,好比grunt-file-copy
就是這樣實現的。可是對於體積較大的二進制文件,好比音頻、視頻文件,動輒幾個GB大小,若是使用這種方法,很容易使內存「爆倉」。理想的方法應該是讀一部分,寫一部分,無論文件有多大,只要時間容許,總會處理完成,這裏就須要用到流的概念。java
如上面高大上的圖片所示,咱們把文件比做裝水的桶,而水就是文件裏的內容,咱們用一根管子(pipe)鏈接兩個桶使得水從一個桶流入另外一個桶,這樣就慢慢的實現了大文件的複製過程。node
Stream
在nodejs中是EventEmitter
的實現,而且有多種實現形式,例如:shell
上面的文件複製能夠簡單實現一下:segmentfault
var fs = require('fs'); var readStream = fs.createReadStream('/path/to/source'); var writeStream = fs.createWriteStream('/path/to/dest'); readStream.on('data', function(chunk) { // 當有數據流出時,寫入數據 writeStream.write(chunk); }); readStream.on('end', function() { // 當沒有數據時,關閉數據流 writeStream.end(); });
上面的寫法有一些問題,若是寫入的速度跟不上讀取的速度,有可能致使數據丟失。正常的狀況應該是,寫完一段,再讀取下一段,若是沒有寫完的話,就讓讀取流先暫停,等寫完再繼續,因而代碼能夠修改成:socket
var fs = require('fs'); var readStream = fs.createReadStream('/path/to/source'); var writeStream = fs.createWriteStream('/path/to/dest'); readStream.on('data', function(chunk) { // 當有數據流出時,寫入數據 if (writeStream.write(chunk) === false) { // 若是沒有寫完,暫停讀取流 readStream.pause(); } }); writeStream.on('drain', function() { // 寫完後,繼續讀取 readStream.resume(); }); readStream.on('end', function() { // 當沒有數據時,關閉數據流 writeStream.end(); });
或者使用更直接的pipe
tcp
// pipe自動調用了data,end等事件 fs.createReadStream('/path/to/source').pipe(fs.createWriteStream('/path/to/dest'));
下面是一個更加完整的複製文件的過程grunt
var fs = require('fs'), path = require('path'), out = process.stdout; var filePath = '/Users/chen/Movies/Game.of.Thrones.S04E07.1080p.HDTV.x264-BATV.mkv'; var readStream = fs.createReadStream(filePath); var writeStream = fs.createWriteStream('file.mkv'); var stat = fs.statSync(filePath); var totalSize = stat.size; var passedLength = 0; var lastSize = 0; var startTime = Date.now(); readStream.on('data', function(chunk) { passedLength += chunk.length; if (writeStream.write(chunk) === false) { readStream.pause(); } }); readStream.on('end', function() { writeStream.end(); }); writeStream.on('drain', function() { readStream.resume(); }); setTimeout(function show() { var percent = Math.ceil((passedLength / totalSize) * 100); var size = Math.ceil(passedLength / 1000000); var diff = size - lastSize; lastSize = size; out.clearLine(); out.cursorTo(0); out.write('已完成' + size + 'MB, ' + percent + '%, 速度:' + diff * 2 + 'MB/s'); if (passedLength < totalSize) { setTimeout(show, 500); } else { var endTime = Date.now(); console.log(); console.log('共用時:' + (endTime - startTime) / 1000 + '秒。'); } }, 500);
能夠把上面的代碼保存爲copy.js
試驗一下ui
咱們添加了一個遞歸的setTimeout
(或者直接使用setInterval)來作一個旁觀者,每500ms觀察一次完成進度,並把已完成的大小、百分比和複製速度一併寫到控制檯上,當複製完成時,計算總的耗費時間,效果如圖:spa
咱們複製了一集1080p的權利的遊戲第四季第7集,大概3.78G大小,因爲使用了SSD,能夠看到速度仍是很是不錯的,哈哈哈~
複製完成後,顯示總花費時間
結合nodejs的readline
, process.argv
等模塊,咱們能夠添加覆蓋提示、強制覆蓋、動態指定文件路徑等完整的複製方法,有興趣的能夠實現一下,實現完成,能夠
ln -s /path/to/copy.js /usr/local/bin/mycopy
這樣就可使用本身寫的mycopy
命令替代系統的cp
命令