Найшвидший спосіб копіювання файлу в node.js


488

Проект, над яким я працюю (node.js), передбачає безліч операцій з файловою системою (копіювання / читання / запис тощо). Я хотів би знати, які методи найшвидші, і я би радий отримати пораду. Дякую.


42
Це гарне запитання, хоча цікаво, що він отримує 25 оновлень, коли інші подібні запитання формату одразу отримають 3 чи 4 скорочення за те, що вони не відповідають "стандартам ТА" (можливо, тег javascript сканує добріші люди :)
Ben

22
Здебільшого ми просто нові та схвильовані щодо всього цього "файлового" бізнесу після багатьох років нормалізації роботи браузерів.
Ерік Реппен

3
Єдина правильна відповідь на сторінці - це ця . Жодна з інших відповідей фактично не копіює файли. У файлах на MacOS та Windows є інші метадані, які втрачаються шляхом копіювання байтів. Приклади даних, які не скопійовані жодною іншою відповіддю на цій сторінці, вікнами та мако . Навіть у Unix інші відповіді не копіюють дату створення, що часто важливо при копіюванні файлу.
gman

Відповіді:


717

Це хороший спосіб скопіювати файл в один рядок коду за допомогою потоків:

var fs = require('fs');

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

У вузлі v8.5.0 додано copyFile

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
Пам'ятайте лише, що в реальному житті ви хочете перевірити createReadStreamі createWriteStreamпомилки, і помилки, щоб не отримати однолінійного вкладиша (хоча це все одно буде так само швидко).
ebohlman

18
Наскільки це швидше / повільніше, ніж виконання сировини cp test.log newLog.logчерез require('child_process').exec?
Ленс Поллард

41
Ну copyне є портативним у Window, всупереч повному рішенню Node.js.
Жан

12
На жаль, у моїй системі використання потоків надзвичайно повільно порівняно з child_process.execFile('/bin/cp', ['--no-target-directory', source, target]).
Роберт

12
Я використовував цей метод, і все, що я отримав, це порожній файл при записі. будь-які ідеї чому? fs.createReadStream('./init/xxx.json').pipe(fs.createWriteStream('xxx.json'));
Тіммерз

293

Той самий механізм, але це додає поводження з помилками:

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
Варто зазначити, що прапор cbCalled необхідний, оскільки помилки в трубі викликають помилку в обох потоках. Джерела та потоки призначення.
Гастон Санчес

4
Як ви вирішите помилку, якщо вихідний файл не існує? У цьому випадку все ще створюється файл призначення.
Мішель Хуа

1
Я думаю, що помилка у WriteStreamзаповіті лише відколює її. Вам би довелося зателефонувати rd.destroy()собі. Принаймні, так сталося зі мною. На жаль, не так багато документації, окрім вихідного коду.
Роберт

що означає cbпідставка? що ми маємо передати як третій аргумент?
SaiyanGirl

4
@SaiyanGirl 'cb' означає "зворотний виклик". Ви повинні пройти функцію.
Брайан Дж. Міллер

143

Я не зміг з createReadStream/createWriteStreamякихось причин змусити метод працювати, але за допомогою fs-extraмодуля npm він працював відразу. Я не впевнений у різниці в продуктивності.

fs-extra

npm install --save fs-extra

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

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

3
Зараз це найкращий варіант
Заїн Різві

11
Використання синхронного коду у вузлі вбиває продуктивність вашої програми.
mvillar

3
О, будь ласка ... Питання про найшвидший спосіб копіювання файлу. Хоча швидкий завжди суб'єктивний, я не думаю, що синхронний фрагмент коду тут не має жодної справи.
sampathsris

24
Найшвидший у виконанні чи найшвидший у виконанні? Різні пріоритети означають, що це правильна відповідь.
Патрік Гундерсон

14
fs-extra також має асинхронні методи, тобто fs.copy(src, dst, callback);вони повинні вирішити занепокоєння @ mvillar.
Марк Дурдін

134

З Node.js 8.5.0 у нас є нові методи fs.copyFile та fs.copyFileSync .

Приклад використання:

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
Це єдина правильна відповідь на сторінці. Жодна з інших відповідей фактично не копіює файли. У файлах на MacOS та Windows є інші метадані, які втрачаються шляхом копіювання байтів. Приклади даних, які не скопійовані жодною іншою відповіддю на цій сторінці, вікнами та мако . Навіть у Unix інша відповідь не копіює дату створення, що часто важливо при копіюванні файлу.
gman

ну, на жаль, це не вдається скопіювати все на mac. Сподіваємось, вони це виправлять: github.com/nodejs/node/isissue/30575
gman

BTW майте на увазі, що copyFile()помилка під час перезапису довших файлів. Надано uv_fs_copyfile()до Node v8.7.0 (libuv 1.15.0). дивіться github.com/libuv/libuv/pull/1552
Антон Рудешко

74

Швидкий у написанні та зручний у використанні, з обіцянками та управління помилками.

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

Те саме з синтаксисом async / wait:

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
Що станеться, коли більше немає вводу (зламана мережа), але запис все-таки вдається? Чи буде викликано як відхилення (від читання), так і рішення (з запису)? Що робити, якщо обидва читання / запису не вдається (поганий сектор диска під час читання, повний диск під час запису)? Тоді відхилення буде викликано двічі. Обіцяне рішення, засноване на відповіді Майка з прапором (на жаль), здається, єдиним життєздатним рішенням, яке належним чином враховує обробку помилок.
Лекенштейн

Обіця буде вирішена, як тільки копія вдасться. Якщо його буде відхилено, його стан буде врегульовано, а виклик відхилення декілька разів не матиме значень.
benweet

2
Я просто перевіряв 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);});і подивився на специфікацію на це , і ви маєте рацію: Спроба вирішити або відхилити дозволена обіцянку не має ніякого ефекту. Можливо, ви могли б розширити свою відповідь і пояснити, чому ви написали функцію таким чином? Дякую :-)
Лекенштейн

2
До речі, closeмає бути finishдля записуваних потоків.
Лекенштейн

І якщо вам цікаво, чому ваша програма ніколи не закривається після помилок на трубі /dev/stdin, це помилка github.com/joyent/node/isissue/25375
Lekensteyn

43

Ну, зазвичай добре уникати асинхронних файлових операцій. Ось короткий приклад синхронізації (тобто відсутність обробки помилок):

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

8
Сказати, що загалом це вкрай помилково, тим більше, що це призводить до того, що люди повторно видають файли для кожного запиту, зробленого на їх сервері. Це може вийти дорого.
Каталізатор

8
використання *Syncметодів повністю проти філософії nodejs! Я також думаю, що вони повільно застаріли. Вся ідея nodejs полягає в тому, що це один потік і керований подіями.
gillyb

11
@gillyb Єдиною причиною, яку я можу подумати щодо їх використання, є простота - якщо ви пишете швидкий сценарій, який ви будете використовувати лише один раз, ви, ймовірно, не будете всім, що турбуєсь про блокування процесу.
starbeamrainbowlabs

13
Я не знаю, як вони застаріли. Методи синхронізації майже завжди є жахливою ідеєю на веб-сервері, але іноді ідеально підходить як-небудь на зразок node-webkit, де він лише блокує дії у вікні, коли файли копіюють. Підкиньте gif завантаження та, можливо, бар завантаження, який оновлюється в певні моменти, і дозвольте методам синхронізації блокувати всі дії, поки не буде виконано копіювання. Це насправді не найкраща практика настільки, як коли і де вони мають своє місце.
Ерік Реппен

6
Методи синхронізації прекрасні, коли ви взаємодієте з іншою операцією синхронізації, або якщо ви хочете, це виконувати послідовну операцію (тобто ви б в будь-якому випадку емулявали синхронізацію). Якщо операції послідовні, просто уникайте зворотного виклику (та / або обіцянки супу) та використовуйте метод синхронізації. Як правило, їх слід застосовувати з обережністю на серверах, але вони прекрасні для більшості випадків, що включають сценарії CLI.
srcspider

18

Рішення Майка Шиллінга з вирішенням помилок із скороченням для обробника подій помилки.

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

Якщо вам не байдуже, що це асинхронізація і не копіює файли розміром гігабайт, і не бажає додавати іншу залежність лише від однієї функції:

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

4
Мені подобається ця відповідь. Чіткий і простий.
Роб Глісон

7
@RobGleeson, і потрібна стільки пам’яті, скільки вміст файлу ... Мене вражає кількість анонсів там.
Костянтин

Я додав застереження "і не копіюють файли розміром гігабайт".
Ендрю Чайлдс

fs.existsSyncВиклик повинен бути опущений. Файл може зникнути за час між fs.existsSyncдзвінком та fs.readFileSyncвикликом, що означає, що fs.existsSyncдзвінок не захищає нас ні від чого.
qntm

Крім того, повернення у falseвипадку fs.existsSyncневдачі - це, мабуть, погана ергономіка, оскільки мало хто з споживачів copySyncподумає вручну перевірити значення повернення кожного разу, коли воно викликане, більше, ніж ми робимо для fs.writeFileSync співавторів та ін. . Кидати виняток насправді бажано.
qntm

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

Це те, що я особисто використовую, щоб скопіювати файл та замінити інший файл за допомогою node.js :)


1
Це не дає відповіді на запитання, яке стосується того, як ефективно копіювати файли в IO-важкому додатку.
Джаред Сміт

@JaredSmith Щоправда, але пошук у google веде мене сюди, і це те, чого я хотів.
codepleb

1

Для швидких копій слід використовувати fs.constants.COPYFILE_FICLONEпрапор. Це дозволяє (для файлових систем, що підтримують це) насправді не копіювати вміст файлу. Просто створюється новий запис файлу, але він вказує на "клон" копіювання на запис "вихідний файл".

Не робити нічого / менш - це найшвидший спосіб зробити щось;)

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

Використовуючи обіцянки замість цього:

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

рішення benweet перевіряє видимість файлу перед копією:

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

Чому б не використовувати nodejs, вбудований у функцію копіювання?

Він забезпечує як асинхронізацію, так і синхронізацію версії:

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
Не вимагає, оскільки ця відповідь є дублікатом.
Qwertie

-1

Рішення Майка , але з обіцянками:

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 Тому що я хотів вирішити асинхронний ...
mpen

-1

Вдосконалення однієї іншої відповіді.

Особливості:

  • Якщо папок dst не існує, він автоматично створить його. Інша відповідь призведе лише до помилок.
  • Він повертає a promise, що полегшує його використання у більшому проекті.
  • Це дозволяє копіювати кілька файлів, і обіцянка буде виконана, коли всі вони будуть скопійовані.

Використання:

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

Код:

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

всі вищезазначені рішення, які не перевіряють існування вихідного файлу, небезпечні ... наприклад

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

інакше існує ризик у сценарії, якщо джерело та ціль помилково замінені, ваші дані будуть назавжди втрачені, не помічаючи жодної помилки.


Це також має переговорну умову: файл може бути знищений між статизуванням його та читанням / записом / копіюванням. Завжди краще просто спробувати операцію і вирішити будь-яку виникаючу помилку.
Джаред Сміт

перевірка існування цілі перед операцією запису гарантує, що ви не перезапишете ціль випадково, наприклад, охоплює сценарій, коли призначення та джерело користувач встановлює помилково однаково ... тоді вже пізно чекати, коли операція запису завершиться ... хто мені дав (-1), будь ласка, перегляньте ваш рейтинг, коли цей інцидент трапиться у вашому проекті :-) повторно. гонки - на важких трафічних сайтах завжди рекомендується проводити одну операцію по обробці технологічних процесів, яка вимагає забезпечення синхронізації - так, це тоді вузьке місце продуктивності
stancikcom

Я не порушив голос, тому що ви помиляєтесь , я спростував, тому що це не відповідь на питання. Це має бути застережливим коментарем щодо існуючої відповіді.
Джаред Сміт

добре - у вас правильне рішення, наприклад, andrew childs (з 18 оновленнями), буде втрачено на ресурсах на сервері / великих файлах ... Я б писав йому коментарі, але я не маю репутації коментувати - тому ви бачили мій пост самостійним. ... але Джаред, ваш пониження означає для мене просту дорогу - мовчіть, і нехай люди пишуть і діляться небезпечним кодом, який здебільшого "працює" ...
stancikcom

Я розумію, нікому не подобаються негативні відгуки. Але це просто голос. Я відстоюю свою причину, оскільки це не відповідає на запитання ОП, і воно є досить коротким, щоб бути коментарем. Ви можете приймати його як завгодно, але якщо ви видуєте таку річ непропорційно, ви переконаєтеся, що переповнення стека буде дуже засмучуючим.
Джаред Сміт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.