nodejs文件写入过程(Node.js逐行读取文件内容的4种方式)
nodejs文件写入过程(Node.js逐行读取文件内容的4种方式)node readfilesync.js内存消耗 200MB ,费时 2-3 分钟。执行脚本在写代码之前,要做好以下准备:故意选了一个相对较大(90MB)、行数较多(798148 行)的 broadband.SQL 文件,以便做性能测试。const fs = require('fs'); const allFileContents = fs.readFileSync('broadband.sql' 'utf-8'); allFileContents.split(/\r?\n/).forEach(line => { console.log(`Line from file: ${line}`); }); const used = process.memoryUsage().heapUsed / 1024 / 1024; cons
• 原文:https://geshan.com.np/blog/2021/10/NodeJs-read-File-line-by-line/
• 翻译:码中人
Node.js可以同步或异步的方式逐行读取文件内容。其中,异步方式可以读取大型文件而不需要同时加载文件的所有内容。
本文介绍 4 种逐行读取文件内容的Node.js操作,并做了性能测试,优劣评判。
- 准备
- 测试文件
- 同步读取
- Readline
- N-readlines
- Line reader
- 其它方法
- 快速对比
- 总结
在写代码之前,要做好以下准备:
- 安装 Node.js 10
- 熟悉 NPM
- 了解 NodeJs 事件驱动和非阻塞机制
- 了解 stream 流数据
故意选了一个相对较大(90MB)、行数较多(798148 行)的 broadband.SQL 文件,以便做性能测试。
同步方式const fs = require('fs');
const allFileContents = fs.readFileSync('broadband.sql' 'utf-8');
allFileContents.split(/\r?\n/).forEach(line => {
console.log(`Line from file: ${line}`);
});
const used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`The script uses approximately ${Math.round(used * 100) / 100} MB`);
把文件读取内存之中,按行分割,循环打印各行。
执行脚本
node readfilesync.js
内存消耗 200MB ,费时 2-3 分钟。
The script uses approximately 238.74 MB
如果文件大小 1GB,不推荐使用同步读取的方式,内存消耗太大。
接下来,我们将研究一种更高效的异步方式,通过 readline 和另一个原生 Node.js 模块的 stream 逐行读取文件。
ReadlineReadline 是Node.js原生模块,无需安装。它提供了用于从可读流(例如 process.stdin)每次一行地读取数据的接口。每当 input 流接收到行尾输入(\n、\r 或 \r\n)时,则会触发 'line' 事件。
下面是带有可读流的 readline 的代码示例:
const events = require('events');
const fs = require('fs');
const readline = require('readline');
(async function processLineByLine() {
try {
const rl = readline.createInterface({
input: fs.createReadStream('broadband.sql')
crlfDelay: Infinity
});
rl.on('line' (line) => {
console.log(`Line from file: ${line}`);
});
await events.once(rl 'close');
console.log('Reading file line by line with readline done.');
const used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`The script uses approximately ${Math.round(used * 100) / 100} MB`);
} catch (err) {
console.error(err);
}
})();
理解一下以上代码:
- 首先引入 Node.js 的 3 个原生模块 events fs 和 readline。
- 定义了一个名为 processLineByLine 的异步函数,它为 readline 创建了一个接口,其中输入是我们传递 90 MB 测试文件的读取流。
- 设置为无穷大的 crlfDelay 。crlfDelay 如果 \r 和 \n 之间的延迟超过 crlfDelay 毫秒,则 \r 和 \n 都将被视为单独的行尾输入。 crlfDelay 将被强制为不小于 100 的数字。 它可以设置为 Infinity,在这种情况下,\r 后跟 \n 将始终被视为单个换行符(这对于具有 \r\n 行分隔符的文件读取可能是合理的)。 默认值: 100。
- 在可读流交互时,每读取一行,就会触发 rl.on 读行函数。此时,就可以从流中读取的行的内容。
- 然后我们用 events.once 监听 readline 关闭事件,它创建一个 Promise,该 Promise 将使用一个包含所有发送给给定事件的参数的数组来解析。在这种情况下,它将是一个空数组。
- 最后记录内存使用情况
执行脚本
node readline.js
结果耗时很长(5-8 分钟左右),内容占用率较低。
The script uses approximately 5.77 MB
N-readlinesN-readline 是一个 NPM 模块,它可以逐行读取文件,而不会将整个文件缓冲在内存中。它在不使用流的情况下,通过使用 Buffer 和本机文件系统模块以块的形式读取文件的内容。即使它以同步方式工作,它也不会将整个文件加载到内存中。
首先,安装 n-readlines 模块
npm i --save n-readlines:
通过 N-readlines 逐行读取文件如下:
const nReadlines = require('n-readlines');
const broadbandLines = new nReadlines('broadband.sql');
let line;
let lineNumber = 1;
while (line = broadbandLines.next()) {
console.log(`Line ${lineNumber} has: ${line.toString('ascii')}`);
lineNumber ;
}
console.log('end of file.');
const used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`The script uses approximately ${Math.round(used * 100) / 100} MB`);
以上代码相对较为简洁。
- • 首先,引入 N-readlines 模块
- • 创建一个读取文件的实例。默认参数是文件名, readChunk 和 newLineCharacter 可以作为第二个参数传入 。这里只使用文件名参数。
- • 定义两个变量 line 和 lineNumber 。 Line 变量将保存文件每一行的字符串,而 lineNumber 将保存从 1 到文件行数的行号。
- • 最后,文件读取结束之后打印出大概的内存使用情况。
执行脚本
node n-readlines.js
耗时巨长,内存消耗小。
The script uses approximately 4.09 MB
Line readerLine reader 模块将自己定义为“支持用户定义的行分隔符的异步、缓冲、逐行文件/流读取器”。它提供 eachLine 函数读取给定文件的每一行。eachLine 的回调函数中的 last 变量可用于确定是否已到达文件的最后一行。
下面是使用 line reader 读取我们相对较大的 90 MB SQL 文件的工作示例,我们使用 npm i --save line-reader 安装它。完整代码如下:
const lineReader = require('line-reader');
lineReader.eachLine('broadband.sql' function(line last) {
console.log(`Line from file: ${line}`);
if(last) {
console.log('Last line printed.');
const used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`The script uses approximately ${Math.round(used * 100) / 100} MB`);
}
});
代码解析
- • 首先,引入 line-reader 模块
- • 调用 eachLine 函数,将文件名(或文件路径)作为第一个参数传递。
- • 第 2 个参数是一个回调函数,包含 line 和 last 变量。
- • 在回调函数中打印行内容。
- • 最后,如果我们发现最后一个变量为真,这表明我们已经到达文件的末尾,打印出用于逐行读取文件的内存消耗。
执行脚本
node line-reader.js
时长感人,内存占用极少。
The script uses approximately 2.48 MB
其它方式还有其他选项可以使用 Node.js 逐行读取文件。有一个非常流行的 NPM 模块叫做 readline ,但由于与原生 Node.js 模块的名称冲突,现在它已重命名为 Line By LIne。它的工作方式与本机 readline 模块非常相似
其他不太流行但可用的模块有 readline 和 readlines-ng,它们在每周仅被下载了大约 3 次。
使用 JavaScript 数组函数,可以非常方便对于文件内容的进一步处理。
快速比较在 NPM Trends 上对这四个 NPM 模块的快速比较显示,N-readlines 是下载量最大的模块,上周下载量为 56K。第二个是上周下载量为 46K 的 line-reader,但请记住 line-reader 最近一次更新是在 6 年前。以下是过去 1 年的下载快照:
推荐优先选择流行的类库,最近更新的类库是 n-readlines,更新时间为 1 年前。
readline 和 readlines ng 的下载量约为每周 3 次,而 line reader 和 n-readlines 的下载量分别为 46K 和 56K。
在内存和 CPU 使用率方面,除了第一个 fs.readfilesync 之外的所有其他基于流或回调的方法,都消耗内存低于 10 MB ,并在 10 秒前完成,CPU 使用率为 70-94%。对于 90 MB 的文件,读取文件同步消耗了 225 MB 的内存。(实测,时间的消耗长达 10 几分钟的都有,可能是我电脑配置太低了)
总结我们研究了如何在 Node.js 中逐行读取文件。这是个小问题,在 Node.js 中可以使用多种方法来解决。
我们还分析了 3 种方法中每种方法的内存使用情况和时间。
最后,我们快速比较了各类库的受欢迎程度,希望能对你有帮助。