Cara tercepat untuk menyalin file di node.js


488

Proyek yang sedang saya kerjakan (node.js) menyiratkan banyak operasi dengan sistem file (menyalin / membaca / menulis dll). Saya ingin tahu metode mana yang paling cepat, dan saya senang mendapatkan saran. Terima kasih.


42
Ini pertanyaan yang bagus, meskipun menarik bahwa mendapat 25 upvotes ketika pertanyaan format serupa lainnya akan mendapatkan 3 atau 4 downvotes segera karena tidak memenuhi "standar" SO (mungkin tag javascript dirayapi oleh orang-orang yang ramah :)
Ben

22
Sebagian besar kami baru segar dan bersemangat tentang seluruh bisnis "file" ini setelah bertahun-tahun normalisasi browser.
Erik Reppen

3
Satu-satunya jawaban yang benar di halaman adalah yang ini . Tidak ada jawaban lain yang benar-benar menyalin file. File di MacOS dan Windows memiliki metadata lain yang hilang hanya dengan menyalin byte. Contoh data yang tidak disalin oleh jawaban lain di halaman ini, jendela dan makro . Bahkan di Unix jawaban lain tidak menyalin tanggal pembuatan, sesuatu yang sering penting ketika menyalin file.
GM

Jawaban:


717

Ini adalah cara yang baik untuk menyalin file dalam satu baris kode menggunakan stream:

var fs = require('fs');

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

Dalam simpul v8.5.0, copyFile ditambahkan

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
Ingatlah bahwa dalam kehidupan nyata, Anda ingin memeriksa kesalahan createReadStreamdan juga createWriteStreamkesalahannya, sehingga Anda tidak akan mendapatkan satu kalimat (meskipun itu akan tetap sama cepatnya).
ebohlman

18
Seberapa cepat / lambat ini daripada mengeksekusi mentah cp test.log newLog.logmelalui require('child_process').exec?
Lance Pollard

41
Yah copytidak portabel di Window, bertentangan dengan solusi Node.js penuh.
Jean

12
Sayangnya pada sistem saya menggunakan stream sangat lambat dibandingkan dengan child_process.execFile('/bin/cp', ['--no-target-directory', source, target]).
Robert

12
Saya menggunakan metode ini dan yang saya dapatkan hanyalah file kosong yang sedang ditulis. ada ide kenapa? fs.createReadStream('./init/xxx.json').pipe(fs.createWriteStream('xxx.json'));
Timmerz

293

Mekanisme yang sama, tetapi ini menambahkan penanganan kesalahan:

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
Perlu dicatat bahwa flag cbCalled diperlukan karena kesalahan pipa memicu kesalahan pada kedua aliran. Sumber dan tujuan stream.
Gaston Sanchez

4
Bagaimana Anda menangani kesalahan jika file sumber tidak ada? File tujuan masih akan dibuat dalam kasus itu.
Michel Hua

1
Saya pikir kesalahan dalam WriteStreamhanya akan menghapusnya. Anda harus menyebut rd.destroy()diri Anda sendiri. Setidaknya itulah yang terjadi pada saya. Sayangnya tidak ada banyak dokumentasi kecuali dari kode sumber.
Robert

apa artinya cbberdiri? apa yang harus kita sampaikan sebagai argumen ketiga?
SaiyanGirl

4
@SaiyanGirl 'cb' adalah singkatan dari "callback". Anda harus melewati suatu fungsi.
Brian J. Miller

143

Saya tidak bisa mendapatkan createReadStream/createWriteStreammetode ini bekerja karena alasan tertentu, tetapi menggunakan fs-extramodul npm itu berhasil segera. Saya tidak yakin dengan perbedaan kinerja.

fs-extra

npm install --save fs-extra

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

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

3
Ini adalah pilihan terbaik sekarang
Zain Rizvi

11
Menggunakan kode sinkron dalam simpul membunuh kinerja aplikasi Anda.
mvillar

3
Oh tolong ... Pertanyaannya adalah tentang metode tercepat untuk menyalin file. Walaupun tercepat selalu subyektif, saya tidak berpikir sepotong kode sinkron memiliki bisnis di sini.
sampathsris

24
Tercepat untuk mengimplementasikan atau tercepat untuk dijalankan? Prioritas yang berbeda berarti ini adalah jawaban yang valid.
Patrick Gunderson

14
fs-extra juga memiliki metode asinkron, yaitu fs.copy(src, dst, callback);, dan ini harus menyelesaikan masalah @ mvillar.
Marc Durdin

134

Sejak Node.js 8.5.0 kami memiliki fs.copyFile baru dan fs.copyFileSync metode.

Contoh Penggunaan:

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
Ini adalah satu-satunya jawaban yang benar di halaman. Tidak ada jawaban lain yang benar-benar menyalin file. File di MacOS dan Windows memiliki metadata lain yang hilang hanya dengan menyalin byte. Contoh data yang tidak disalin oleh jawaban lain di halaman ini, jendela dan makro . Bahkan di Unix jawaban lain tidak menyalin tanggal pembuatan, sesuatu yang sering penting ketika menyalin file.
GM

nah sayangnya ini gagal untuk menyalin semuanya di mac. Semoga mereka akan memperbaikinya: github.com/nodejs/node/issues/30575
gman

BTW perlu diingat bahwa copyFile()disadap saat menimpa file yang lebih panjang. Atas perkenan uv_fs_copyfile()sampai Node v8.7.0 (libuv 1.15.0). lihat github.com/libuv/libuv/pull/1552
Anton Rudeshko

74

Cepat untuk menulis dan nyaman digunakan, dengan manajemen janji dan kesalahan.

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;
  });
}

Sama dengan sintaks async / await:

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
Apa yang terjadi ketika tidak ada lagi input (berbagi jaringan rusak), tetapi penulisan masih berhasil? Akankah keduanya menolak (dari membaca) dan menyelesaikan (dari menulis) dipanggil? Bagaimana jika keduanya membaca / menulis gagal (sektor disk buruk saat membaca, disk penuh saat menulis)? Kemudian tolak akan dipanggil dua kali. Solusi Janji berdasarkan jawaban Mike dengan bendera (sayangnya) tampaknya menjadi satu-satunya solusi yang layak yang mempertimbangkan penanganan kesalahan.
Lekensteyn

Janji itu diselesaikan setelah salinan berhasil. Jika ditolak, keadaannya diselesaikan dan pemanggilan penolakan berulang kali tidak akan membuat perbedaan.
benweet

2
Saya baru saja menguji 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);});dan mencari spesifikasi ini dan Anda benar: Mencoba untuk menyelesaikan atau menolak janji yang telah diselesaikan tidak akan berpengaruh. Mungkin Anda bisa memperluas jawaban dan menjelaskan mengapa Anda telah menulis fungsi dengan cara ini? Terima kasih :-)
Lekensteyn

2
By the way, closeharus finishuntuk stream yang dapat ditulisi.
Lekensteyn

Dan jika Anda bertanya-tanya mengapa aplikasi Anda tidak pernah ditutup setelah kesalahan pipa aktif /dev/stdin, itu adalah bug github.com/joyent/node/issues/25375
Lekensteyn

43

Yah, biasanya ada baiknya menghindari operasi file yang tidak sinkron. Berikut adalah contoh sinkronisasi pendek (mis. Tidak ada penanganan kesalahan):

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

8
Mengatakan bahwa secara umum adalah sangat salah, terutama karena itu menyebabkan orang menghirup kembali file untuk setiap permintaan yang dilakukan ke server mereka. Ini bisa mahal.
Catalyst

8
menggunakan *Syncmetode ini benar-benar bertentangan dengan filosofi nodejs '! Saya juga berpikir mereka perlahan-lahan ditinggalkan. Seluruh ide dari nodejs adalah bahwa itu single threaded dan event-driven.
gillyb

11
@gillyb Satu-satunya alasan yang dapat saya pikirkan untuk menggunakannya adalah karena kesederhanaannya - jika Anda menulis skrip cepat yang hanya akan Anda gunakan sekali, Anda mungkin tidak akan repot-repot memblokir proses.
starbeamrainbowlabs

13
Saya tidak tahu mereka sudah ditinggalkan. Metode sinkronisasi hampir selalu merupakan ide yang mengerikan di server web tetapi kadang-kadang ideal untuk sesuatu seperti node-webkit di mana ia hanya mengunci tindakan di jendela saat file sedang disalin. Lemparkan gif pemuatan dan mungkin bilah muat yang diperbarui pada titik tertentu dan biarkan metode sinkronisasi memblokir semua tindakan hingga penyalinan selesai. Ini bukan benar-benar hal praktik terbaik seperti kapan dan di mana mereka memiliki tempat mereka.
Erik Reppen

6
Metode sinkronisasi baik-baik saja ketika Anda berinteraksi dengan operasi sinkronisasi lain atau apa yang Anda inginkan adalah melakukan operasi sekuensial (mis. Anda tetap akan meniru sinkronisasi). Jika operasi berurutan hindari neraka panggilan balik (dan / atau sup janji) dan gunakan metode sinkronisasi. Secara umum mereka harus digunakan dengan hati-hati pada server tetapi baik untuk sebagian besar kasus yang melibatkan skrip CLI.
srcspider

18

Solusi Mike Schilling dengan penanganan kesalahan dengan jalan pintas untuk penangan peristiwa kesalahan.

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

Jika Anda tidak peduli itu async, dan tidak menyalin file berukuran gigabyte, dan lebih suka tidak menambahkan ketergantungan lain hanya untuk satu fungsi:

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

4
Saya suka jawaban ini. Jelas dan sederhana.
Rob Gleeson

7
@RobGleeson, dan membutuhkan memori sebanyak konten file ... Saya kagum dengan jumlah upvotes di sana.
Konstantin

Saya telah menambahkan peringatan "dan tidak menyalin file berukuran gigabyte".
Andrew Childs

The fs.existsSyncpanggilan harus dihilangkan. File dapat hilang pada waktu antara fs.existsSyncpanggilan dan fs.readFileSyncpanggilan, yang berarti fs.existsSyncpanggilan tidak melindungi kita dari apa pun.
qntm

Selain itu, pengembalian falsejika fs.existsSyncgagal cenderung ergonomi yang buruk karena hanya sedikit konsumen yang copySyncakan berpikir untuk memeriksa secara manual nilai pengembalian setiap kali disebut, lebih dari yang kami lakukan untuk fs.writeFileSync et al. . Melempar pengecualian sebenarnya lebih disukai.
qntm

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

Inilah yang saya pribadi gunakan untuk menyalin file dan mengganti file lain menggunakan node.js :)


1
Ini tidak menjawab pertanyaan, yaitu tentang bagaimana cara menyalin file secara efisien dalam aplikasi yang beratnya IO.
Jared Smith

@ JaredSmith Benar, tapi pencarian google saya membawa saya ke sini dan inilah yang saya inginkan.
codepleb

1

Untuk salinan cepat Anda harus menggunakan fs.constants.COPYFILE_FICLONEbendera. Ini memungkinkan (untuk sistem file yang mendukung ini) untuk tidak benar-benar menyalin konten file. Hanya entri file baru yang dibuat, tetapi itu menunjuk ke Copy-on-Write "klon" dari file sumber.

Untuk tidak melakukan apa-apa / kurang adalah cara tercepat untuk melakukan sesuatu;)

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");
  }
);

Sebaliknya, menggunakan janji:

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
GM

0

solusi benweet memeriksa visibilitas file sebelum menyalin:

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

Mengapa tidak menggunakan nodejs built in fungsi salin?

Ini memberikan versi async dan sinkronisasi:

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
Tidak memilih karena jawaban ini adalah duplikat.
Qwertie

-1

Solusi Mike , tetapi dengan janji:

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 Karena saya menginginkan solusi async ...?
mpen

-1

Perbaikan satu jawaban lainnya.

Fitur:

  • Jika folder dst tidak ada, itu akan secara otomatis membuatnya. Jawaban lainnya hanya akan melempar kesalahan.
  • Ini mengembalikan a promise, yang membuatnya lebih mudah digunakan dalam proyek yang lebih besar.
  • Ini memungkinkan Anda untuk menyalin beberapa file, dan janji itu akan dilakukan ketika semuanya disalin.

Pemakaian:

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

Kode:

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

semua solusi di atas yang tidak memeriksa keberadaan file sumber berbahaya ... mis

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

jika tidak ada risiko dalam skenario jika sumber dan target diganti secara tidak sengaja, data Anda akan hilang secara permanen tanpa memperhatikan adanya kesalahan.


Ini juga memiliki kondisi lomba: file dapat dihancurkan antara membuat stat dan membaca / menulis / menyalin. Itu selalu lebih baik untuk hanya mencoba operasi dan mengatasi kesalahan yang dihasilkan.
Jared Smith

memeriksa keberadaan target sebelum operasi tulis memastikan Anda tidak menimpa target secara tidak sengaja misalnya mencakup skenario bahwa tujuan dan sumber ditetapkan oleh pengguna secara tidak sengaja sama ... maka sudah terlambat untuk menunggu operasi penulisan gagal ... yang memberi saya (-1) tolong tinjau peringkat Anda setelah kejadian ini terjadi di proyek Anda :-) re. ras - di situs trafik berat selalu disarankan untuk memiliki satu proses penanganan operasi yang membutuhkan jaminan sinkronisasi - ya itu kemudian bottleneck kinerja
stancikcom

Saya tidak mengundurkan diri karena Anda salah , saya mengundurkan diri karena ini bukan jawaban untuk pertanyaan itu. Itu harus menjadi komentar peringatan pada jawaban yang ada.
Jared Smith

baik - Anda contoh yang tepat solusi andrew childs (dengan 18 upvotes) akan kehabisan sumber daya pada server / file besar ... saya akan menulis komentar kepadanya tetapi saya tidak memiliki reputasi untuk berkomentar - karena itu Anda telah melihat posting saya standalone. ... tapi Jared penurunan peringkat Anda berarti jalan pintas yang mudah bagi saya - tetap diam dan biarkan orang menulis dan berbagi kode berbahaya yang sebagian besar "berfungsi" ...
stancikcom

Saya mengerti, tidak ada yang suka umpan balik negatif. Tapi itu hanya downvote. Saya mendukung alasan saya untuk memberikannya, karena ini tidak menjawab pertanyaan yang diajukan OP dan cukup singkat untuk dijadikan komentar. Anda dapat mengambilnya sesuka Anda, tetapi jika Anda meledakkan hal semacam itu di luar proporsi Anda akan menemukan stack overflow menjadi pengalaman yang sangat membuat frustrasi.
Jared Smith
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.