面试题答案
一键面试方案设计思路
- 资源管理:Node.js 提供了
fs
模块进行文件操作。为了避免文件描述符数量限制等资源问题,可以使用连接池的思想。维护一个文件描述符的池,当需要进行文件操作时从池中获取文件描述符,操作完成后归还到池中。 - 并发控制:可以使用
async/await
结合Promise
来控制并发操作的数量。通过设置一个最大并发数,当并发操作达到最大数时,新的操作进入等待队列,直到有操作完成腾出空间。 - 顺序依赖:对于有顺序依赖的文件操作,可以将这些操作按顺序放入一个数组中,然后使用
async/await
依次执行这些操作。
代码示例
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
// 模拟文件操作任务数组
const fileTasks = [
{ filePath: path.join(__dirname, 'file1.txt'), operation: 'write', data: 'content1' },
{ filePath: path.join(__dirname, 'file2.txt'), operation:'read' },
{ filePath: path.join(__dirname, 'file3.txt'), operation: 'write', data: 'content3' }
];
// 最大并发数
const maxConcurrent = 3;
// 文件描述符池
const fileDescriptorPool = [];
// 初始化文件描述符池(假设最多支持10个文件描述符)
for (let i = 0; i < 10; i++) {
fileDescriptorPool.push(i);
}
// 获取文件描述符
function getFileDescriptor() {
return fileDescriptorPool.pop();
}
// 归还文件描述符
function releaseFileDescriptor(fd) {
fileDescriptorPool.push(fd);
}
// 封装文件操作
const readFileAsync = promisify(fs.readFile);
const writeFileAsync = promisify(fs.writeFile);
async function fileOperation(task) {
const fd = getFileDescriptor();
try {
if (task.operation ==='read') {
const data = await readFileAsync(task.filePath, 'utf8');
console.log(`Read from ${task.filePath}: ${data}`);
} else if (task.operation === 'write') {
await writeFileAsync(task.filePath, task.data);
console.log(`Write to ${task.filePath}: ${task.data}`);
}
} finally {
releaseFileDescriptor(fd);
}
}
async function executeTasks() {
let currentConcurrent = 0;
const taskQueue = [];
for (const task of fileTasks) {
if (currentConcurrent < maxConcurrent) {
currentConcurrent++;
taskQueue.push(fileOperation(task).then(() => {
currentConcurrent--;
}));
} else {
// 等待有任务完成,腾出空间
await Promise.race(taskQueue);
currentConcurrent++;
taskQueue.push(fileOperation(task).then(() => {
currentConcurrent--;
}));
}
}
// 等待所有任务完成
await Promise.all(taskQueue);
}
executeTasks();
上述代码通过以下方式实现了资源管理和并发控制:
- 资源管理:通过维护
fileDescriptorPool
来模拟文件描述符池,在文件操作前后获取和归还文件描述符。 - 并发控制:设置
maxConcurrent
为最大并发数,使用currentConcurrent
记录当前并发数,通过Promise.race
和Promise.all
控制任务的并发执行。 - 顺序依赖:通过将文件操作任务按顺序放入
fileTasks
数组,然后依次执行这些任务,从而满足顺序依赖。