Самый быстрый способ скопировать файл в node.js


488

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


42
Это хороший вопрос, хотя интересно, что он получает 25 голосов «за», когда другие вопросы аналогичного формата сразу получают 3 или 4 отзыва за то, что они не соответствуют «стандартам» SO (возможно, тег javascript сканируется более добрыми людьми :)
— Бен

22
В основном мы просто новички и взволнованы этим целым бизнесом по работе с файлами после многих лет нормализации работы браузеров.
— Эрик Реппен

3
Единственный правильный ответ на странице - этот . Ни один из других ответов на самом деле не копирует файлы. Файлы в MacOS и Windows содержат другие метаданные, которые теряются при копировании байтов. Примеры данных, не скопированных другими ответами на этой странице, окна и макросы . Даже в Unix другие ответы не копируют дату создания, что часто важно при копировании файла.
— человек

Ответы:


718

Это хороший способ скопировать файл в одну строку кода, используя потоки:

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 он сразу заработал. Я не уверен в разнице в производительности, хотя.

фс-экстра

npm install --save fs-extra

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

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

3
Сейчас это лучший вариант
— Зейн Ризви,

11
Использование синхронного кода в узле снижает производительность вашего приложения.
— Мвиллар

3
О, пожалуйста ... Вопрос о самом быстром способе копирования файла. Хотя самый быстрый всегда субъективен, я не думаю, что синхронный фрагмент кода имеет какое-то дело.
— Сампатрисрис

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 другой ответ не копирует дату создания, что часто важно при копировании файла.
— человек

ну, к сожалению, это не в состоянии скопировать все на Mac. Надеюсь, они это исправят: github.com/nodejs/node/issues/30575
— gman

Кстати, имейте в виду, что copyFile()ошибка при перезаписи более длинных файлов. Предоставлено uv_fs_copyfile()Till 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 / 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
Что происходит, когда больше нет входных данных (нарушен общий сетевой ресурс), но запись по-прежнему успешно выполняется? Будут ли вызваны как отклонение (от чтения), так и разрешение (от записи)? Что, если оба чтения / записи терпят неудачу (плохие сектора диска во время чтения, полный диск во время записи)? Тогда отклонение будет вызвано дважды. Решение Promise, основанное на ответе Майка с флагом (к сожалению), кажется единственным жизнеспособным решением, которое должным образом учитывает обработку ошибок.
— Лекенштейн

Обещание разрешается после успешного завершения копирования. Если он отклонен, его состояние устанавливается, и вызов reject несколько раз не будет иметь значения.
— Бенвит

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/issues/25375
— Lekensteyn

43

Ну, обычно хорошо избегать асинхронных файловых операций. Вот краткий (т.е. без обработки ошибок) пример синхронизации:

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

8
Сказать, что в целом это крайне неверно, особенно потому, что это приводит к тому, что люди переплавляют файлы для каждого запроса к их серверу. Это может стать дорогим.
— Катализатор

8
использование *Syncметодов полностью противоречит философии nodejs! Я также думаю, что они медленно осуждаются. Вся идея nodejs заключается в том, что он однопоточный и управляемый событиями.
— Гиллиб

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вызов не защищает нас от чего-либо.
— 2

Кроме того, возврат, falseесли fs.existsSyncпроизошел сбой, вероятно, является плохой эргономикой, потому что немногие потребители copySyncбудут думать, чтобы вручную проверять возвращаемое значение каждый раз, когда он вызывается , больше, чем мы делаем для fs.writeFileSync et al. , Бросать исключение на самом деле предпочтительнее.
— 2

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

Это то, что я лично использую, чтобы скопировать файл и заменить другой файл, используя node.js :)


1
Это не отвечает на вопрос о том, как эффективно копировать файлы в приложениях с интенсивным вводом-выводом.
— Джаред Смит

@JaredSmith Правда, но мой поиск в Google привел меня сюда, и это то, что я хотел.
— codepleb

1

Для быстрых копий вы должны использовать fs.constants.COPYFILE_FICLONEфлаг. Это позволяет (для файловых систем, которые поддерживают это) фактически не копировать содержимое файла. Просто создается новая запись в файле, но она указывает на Copy-on-Write «клон» исходного файла .

Ничего не делать - это самый быстрый способ что-то сделать;)

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
— мужчина

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

в противном случае в сценарии существует риск, если источник и цель по ошибке будут заменены, ваши данные будут навсегда потеряны без замечания какой-либо ошибки.


Это также имеет условие состязания: файл может быть уничтожен между его статистикой и чтением / записью / копированием. Всегда лучше просто попробовать операцию и справиться с любой возникшей ошибкой.
— Джаред Смит

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

Я не отрицал, потому что вы не правы , я отрицал, потому что это не ответ на вопрос. Это должен быть предупредительный комментарий к существующему ответу.
— Джаред Смит

хорошо - вы, например, право, например, решение Andrew Childs (с 18 ответами), исчерпают ресурсы на сервере / большие файлы ... я бы написал ему комментарии, но у меня нет репутации, чтобы комментировать - поэтому вы видели мой пост автономным. ... но Джаред, ваше понижение означает для меня простой путь - молчите и позволяйте людям писать и делиться опасным кодом, который в основном "работает" ...
— stancikcom

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