面试题答案
一键面试性能问题原因
- I/O 阻塞:
fs.readdir
是同步操作,会阻塞主线程,在遍历大量文件和目录时,长时间占用主线程,导致其他任务无法执行,造成应用程序响应缓慢。 - 内存消耗:随着遍历的进行,会不断在内存中创建和处理文件及目录信息,若文件数量巨大,会占用大量内存,可能导致内存溢出。
- 递归深度:若采用递归方式遍历子目录,递归深度过大会消耗过多栈空间,甚至导致栈溢出错误。
性能优化方案及优缺点
方案一:使用异步操作(fs.readdirSync
改为fs.readdir
)
优点:
- 不会阻塞主线程,允许其他任务并行执行,提高应用程序的响应性。
- 适用于需要同时处理其他任务,如网络请求、用户交互等的场景。
缺点:
- 代码复杂度增加,需要使用回调函数、Promise 或 async/await 来处理异步操作,相比同步代码更难理解和维护。
- 错误处理相对复杂,需要在每个异步操作的回调或 Promise 链中处理错误。
示例代码(使用 async/await):
const fs = require('fs');
const path = require('path');
async function traverseDirectory(dir) {
try {
const files = await fs.promises.readdir(dir);
for (const file of files) {
const filePath = path.join(dir, file);
const stats = await fs.promises.stat(filePath);
if (stats.isDirectory()) {
await traverseDirectory(filePath);
} else {
console.log(filePath);
}
}
} catch (err) {
console.error(err);
}
}
traverseDirectory('.');
方案二:使用流(fs.createReadStream
和fs.createWriteStream
)
优点:
- 逐块读取和处理数据,而不是一次性加载整个文件或目录内容到内存,大大减少内存消耗,适用于处理超大文件和目录结构。
- 可以实现数据的实时处理,提高处理效率。
缺点:
- 流的操作相对复杂,需要熟悉流的事件机制,如
data
、end
等事件,代码编写难度较大。 - 对于简单的目录遍历场景,使用流可能会过度设计,增加不必要的代码复杂度。
示例代码(简单使用流遍历目录并输出文件路径):
const fs = require('fs');
const path = require('path');
function traverseDirectoryWithStream(dir) {
const readStream = fs.createReadStream(dir, { encoding: 'utf8' });
readStream.on('data', (chunk) => {
const files = chunk.toString().split('\n');
for (const file of files) {
if (file) {
const filePath = path.join(dir, file);
const stats = fs.statSync(filePath);
if (stats.isDirectory()) {
traverseDirectoryWithStream(filePath);
} else {
console.log(filePath);
}
}
}
});
readStream.on('end', () => {
console.log('Traversal completed');
});
readStream.on('error', (err) => {
console.error(err);
});
}
traverseDirectoryWithStream('.');
方案三:限制递归深度
优点:
- 有效避免栈溢出错误,特别是在目录结构非常深的情况下。
- 可以根据实际需求灵活控制遍历的深度,提高遍历效率。
缺点:
- 可能无法完整遍历整个目录结构,丢失深层目录下的文件信息,不适用于需要获取全部文件和目录信息的场景。
- 需要事先确定合适的递归深度,若设置不当,可能达不到预期的遍历效果。
示例代码:
const fs = require('fs');
const path = require('path');
function traverseDirectoryLimitedDepth(dir, maxDepth, currentDepth = 0) {
if (currentDepth > maxDepth) return;
try {
const files = fs.readdirSync(dir);
for (const file of files) {
const filePath = path.join(dir, file);
const stats = fs.statSync(filePath);
if (stats.isDirectory()) {
traverseDirectoryLimitedDepth(filePath, maxDepth, currentDepth + 1);
} else {
console.log(filePath);
}
}
} catch (err) {
console.error(err);
}
}
traverseDirectoryLimitedDepth('.', 3);