Node.js 文件系统基础

章节介绍

Node.js 的文件系统(fs)模块提供了与文件系统交互的 API,让开发者能够读取、写入、创建、删除文件和目录。文件系统操作是 Node.js 的核心功能之一,广泛应用于日志记录、配置管理、数据处理等场景。本教程将详细介绍 Node.js 文件系统的基础操作。

核心知识点

fs 模块概述

fs 模块是 Node.js 的核心模块之一,提供了同步和异步两种方式的文件系统操作。

// 引入 fs 模块
const fs = require('fs');
const path = require('path');

// fs 模块的主要功能:
// 1. 文件读写
// 2. 目录操作
// 3. 文件信息获取
// 4. 文件监听
// 5. 文件权限管理

文件路径处理

在操作文件之前,需要先了解如何正确处理文件路径。

const path = require('path');

// 路径拼接
const filePath = path.join(__dirname, 'files', 'test.txt');
console.log('拼接路径:', filePath);

// 路径解析
const parsed = path.parse(filePath);
console.log('路径解析:', parsed);
// 输出:{
//   root: '/',
//   dir: '/path/to/files',
//   base: 'test.txt',
//   ext: '.txt',
//   name: 'test'
// }

// 获取目录名
const dirName = path.dirname(filePath);
console.log('目录名:', dirName);

// 获取文件名
const baseName = path.basename(filePath);
console.log('文件名:', baseName);

// 获取扩展名
const extName = path.extname(filePath);
console.log('扩展名:', extName);

// 路径规范化
const normalized = path.normalize('/path/to/../to/./file.txt');
console.log('规范化路径:', normalized);

// 绝对路径
const absolute = path.resolve('file.txt');
console.log('绝对路径:', absolute);

// 相对路径
const relative = path.relative('/path/to/file1.txt', '/path/to/file2.txt');
console.log('相对路径:', relative);

// 判断是否为绝对路径
console.log('是否为绝对路径:', path.isAbsolute(filePath));

// 路径分隔符
console.log('路径分隔符:', path.sep);  // Windows: '\', Unix: '/'

// 环境变量分隔符
console.log('环境变量分隔符:', path.delimiter);  // Windows: ';', Unix: ':'

文件读取

Node.js 提供了多种文件读取方式。

同步读取

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

// 读取文件(同步)
try {
  const filePath = path.join(__dirname, 'test.txt');
  const content = fs.readFileSync(filePath, 'utf8');
  console.log('文件内容:');
  console.log(content);
} catch (error) {
  console.error('读取文件失败:', error.message);
}

// 读取文件(Buffer)
try {
  const filePath = path.join(__dirname, 'test.txt');
  const buffer = fs.readFileSync(filePath);
  console.log('Buffer 长度:', buffer.length);
  console.log('文件内容:', buffer.toString('utf8'));
} catch (error) {
  console.error('读取文件失败:', error.message);
}

异步读取

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

// 读取文件(回调方式)
const filePath = path.join(__dirname, 'test.txt');
fs.readFile(filePath, 'utf8', (error, content) => {
  if (error) {
    console.error('读取文件失败:', error.message);
    return;
  }
  
  console.log('文件内容:');
  console.log(content);
});

// 读取文件(Promise 方式)
const fsPromises = require('fs').promises;

async function readFileAsync() {
  try {
    const filePath = path.join(__dirname, 'test.txt');
    const content = await fsPromises.readFile(filePath, 'utf8');
    console.log('文件内容:');
    console.log(content);
  } catch (error) {
    console.error('读取文件失败:', error.message);
  }
}

readFileAsync();

文件写入

同步写入

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

// 写入文件(同步)
try {
  const filePath = path.join(__dirname, 'output.txt');
  const content = 'Hello, Node.js!';
  fs.writeFileSync(filePath, content, 'utf8');
  console.log('文件写入成功');
} catch (error) {
  console.error('写入文件失败:', error.message);
}

// 追加内容到文件
try {
  const filePath = path.join(__dirname, 'output.txt');
  const additionalContent = '\n追加的内容';
  fs.appendFileSync(filePath, additionalContent, 'utf8');
  console.log('内容追加成功');
} catch (error) {
  console.error('追加内容失败:', error.message);
}

异步写入

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

// 写入文件(回调方式)
const filePath = path.join(__dirname, 'output.txt');
const content = 'Hello, Node.js!';

fs.writeFile(filePath, content, 'utf8', (error) => {
  if (error) {
    console.error('写入文件失败:', error.message);
    return;
  }
  
  console.log('文件写入成功');
});

// 追加内容到文件(回调方式)
fs.appendFile(filePath, '\n追加的内容', 'utf8', (error) => {
  if (error) {
    console.error('追加内容失败:', error.message);
    return;
  }
  
  console.log('内容追加成功');
});

// 写入文件(Promise 方式)
const fsPromises = require('fs').promises;

async function writeFileAsync() {
  try {
    const filePath = path.join(__dirname, 'output.txt');
    const content = 'Hello, Node.js!';
    await fsPromises.writeFile(filePath, content, 'utf8');
    console.log('文件写入成功');
  } catch (error) {
    console.error('写入文件失败:', error.message);
  }
}

writeFileAsync();

文件信息获取

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

// 获取文件信息(同步)
try {
  const filePath = path.join(__dirname, 'test.txt');
  const stats = fs.statSync(filePath);
  
  console.log('文件信息:');
  console.log('  是否为文件:', stats.isFile());
  console.log('  是否为目录:', stats.isDirectory());
  console.log('  文件大小:', stats.size, '字节');
  console.log('  创建时间:', stats.birthtime);
  console.log('  修改时间:', stats.mtime);
  console.log('  访问时间:', stats.atime);
  console.log('  权限模式:', stats.mode);
  console.log('  inode:', stats.ino);
  console.log('  设备 ID:', stats.dev);
} catch (error) {
  console.error('获取文件信息失败:', error.message);
}

// 获取文件信息(异步)
const filePath = path.join(__dirname, 'test.txt');
fs.stat(filePath, (error, stats) => {
  if (error) {
    console.error('获取文件信息失败:', error.message);
    return;
  }
  
  console.log('文件信息:');
  console.log('  是否为文件:', stats.isFile());
  console.log('  是否为目录:', stats.isDirectory());
  console.log('  文件大小:', stats.size, '字节');
});

// 检查文件是否存在
fs.access(filePath, fs.constants.F_OK, (error) => {
  if (error) {
    console.log('文件不存在');
  } else {
    console.log('文件存在');
  }
});

// 检查文件是否可读
fs.access(filePath, fs.constants.R_OK, (error) => {
  if (error) {
    console.log('文件不可读');
  } else {
    console.log('文件可读');
  }
});

// 检查文件是否可写
fs.access(filePath, fs.constants.W_OK, (error) => {
  if (error) {
    console.log('文件不可写');
  } else {
    console.log('文件可写');
  }
});

目录操作

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

// 创建目录
try {
  const dirPath = path.join(__dirname, 'new-directory');
  fs.mkdirSync(dirPath);
  console.log('目录创建成功');
} catch (error) {
  console.error('创建目录失败:', error.message);
}

// 创建多级目录
try {
  const dirPath = path.join(__dirname, 'parent', 'child', 'grandchild');
  fs.mkdirSync(dirPath, { recursive: true });
  console.log('多级目录创建成功');
} catch (error) {
  console.error('创建目录失败:', error.message);
}

// 读取目录
try {
  const dirPath = __dirname;
  const files = fs.readdirSync(dirPath);
  console.log('目录内容:');
  files.forEach(file => {
    console.log('  ', file);
  });
} catch (error) {
  console.error('读取目录失败:', error.message);
}

// 读取目录(带详细信息)
try {
  const dirPath = __dirname;
  const files = fs.readdirSync(dirPath, { withFileTypes: true });
  
  console.log('目录内容(详细信息):');
  files.forEach(file => {
    const type = file.isDirectory() ? '目录' : '文件';
    console.log(`  ${file.name} (${type})`);
  });
} catch (error) {
  console.error('读取目录失败:', error.message);
}

// 删除目录
try {
  const dirPath = path.join(__dirname, 'new-directory');
  fs.rmdirSync(dirPath);
  console.log('目录删除成功');
} catch (error) {
  console.error('删除目录失败:', error.message);
}

// 删除非空目录(需要递归删除)
function deleteDirectory(dirPath) {
  if (fs.existsSync(dirPath)) {
    const files = fs.readdirSync(dirPath);
    
    files.forEach(file => {
      const filePath = path.join(dirPath, file);
      const stats = fs.statSync(filePath);
      
      if (stats.isDirectory()) {
        deleteDirectory(filePath);
      } else {
        fs.unlinkSync(filePath);
      }
    });
    
    fs.rmdirSync(dirPath);
  }
}

try {
  const dirPath = path.join(__dirname, 'parent');
  deleteDirectory(dirPath);
  console.log('目录及其内容删除成功');
} catch (error) {
  console.error('删除目录失败:', error.message);
}

文件删除和重命名

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

// 删除文件
try {
  const filePath = path.join(__dirname, 'output.txt');
  fs.unlinkSync(filePath);
  console.log('文件删除成功');
} catch (error) {
  console.error('删除文件失败:', error.message);
}

// 重命名文件
try {
  const oldPath = path.join(__dirname, 'old-name.txt');
  const newPath = path.join(__dirname, 'new-name.txt');
  fs.renameSync(oldPath, newPath);
  console.log('文件重命名成功');
} catch (error) {
  console.error('重命名文件失败:', error.message);
}

// 移动文件
try {
  const sourcePath = path.join(__dirname, 'file.txt');
  const destPath = path.join(__dirname, 'subdirectory', 'file.txt');
  fs.renameSync(sourcePath, destPath);
  console.log('文件移动成功');
} catch (error) {
  console.error('移动文件失败:', error.message);
}

实用案例分析

案例 1:文件管理工具

创建一个功能完善的文件管理工具。

// file-manager.js
const fs = require('fs');
const path = require('path');

class FileManager {
  constructor(basePath = process.cwd()) {
    this.basePath = basePath;
  }
  
  // 创建文件
  createFile(filename, content = '') {
    const filePath = path.join(this.basePath, filename);
    
    try {
      fs.writeFileSync(filePath, content, 'utf8');
      console.log(`文件 ${filename} 创建成功`);
      return true;
    } catch (error) {
      console.error(`创建文件失败:${error.message}`);
      return false;
    }
  }
  
  // 读取文件
  readFile(filename) {
    const filePath = path.join(this.basePath, filename);
    
    try {
      const content = fs.readFileSync(filePath, 'utf8');
      console.log(`文件 ${filename} 的内容:`);
      console.log(content);
      return content;
    } catch (error) {
      console.error(`读取文件失败:${error.message}`);
      return null;
    }
  }
  
  // 更新文件
  updateFile(filename, content) {
    const filePath = path.join(this.basePath, filename);
    
    try {
      if (!fs.existsSync(filePath)) {
        console.error(`文件 ${filename} 不存在`);
        return false;
      }
      
      fs.writeFileSync(filePath, content, 'utf8');
      console.log(`文件 ${filename} 更新成功`);
      return true;
    } catch (error) {
      console.error(`更新文件失败:${error.message}`);
      return false;
    }
  }
  
  // 删除文件
  deleteFile(filename) {
    const filePath = path.join(this.basePath, filename);
    
    try {
      if (!fs.existsSync(filePath)) {
        console.error(`文件 ${filename} 不存在`);
        return false;
      }
      
      fs.unlinkSync(filePath);
      console.log(`文件 ${filename} 删除成功`);
      return true;
    } catch (error) {
      console.error(`删除文件失败:${error.message}`);
      return false;
    }
  }
  
  // 重命名文件
  renameFile(oldName, newName) {
    const oldPath = path.join(this.basePath, oldName);
    const newPath = path.join(this.basePath, newName);
    
    try {
      if (!fs.existsSync(oldPath)) {
        console.error(`文件 ${oldName} 不存在`);
        return false;
      }
      
      fs.renameSync(oldPath, newPath);
      console.log(`文件 ${oldName} 重命名为 ${newName} 成功`);
      return true;
    } catch (error) {
      console.error(`重命名文件失败:${error.message}`);
      return false;
    }
  }
  
  // 复制文件
  copyFile(source, destination) {
    const sourcePath = path.join(this.basePath, source);
    const destPath = path.join(this.basePath, destination);
    
    try {
      if (!fs.existsSync(sourcePath)) {
        console.error(`源文件 ${source} 不存在`);
        return false;
      }
      
      const content = fs.readFileSync(sourcePath);
      fs.writeFileSync(destPath, content);
      console.log(`文件 ${source} 复制到 ${destination} 成功`);
      return true;
    } catch (error) {
      console.error(`复制文件失败:${error.message}`);
      return false;
    }
  }
  
  // 创建目录
  createDirectory(dirname) {
    const dirPath = path.join(this.basePath, dirname);
    
    try {
      fs.mkdirSync(dirPath, { recursive: true });
      console.log(`目录 ${dirname} 创建成功`);
      return true;
    } catch (error) {
      console.error(`创建目录失败:${error.message}`);
      return false;
    }
  }
  
  // 列出目录内容
  listDirectory(dirname = '.') {
    const dirPath = path.join(this.basePath, dirname);
    
    try {
      const files = fs.readdirSync(dirPath, { withFileTypes: true });
      
      console.log(`目录 ${dirname} 的内容:`);
      files.forEach(file => {
        const type = file.isDirectory() ? '[DIR]' : '[FILE]';
        console.log(`  ${type} ${file.name}`);
      });
      
      return files;
    } catch (error) {
      console.error(`列出目录失败:${error.message}`);
      return [];
    }
  }
  
  // 获取文件信息
  getFileInfo(filename) {
    const filePath = path.join(this.basePath, filename);
    
    try {
      const stats = fs.statSync(filePath);
      
      const info = {
        name: filename,
        size: stats.size,
        isFile: stats.isFile(),
        isDirectory: stats.isDirectory(),
        created: stats.birthtime,
        modified: stats.mtime,
        accessed: stats.atime
      };
      
      console.log('文件信息:');
      console.log(`  名称:${info.name}`);
      console.log(`  大小:${info.size} 字节`);
      console.log(`  类型:${info.isFile ? '文件' : '目录'}`);
      console.log(`  创建时间:${info.created}`);
      console.log(`  修改时间:${info.modified}`);
      
      return info;
    } catch (error) {
      console.error(`获取文件信息失败:${error.message}`);
      return null;
    }
  }
  
  // 搜索文件
  searchFiles(pattern, dirname = '.') {
    const dirPath = path.join(this.basePath, dirname);
    const results = [];
    
    function search(currentPath) {
      try {
        const files = fs.readdirSync(currentPath, { withFileTypes: true });
        
        files.forEach(file => {
          const filePath = path.join(currentPath, file.name);
          
          if (file.isDirectory()) {
            search(filePath);
          } else if (file.name.match(pattern)) {
            results.push(filePath);
          }
        });
      } catch (error) {
        console.error(`搜索文件失败:${error.message}`);
      }
    }
    
    search(dirPath);
    
    console.log(`搜索结果(模式:${pattern}):`);
    results.forEach(file => {
      console.log(`  ${file}`);
    });
    
    return results;
  }
}

// 使用示例
const fileManager = new FileManager();

// 创建文件
fileManager.createFile('test.txt', 'Hello, World!');

// 读取文件
fileManager.readFile('test.txt');

// 更新文件
fileManager.updateFile('test.txt', 'Hello, Node.js!');

// 获取文件信息
fileManager.getFileInfo('test.txt');

// 创建目录
fileManager.createDirectory('subdirectory');

// 复制文件
fileManager.copyFile('test.txt', 'subdirectory/test.txt');

// 列出目录内容
fileManager.listDirectory();

// 搜索文件
fileManager.searchFiles(/\.txt$/);

// 重命名文件
fileManager.renameFile('test.txt', 'renamed.txt');

// 删除文件
fileManager.deleteFile('renamed.txt');

案例 2:日志记录器

创建一个功能完善的日志记录器。

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

class Logger {
  constructor(options = {}) {
    this.name = options.name || 'App';
    this.logDir = options.logDir || path.join(__dirname, 'logs');
    this.maxFileSize = options.maxFileSize || 10 * 1024 * 1024; // 10MB
    this.maxFiles = options.maxFiles || 5;
    this.level = options.level || 'info';
    
    this.levels = {
      error: 0,
      warn: 1,
      info: 2,
      debug: 3
    };
    
    this.ensureLogDir();
  }
  
  ensureLogDir() {
    if (!fs.existsSync(this.logDir)) {
      fs.mkdirSync(this.logDir, { recursive: true });
    }
  }
  
  getLogFile() {
    const date = new Date();
    const filename = `${this.name}-${date.toISOString().split('T')[0]}.log`;
    return path.join(this.logDir, filename);
  }
  
  formatMessage(level, message) {
    const timestamp = new Date().toISOString();
    return `[${timestamp}] [${this.name}] [${level.toUpperCase()}] ${message}\n`;
  }
  
  shouldLog(level) {
    return this.levels[level] <= this.levels[this.level];
  }
  
  rotateLogFile(filePath) {
    try {
      const stats = fs.statSync(filePath);
      
      if (stats.size >= this.maxFileSize) {
        const baseName = path.basename(filePath, '.log');
        const dirName = path.dirname(filePath);
        
        // 删除最旧的日志文件
        const files = fs.readdirSync(dirName)
          .filter(file => file.startsWith(baseName) && file.endsWith('.log'))
          .sort();
        
        while (files.length >= this.maxFiles) {
          const oldestFile = files.shift();
          fs.unlinkSync(path.join(dirName, oldestFile));
        }
        
        // 重命名当前日志文件
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        const newName = `${baseName}-${timestamp}.log`;
        fs.renameSync(filePath, path.join(dirName, newName));
      }
    } catch (error) {
      console.error('日志文件轮转失败:', error.message);
    }
  }
  
  log(level, message) {
    if (!this.shouldLog(level)) {
      return;
    }
    
    const formattedMessage = this.formatMessage(level, message);
    const logFile = this.getLogFile();
    
    // 输出到控制台
    switch (level) {
      case 'error':
        console.error(formattedMessage);
        break;
      case 'warn':
        console.warn(formattedMessage);
        break;
      case 'debug':
        console.debug(formattedMessage);
        break;
      default:
        console.log(formattedMessage);
    }
    
    // 写入文件
    try {
      this.rotateLogFile(logFile);
      fs.appendFileSync(logFile, formattedMessage, 'utf8');
    } catch (error) {
      console.error('写入日志文件失败:', error.message);
    }
  }
  
  error(message) {
    this.log('error', message);
  }
  
  warn(message) {
    this.log('warn', message);
  }
  
  info(message) {
    this.log('info', message);
  }
  
  debug(message) {
    this.log('debug', message);
  }
}

// 使用示例
const logger = new Logger({
  name: 'MyApp',
  logDir: path.join(__dirname, 'logs'),
  maxFileSize: 1024,  // 1KB(用于测试)
  maxFiles: 3,
  level: 'debug'
});

logger.info('应用启动');
logger.debug('调试信息');
logger.warn('警告信息');
logger.error('错误信息');

// 模拟大量日志
for (let i = 0; i < 100; i++) {
  logger.info(`日志消息 ${i}`);
}

案例 3:配置文件管理器

创建一个配置文件管理器,支持 JSON 格式的配置文件。

// config-manager.js
const fs = require('fs');
const path = require('path');

class ConfigManager {
  constructor(configPath) {
    this.configPath = configPath;
    this.config = {};
    this.loadConfig();
  }
  
  // 加载配置文件
  loadConfig() {
    try {
      if (fs.existsSync(this.configPath)) {
        const content = fs.readFileSync(this.configPath, 'utf8');
        this.config = JSON.parse(content);
        console.log('配置文件加载成功');
      } else {
        console.log('配置文件不存在,使用默认配置');
        this.config = {};
      }
    } catch (error) {
      console.error('加载配置文件失败:', error.message);
      this.config = {};
    }
  }
  
  // 保存配置文件
  saveConfig() {
    try {
      const content = JSON.stringify(this.config, null, 2);
      fs.writeFileSync(this.configPath, content, 'utf8');
      console.log('配置文件保存成功');
      return true;
    } catch (error) {
      console.error('保存配置文件失败:', error.message);
      return false;
    }
  }
  
  // 获取配置值
  get(key, defaultValue = undefined) {
    const keys = key.split('.');
    let value = this.config;
    
    for (const k of keys) {
      if (value && typeof value === 'object' && k in value) {
        value = value[k];
      } else {
        return defaultValue;
      }
    }
    
    return value;
  }
  
  // 设置配置值
  set(key, value) {
    const keys = key.split('.');
    let current = this.config;
    
    for (let i = 0; i < keys.length - 1; i++) {
      const k = keys[i];
      if (!(k in current) || typeof current[k] !== 'object') {
        current[k] = {};
      }
      current = current[k];
    }
    
    current[keys[keys.length - 1]] = value;
    return this.saveConfig();
  }
  
  // 删除配置值
  delete(key) {
    const keys = key.split('.');
    let current = this.config;
    
    for (let i = 0; i < keys.length - 1; i++) {
      const k = keys[i];
      if (!(k in current) || typeof current[k] !== 'object') {
        return false;
      }
      current = current[k];
    }
    
    const lastKey = keys[keys.length - 1];
    if (lastKey in current) {
      delete current[lastKey];
      return this.saveConfig();
    }
    
    return false;
  }
  
  // 获取所有配置
  getAll() {
    return { ...this.config };
  }
  
  // 合并配置
  merge(newConfig) {
    this.config = this.deepMerge(this.config, newConfig);
    return this.saveConfig();
  }
  
  // 深度合并对象
  deepMerge(target, source) {
    const result = { ...target };
    
    for (const key in source) {
      if (source[key] instanceof Object && key in target) {
        result[key] = this.deepMerge(target[key], source[key]);
      } else {
        result[key] = source[key];
      }
    }
    
    return result;
  }
  
  // 重置配置
  reset() {
    this.config = {};
    return this.saveConfig();
  }
}

// 使用示例
const configPath = path.join(__dirname, 'config.json');
const configManager = new ConfigManager(configPath);

// 设置配置
configManager.set('app.name', 'My App');
configManager.set('app.version', '1.0.0');
configManager.set('app.port', 3000);
configManager.set('database.host', 'localhost');
configManager.set('database.port', 5432);
configManager.set('database.name', 'myapp');

// 获取配置
console.log('应用名称:', configManager.get('app.name'));
console.log('应用端口:', configManager.get('app.port'));
console.log('数据库主机:', configManager.get('database.host'));
console.log('不存在的配置:', configManager.get('nonexistent', '默认值'));

// 获取所有配置
console.log('所有配置:');
console.log(configManager.getAll());

// 合并配置
configManager.merge({
  app: {
    debug: true
  },
  logging: {
    level: 'info'
  }
});

console.log('合并后的配置:');
console.log(configManager.getAll());

// 删除配置
configManager.delete('app.debug');
console.log('删除后的配置:');
console.log(configManager.getAll());

代码示例

示例 1:文件复制工具

// file-copier.js
const fs = require('fs');
const path = require('path');

function copyFile(source, destination, callback) {
  const readStream = fs.createReadStream(source);
  const writeStream = fs.createWriteStream(destination);
  
  readStream.on('error', (error) => {
    console.error('读取文件失败:', error.message);
    callback(error);
  });
  
  writeStream.on('error', (error) => {
    console.error('写入文件失败:', error.message);
    callback(error);
  });
  
  writeStream.on('finish', () => {
    console.log('文件复制成功');
    callback(null);
  });
  
  readStream.pipe(writeStream);
}

// 使用示例
const sourceFile = path.join(__dirname, 'source.txt');
const destFile = path.join(__dirname, 'destination.txt');

copyFile(sourceFile, destFile, (error) => {
  if (error) {
    console.error('复制失败');
  } else {
    console.log('复制完成');
  }
});

示例 2:目录同步工具

// directory-sync.js
const fs = require('fs');
const path = require('path');

function syncDirectories(source, destination) {
  // 确保目标目录存在
  if (!fs.existsSync(destination)) {
    fs.mkdirSync(destination, { recursive: true });
  }
  
  // 读取源目录
  const files = fs.readdirSync(source, { withFileTypes: true });
  
  files.forEach(file => {
    const sourcePath = path.join(source, file.name);
    const destPath = path.join(destination, file.name);
    
    if (file.isDirectory()) {
      // 递归同步子目录
      syncDirectories(sourcePath, destPath);
    } else {
      // 复制文件
      const sourceStats = fs.statSync(sourcePath);
      let shouldCopy = true;
      
      // 检查目标文件是否存在
      if (fs.existsSync(destPath)) {
        const destStats = fs.statSync(destPath);
        
        // 如果目标文件更新,则不复制
        if (destStats.mtime >= sourceStats.mtime) {
          shouldCopy = false;
        }
      }
      
      if (shouldCopy) {
        console.log(`复制文件:${file.name}`);
        const content = fs.readFileSync(sourcePath);
        fs.writeFileSync(destPath, content);
      }
    }
  });
  
  // 删除目标目录中不存在于源目录的文件
  const destFiles = fs.readdirSync(destination);
  destFiles.forEach(file => {
    const sourcePath = path.join(source, file);
    const destPath = path.join(destination, file);
    
    if (!fs.existsSync(sourcePath)) {
      console.log(`删除文件:${file}`);
      fs.unlinkSync(destPath);
    }
  });
}

// 使用示例
const sourceDir = path.join(__dirname, 'source');
const destDir = path.join(__dirname, 'destination');

syncDirectories(sourceDir, destDir);
console.log('目录同步完成');

示例 3:文件监控器

// file-watcher.js
const fs = require('fs');
const path = require('path');

class FileWatcher {
  constructor(watchPath) {
    this.watchPath = watchPath;
    this.watchers = new Map();
  }
  
  start() {
    console.log(`开始监控:${this.watchPath}`);
    
    const watcher = fs.watch(this.watchPath, (eventType, filename) => {
      if (filename) {
        const filePath = path.join(this.watchPath, filename);
        
        switch (eventType) {
          case 'rename':
            if (fs.existsSync(filePath)) {
              console.log(`文件创建:${filename}`);
            } else {
              console.log(`文件删除:${filename}`);
            }
            break;
          case 'change':
            console.log(`文件修改:${filename}`);
            break;
        }
      }
    });
    
    this.watchers.set(this.watchPath, watcher);
  }
  
  stop() {
    this.watchers.forEach((watcher, path) => {
      watcher.close();
      console.log(`停止监控:${path}`);
    });
    this.watchers.clear();
  }
}

// 使用示例
const watchPath = __dirname;
const fileWatcher = new FileWatcher(watchPath);

fileWatcher.start();

// 5 秒后停止监控
setTimeout(() => {
  fileWatcher.stop();
}, 5000);

实现技巧与注意事项

文件操作最佳实践

  1. 使用异步操作:避免阻塞事件循环
  2. 错误处理:始终处理可能的错误
  3. 路径处理:使用 path 模块处理路径
  4. 资源清理:确保文件描述符正确关闭

性能优化建议

  1. 使用流处理大文件:避免一次性读取大文件
  2. 批量操作:减少文件系统调用次数
  3. 缓存文件信息:避免重复读取文件信息
  4. 使用 Promise:简化异步代码

安全注意事项

  1. 路径验证:防止路径遍历攻击
  2. 权限检查:确保有足够的权限
  3. 输入验证:验证用户输入的文件名
  4. 备份重要文件:避免意外删除

常见问题与解决方案

问题 1:文件路径问题

// 问题代码:硬编码路径
const filePath = 'C:\\Users\\user\\file.txt';  // Windows 特定

// 解决方案:使用 path 模块
const path = require('path');
const filePath = path.join(__dirname, 'file.txt');

// 或使用跨平台路径分隔符
const filePath = 'files' + path.sep + 'file.txt';

问题 2:异步操作顺序问题

// 问题代码:异步操作顺序不确定
fs.readFile('file1.txt', (err, data1) => {
  console.log('File 1');
});
fs.readFile('file2.txt', (err, data2) => {
  console.log('File 2');
});

// 解决方案:使用 Promise 或 async/await
const fsPromises = require('fs').promises;

async function readFiles() {
  const data1 = await fsPromises.readFile('file1.txt');
  console.log('File 1');
  
  const data2 = await fsPromises.readFile('file2.txt');
  console.log('File 2');
}

readFiles();

问题 3:文件编码问题

// 问题代码:未指定编码
const content = fs.readFileSync('file.txt');  // 返回 Buffer

// 解决方案:指定编码
const content = fs.readFileSync('file.txt', 'utf8');

// 或使用 Buffer 转换
const buffer = fs.readFileSync('file.txt');
const content = buffer.toString('utf8');

问题 4:大文件处理问题

// 问题代码:一次性读取大文件
const content = fs.readFileSync('large-file.txt');  // 可能导致内存溢出

// 解决方案:使用流处理
const readStream = fs.createReadStream('large-file.txt');
const writeStream = fs.createWriteStream('output.txt');

readStream.on('data', (chunk) => {
  console.log(`读取了 ${chunk.length} 字节`);
});

readStream.on('end', () => {
  console.log('读取完成');
});

readStream.pipe(writeStream);

总结

本教程详细介绍了 Node.js 文件系统的基础操作,包括文件读写、目录操作、文件信息获取等重要内容。文件系统操作是 Node.js 编程的核心功能之一,掌握它对于开发各种应用程序至关重要。

通过本集的学习,您应该能够:

  1. 理解 Node.js 文件系统模块的基本概念
  2. 使用 fs 模块进行文件读写操作
  3. 进行目录的创建、读取和删除操作
  4. 获取和管理文件信息
  5. 创建实用的文件管理工具
  6. 避免常见的文件系统操作错误

在下一集中,我们将学习 Node.js 文件系统进阶,包括文件流、异步操作等高级特性。继续加油,您的 Node.js 技能正在不断提升!

« 上一篇 Node.js 全局对象与全局变量 下一篇 » Node.js 文件系统进阶