在 Node.js 中复制文件的最快方法
Posted
技术标签:
【中文标题】在 Node.js 中复制文件的最快方法【英文标题】:Fastest way to copy a file in Node.js 【发布时间】:2012-07-02 21:04:39 【问题描述】:我正在处理的项目(Node.js)意味着对文件系统的大量操作(复制、读取、写入等)。
哪种方法最快?
【问题讨论】:
这是一个很好的问题,尽管有趣的是,当其他类似格式的问题因为不符合 SO“标准”而立即获得 3 或 4 个反对票时,它获得了 25 个赞成票(也许 javascript 标记已被抓取)由更善良的人:) 在经过多年的浏览器规范化之后,我们对整个“文件”业务感到新鲜和兴奋。 页面上唯一正确的答案是this one。其他答案都没有真正复制文件。 MacOS 和 Windows 上的文件具有其他元数据,这些元数据仅因复制字节而丢失。此页面上的任何其他答案未复制的数据示例,windows 和 macos。即使在 Unix 上,其他答案也不会复制创建日期,这在复制文件时通常很重要。 【参考方案1】:使用标准的内置方式fs.copyFile
:
const fs = require('fs');
// File 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');
);
如果您必须支持旧版本的 Node.js - 在不支持 fs.copyFile
的版本中,您可以这样做:
const fs = require('fs');
fs.createReadStream('test.log').pipe(fs.createWriteStream('newLog.log'));
【讨论】:
请记住,在现实生活中,您需要同时检查createReadStream
和 createWriteStream
是否有错误,这样您就不会得到一个单行字(尽管它仍然只是一样快)。
这比通过require('child_process').exec
执行原始cp test.log newLog.log
快/慢多少?
好吧,copy
在 Window 上不可移植,这与完整的 Node.js 解决方案相反。
不幸的是,与child_process.execFile('/bin/cp', ['--no-target-directory', source, target])
相比,在我的系统上使用流的速度非常慢。
我使用了这种方法,我得到的只是一个空白文件。任何想法为什么? fs.createReadStream('./init/xxx.json').pipe(fs.createWriteStream('xxx.json'));
【参考方案2】:
相同的机制,但增加了错误处理:
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;
【讨论】:
值得注意的是,需要 cbCalled 标志,因为管道错误会在两个流上触发错误。源流和目标流。 如果源文件不存在,如何处理错误?在这种情况下仍会创建目标文件。 我认为WriteStream
中的错误只会取消管道。您必须自己致电rd.destroy()
。至少那是发生在我身上的事。遗憾的是,除了源代码之外,没有太多文档。
cb
代表什么?我们应该传入什么作为第三个参数?
@SaiyanGirl 'cb' 代表“回调”。你应该传入一个函数。【参考方案3】:
嗯,通常最好避免异步文件操作。这是简短的(即没有错误处理)同步示例:
var fs = require('fs');
fs.writeFileSync(targetFile, fs.readFileSync(sourceFile));
【讨论】:
说这通常是非常错误的,特别是因为它会导致人们为向他们的服务器发出的每个请求重新发送文件。这可能会很昂贵。 使用*Sync
方法完全违背了nodejs的理念!我也认为它们正在慢慢被弃用。 nodejs 的整体理念是它是单线程和事件驱动的。
@gillyb 我能想到使用它们的唯一原因是为了简单——如果你正在编写一个只会使用一次的快速脚本,你可能不会那么烦恼阻塞进程。
我不知道它们已被弃用。同步方法在 web 服务器上几乎总是一个糟糕的想法,但有时在 node-webkit 之类的东西中很理想,它只在文件复制时锁定窗口中的操作。抛出一个加载 gif,可能还有一个在某些点更新的加载栏,让同步方法阻止所有操作,直到复制完成。与其说是最佳实践,不如说是他们在何时何地拥有自己的位置。
当您与另一个同步操作交互或您想要执行顺序操作(即,无论如何您都将模拟同步)时,同步方法很好。如果操作是顺序的,只需避免回调地狱(和/或承诺汤)并使用同步方法。一般来说,它们应该在服务器上谨慎使用,但在大多数涉及 CLI 脚本的情况下都可以。【参考方案4】:
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;
【讨论】:
【参考方案5】:由于某种原因,我无法让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');
【讨论】:
这是现在最好的选择 在节点中使用同步代码会降低您的应用程序性能。 哦,拜托...问题是关于复制文件的最快方法。虽然 fastest 总是主观的,但我认为同步的代码在这里没有任何意义。 最快实现还是最快执行?不同的优先级意味着这是一个有效的答案。 fs-extra 也有异步方法,即fs.copy(src, dst, callback);
,这些应该可以解决@mvillar 的顾虑。【参考方案6】:
编写速度快,使用方便,带有承诺和错误管理:
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;
【讨论】:
没有输入时会发生什么(网络共享中断),但写入仍然成功?是否会同时调用拒绝(来自读取)和解决(来自写入)?如果读/写都失败(读取期间磁盘扇区损坏,写入期间磁盘满)怎么办?然后拒绝将被调用两次。基于 Mike 的带有标志的回答的 Promise 解决方案(不幸的是)似乎是唯一可行的解决方案,它正确考虑了错误处理。 复制成功后,promise 就解决了。如果它被拒绝,它的状态就被解决了,多次调用拒绝没有任何区别。 我刚刚测试了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););
并查找了spec,您是对的:尝试解决或拒绝已解决的承诺无效。也许您可以扩展您的答案并解释您为什么以这种方式编写函数?谢谢:-)
顺便说一下,close
应该是 finish
用于可写流。
如果你想知道为什么你的应用程序在/dev/stdin
出现管道错误后永远不会关闭,那是一个错误github.com/joyent/node/issues/25375【参考方案7】:
如果您不关心它是异步的,并且不复制千兆字节大小的文件,并且宁愿不为单个函数添加另一个依赖项:
function copySync(src, dest)
var data = fs.readFileSync(src);
fs.writeFileSync(dest, data);
【讨论】:
@RobGleeson,并且需要与文件内容一样多的内存......我对那里的投票数感到惊讶。 我添加了“并且没有复制千兆字节大小的文件”警告。 应该省略fs.existsSync
调用。该文件可能会在fs.existsSync
调用和fs.readFileSync
调用之间的时间内消失,这意味着fs.existsSync
调用不能保护我们免受任何伤害。
此外,如果fs.existsSync
失败,则返回false
可能很不符合人体工程学,因为很少有copySync
的消费者会在每次调用它时手动检查返回值,比我们为@ 所做的更多987654329@等人。抛出异常实际上更可取。
OP 没有特别提到他们的文件是 UTF-8 文本,所以我也从 sn-p 中删除了 'utf-8'
编码,这意味着这现在适用于任何文件。 data
现在是 Buffer
,而不是 String
。【参考方案8】:
benweet's solution,还要在复制前检查文件的可见性:
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);
);
);
【讨论】:
【参考方案9】:Mike's solution,但有承诺:
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 因为我想要一个异步解决方案...?【参考方案10】:改进另一个答案。
特点:
如果 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);
【讨论】:
还有什么答案? @PeterMortensen Mike Schilling's。【参考方案11】:以前所有不检查源文件是否存在的解决方案都是危险的......例如,
fs.stat(source, function(err,stat) if (err) reject(err)
否则在发生错误替换源和目标的情况下,您的数据将永久丢失而不会注意到任何错误。
【讨论】:
这也有一个竞争条件:文件可能在统计它和读/写/复制之间被破坏。尝试操作并处理任何由此产生的错误总是更好。 在写入操作之前检查目标的存在确保您不会意外覆盖目标,例如涵盖了目标和源被用户错误设置为相同的场景......然后等待写入操作失败......谁给了我(-1)请在您的项目中发生此事件后查看您的排名:-) 回覆。比赛 - 总是建议在流量大的网站上使用一个需要同步保证的进程处理操作 - 是的,这就是性能瓶颈 我没有投反对票,因为你是错,我投反对票是因为这不是问题的答案。这应该是对现有答案的警示性评论。 好吧 - 你是对的,例如安德鲁孩子的解决方案(有 18 个赞成票)将耗尽服务器上的资源/大文件......我会写 cmets 给他,但我没有评论的声誉 - 因此你已经看到我的帖子独立......但 Jared 你的降级对我来说意味着一个简单的方法 - 保持沉默,让人们编写和分享大部分“有效”的危险代码...... 我明白了,没有人喜欢负反馈。但这只是投反对票。我坚持我给出它的理由,因为这不能回答 OP 提出的问题并且足够短,可以作为评论。你可以随心所欲地使用它,但如果你把这种事情搞得不成比例,你会发现堆栈溢出是一种非常令人沮丧的体验。【参考方案12】:从 Node.js 8.5.0 开始,我们有了新的 fs.copyFile 和 fs.copyFileSync 方法。
使用示例:
var fs = require('fs');
// File "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');
);
【讨论】:
这是页面上唯一正确的答案。其他答案都没有真正复制文件。 MacOS 和 Windows 上的文件具有其他元数据,这些元数据仅因复制字节而丢失。此页面上的任何其他答案未复制的数据示例,windows 和 macos。即使在 Unix 上,其他答案也不会复制创建日期,这在复制文件时通常很重要。 很遗憾,这无法复制 mac 上的所有内容。希望他们能修复它:github.com/nodejs/node/issues/30575 BTW 请记住,copyFile()
在覆盖较长文件时会出现错误。由uv_fs_copyfile()
提供直到 Node v8.7.0 (libuv 1.15.0)。见github.com/libuv/libuv/pull/1552【参考方案13】:
使用 Node.js 内置的复制功能
它提供异步和同步版本:
const fs = require('fs');
// File "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');
);
fs.copyFileSync(src, dest[, mode])
【讨论】:
不赞成,因为这个答案是重复的。【参考方案14】:对于快速复制,您应该使用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
Re“什么都不做/少做是做某事的最快方法”:是的,确实。这是优化的第一条规则 - 消除不必要的操作。这与使现有的运行速度更快形成对比,例如通过摆弄编译器标志。【参考方案15】:
const fs = require("fs");
fs.copyFileSync("filepath1", "filepath2"); //fs.copyFileSync("file1.txt", "file2.txt");
这是我个人用来复制文件并使用 Node.js 替换另一个文件的方法:)
【讨论】:
这并没有回答这个问题,即如何在IO密集型应用程序中有效地复制文件。 @JaredSmith 没错,但我的谷歌搜索把我引到这里,这就是我想要的。 我想知道为什么异步函数中的 copyFileSync 性能不佳。我认为它会被优化以匹配 copyFile 或流复制。【参考方案16】:您可以使用fs-extra
模块非常轻松地做到这一点:
const fse = require('fs-extra');
let srcDir = 'path/to/file';
let destDir = 'pat/to/destination/directory';
fse.moveSync(srcDir, destDir, function (err)
// To move a file permanently from a directory
if (err)
console.error(err);
else
console.log("success!");
);
或者
fse.copySync(srcDir, destDir, function (err)
// To copy a file from a directory
if (err)
console.error(err);
else
console.log("success!");
);
【讨论】:
【参考方案17】:您可能想要使用 async/await,因为 node v10.0.0
可以使用内置的 fs Promises API
。
例子:
const fs = require('fs')
const copyFile = async (src, dest) =>
await fs.promises.copyFile(src, dest)
注意:
从
node v11.14.0, v10.17.0
开始,API 不再是实验性的。
更多信息:
Promises API
Promises copyFile
【讨论】:
【参考方案18】:我写了一个小工具来测试不同的方法:
https://www.npmjs.com/package/copy-speed-test
运行它
npx copy-speed-test --source someFile.zip --destination someNonExistentFolder
它使用 child_process.exec() 进行本机复制,使用 fs.copyFile 进行复制文件,并使用具有各种不同缓冲区大小的 createReadStream(您可以通过在命令行上传递它们来更改缓冲区大小。运行 npx copy -speed-test -h 了解更多信息)。
【讨论】:
以上是关于在 Node.js 中复制文件的最快方法的主要内容,如果未能解决你的问题,请参考以下文章
使用 node.js 将 Azure 文件共享复制到 Blob
使用node.js中fs模块的copyFileSync方法复制文件报错“operation not permitted, copyfile ‘G: est.txt‘ -> ‘G:Trash‘“(代码片