在node.js中复制文件的最快方法


488

我正在处理的项目(node.js)包含文件系统的大量操作(复制/读取/写入等)。我想知道哪些方法是最快的,我很乐意得到建议。谢谢。


42
这是一个很好的问题,尽管有趣的是,当其他类似格式的问题由于不符合SO“标准”而可能立即获得3或4个下投票时,它会获得25个上投票(也许javascript标记是由善良的人抓取的:)
Ben

22
通常,经过多年规范化浏览器之后,我们才对整个“文件”业务感到新鲜和兴奋。
Erik Reppen

3
页面上唯一正确的答案就是这个。其他答案均未实际复制文件。MacOS和Windows上的文件还有其他元数据,这些元数据仅由于复制字节而丢失。未通过此页面上的其他答案Windowsmacos复制的数据示例。即使在Unix上,其他答案也不会复制创建日期,这在复制文件时通常很重要。
gman

Answers:


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 2012年

18
这比执行原有多快/慢是cp test.log newLog.log通过require('child_process').exec
Lance Pollard

41
copy,与完整的Node.js解决方案相反,它不是在Windows上可移植的。
吉恩(Jean)

12
不幸的是,与相比,在我的系统上使用流的速度非常慢 child_process.execFile('/bin/cp', ['--no-target-directory', source, target])
罗伯特

12
我使用了这种方法,得到的只是一个空白文件。有什么想法吗?fs.createReadStream('./init/xxx.json').pipe(fs.createWriteStream('xxx.json'));
Timmerz 2014年

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标志,因为管道错误会在两个流上触发一个错误。源流和目标流。
加斯顿·桑切斯2014年

4
如果源文件不存在,如何处理该错误?在这种情况下仍会创建目标文件。
Michel Hua

1
我认为WriteStream遗嘱中的错误只会解开它。您必须打电话给rd.destroy()自己。至少那是发生在我身上的事情。遗憾的是,除了源代码外,没有多少文档。
罗伯特

代表什么cb?作为第三个论点我们应该传递什么?
SaiyanGirl 2015年

4
@SaiyanGirl'cb'代表“回调”。您应该传递一个函数。
Brian J. Miller

143

createReadStream/createWriteStream由于某种原因,我无法使该方法正常工作,但是使用fs-extranpm模块,它可以立即工作。我不确定性能是否有所不同。

fs-extra

npm install --save fs-extra

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

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

3
现在,这是最好的选择
Zain Rizvi 2015年

11
在节点中使用同步代码会降低应用程序性能。
2015年

3
哦,请...问题是有关复制文件的最快方法。虽然最快总是主观的,但我认为同步代码在这里没有任何作用。
sampathsris 2015年

24
最快实施还是最快执行?不同的优先级意味着这是一个有效的答案。
Patrick Gunderson

14
fs-extra还具有异步方法,即fs.copy(src, dst, callback);,这些应解决@mvillar的问题。
马克·杜丁

134

从Node.js 8.5.0开始,我们有了新的fs.copyFilefs.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上的文件还有其他元数据,这些元数据仅由于复制字节而丢失。未通过此页面上的其他答案Windowsmacos复制的数据示例。即使在Unix上,其他答案也不会复制创建日期,这在复制文件时通常很重要。
gman

很遗憾,这无法复制Mac上的所有内容。希望他们能解决它:github.com/nodejs/node/issues/30575
gman

顺便说一句,请记住,copyFile()在覆盖更长的文件时会出现错误。由uv_fs_copyfile()直到Node v8.7.0(libuv 1.15.0)提供。参见github.com/libuv/libuv/pull/1552
Anton Rudeshko

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
当没有更多输入(网络共享中断)但写入仍然成功时,会发生什么?会同时拒绝(从读取)和解析(从写入)吗?如果两个读/写都失败(读时坏磁盘扇区,写时满磁盘)怎么办?然后拒绝将被调用两次。基于Mike带标志的答案的Promise解决方案(不幸的是)似乎是唯一能够正确考虑错误处理的可行解决方案。
Lekensteyn

复制成功后,诺言便会解决。如果被拒绝,则其状态将得到解决,多次调用拒绝将不会有任何影响。
benweet 2015年

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);});并查看了相关规范,您就是对的:尝试解决或拒绝已解决的承诺没有任何效果。也许您可以扩展答案并解释为什么要用这种方式编写函数?谢谢:-)
Lekensteyn

2
顺便说一句,close应该finish用于可写流。
Lekensteyn

并且,如果您想知道为什么应用程序在出现管道错误后永远不会关闭/dev/stdin,那就是一个错误github.com/joyent/node/issues/25375
Lekensteyn 2015年

43

好吧,通常最好避免异步文件操作。这是简短的(即无错误处理)同步示例:

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

8
总体而言,这是极其错误的,特别是因为它导致人们针对服务器提出的每个请求重新构建文件。这会变得昂贵。
催化剂

8
使用这些*Sync方法完全违反了nodejs的理念!我还认为它们正在逐渐被弃用。nodejs的整体思想是它是单线程的并且是事件驱动的。
gillyb 2014年

11
@gillyb我能想到的使用它们的唯一原因是为了简单-如果您正在编写仅使用一次的快速脚本,则可能不会为阻塞该过程而烦恼。
starbeamrainbowlabs 2014年

13
我不知道他们已被弃用。同步方法在Web服务器上几乎总是一个糟糕的主意,但有时在诸如node-webkit之类的应用中非常理想,该方法仅在复制文件时锁定窗口中的操作。抛出加载gif以及可能在特定点更新的加载栏,并让sync方法阻止所有操作,直到复制完成。实际上,这并不是最佳实践,而是何时何地放置他们的东西。
埃里克·雷彭

6
当您与另一个同步操作进行交互或想要执行顺序操作时(例如,无论如何您都将模拟同步),同步方法很好。如果操作是顺序的,则避免使用回调地狱(和/或promise soup)并使用sync方法。通常,在服务器上使用它们时应格外小心,但在大多数涉及CLI脚本的情况下都可以使用。
srcspider 2015年

18

Mike Schilling的错误处理解决方案,带有错误事件处理程序的快捷方式。

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文件夹不存在,它将自动创建它。另一个答案只会抛出错误。
  • 它返回一个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),请在您的项目中发生此事件后检查您的排名:-) re。比赛-始终建议在交通繁忙的地点进行一个需要同步保证的流程处理操作-是的,这就是性能瓶颈
stancikcom

我不是因为您了才投票,而是因为这不是问题的答案而投票。应该是对现有答案的警告性评论。
贾里德·史密斯

好吧-您的权利,例如,安德鲁·希尔兹(Andrew Childs)的解决方案(带有18个投票)将耗尽服务器上的资源/大文件...我会给他写评论,但我没有发表评论的声誉-因此,您看到了我的独立文章。 ...但是Jared的降级对我来说是一个简单的方法-保持沉默,让人们编写和共享主要是“起作用”的危险代码...
stancikcom

我明白了,没有人喜欢负面反馈。但这只是一个无奈之选。我坚持给出它的理由,因为这不能回答OP提出的问题,并且足够简短,可以发表评论。您可以随心所欲地使用它,但是如果您不按比例地进行此类操作,则会发现堆栈溢出是非常令人沮丧的体验。
贾里德·史密斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.