nodejs中流(stream)的理解

nodejs的fs模塊並無提供一個copy的方法,但咱們能夠很容易的實現一個,好比:javascript

var source = fs.readFileSync('/path/to/source', {encoding: 'utf8'});
fs.writeFileSync('/path/to/dest', source);

這種方式是把文件內容所有讀入內存,而後再寫入文件,對於小型的文本文件,這沒有多大問題,好比grunt-file-copy就是這樣實現的。可是對於體積較大的二進制文件,好比音頻、視頻文件,動輒幾個GB大小,若是使用這種方法,很容易使內存「爆倉」。理想的方法應該是讀一部分,寫一部分,無論文件有多大,只要時間容許,總會處理完成,這裏就須要用到流的概念。java

stream

如上面高大上的圖片所示,咱們把文件比做裝水的桶,而水就是文件裏的內容,咱們用一根管子(pipe)鏈接兩個桶使得水從一個桶流入另外一個桶,這樣就慢慢的實現了大文件的複製過程。node

Stream在nodejs中是EventEmitter的實現,而且有多種實現形式,例如:shell

  • http responses request
  • fs read write streams
  • zlib streams
  • tcp sockets
  • child process stdout and stderr

上面的文件複製能夠簡單實現一下: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();
});

或者使用更直接的pipetcp

// 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

copy1

咱們複製了一集1080p的權利的遊戲第四季第7集,大概3.78G大小,因爲使用了SSD,能夠看到速度仍是很是不錯的,哈哈哈~
複製完成後,顯示總花費時間

copy2

結合nodejs的readlineprocess.argv等模塊,咱們能夠添加覆蓋提示、強制覆蓋、動態指定文件路徑等完整的複製方法,有興趣的能夠實現一下,實現完成,能夠

ln -s /path/to/copy.js /usr/local/bin/mycopy

這樣就可使用本身寫的mycopy命令替代系統的cp命令

相關文章
相關標籤/搜索