Node.js 读取文件怎么写?附 4 种常见方法一次讲清

编程狮(w3cschool.cn) 2026-09-03 17:17:12 浏览数 (37)
反馈

Node.js 读文件最常用的是 fs.readFile 异步读取:引入 fs 模块后调用 fs.readFile 方法,把文件路径和编码传进去,在回调里就能拿到字符串内容。本文一次讲清 4 种主流读文件方式:fs.readFile 异步回调、fs.readFileSync 同步阻塞、fs.promises 配合 async/await、以及适合大文件的流式读取 createReadStream。你还会看到「忘记指定编码得到 Buffer」「同步读取阻塞事件循环」「大文件一次性读进内存直接爆掉」这些真实开发里会踩的坑怎么填。读完你不仅能把文件读出来,还知道每种写法该用在哪个场景、该避开哪些坑。今天编程狮就把这块讲透,让你下次读文件不再纠结用哪一个。

Node.js 读取文件方法图

一、为什么需要读取文件

读取文件,本质是把磁盘上的内容搬进内存,交给 JavaScript 去处理。最常见的场景包括:读取配置文件拿到运行参数、读取用户上传的文本做解析、读取日志做统计、读取模板渲染页面。Node.js 是服务端运行时,磁盘 IO 是它的核心能力之一,所以「怎么读文件」是每个 Node.js 开发者都绕不开的基本功。

⚠️ 注意:Node.js 里所有文件操作都来自内置的 fs 模块,使用前必须先 const fs = require('fs')。不引入直接用会报 ReferenceError,这是新手第一步最容易卡住的地方。

另一个容易混的点是「读出来到底是什么」。如果你在调用时没指定编码,拿到的是 Buffer(二进制缓冲区),不是字符串;只有显式传了 'utf8',回调里才是人能直接读的文本。判断该用哪种读取方式,先看你的约束:文件大不大、要不要阻塞当前流程、下游代码是回调风格还是 async 风格。这三类情况手法完全不同,下面逐个来。另外,读取文件常常和「路径处理」「异常兜底」一起出现,理清它们的关系,处理本地资源会顺手很多。

二、方法一:fs.readFile 异步回调(最推荐)

适合谁:绝大多数业务场景,尤其是 Web 服务里读配置、读静态资源。代价:回调嵌套,多个文件顺序读时会层层缩进。

const fs = require('fs');
fs.readFile('./data.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

fs.readFile 把文件读到内存后,才把结果交给回调,期间主线程不阻塞,事件循环可以继续处理别的请求。它是 Node.js 最经典的异步写法,想系统学 Node.js 基础,Node.js 入门教程 有完整的学习路径。

选异步还是后面三种写法,先看你的约束。接口里读一个小配置,异步最稳,不会拖慢整个服务;如果你需要边读边解析、读多个文件再合并,回调嵌套会很难看,这时候就不如用 async/await。一个常见误用是忘记判断 err——文件不存在、权限不够都会走错误分支,不处理就会让进程直接崩。性能上,fs.readFile 会一次性把整个文件读进内存,几千几万行的文本都秒回,但遇上几百 MB 的大文件就危险了,那得用第五节讲的流式读取。实际项目里,读取文件往往发生在「启动加载配置」「按路由读模板」这一步,给后续逻辑提供原材料。

三、方法二:fs.readFileSync 同步读取

适合谁:脚本启动阶段、命令行工具,或者就在文件开头读一次配置。代价:会阻塞事件循环,不能在高频请求里用。

const fs = require('fs');
try {
  const data = fs.readFileSync('./data.txt', 'utf8');
  console.log(data);
} catch (err) {
  console.error('读取失败', err);
}

fs.readFileSync 直接把结果作为返回值吐出来,代码从上往下读,没有回调,写起来最直观。但它会卡住当前线程——在读的这几百毫秒里,同一个进程的其他请求全得等着。所以同步读取只适合「启动时读一次」或「一次性脚本」,千万不要写在 HTTP 接口的处理函数里。

这种写法在「给一组本地数据临时编号」时很顺手,比如部署脚本读环境变量、构建工具读 package.json。但它有个天生短板:同步阻塞意味着高并发下吞吐骤降。如果你的服务之后还要被很多人同时访问,那不如在源头就改用异步。另外 fs.readFileSync 抛错要用 try/catch 接住,而不是回调里的 err 参数,这是同步和异步最大的写法差异。当你需要的不只是「读」,而是「读到的内容马上就要用」,同步读取就是首选,前提是它不在性能热路径上。

四、方法三:fs.promises + async/await

适合谁:现代 Node.js 项目,下游代码已经是 Promise/async 风格。代价:需要函数标 async,对老代码要改造。

const fs = require('fs').promises;
async function main() {
  const data = await fs.readFile('./data.txt', 'utf8');
  console.log(data);
}
main().catch(console.error);

fs.promises 返回的是 Promise,await 之后代码看起来和同步几乎一样,却不会阻塞事件循环。多个文件顺序读时,写 await a(); await b(); 比嵌套回调清爽太多。Node.js 内置模块速查可以看 Node.js 模块速查手册,把 fs 家族的方法铺平。

这种写法在「读多个文件再汇总」时最顺手,比如先读配置再读模板再读数据,用 Promise.all 还能并行读。但它有个天生边界:await 必须在 async 函数里,顶层不能直接 await(老版本 Node 需要包一层)。如果你的代码之后还要被按步骤组合,那 async/await 比回调好维护。另外 fs.promises 和 fs 是两个对象,require('fs').promises 才是 Promise 版,别直接对 fs.readFileawait,那是回调风格、返回不了 Promise。把这几类的边界厘清,读文件的代码基本不会再出结构混乱的问题。

五、方法四:流式读取与怎么选(含坑)

适合谁:大文件(几十 MB 以上)、需要边读边处理(比如逐行解析日志)。代价:写法比一次性读取复杂,要监听事件。

const fs = require('fs');
const rs = fs.createReadStream('./big.log', 'utf8');
rs.on('data', chunk => process.stdout.write(chunk));
rs.on('end', () => console.log('\n读取完成'));

createReadStream 把文件切成一块块流式吐出,内存里始终只驻留当前这块,所以几百 MB 的日志也能轻松处理。流式读取专门解决「大文件一次性读进内存直接爆掉」的问题,是处理大文件的唯一正解。

四种方法怎么选,记住这张对照:小文件、异步服务用 fs.readFile;脚本启动读一次用 fs.readFileSync;现代 async 项目用 fs.promises;大文件或边读边处理用 createReadStream。高频坑只有三个:第一,忘记指定文件编码、漏传 'utf8',得到的是 Buffer 而不是字符串,打印出来是一堆乱码数字;第二,在 HTTP 接口里用同步读取,阻塞事件循环把整个服务拖垮;第三,大文件硬用 readFile 一次性读,内存直接打爆进程。第一条最阴险——它不报错,只是类型不对,排查时很难第一时间想到是编码没指定。所以凡是来源不可控的文件,读之前先想清楚「我要字符串还是 Buffer、文件有多大、这段代码在不在热路径上」。

Node.js 读取文件怎么选

总结

Node.js 读取文件,日常首选 fs.readFile 异步回调,不阻塞、好读;启动阶段读一次用 fs.readFileSync 同步;现代 async 项目用 fs.promisesasync/await;大文件或边读边处理用 createReadStream 流式读取。要点带走:

  • 调用时务必传 'utf8',否则拿到的是 Buffer 而不是字符串;
  • 同步读取会阻塞事件循环,千万别写在接口处理函数里;
  • 大文件别用 readFile 一次性读,用流式读取才稳。

延伸学习

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

  1. 先过一遍 JavaScript 基础实战,把回调和 Promise 铺平;
  2. Node.js 入门课程
  3. 想看另一种讲法,参考 Node.js 文件操作笔记 的实战思路;

常见问题

Q:读出来的内容是一堆数字而不是文字,怎么回事?

A:因为你调用时没传编码参数。fs.readFile 在不指定编码时会返回 Buffer(二进制缓冲区),打印出来就是一串数字。只要在第二个参数传 'utf8',拿到的就是正常的字符串。记住:要文本就显式写编码,要原始二进制才用 Buffer。

Q:fs.readFile 和 fs.readFileSync 该用哪个?

A:看代码位置。在 Web 接口、需要并发处理请求的地方,必须用异步的 fs.readFile,否则会阻塞整个事件循环;在命令行脚本、服务启动只读取一次配置的地方,用同步的 fs.readFileSync 写起来更直观。一句话:热路径用异步,启动初始化可用同步。

Q:几百 MB 的大文件怎么读才不会内存爆掉?

A:别用 readFile 一次性读进内存,改用 fs.createReadStream 流式读取。它会把文件分块吐出,内存里只保留当前这一块,配合 data 事件边读边处理。这是处理大日志、大导出文件的唯一稳妥方式。

0 人点赞