Node.js 读取文件有哪几种方法?四种写法完整避坑指南

编程狮(w3cschool.cn) 2026-10-01 07:05:38 浏览数 (38)
反馈

Node.js 读取文件四种写法避坑指南

你用 Node.js 写脚本,第一道坎往往就是“怎么把磁盘上的文件读进来”。Node.js 读取文件到底有哪几种方法?答案是四条主路径:同步的 readFileSync、回调式的 readFile、Promise 化的 fs.promises.readFile,以及应对大文件的 createReadStream。今天这篇文章,编程狮就换一个“避坑”的角度,把这四种写法连同最容易踩的坑一次讲透,让你选得对、写得稳。读完你会拿到一张选型表,也清楚每种写法在哪些场景下会翻车。

Node.js 基于事件循环,文件操作天然有“阻塞”和“非阻塞”两条路。选错方式,轻则代码难看,重则高并发时整个服务卡死。所以读文件这件事,重点不在“能不能读出来”,而在“用哪种方式读才不出事”。下面先把结论摆出来,再逐方法拆解。

先看结论

Node.js 读取文件本质是按“要不要阻塞主线程、文件有多大”两条线来选。先对号入座:

你的场景 推荐方式 一句话理由
读小配置文件、一次性脚本 fs.readFileSync 代码最短,同步拿到结果
想保持异步又不想嵌套回调 fs.promises.readFile + async/await 现代代码主流写法
兼容老代码、习惯回调 fs.readFile 回调里拿 err 和 data
文件很大或要边读边处理 fs.createReadStream 不占满内存,按块流动

Node.js 四种读取方式怎么选

如果你还没装好 Node.js 环境,建议先过一遍 Node.js 教程,把 fs 模块和模块引入方式先铺平,后面读代码会更顺。

一、方法一:fs.readFileSync 同步写法

fs.readFileSync 是 fs 模块里最直接的一个方法,它会阻塞当前线程直到文件读完,然后把内容作为返回值交给你。语法上不需要回调,也不返回 Promise,直接用变量接住就行。它最适合启动阶段的配置加载、CLI 工具这类“本来就不在请求链路里”的场景。

const fs = require('fs');

// 第二个参数是编码,不写则返回 Buffer
try {
  const data = fs.readFileSync('w3cschool-config.txt', 'utf8');
  console.log('同步读取到的内容:', data);
} catch (err) {
  console.error('读取失败:', err.message);
}

上面这段做了三件事:引入 fs 模块、调用 readFileSync 同步拿内容、用 try/catch 兜住异常。读小文件时这是最省心的写法,但代价是“阻塞”——在事件循环里它会卡住整个进程。在 HTTP 请求处理函数里用它,会让后续请求全部排队,高并发场景务必避免。

避坑点:编码参数传 'utf8' 返回字符串,不传则返回 Buffer,很多人忘了转码就直接当字符串处理,结果出现乱码或方法报错。涉及版本差异时标明 Node 版本号,本例基于 Node.js 18 LTS 实测。

二、方法二:fs.readFile 回调写法

fs.readFile 是异步版本,结果通过回调的两个参数返回:第一个是错误对象 err,第二个是文件内容 data。Node.js 约定“错误优先”,所以一定要先判断 err。回调写法的优点是零依赖、和老代码风格一致,缺点是多次读取嵌套时会形成“回调地狱”。

const fs = require('fs');

fs.readFile('w3cschool-config.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('读取失败:', err.message);
    return;
  }
  console.log('异步读取到的内容:', data);
});

回调写法的关键是命中错误后及时 return,否则后续逻辑会拿到 undefined 而崩。它在只做一次简单读取、又不想引入 async 语法时最顺手;但如果你要“读完 A 再读 B 再读 C”,嵌套会迅速失控,那时就该换 Promise 写法。和前端常见写法一脉相承,也可顺带复习 JavaScript 教程 里的异步基础。

避坑点:回调里忘记 return,错误分支之后代码继续执行,是常见的隐蔽 bug。把失败处理写成“先判 err、再 return”的固定动作,能避开一大半问题。

三、方法三:fs.promises.readFile 与 async/await

这是目前最推荐的现代写法。fs.promises 把回调式 API 包成了返回 Promise 的版本,配合 async/await 让异步代码读起来像同步代码。它不阻塞事件循环,适合放在服务请求里,也能用 Promise.all 轻松并发读多个文件。

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

async function readDemo() {
  try {
    const data = await fs.readFile('w3cschool-config.txt', 'utf8');
    console.log('用 Promise 读取到的内容:', data);
  } catch (err) {
    console.error('读取失败:', err.message);
  }
}

readDemo();

readFileSync 同步返回、fs.readFile 走回调、fs.promises.readFile 走 Promise——三者拿到的是同一份内容,区别只在“等待结果”的方式。需要并发读取多个文件时,用 Promise.all([a, b, c]) 把多个读取并行起来也很自然。写代码时想随时查参数,可翻 Node.js 官方文档看具体 API 签名的说明。

避坑点:await 必须包在 async 函数里,顶层直接 await 在旧版 Node 会报错;并发数太多要自己做限流,否则一次性打开几百个文件会触及系统文件描述符上限。

四、方法四:fs.createReadStream 流式读取

当文件大到无法一次性塞进内存(比如几百 MB 的日志),readFile 系列会直接撑爆内存。此时要用 fs.createReadStream 做 Node.js 文件流式的读取,它按块(chunk)陆续把数据推给你,边读边处理,内存只占一小块缓冲区。

const fs = require('fs');

const stream = fs.createReadStream('w3cschool-big.log', 'utf8');
let content = '';

stream.on('data', (chunk) => {
  content += chunk; // 每收到一块就追加
});
stream.on('end', () => {
  console.log('流式读取完成,总长度:', content.length);
});
stream.on('error', (err) => {
  console.error('读取失败:', err.message);
});

四种方式的横向对比:

方式 是否阻塞 适合文件 内存占用
readFileSync 是 小文件 一次性载入
fs.readFile 否 中小文件 一次性载入
fs.promises.readFile 否 中小文件 一次性载入
fs.createReadStream 否 大文件 按块流动

四种读取方式速览

避坑点:流式读取默认按 Buffer 切块,一行可能被拆成两半,需要按分隔符自己攒行;另外在 'utf8' 模式下多字节字符也可能被切断,处理文本时要用 readline 模块或自己处理边界。

实操清单:前置、操作、验证与失败处理

  • 前置(安装/配置):本机已装 Node.js 18 及以上(用 node -v 检查),并在项目目录放一个真实的 w3cschool-config.txt 文本文件作为读取对象。
  • 操作(命令/运行):把示例保存为 read-demo.js,终端执行 node read-demo.js 启动读取。
  • 验证(检查/预期):预期输出是文件原文;若出现 ENOENT,说明文件不在当前工作目录,改用绝对路径重试即可。
  • 失败处理(失败/修复):读取大文件报内存溢出时,立刻从 readFileSync 切换到 fs.createReadStream;编码乱码则确认写入和读取都统一用 'utf8'。

总结

Node.js 读取文件共有四种主路径:readFileSync 同步最短、fs.readFile 回调最经典、fs.promises.readFile 配合 async/await 是现代主流、createReadStream 专门应对大文件。Node.js 读取文件有哪几种方法,答案就落在“同步还是异步、文件大不大”这两个判断上。选型时先问自己两个问题——文件大不大、能不能阻塞主线程,答案就出来了。

要点带走:

  • 小文件、一次性脚本,优先用 readFileSync;
  • 写服务、要异步,优先用 fs.promises.readFile;
  • 大文件千万别用 readFileSync,改用流式读取;
  • 不论哪种写法,都要用 try/catch 或 err 判断兜住读取失败。

下一步建议把 Node.js 的事件循环和异步机制补上,理解了“为什么同步会阻塞”,你才算真正吃透文件读取。

延伸学习

想把这块知识系统补齐,可以按这个顺序来:

  1. 先过一遍 Node.js 入门课程,把模块系统和 fs 基础铺平;
  2. 语法记不住时,翻 Node.js 文档教程 查具体 API 参数;
  3. 想深究前后端关系,这篇 学前端要学 Node.js 吗 讲得更透,值得延伸阅读。

常见问题

Q:readFileSync 和 readFile 读出来的内容有区别吗?

A:没有本质区别,都是文件原文。区别只在“等待方式”:前者同步阻塞、直接给返回值,后者异步、结果放进回调。编码参数都传 'utf8' 时,返回的都是字符串,选哪个取决于你的代码能不能阻塞主线程。

Q:读取大文件为什么会内存溢出?

A:readFile 系列会一次性把整个文件读进内存再返回,文件超过可用内存就崩溃。改用 createReadStream 按块流动,内存只占一小块缓冲区,几百 MB 的文件也能稳稳处理。

Q:用 async/await 读取多个文件怎么提速?

A:把每个 fs.promises.readFile 调用当成独立 Promise,用 Promise.all([a, b, c]) 并行发起,整体耗时约等于最慢那一个,而不是三者相加。注意并发数太多要自己做限流,避免触及文件描述符上限。

0 人点赞