Самый быстрый способ скопировать файл в 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.