Skip to content

第9章:文件系统进阶

9.1 同步与异步文件操作对比

Node.js 的 fs 模块提供了同步和异步两种文件操作方式。

异步文件操作

特点

  • 不会阻塞主线程
  • 性能更好,适合处理大量文件操作
  • 代码更复杂,需要使用回调函数或 Promise

示例

javascript
const fs = require('fs').promises;

// 异步读取文件
async function asyncReadFile() {
  try {
    const data = await fs.readFile('example.txt', 'utf8');
    console.log('文件内容:', data);
  } catch (err) {
    console.error('读取文件失败:', err);
  }
}

asyncReadFile();
console.log('其他操作...');

同步文件操作

特点

  • 会阻塞主线程
  • 代码更简洁,易于理解
  • 适合处理少量文件操作

示例

javascript
const fs = require('fs');

// 同步读取文件
function syncReadFile() {
  try {
    const data = fs.readFileSync('example.txt', 'utf8');
    console.log('文件内容:', data);
  } catch (err) {
    console.error('读取文件失败:', err);
  }
}

syncReadFile();
console.log('其他操作...');

对比总结

操作类型优点缺点适用场景
异步操作非阻塞,性能好代码复杂大量文件操作,生产环境
同步操作代码简单,易理解阻塞主线程少量文件操作,脚本工具

9.2 文件夹操作

创建文件夹

异步创建文件夹

javascript
const fs = require('fs').promises;

async function createDirectory() {
  try {
    await fs.mkdir('new-folder', { recursive: true });
    console.log('文件夹创建成功');
  } catch (err) {
    console.error('创建文件夹失败:', err);
  }
}

createDirectory();

同步创建文件夹

javascript
const fs = require('fs');

function createDirectorySync() {
  try {
    fs.mkdirSync('new-folder', { recursive: true });
    console.log('文件夹创建成功');
  } catch (err) {
    console.error('创建文件夹失败:', err);
  }
}

createDirectorySync();

删除文件夹

异步删除文件夹

javascript
const fs = require('fs').promises;

async function deleteDirectory() {
  try {
    await fs.rm('new-folder', { recursive: true, force: true });
    console.log('文件夹删除成功');
  } catch (err) {
    console.error('删除文件夹失败:', err);
  }
}

deleteDirectory();

同步删除文件夹

javascript
const fs = require('fs');

function deleteDirectorySync() {
  try {
    fs.rmSync('new-folder', { recursive: true, force: true });
    console.log('文件夹删除成功');
  } catch (err) {
    console.error('删除文件夹失败:', err);
  }
}

deleteDirectorySync();

遍历文件夹

异步遍历文件夹

javascript
const fs = require('fs').promises;
const path = require('path');

async function traverseDirectory(dir) {
  try {
    const files = await fs.readdir(dir);
    
    for (const file of files) {
      const filePath = path.join(dir, file);
      const stats = await fs.stat(filePath);
      
      if (stats.isDirectory()) {
        console.log(`目录: ${filePath}`);
        await traverseDirectory(filePath); // 递归遍历子目录
      } else {
        console.log(`文件: ${filePath}`);
      }
    }
  } catch (err) {
    console.error('遍历文件夹失败:', err);
  }
}

traverseDirectory('./');

同步遍历文件夹

javascript
const fs = require('fs');
const path = require('path');

function traverseDirectorySync(dir) {
  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()) {
        console.log(`目录: ${filePath}`);
        traverseDirectorySync(filePath); // 递归遍历子目录
      } else {
        console.log(`文件: ${filePath}`);
      }
    }
  } catch (err) {
    console.error('遍历文件夹失败:', err);
  }
}

traverseDirectorySync('./');

9.3 文件批量操作、批量读取

批量读取文件

示例:批量读取多个文件

javascript
const fs = require('fs').promises;

async function batchReadFiles() {
  const files = ['file1.txt', 'file2.txt', 'file3.txt'];
  
  try {
    // 使用 Promise.all 并行读取文件
    const contents = await Promise.all(
      files.map(file => fs.readFile(file, 'utf8'))
    );
    
    // 处理读取的内容
    files.forEach((file, index) => {
      console.log(`=== ${file} ===`);
      console.log(contents[index]);
      console.log('');
    });
  } catch (err) {
    console.error('批量读取文件失败:', err);
  }
}

batchReadFiles();

批量写入文件

示例:批量写入多个文件

javascript
const fs = require('fs').promises;

async function batchWriteFiles() {
  const files = [
    { name: 'file1.txt', content: '内容1' },
    { name: 'file2.txt', content: '内容2' },
    { name: 'file3.txt', content: '内容3' }
  ];
  
  try {
    // 使用 Promise.all 并行写入文件
    await Promise.all(
      files.map(file => fs.writeFile(file.name, file.content))
    );
    
    console.log('批量写入文件成功');
  } catch (err) {
    console.error('批量写入文件失败:', err);
  }
}

batchWriteFiles();

9.4 文件路径处理进阶

路径解析

javascript
const path = require('path');

// 解析路径
const filePath = '/home/user/project/file.js';
const parsedPath = path.parse(filePath);

console.log('解析路径:', parsedPath);
console.log('目录名:', parsedPath.dir);
console.log('文件名:', parsedPath.base);
console.log('扩展名:', parsedPath.ext);
console.log('文件名(不含扩展名):', parsedPath.name);

路径拼接

javascript
const path = require('path');

// 拼接路径
const dir = '/home/user';
const subdir = 'project';
const file = 'file.js';

const fullPath = path.join(dir, subdir, file);
console.log('拼接路径:', fullPath);

绝对路径

javascript
const path = require('path');

// 获取绝对路径
const relativePath = './file.js';
const absolutePath = path.resolve(relativePath);
console.log('绝对路径:', absolutePath);

// 基于当前目录获取绝对路径
const baseDir = '/home/user';
const absolutePathFromBase = path.resolve(baseDir, 'project', 'file.js');
console.log('基于基础目录的绝对路径:', absolutePathFromBase);

路径规范化

javascript
const path = require('path');

// 规范化路径
const messyPath = '/home//user/../project/file.js';
const normalizedPath = path.normalize(messyPath);
console.log('规范化路径:', normalizedPath);

路径比较

javascript
const path = require('path');

// 比较路径
const path1 = '/home/user/project/file.js';
const path2 = './project/file.js';

// 转换为绝对路径后比较
const absolutePath1 = path.resolve(path1);
const absolutePath2 = path.resolve(path2);

console.log('路径1:', absolutePath1);
console.log('路径2:', absolutePath2);
console.log('路径是否相同:', absolutePath1 === absolutePath2);

9.5 实操案例

案例1:实现文件复制

步骤1:创建 copyFile.js 文件

javascript
// copyFile.js
const fs = require('fs').promises;
const path = require('path');

async function copyFile(source, destination) {
  try {
    // 读取源文件
    const data = await fs.readFile(source);
    
    // 确保目标目录存在
    const destDir = path.dirname(destination);
    await fs.mkdir(destDir, { recursive: true });
    
    // 写入目标文件
    await fs.writeFile(destination, data);
    
    console.log(`文件复制成功: ${source} -> ${destination}`);
  } catch (err) {
    console.error('文件复制失败:', err);
  }
}

// 测试
copyFile('source.txt', 'destination.txt');
copyFile('source.txt', 'subdir/destination.txt');

步骤2:创建源文件

bash
echo "Hello, Node.js!" > source.txt

步骤3:运行程序

bash
node copyFile.js

案例2:文件夹遍历

步骤1:创建 traverse.js 文件

javascript
// traverse.js
const fs = require('fs').promises;
const path = require('path');

async function traverseDirectory(dir, indent = 0) {
  try {
    const files = await fs.readdir(dir);
    
    for (const file of files) {
      const filePath = path.join(dir, file);
      const stats = await fs.stat(filePath);
      
      const indentStr = '  '.repeat(indent);
      
      if (stats.isDirectory()) {
        console.log(`${indentStr}📁 ${file}/`);
        await traverseDirectory(filePath, indent + 1); // 递归遍历子目录
      } else {
        console.log(`${indentStr}📄 ${file} (${stats.size} bytes)`);
      }
    }
  } catch (err) {
    console.error('遍历文件夹失败:', err);
  }
}

// 测试
traverseDirectory('./');

步骤2:运行程序

bash
node traverse.js

案例3:日志写入

步骤1:创建 logger.js 文件

javascript
// logger.js
const fs = require('fs').promises;
const path = require('path');

class Logger {
  constructor(logDir = './logs') {
    this.logDir = logDir;
    this.ensureLogDir();
  }
  
  async ensureLogDir() {
    try {
      await fs.mkdir(this.logDir, { recursive: true });
    } catch (err) {
      console.error('创建日志目录失败:', err);
    }
  }
  
  async log(message, level = 'info') {
    try {
      const timestamp = new Date().toISOString();
      const logMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
      
      const logFile = path.join(this.logDir, `${new Date().toISOString().split('T')[0]}.log`);
      
      await fs.appendFile(logFile, logMessage);
      console.log(logMessage.trim());
    } catch (err) {
      console.error('写入日志失败:', err);
    }
  }
  
  async info(message) {
    await this.log(message, 'info');
  }
  
  async error(message) {
    await this.log(message, 'error');
  }
  
  async warn(message) {
    await this.log(message, 'warn');
  }
  
  async debug(message) {
    await this.log(message, 'debug');
  }
}

// 测试
const logger = new Logger();

async function testLogger() {
  await logger.info('应用启动');
  await logger.warn('配置文件未找到');
  await logger.error('数据库连接失败');
  await logger.debug('调试信息');
  await logger.info('应用关闭');
}

testLogger();

步骤2:运行程序

bash
node logger.js

步骤3:查看日志文件

bash
ls logs/
cat logs/$(date +%Y-%m-%d).log

小结

  • Node.js 的 fs 模块提供了同步和异步两种文件操作方式
  • 异步操作非阻塞,性能更好,适合生产环境
  • 同步操作代码简洁,适合少量文件操作
  • 可以使用 fs 模块进行文件夹的创建、删除和遍历
  • 可以使用 path 模块进行路径的解析、拼接和规范化
  • 批量操作可以使用 Promise.all 并行处理多个文件
  • 实际项目中,文件系统操作是常见的需求,需要熟练掌握

现在,你已经了解了文件系统的进阶操作,接下来让我们学习 Express 框架入门。

© 2026 编程马·菜鸟教程 版权所有