面试题答案
一键面试优化策略
- 使用流(Stream):流是处理大文件的核心,它能够逐块读取和写入数据,而不是一次性将整个文件加载到内存中。Node.js提供了可读流(
Readable Stream
)、可写流(Writable Stream
)和转换流(Transform Stream
)。 - 管道(Pipe)操作:通过管道操作,可以将可读流直接连接到可写流,让数据在流之间高效流动,减少中间数据的存储。
- 分块处理:设置合适的
highWaterMark
,它表示流在内部缓冲的最大数据量,当缓冲区数据达到这个值时,流会暂停读取新数据,直到缓冲区的数据被消费。 - 并发控制:由于是批量处理文件,要控制并发数量,避免过多的文件操作同时进行导致系统资源耗尽。可以使用
async
和await
结合队列控制并发。
示例代码
const fs = require('fs');
const path = require('path');
const { Transform } = require('stream');
const { promisify } = require('util');
const queue = require('async-queue');
// 转换流示例,这里简单将文件内容转为大写
class ToUpperCase extends Transform {
_transform(chunk, encoding, callback) {
const upperChunk = chunk.toString().toUpperCase();
callback(null, upperChunk);
}
}
// 批量处理文件的函数
async function processFiles(filePaths, outputDir) {
const q = queue(async (filePath) => {
const readableStream = fs.createReadStream(filePath, {
highWaterMark: 64 * 1024 // 64KB分块读取
});
const writePath = path.join(outputDir, path.basename(filePath));
const writableStream = fs.createWriteStream(writePath, {
highWaterMark: 64 * 1024 // 64KB分块写入
});
const transformStream = new ToUpperCase();
readableStream
.pipe(transformStream)
.pipe(writableStream);
await promisify(streamFinished)(writableStream);
}, 5); // 控制并发数为5
filePaths.forEach(filePath => q.push(filePath));
await q.drain();
}
// 示例文件路径和输出目录
const filePaths = [
'path/to/file1.txt',
'path/to/file2.txt',
// 更多文件路径
];
const outputDir = 'path/to/output';
processFiles(filePaths, outputDir).then(() => {
console.log('所有文件处理完成');
}).catch(console.error);
function streamFinished(stream) {
return new Promise((resolve, reject) => {
stream.on('finish', resolve);
stream.on('error', reject);
});
}
此代码通过以下步骤实现文件的批量处理:
- 定义转换流:
ToUpperCase
类继承自Transform
流,用于将读取的数据块转换为大写。 - 创建队列:使用
async-queue
控制并发处理文件的数量,这里设置为5个文件同时处理。 - 读取、转换和写入:对每个文件创建可读流、转换流和可写流,并通过
pipe
方法连接起来,让数据在流之间高效流动。 - 等待所有任务完成:使用
await q.drain()
等待队列中的所有任务完成,确保所有文件处理完毕。