Node.js dosya kopyalamanın en hızlı yolu


488

Üzerinde çalıştığım proje (node.js) dosya sistemi (kopyalama / okuma / yazma vb.) Hangi yöntemlerin en hızlı olduğunu bilmek istiyorum ve tavsiye almaktan mutluluk duyarım. Teşekkürler.


42
Bu iyi bir soru, ancak diğer benzer format soruları SO "standartlarını" karşılamadığı için hemen 3 veya 4 aşağı oy alacağı 25 upvotes alır ilginç (belki javascript etiketi kinder insanlar tarafından taranır :)
Ben

22
Çoğunlukla tarayıcıların yıllar süren normalleştirilmesinden sonra bu "dosyalar" işiyle ilgili yeni ve heyecanlıyız.
Erik Reppen

3
Sayfasında yalnızca doğru cevaptır bu bir . Diğer cevapların hiçbiri aslında dosyaları kopyalamaz. MacOS ve Windows'taki dosyalar sadece bayt kopyalanarak kaybolan başka meta verilere sahiptir. Bu sayfadaki diğer yanıtlar tarafından kopyalanmayan veri örnekleri, pencereler ve macos . Unix'te bile diğer yanıtlar oluşturma tarihini kopyalamaz, bu da bir dosyayı kopyalarken sıklıkla önemli olan bir şeydir.
gman

Yanıtlar:


717

Bu, akışları kullanarak bir dosyayı bir kod satırında kopyalamanın iyi bir yoludur:

var fs = require('fs');

fs.createReadStream('test.log').pipe(fs.createWriteStream('newLog.log'));

V8.5.0 düğümünde copyFile eklendi

const fs = require('fs');

// destination.txt will be created or overwritten by default.
fs.copyFile('source.txt', 'destination.txt', (err) => {
  if (err) throw err;
  console.log('source.txt was copied to destination.txt');
});

64
Sadece gerçek hayatta, hem createReadStreamve hem de createWriteStreamhataları kontrol etmek isteyeceğinizi unutmayın , bu yüzden tek bir astar elde edemezsiniz (yine de aynı derecede hızlı olacaktır).
ebohlman

18
Bu ham cp test.log newLog.logyoldan yürütmekten ne kadar hızlı / yavaş require('child_process').exec?
Lance Pollard

41
Peki copy, tam bir Node.js çözümünün aksine, pencerede taşınabilir değildir.
Jean

12
Ne yazık ki benim sistemde akışları kullanarak karşılaştırıldığında son derece yavaş child_process.execFile('/bin/cp', ['--no-target-directory', source, target]).
Robert

12
Bu yöntemi kullandım ve elimdeki tek şey yazmada boş bir dosyaydı. neden herhangi bir fikir? fs.createReadStream('./init/xxx.json').pipe(fs.createWriteStream('xxx.json'));
Timmerz

293

Aynı mekanizma, ancak bu hata işleme ekler:

function copyFile(source, target, cb) {
  var cbCalled = false;

  var rd = fs.createReadStream(source);
  rd.on("error", function(err) {
    done(err);
  });
  var wr = fs.createWriteStream(target);
  wr.on("error", function(err) {
    done(err);
  });
  wr.on("close", function(ex) {
    done();
  });
  rd.pipe(wr);

  function done(err) {
    if (!cbCalled) {
      cb(err);
      cbCalled = true;
    }
  }
}

5
Boru hataları her iki akışta da bir hata tetiklediğinden cbCalled bayrağının gerekli olduğunu belirtmek gerekir. Kaynak ve hedef akışları.
Gaston Sanchez

4
Kaynak dosya yoksa hatayı nasıl ele alırsınız? Bu durumda hedef dosya hala oluşturulur.
Michel Hua

1
Bence bir hata WriteStreamsadece onu açmak olacaktır. rd.destroy()Kendinizi aramalısınız . En azından başıma bu geldi. Ne yazık ki kaynak kodu dışında çok fazla belge yok.
Robert

ne anlama geliyor cb? üçüncü argüman olarak ne geçmeliyiz?
SaiyanGirl

4
@SaiyanGirl 'cb', "geri arama" anlamına gelir. Bir işlevi geçmelisiniz.
Brian J. Miller

143

Bir createReadStream/createWriteStreamnedenden dolayı yöntemi çalıştıramadım, ancak fs-extranpm modülünü kullanarak hemen çalıştı. Yine de performans farkından emin değilim.

fs-ekstra

npm install --save fs-extra

var fs = require('fs-extra');

fs.copySync(path.resolve(__dirname,'./init/xxx.json'), 'xxx.json');

3
Şimdi en iyi seçenek bu
Zain Rizvi

11
Düğümde senkronize kod kullanılması uygulama performansınızı öldürür.
mvillar

3
Oh lütfen ... Soru, bir dosyayı kopyalamanın en hızlı yöntemi hakkında . En hızlı her zaman öznel olsa da , senkron bir kod parçasının burada herhangi bir işi olduğunu düşünmüyorum.
sampathsris

24
Uygulanması en hızlı ya da yürütülmesi en hızlı? Farklı öncelikler, bunun geçerli bir cevap olduğu anlamına gelir.
Patrick Gunderson

14
fs- fs.copy(src, dst, callback);extra'nun da eşzamansız yöntemleri vardır ve bunlar @ mvillar'ın endişesini çözmelidir.
Marc Durdin

134

Node.js 8.5.0'dan beri yeni fs.copyFile ve fs.copyFileSync yöntemlerimiz var.

Kullanım Örneği:

var fs = require('fs');

// destination.txt will be created or overwritten by default.
fs.copyFile('source.txt', 'destination.txt', (err) => {
    if (err) throw err;
    console.log('source.txt was copied to destination.txt');
});

2
Bu sayfadaki tek doğru cevaptır. Diğer cevapların hiçbiri aslında dosyaları kopyalamaz. MacOS ve Windows'taki dosyalar sadece bayt kopyalanarak kaybolan başka meta verilere sahiptir. Bu sayfadaki diğer yanıtlar tarafından kopyalanmayan veri örnekleri, pencereler ve macos . Unix'te bile diğer yanıt, oluşturma tarihini kopyalamıyor, bu da bir dosyayı kopyalarken sıklıkla önemli olan bir şey.
gman

ne yazık ki bu mac her şeyi kopyalayamaz. Umarım düzeltirler: github.com/nodejs/node/issues/30575
gman

BTW copyFile(), daha uzun dosyaların üzerine yazarken hata olduğunu unutmayın . Nezaket uv_fs_copyfile()Düğümü v8.7.0 (libuv 1.15.0) kadar. bkz. github.com/libuv/libuv/pull/1552
Anton Rudeshko

74

Hızlı yazma ve kullanımı kolay, söz ve hata yönetimi ile.

function copyFile(source, target) {
  var rd = fs.createReadStream(source);
  var wr = fs.createWriteStream(target);
  return new Promise(function(resolve, reject) {
    rd.on('error', reject);
    wr.on('error', reject);
    wr.on('finish', resolve);
    rd.pipe(wr);
  }).catch(function(error) {
    rd.destroy();
    wr.end();
    throw error;
  });
}

Eşzamansız / sözdizimi ile aynı:

async function copyFile(source, target) {
  var rd = fs.createReadStream(source);
  var wr = fs.createWriteStream(target);
  try {
    return await new Promise(function(resolve, reject) {
      rd.on('error', reject);
      wr.on('error', reject);
      wr.on('finish', resolve);
      rd.pipe(wr);
    });
  } catch (error) {
    rd.destroy();
    wr.end();
    throw error;
  }
}

1
Başka girdi yoksa (bozuk ağ paylaşımı), ancak yazma yine de başarılı olduğunda ne olur? Hem reddetme (okumadan) hem de çözümleme (yazmadan) çağrılır mı? Hem okuma / yazma başarısız olursa (okuma sırasında bozuk disk kesimleri, yazma sırasında tam disk)? Sonra reddetme iki kez çağrılacaktır. Mike'ın bir bayrakla verdiği cevaba dayanan bir Promise çözümü (ne yazık ki), hata işlemeyi düzgün bir şekilde değerlendiren tek geçerli çözüm gibi görünüyor.
Lekensteyn

Kopya başarılı olduktan sonra söz çözümlenir. Reddedilirse, durumu çözülür ve birden çok kez reddetme çağrısı fark etmez.
benweet

2
Sadece test new Promise(function(resolve, reject) { resolve(1); resolve(2); reject(3); reject(4); console.log("DONE"); }).then(console.log.bind(console), function(e){console.log("E", e);});ve başını kaldırdı spesifikasyonları bu konuda evet haklısınız: kararlılığının çalışılıyor veya çözülmesi söz etkisi yoktur reddeder. Belki cevabınızı uzatabilir ve işlevi neden bu şekilde yazdığınızı açıklayabilirsiniz? Teşekkürler :-)
Lekensteyn

2
Bu arada, Yazılabilir akışlar için closeolmalıdır finish.
Lekensteyn

Ve uygulamanızın boru hatalarından sonra neden hiç kapanmadığını merak ediyorsanız /dev/stdin, bu bir hata github.com/joyent/node/issues/25375
Lekensteyn

43

Genellikle, eşzamansız dosya işlemlerinden kaçınmak iyidir. Kısa (yani hata işleme yok) senkronizasyon örneği:

var fs = require('fs');
fs.writeFileSync(targetFile, fs.readFileSync(sourceFile));

8
Genel olarak son derece yanlış olduğunu söylemek, özellikle insanların sunucularına yapılan her istek için dosyaları yeniden slurping neden olduğu için. Bu pahalı olabilir.
Catalyst

8
*Syncyöntemleri kullanmak tamamen düğümlerin felsefesine karşıdır! Ayrıca yavaş yavaş reddedildiklerini düşünüyorum. Düğümün tüm fikri, tek dişli ve olay güdümlü olmasıdır.
gillyb

11
@gillyb Bunları kullanmayı düşünebilmemin tek nedeni basitliktir - yalnızca bir kez kullanacağınız hızlı bir komut dosyası yazıyorsanız, büyük olasılıkla işlemi engellemekten rahatsız olmayacaksınız.
starbeamrainbowlabs

13
Onlardan itiraz edildiklerinin farkında değilim. Senkronizasyon yöntemleri neredeyse her zaman bir web sunucusunda korkunç bir fikirdir, ancak bazen dosyalar kopyalanırken pencerede eylemi kilitlediği düğüm-webkit gibi bir şey için idealdir. Bir yükleme gif ve belki belirli noktalarda güncellenen bir yükleme çubuğu atın ve senkronizasyon yöntemlerinin kopyalama tamamlanana kadar tüm eylemleri engellemesine izin verin. Ne zaman ve nerede kendi yer şey var o kadar iyi bir uygulama şey gerçekten değil.
Erik Reppen

6
Senkronizasyon yöntemleri, başka bir senkronizasyon işlemi ile etkileşime girdiğinizde veya sıralı işlem yapmak istediğinizde (yani, yine de senkronizasyonu taklit ediyorsanız) iyidir. İşlemler sıralıysa, geri arama cehenneminden (ve / veya söz çorbasından) kaçının ve senkronizasyon yöntemini kullanın. Genel olarak sunucularda dikkatli kullanılmaları gerekir, ancak CLI komut dosyaları içeren çoğu durumda uygundur.
srcspider

18

Mike Schilling'in hata işleme çözümü, hata olay işleyicisi için bir kısayol ile.

function copyFile(source, target, cb) {
  var cbCalled = false;

  var rd = fs.createReadStream(source);
  rd.on("error", done);

  var wr = fs.createWriteStream(target);
  wr.on("error", done);
  wr.on("close", function(ex) {
    done();
  });
  rd.pipe(wr);

  function done(err) {
    if (!cbCalled) {
      cb(err);
      cbCalled = true;
    }
  }
}

18

Eşzamansız olmasını umursamıyorsanız ve gigabayt boyutlu dosyaları kopyalamıyorsanız ve yalnızca tek bir işlev için başka bir bağımlılık eklemek istemiyorsanız:

function copySync(src, dest) {
  var data = fs.readFileSync(src);
  fs.writeFileSync(dest, data);
}

4
Bu cevabı beğendim. Açık ve basit.
Rob Gleeson

7
@RobGleeson ve dosya içeriği kadar bellek gerektirir ... Orada upvotes sayısı ile şaşırdım.
Konstantin

Bir "ve gigabayt boyutlu dosyaları kopyalamıyorum" uyarısı ekledim.
Andrew Childs

fs.existsSyncÇağrı çıkarılmalıdır. Dosya, fs.existsSyncçağrı ve çağrı arasındaki sürede kaybolabilir fs.readFileSync, yani fs.existsSyncçağrı bizi hiçbir şeyden korumaz.
qntm

Buna ek olarak, falseeğer fs.existsSyncbaşarısız olursa geri dönmek muhtemelen düşük ergonomiye sahiptir, çünkü az sayıda tüketici copySyncher çağrıldığında geri dönüş değerini el ile incelemeyi düşünecektir fs.writeFileSync . . Bir istisna atmak aslında tercih edilir.
qntm

2
   const fs = require("fs");
   fs.copyFileSync("filepath1", "filepath2"); //fs.copyFileSync("file1.txt", "file2.txt");

Kişisel olarak bir dosyayı kopyalamak ve node.js kullanarak başka bir dosyayı değiştirmek için kullandığım şey budur :)


1
Bu, IO-heavy uygulamasında dosyaları verimli bir şekilde nasıl kopyalayacağınız sorusuna cevap vermez.
Jared Smith

@JaredSmith Doğru, ancak google aramam beni buraya getiriyor ve istediğim şey bu.
kodple

1

Hızlı kopyalar için fs.constants.COPYFILE_FICLONEbayrağı kullanmalısınız . (Bunu destekleyen dosya sistemleri için) dosyanın içeriğini gerçekten kopyalamamasına izin verir. Yalnızca yeni bir dosya girişi oluşturulur, ancak Yazarken Kopyala'yı gösterir kaynak dosyanın "klonunu" gösterir.

Hiçbir şey / daha az şey yapmak, bir şey yapmanın en hızlı yoludur;)

https://nodejs.org/api/fs.html#fs_fs_copyfile_src_dest_flags_callback

let fs = require("fs");

fs.copyFile(
  "source.txt",
  "destination.txt",
  fs.constants.COPYFILE_FICLONE,
  (err) => {
    if (err) {
      // TODO: handle error
      console.log("error");
    }
    console.log("success");
  }
);

Bunun yerine vaatleri kullanmak:

let fs = require("fs");
let util = require("util");
let copyFile = util.promisify(fs.copyFile);


copyFile(
  "source.txt",
  "destination.txt",
  fs.constants.COPYFILE_FICLONE
)
  .catch(() => console.log("error"))
  .then(() => console.log("success"));

fs.promises.copyFile
gman

0

kopyadan önce dosyanın görünürlüğünü kontrol eden benweet çözümü:

function copy(from, to) {
    return new Promise(function (resolve, reject) {
        fs.access(from, fs.F_OK, function (error) {
            if (error) {
                reject(error);
            } else {
                var inputStream = fs.createReadStream(from);
                var outputStream = fs.createWriteStream(to);

                function rejectCleanup(error) {
                    inputStream.destroy();
                    outputStream.end();
                    reject(error);
                }

                inputStream.on('error', rejectCleanup);
                outputStream.on('error', rejectCleanup);

                outputStream.on('finish', resolve);

                inputStream.pipe(outputStream);
            }
        });
    });
}

0

Kopyalama işlevinde yerleşik nodejs neden kullanılmıyor?

Hem eşzamansız hem de eşitleme sürümünü sağlar:

const fs = require('fs');

// destination.txt will be created or overwritten by default.
fs.copyFile('source.txt', 'destination.txt', (err) => {
  if (err) throw err;
  console.log('source.txt was copied to destination.txt');
});

https://nodejs.org/api/fs.html#fs_fs_copyfilesync_src_dest_flags


3
Bu cevap yinelendiği için oylama yapılmıyor.
Qwertie

-1

Mike'ın çözümü , ancak vaatlerle:

const FileSystem = require('fs');

exports.copyFile = function copyFile(source, target) {
    return new Promise((resolve,reject) => {
        const rd = FileSystem.createReadStream(source);
        rd.on('error', err => reject(err));
        const wr = FileSystem.createWriteStream(target);
        wr.on('error', err => reject(err));
        wr.on('close', () => resolve());
        rd.pipe(wr);
    });
};

@Royi Çünkü zaman uyumsuz bir çözüm istedim ...?
mpen

-1

Bir diğer cevabın geliştirilmesi.

Özellikleri:

  • Dst klasörleri yoksa, otomatik olarak oluşturur. Diğer cevap sadece hata verecektir.
  • A döndürür promise, bu da daha büyük bir projede kullanımını kolaylaştırır.
  • Birden fazla dosyayı kopyalamanıza izin verir ve hepsi kopyalandığında söz verilecektir.

Kullanımı:

var onePromise = copyFilePromise("src.txt", "dst.txt");
var anotherPromise = copyMultiFilePromise(new Array(new Array("src1.txt", "dst1.txt"), new Array("src2.txt", "dst2.txt")));

Kod:

function copyFile(source, target, cb) {
    console.log("CopyFile", source, target);

    var ensureDirectoryExistence = function (filePath) {
        var dirname = path.dirname(filePath);
        if (fs.existsSync(dirname)) {
            return true;
        }
        ensureDirectoryExistence(dirname);
        fs.mkdirSync(dirname);
    }
    ensureDirectoryExistence(target);

    var cbCalled = false;
    var rd = fs.createReadStream(source);
    rd.on("error", function (err) {
        done(err);
    });
    var wr = fs.createWriteStream(target);
    wr.on("error", function (err) {
        done(err);
    });
    wr.on("close", function (ex) {
        done();
    });
    rd.pipe(wr);
    function done(err) {
        if (!cbCalled) {
            cb(err);
            cbCalled = true;
        }
    }
}

function copyFilePromise(source, target) {
    return new Promise(function (accept, reject) {
        copyFile(source, target, function (data) {
            if (data === undefined) {
                accept();
            } else {
                reject(data);
            }
        });
    });
}

function copyMultiFilePromise(srcTgtPairArr) {
    var copyFilePromiseArr = new Array();
    srcTgtPairArr.forEach(function (srcTgtPair) {
        copyFilePromiseArr.push(copyFilePromise(srcTgtPair[0], srcTgtPair[1]));
    });
    return Promise.all(copyFilePromiseArr);
}

-2

Kaynak dosyanın varlığını kontrol etmeyen yukarıdaki tüm çözümler tehlikelidir ... örn.

fs.stat(source, function(err,stat) { if (err) { reject(err) }

Aksi takdirde, kaynak ve hedefin bir hata ile değiştirilmesi durumunda senaryoda bir risk vardır, verileriniz herhangi bir hata fark etmeden kalıcı olarak kaybolacaktır.


Bunun da bir yarış koşulu vardır: dosya, stating ile okuma / yazma / kopyalama arasında yok edilebilir. İşlemi denemek ve ortaya çıkan herhangi bir hatayla uğraşmak her zaman daha iyidir.
Jared Smith

bir yazma işleminden önce hedefin varlığını denetlemek, yanlışlıkla hedefin üzerine yazmamanızı sağlar, örneğin hedef ve kaynağın kullanıcı tarafından yanlışlıkla aynı şekilde ayarlandığı bir senaryoyu kapsar ... sonra yazma işleminin başarısız olmasını beklemek için geç ... whosever bana verdi (-1) bu olay projenizde gerçekleştikten sonra sıralamanızı gözden geçirin :-) yeniden. yarışlar - yoğun trafikli sitelerde her zaman senkronizasyon güvencesi gerektiren bir işlem işleme operasyonu olması önerilir - evet o zaman performans darboğazı
stancikcom

Yanlış yorum yapmadım çünkü yanılıyorsun , aşağı ittim çünkü bu sorunun cevabı değil. Mevcut bir cevap hakkında uyarıcı bir yorum olmalıdır.
Jared Smith

iyi - bir sağ örneğin andrew childs çözümü (18 upvotes ile) bir sunucu / büyük dosyalar üzerinde kaynakları tükenecek ... Ben ona yorum yazmak ama yorum yapmak için itibar yok - bu nedenle benim yazı bağımsız gördüm. ... ama Jared sürüm düşürme benim için basit bir geçiş anlamına gelir - sessiz kalmak ve insanların çoğunlukla "çalışır" tehlikeli kod yazmak ve paylaşmak izin ...
stancikcom

Anladım, kimse olumsuz geribildirimden hoşlanmıyor . Ama bu sadece bir iniş. OP'nin sorduğu soruya cevap vermediği ve yorum yapmak için yeterince kısa olduğu için, bunu verme nedenime dayanıyorum. Ancak istediğiniz gibi alabilir, ancak bu tür bir şeyi orantısız bir şekilde patlarsanız, yığın taşmasını çok sinir bozucu bir deneyim olarak bulacaksınız.
Jared Smith
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.