将多个文件合并为一个文件,常见的场景是类似于大文件分片上传,事先根据一定的文件大小拆分为多个小文件上传到服务端,最后服务端在合并起来。怎么合并?一种简单的办法是使用 fs.readFile 读取,fs.writeFile 追加写入,这种方式是将文件数据先读入应用内存再写入,不是很推荐,Node.js 本身提供了 Stream 模块可以更好的处理这种场景。
在 Stream 中合并文件之前一个比较常用的 API 是 pipe,但是这个 API 对于错误处理不是很友好,一不小心还能搞出文件句柄内存泄漏问题。
本文先介绍 pipe 方法的使用及什么情况下会遇到文件句柄的内存泄漏问题,之后再分别介绍 Stream 合并的两种实现模式。
创建一个可读流 readable 和一个可写流 writeable,通过管道 pipe 将可写流绑定到可读流,一个简单的 Stream 操作就完成了。
const fs = require('fs');
const readable = fs.createReadStream('./test1.txt');
const writeable = fs.createWriteStream('./test2.txt');
readable.pipe(writeable);
pipe 方法的两个参数:
readable.pipe(destination[, options])
默认情况下我们是不需要手动调用写入流的 end 方法关闭的。
现在我们改一下,设置 end 为 false 写入的目标流将会一直处于打开状态, 此时就需要监听可读流的 end 事件,结束之后手动调用可写流的 end 方法结束(为什么要这样做?下文 Stream 串行合并会再用到这一特性)。
// readable.pipe(writeable);
readable.pipe(writeable, {
end: false,
});
readable.on('end', function() {
writeable.end('结束');
});
还需要注意一点如果可读流期间发生什么错误,则写入的目标流将不会关闭,例如:process.stderr 和 process.stdout 可写流在 Nodejs 进程退出前将永远不会关闭,所以需要监听错误事件,手动关闭可写流,防止内存泄漏。
Linux 下一切皆文件,为了测试,在创建可读流时,你可以不创建 test1.txt 文件,让可读流自动触发 error 事件并且将 writeable 的 close 方法注释掉,通过 linux 命令 ls -l /proc/${pid}/fd
查看 error 和非 error 前后的文件句柄变化。
readable.on('error', function(err) {
console.log('error', err);
// writeable.close();
});
console.log(process.pid); // 打印进程 ID
setInterval(function(){}, 5000) // 让程序不中断,进程不退出
以下为触发 error 错误下 test2.txt 这个文件 fd 将会一直打开,除非进程退出,所以重要的事情再说一遍,如果使用 pipe 一定要做好错误监听手动关闭每个写入流,以防止 “内存泄漏”。
...
l-wx------ 1 root root 64 Apr 10 15:47 19 -> /root/study/test2.txt
...
注意,Mac 下没有 /proc 文件,可通过 docker 测试。不想开两个终端的,也可以在程序 setInterval 定时器函数里使用 child_process 模块的 exec 函数执行 ls -l /proc/${process.pid}/fd
命令。
const { exec } = require('child_process');
setInterval(function(){
exec(`ls -l /proc/${process.pid}/fd`, (error, stdout, stderr) => {
console.log(`stdout: \n`, stdout);
})
}, 5000) // 让程序不中断,进程不退出
Stream 模块的一个新 API pipeline 方法,添加于 Node.js v10.0,Promise 风格需要 Node.js v15.0+ 支持。相比较于 pipe 方法增加了错误处理机制,当管道中的某个流发生错误,它会自动处理并释放掉相应的资源。
try {
await pipeline(
readable,
writable
);
console.log('Pipeline succeeded.');
} catch (err) {
console.log('error', err);
}
使用 pipe 方法实现串行模式的流合并,根据前面讲的,设置可读流的 end 为 false 保持写入流一直处于打开状态,直到所有的可读流结束(待合并的文件完成后),我们再将可写流给关闭。
const fs = require('fs');
const path = require('path');
/**
* Stream 合并
* @param { String } sourceFileDirectory 源文件目录
* @param { String } targetFile 目标文件
*/
function streamMerge(sourceFileDirectory, targetFile) {
const scripts = fs.readdirSync(path.resolve(__dirname, sourceFileDirectory)); // 获取源文件目录下的所有文件
const fileWriteStream = fs.createWriteStream(path.resolve(__dirname, targetFile)); // 创建一个可写流
// fs.readdir 读取出来的结果,根据具体的规则做下排序,防止因为顺序不对导致最终合并之后的文件无效。
return streamMergeRecursive(scripts, fileWriteStream, sourceFileDirectory);
}
/**
* Stream 合并的递归调用
* @param { Array } scripts
* @param { Stream } fileWriteStream
*/
function streamMergeRecursive(scripts=[], fileWriteStream, sourceFileDirectory) {
// 递归到尾部情况判断
if (!scripts.length) {
return fileWriteStream.end("console.log('Stream 合并完成')"); // 最后关闭可写流,防止内存泄漏
}
const currentFile = path.resolve(__dirname, sourceFileDirectory, scripts.shift());
const currentReadStream = fs.createReadStream(currentFile); // 获取当前的可读流
currentReadStream.pipe(fileWriteStream, { end: false });
currentReadStream.on('end', function() {
streamMergeRecursive(scripts, fileWriteStream, sourceFileDirectory);
});
currentReadStream.on('error', function(error) { // 监听错误事件,关闭可写流,防止内存泄漏
console.error(error);
fileWriteStream.close();
});
}
streamMerge('./files', './file.js');
流合并也是可以采用并发模式的,核心是通过可写流的 start、end 属性控制。
start 有点类似于数据库查询的 skip,在计算时要求文件分块的下标必须是 0、1、2... 这样的规则,这种方式可以不用关注每一个流分块在文件中的存储顺序,也可以将可读流传输至可写流的指定位置。
例如,有一个大文件 dec47b76e3220432100a1155eff7f402(文件 md5 后的 hash 值) 根据 chunkSize(1048576)拆分为 3 个小文件。
/chunks
└── dec47b76e3220432100a1155eff7f402-1048576
├── dec47b76e3220432100a1155eff7f402-0
├── dec47b76e3220432100a1155eff7f402-1
└── dec47b76e3220432100a1155eff7f402-2
并发模式的 Stream 合并代码实现如下:
/**
* Stream concurrent merge
* @param {String} sourceFileDirectory
* @param {String} targetFile
* @param {Number} chunkSize
*/
export const streamConcurrentMerge = async (sourceFileDirectory, targetFile, chunkSize) => {
const filenames = await fs.readdir(sourceFileDirectory);
await Promise.all(filenames.map(filename => {
const index = filename.split('-').pop();
const start = index * chunkSize;
const end = (index + 1) * chunkSize;
return pipeline(
createReadStream(path.join(sourceFileDirectory, filename)),
createWriteStream(targetFile, {
start,
end,
})
);
}))
}
使用 pipe 时错误处理是件需要注意的事情,特别是出现这种情况 readable.pipe(a).pipe(b).pipe(writable)
其中任何一个流关闭或出错都会导致整个管道停止工作,这个时候就要销毁所有的流,这种复杂的处理起来极其麻烦,推荐使用 stream API pipeline 处理,或使用社区 NPM 库 pump。
将多个文件合并为一个文件,使用流的方式有两种:
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/WisEGFz2yn_ZeekViofMnA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。