12k
All articles

Node.js 文件写入详解:关于 fs.writeFileSync() 的所有知识

介绍在 Node.js 中使用 fs.writeFileSync 写入文件的参数配置、错误处理、flags 选项及同步文件操作的最佳实践。

OpenReplay Team
OpenReplay Team
Node.js 文件写入详解:关于 fs.writeFileSync() 的所有知识

文件操作是许多 Node.js 应用程序的基本部分。无论是创建配置文件、记录数据还是生成报告,了解如何高效地写入文件都是至关重要的。fs.writeFileSync() 方法提供了一种在 Node.js 中处理同步文件写入操作的直接方式。

这份全面指南涵盖了有效使用 fs.writeFileSync() 的所有知识,从基本用法到高级技术和最佳实践。

关键要点

  • fs.writeFileSync() 同步写入数据到文件,在完成前会阻塞事件循环
  • 始终使用 try-catch 块来处理潜在错误
  • 使用适当的标志来控制文件创建和写入行为
  • 考虑在高并发应用中的性能影响
  • 对于较大文件或生产环境,考虑使用异步替代方案
  • 注意安全问题,特别是处理用户提供的路径时

什么是 fs.writeFileSync()?

fs.writeFileSync() 是 Node.js 文件系统(fs)模块中的一个内置方法,用于同步写入数据到文件。与其异步对应方法不同,这个方法会阻塞代码执行,直到文件操作完成。

方法签名为:

fs.writeFileSync(file, data[, options])
  • file:文件路径(字符串、Buffer、URL 或文件描述符)
  • data:要写入的内容(字符串、Buffer、TypedArray 或 DataView)
  • options:可选配置参数(字符串或对象)

fs.writeFileSync() 的基本用法

写入简单文本文件

const fs = require('fs');

try {
  fs.writeFileSync('example.txt', 'Hello, Node.js!');
  console.log('File written successfully');
} catch (err) {
  console.error('Error writing file:', err);
}

这段代码在当前工作目录中创建一个名为 example.txt 的文件,内容为 ""Hello, Node.js!""。

写入 JSON 数据

const fs = require('fs');

const user = {
  name: 'John Doe',
  age: 30,
  email: 'john@example.com'
};

try {
  fs.writeFileSync('user.json', JSON.stringify(user, null, 2));
  console.log('JSON file written successfully');
} catch (err) {
  console.error('Error writing JSON file:', err);
}

理解参数

文件参数

第一个参数指定数据将被写入的文件路径:

// 写入当前目录
fs.writeFileSync('data.txt', 'Some content');

// 写入特定路径
fs.writeFileSync('/var/logs/app.log', 'Log entry');

// 使用文件描述符
const fd = fs.openSync('config.json', 'w');
fs.writeFileSync(fd, '{""setting"": ""value""}');
fs.closeSync(fd);

重要:如果指定的目录不存在,Node.js 将抛出错误。fs.writeFileSync() 方法不能创建目录。

数据参数

第二个参数包含你想要写入的内容:

// 写入字符串
fs.writeFileSync('file.txt', 'Plain text content');

// 写入 Buffer
const buffer = Buffer.from('Binary content');
fs.writeFileSync('binary.dat', buffer);

// 写入 TypedArray
const uint8Array = new Uint8Array([72, 101, 108, 108, 111]);  // ASCII 码中的 ""Hello""
fs.writeFileSync('typed-array.txt', uint8Array);

使用 Buffer

Buffer 在处理二进制数据时特别有用:

const fs = require('fs');

// 从字符串创建 buffer
const stringData = 'Hello World';
const buffer = Buffer.from(stringData, 'utf8');
fs.writeFileSync('buffer-example.txt', buffer);

// 从 JSON 创建 buffer
const userData = { name: 'Alice', age: 28 };
const jsonBuffer = Buffer.from(JSON.stringify(userData));
fs.writeFileSync('user-data.json', jsonBuffer);

选项参数

第三个参数允许你自定义文件写入行为:

fs.writeFileSync('config.txt', 'Configuration data', {
  encoding: 'utf8',    // 字符编码(默认:'utf8')
  mode: 0o666,         // 文件权限(默认:0o666)
  flag: 'w'            // 文件系统标志(默认:'w')
});

常用标志选项

  • 'w':打开文件用于写入,如果不存在则创建,如果存在则截断(默认)
  • 'a':打开文件用于追加,如果不存在则创建
  • 'wx':类似 ‘w’,但如果路径存在则失败
  • 'ax':类似 ‘a’,但如果路径存在则失败
  • 'r+':打开文件用于读写,文件必须存在
// 追加到文件而不是覆盖
fs.writeFileSync('log.txt', 'New log entryn', { flag: 'a' });

// 仅当文件不存在时创建新文件
try {
  fs.writeFileSync('config.json', '{}', { flag: 'wx' });
  console.log('New config file created');
} catch (err) {
  if (err.code === 'EEXIST') {
    console.log('Config file already exists');
  } else {
    console.error('Error:', err);
  }
}

fs.writeFileSync() 的错误处理

由于 fs.writeFileSync() 是同步的,错误会直接抛出。始终将其包装在 try-catch 块中:

try {
  fs.writeFileSync('/path/to/file.txt', 'Content');
} catch (error) {
  // 处理特定错误类型
  if (error.code === 'ENOENT') {
    console.error('Directory does not exist');
  } else if (error.code === 'EACCES') {
    console.error('Permission denied');
  } else {
    console.error('Unexpected error:', error);
  }
}

常见错误代码

  • ENOENT:无此文件或目录(通常是父目录不存在)
  • EACCES:权限被拒绝
  • EISDIR:是一个目录(尝试写入目录)
  • EMFILE:打开的文件过多
  • EEXIST:文件已存在(使用 ‘wx’ 或 ‘ax’ 标志时)

实际用例

创建配置文件

const fs = require('fs');

function createDefaultConfig() {
  const config = {
    apiKey: '',
    debug: false,
    logLevel: 'info',
    maxRetries: 3
  };
  
  try {
    fs.writeFileSync('config.json', JSON.stringify(config, null, 2));
    console.log('Default configuration created');
  } catch (err) {
    console.error('Failed to create config file:', err);
  }
}

// 检查配置是否存在,不存在则创建
try {
  fs.accessSync('config.json', fs.constants.F_OK);
  console.log('Config file already exists');
} catch (err) {
  createDefaultConfig();
}

简单日志记录

function logMessage(message) {
  const timestamp = new Date().toISOString();
  const logEntry = `[${timestamp}] ${message}n`;
  
  try {
    fs.writeFileSync('app.log', logEntry, { flag: 'a' });
  } catch (err) {
    console.error('Failed to write to log file:', err);
  }
}

logMessage('Application started');
// 执行某些操作
logMessage('Operation completed');

保存用户输入

const readline = require('readline-sync');
const fs = require('fs');

function saveUserData() {
  const username = readline.question('Enter username: ');
  
  if (!username) {
    console.log('Username cannot be empty');
    return saveUserData();
  }
  
  try {
    const userData = {
      username,
      createdAt: new Date().toISOString()
    };
    
    fs.writeFileSync(`${username}.json`, JSON.stringify(userData, null, 2));
    console.log(`User data saved to ${username}.json`);
  } catch (err) {
    console.error('Failed to save user data:', err);
  }
}

saveUserData();

性能考虑

同步与异步操作

fs.writeFileSync() 会阻塞事件循环直到操作完成,这可能会影响高并发应用程序的性能:

// 同步(阻塞事件循环)
console.time('writeFileSync');
fs.writeFileSync('large-file.txt', 'X'.repeat(1000000));
console.timeEnd('writeFileSync');

// 异步(不阻塞事件循环)
console.time('writeFile');
fs.writeFile('large-file-async.txt', 'X'.repeat(1000000), () => {
  console.timeEnd('writeFile');
});
console.log('This runs immediately while file is being written');

何时使用 fs.writeFileSync()

在以下情况使用 fs.writeFileSync()

  • 写入小文件时
  • 在脚本或 CLI 工具中工作时
  • 文件必须在继续执行前写入时
  • 在启动/初始化阶段时

避免在以下情况使用 fs.writeFileSync()

  • 处理大文件时
  • 在高并发 Web 服务器中
  • 在性能关键的代码路径中
  • 同时处理多个文件操作时

现代替代方案

使用 ES 模块语法

// ESM 语法(需要 Node.js 12+)
import { writeFileSync } from 'fs';
import { join } from 'path';

const filePath = join(process.cwd(), 'data.txt');
writeFileSync(filePath, 'Content using ESM syntax');

使用 fs/promises API

对于保持同步行为但使用更现代的 Promise 方式:

// 使用 fs/promises 和顶级 await(Node.js 14.8+ 与 ESM)
import { writeFile } from 'fs/promises';

try {
  // 这仍然是异步的,但语法更清晰
  await writeFile('example.txt', 'Content with promises');
  console.log('File written successfully');
} catch (err) {
  console.error('Error writing file:', err);
}

安全考虑

文件权限

写入敏感数据时要注意文件权限:

// 为敏感文件设置限制性权限
fs.writeFileSync('credentials.json', JSON.stringify(credentials), {
  mode: 0o600  // 仅所有者可读写
});

路径遍历漏洞

始终验证和净化文件路径,特别是当它们来自用户输入时:

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

function safeWriteFile(filename, content) {
  // 净化文件名以防止路径遍历
  const safeName = path.basename(filename);
  const safePath = path.join('./uploads', safeName);
  
  try {
    fs.writeFileSync(safePath, content);
    return true;
  } catch (err) {
    console.error('Error writing file:', err);
    return false;
  }
}

常见问题排查

""ENOENT: no such file or directory""

这通常意味着目录不存在:

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

function writeFileWithDirectoryCreation(filePath, content) {
  const directory = path.dirname(filePath);
  
  try {
    // 如果目录不存在则创建
    if (!fs.existsSync(directory)) {
      fs.mkdirSync(directory, { recursive: true });
    }
    
    // 现在写入文件
    fs.writeFileSync(filePath, content);
    return true;
  } catch (err) {
    console.error('Error:', err);
    return false;
  }
}

writeFileWithDirectoryCreation('logs/app/data.log', 'Log entry');

""EACCES: permission denied""

检查文件和目录权限:

try {
  fs.writeFileSync('/var/log/app.log', 'Log entry');
} catch (err) {
  if (err.code === 'EACCES') {
    // 尝试写入不同位置
    const homeDir = require('os').homedir();
    fs.writeFileSync(path.join(homeDir, 'app.log'), 'Log entry');
    console.log('Wrote to home directory instead');
  }
}

结论

fs.writeFileSync() 方法是 Node.js 应用程序中处理文件操作的强大工具。它提供了一种直接的方式来同步写入数据到文件,非常适合需要在继续执行前确保完成的场景。通过理解其参数、错误处理和性能影响,你可以在项目中有效地使用这个方法,同时避免常见陷阱。

记住要考虑这个方法的同步特性,并根据应用程序的需求适当使用。对于高并发应用或处理大文件时,考虑使用异步替代方案如 fs.writeFile() 或流。

常见问题

何时应该使用 fs.writeFileSync() 而不是 fs.writeFile()?

当你需要在继续执行前确保完成时使用 fs.writeFileSync(),通常在脚本、CLI 工具或初始化代码中。在大多数其他情况下使用 fs.writeFile(),特别是在性能重要的服务器或应用程序中。

如何追加到文件而不是覆盖它?

在选项参数中使用 'a' 标志:fs.writeFileSync('log.txt', 'New entryn', { flag: 'a' });

fs.writeFileSync() 能创建目录吗?

不能,它只能创建文件。你需要先使用 fs.mkdirSync() 来创建目录。

如何用 fs.writeFileSync() 处理二进制数据?

使用 Buffer 对象:const buffer = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // 'Hello' fs.writeFileSync('binary.dat', buffer);

fs.writeFileSync() 有文件大小限制吗?

API 中没有特定限制,但同步写入非常大的文件可能会阻塞事件循环太长时间。对于大于几 MB 的文件,考虑使用流或异步方法。

如何确保我的文件只有所有者可读?

设置 mode 选项:fs.writeFileSync('sensitive.txt', 'private data', { mode: 0o600 });

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers

We use cookies to improve your experience. By using our site, you accept cookies.