ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

node-interview 面试指南:Node.js IO 核心机制深度解析(Buffer、Stream、Console、文件系统与 Readline)

node-interview 面试指南:Node.js IO 核心机制深度解析(Buffer、Stream、Console、文件系统与 Readline) 文档教程后端【免费下载链接】node-interviewHow to pass the Node.js interview of ElemeFE.项目地址https://gitcode.com/gh_mirrors/no/node-interview点击查看免费下载导读本篇围绕饿了么前端团队ElemeFE开源的高级 Node.js 面试与进阶学习指南 node-interview 中最重要的一章——IOsections/zh-cn/io.md 与英文版 sections/en-us/io.md展开系统梳理 Node.js 处理 IO 密集型业务的底层链路从二进制数据载体 Buffer、多字节安全的 String Decoder到作为 fs/net/http 等核心模块地基的 Stream 抽象、console.log的真实实现、一切皆文件背后的 fd 与 stdio 体系以及 Readline 逐行读取与 REPL 的实用场景。读完本文你将能够回答什么才算 IO 密集型业务console.log是同步还是异步如何用 1MB 内存拷贝任意大小的文件可写流返回 false 之后该怎么办等高频面试题并获得一条从 API 使用通往源码阅读的进阶路径。为什么说 Node.js 以 IO 密集型业务著称所谓IOInput/Output输入/输出指的是程序与外部世界交换数据的过程读写文件、收发网络请求、处理标准输入输出等都属于 IO。而IO 密集型业务指的是以大量读写等待为特征、CPU 计算占比相对较低的业务场景——例如高并发的 Web 网关、日志采集、数据管道转发等。Node.js 之所以在此类场景下脱颖而出是因为它的异步模型不会让进程在等待 IO 完成时阻塞。在仓库的另一章节 sections/zh-cn/event-async.md 中作者将 Node.js 的异步区分为两类正好可以帮助理解 IO 的本质hard asynchronous硬异步真正的 IO 操作依赖 libuv 在事件循环外部完成包括网络读写、文件读写等当然也包括readFileSync、execSync这类同步版本因为它们同样会操作底层 IOsoft asynchronous软异步由setTimeout等定时器模拟出来的异步行为。由于 Node.js 的 JavaScript 执行是单线程的只有当前代码执行完毕进程才会切入事件循环去弹出下一个回调。因此在 Node.js 中做同步的 IO 操作是不明智的——它会阻塞其他所有代码的执行。这正是本篇文章所有主题Buffer、Stream、Console、fs、stdio、Readline背后统一的思想基础理解 IO 怎么做、为什么这么做异步。BufferNode.js 处理二进制数据的基石Buffer 是 Node.js 中用于处理二进制数据的核心类所有与 IO 相关的操作网络、文件等都基于 Buffer。理解它的两个关键特性大小固定不变Buffer 实例非常类似整数数组但其长度一旦创建便不可再调整内存分配在 V8 堆栈之外Buffer 的原始内存空间在 V8 堆外分配因此创建后所占用的内存大小不能再进行调整。为什么new Buffer()被废弃在 Node.js v6.x 之后new Buffer()接口开始被废弃原因是参数类型不同会返回不同类型的 Buffer 对象。当开发者没有正确校验参数、没有正确初始化 Buffer 对象的内容或在不了解的情况下直接使用就会在不经意间向代码中引入安全性和可靠性问题例如未初始化的内存可能残留旧数据。官方推荐的替代接口如下接口用途Buffer.from()根据已有数据字符串、数组、ArrayBuffer 等生成一个 Buffer 对象Buffer.alloc()创建一个已初始化默认零填充的 Buffer 对象Buffer.allocUnsafe()创建一个未初始化的 Buffer 对象速度更快但可能包含旧内存数据补充说明Buffer.allocUnsafe()是性能敏感场景的加速选择但正因为其内容未初始化实际使用时应尽快写入数据。仓库的 OS 章节sections/zh-cn/os.md中记录了一个相关的启动参数--zero-fill-buffers它的作用是在启动时为Buffer 和 SlowBuffer 统一做零填充可以在调试与安全要求较高的环境中使用。TypedArrayBuffer 的底层实现与内存语义ES6 引入 TypedArray 之后Node.js 修改了原 Buffer 的实现选择基于 TypedArray 中的Uint8Array来实现从而提升了一波性能。但使用上有一个必须区分的细节——拷贝内存 vs 共享内存const arr new Uint16Array(2); arr[0] 5000; arr[1] 4000; const buf1 Buffer.from(arr); // 拷贝了该 buffer const buf2 Buffer.from(arr.buffer); // 与该数组共享了内存 console.log(buf1); // 输出: Buffer 88 a0, 拷贝的 buffer 只有两个元素 console.log(buf2); // 输出: Buffer 88 13 a0 0f arr[1] 6000; console.log(buf1); // 输出: Buffer 88 a0 console.log(buf2); // 输出: Buffer 88 13 70 17分析这个例子Buffer.from(arr)传入的是 TypedArray视图本身Node 会按元素复制其内容因此得到的buf1只包含两个元素88 a0即 5000 与 4000 的十六进制表示且与arr互不影响Buffer.from(arr.buffer)传入的是底层的ArrayBufferbuf2与该数组共享同一块内存。因此arr[1] 6000之后buf2的对应字节也同步变成了70 176000 的十六进制。面试时若能讲清视图TypedArray与底层缓冲区ArrayBuffer的区别同时指出 Buffer 是 Uint8Array 的视图/子类关系就能证明你对二进制内存模型有真正的理解。编码知识补充一个汉字占多少字节原文档在 File 章节的编码子标题下标了// TODO并列出三个待展开的方向UTF8/GBK 等编码支持、如何计算一个汉字的长度、BOM。这里补齐这部分基础事实因为它们与 Buffer 的读写实践直接相关UTF-8可变长编码ASCII 字符占 1 字节中文常用汉字U4E00 U9FFF 范围一般占3 字节。在 Node.js 中可用Buffer.byteLength(中, utf8)验证返回 3而中.length返回 1JS 字符串按 UTF-16 码元计数。UTF-16LE / UTF-16BENode 的utf16le编码按 2 字节一个码元存储中文占 2 字节。GBKNode.js 内置的Buffer.toString()与Buffer.from(str, encoding)并不原生支持 GBK内置编码为 utf8、utf16le、latin1、ascii、base64、hex 等处理 GBK 文本通常需要借助iconv-lite一类的第三方转码库。BOMByte Order Mark字节序标记UTF-8 的 BOM 是EF BB BF三个字节UTF-16 的 BOM 是FE FFBE或FF FELE。读取带 BOM 的文件时首字节往往是 BOM需要剥离后再解析内容文件字节数统计也常因此产生多出几个字节的偏差。ES6 相关的编码/Unicode 能力ES6 提供了\u{XXXXX}形式的码点转义、String.prototype.codePointAt()、String.fromCodePoint()等 API可以正确处理超出 BMP基本多文种平面的字符如 emoji、生僻字这些字符在 UTF-8 下可能占 4 字节。String Decoder多字节字符的安全解码器string_decoder模块用于将 Buffer 解码为字符串是Buffer.toString的一个补充它支持多字节的 UTF-8 和 UTF-16 字符并且在数据被切成不完整片段时依然安全。const StringDecoder require(string_decoder).StringDecoder; const decoder new StringDecoder(utf8); const cent Buffer.from([0xC2, 0xA2]); console.log(decoder.write(cent)); // ¢ const euro Buffer.from([0xE2, 0x82, 0xAC]); console.log(decoder.write(euro)); // €¢分币符号在 UTF-8 中是0xC2 0xA2两个字节€欧元符号是0xE2 0x82 0xAC三个字节。String Decoder 的核心价值在于decoder.write()会确保返回的字符串不包含 Buffer 末尾残缺的多字节字符残缺的多字节字符会被保存在内部的一个 buffer 中留待下次调用stringDecoder.write()或stringDecoder.end()时继续处理。这种分片到达、整体解码的场景在实际 IO 中非常常见例如 TCP 流上一个完整字符可能被拆到两个数据包里。看下面的分步示例const StringDecoder require(string_decoder).StringDecoder; const decoder new StringDecoder(utf8); decoder.write(Buffer.from([0xE2])); decoder.write(Buffer.from([0x82])); console.log(decoder.end(Buffer.from([0xAC]))); // €0xE2、0x82单独来看都是残缺字节write()会将它们暂存到内部缓冲区而不产生乱码输出直到最后一个0xAC通过end()传入三个字节拼齐输出完整的€。如果换成直接调用Buffer.from([0xE2]).toString()大概率会得到一类的替换字符。这也是面试中区分会用toString与懂流式解码的经典考点。StreamNode 异步 IO 的核心抽象Node.js 内置的stream模块是多个核心模块的基础。在仓库的 sections/zh-cn/event-async.md 中有一句非常关键的架构描述Stream是基于Events实现的而fs、net、http又是基于Stream实现的——也就是说EventEmitter 在最底层Stream 在其上fs/net/http 在更上层。理解了这条依赖链就理解了 Node 网络与文件模块异步特性的根源。不过流这种编程方式其实很早就流行了。文档用一段大家更熟悉的 C 语言代码演示了什么是流式操作int copy(const char *src, const char *dest) { FILE *fpSrc, *fpDest; char buf[BUF_SIZE] {0}; int lenSrc, lenDest; // 打开 src 文件 if ((fpSrc fopen(src, r)) NULL) { printf(文件 %s 无法打开\n, src); return FAILURE; } // 打开 dest 文件 if ((fpDest fopen(dest, w)) NULL) { printf(文件 %s 无法打开\n, dest); fclose(fpSrc); return FAILURE; } // 从 src 中读取 BUF_SIZE 长的数据到 buf 中 while ((lenSrc fread(buf, 1, BUF_SIZE, fpSrc)) 0) { // 将 buf 中的数据写入 dest 中 if ((lenDest fwrite(buf, 1, lenSrc, fpDest)) ! lenSrc) { printf(写入文件 %s 失败\n, dest); fclose(fpSrc); fclose(fpDest); return FAILURE; } // 写入成功后清空 buf memset(buf, 0, BUF_SIZE); } // 关闭文件 fclose(fpSrc); fclose(fpDest); return SUCCESS; }应用场景很简单假设你要拷贝一个20G 大小的文件如果一次性把 20G 数据全部读入内存内存条可能不够用或者严重影响性能但如果你使用一个1MB 大小的缓存buf每次读取 1MB、写入 1MB那么无论文件多大都只会占用 1MB 内存。流的核心思想就是用固定大小的缓冲区分块搬运任意大小的数据。而在 Node.js 中原理与上述 C 代码类似但读写实现上通过libuv 与 EventEmitter 加上了异步特性。在 Linux/Unix 中你也可以通过|管道直接感受到流式操作cat big.log | grep error并不会先把整个文件读入内存而是边读边筛。四种流类型类使用场景重写方法Readable只读_readWritable只写_writeDuplex读写_read,_writeTransform操作被写入的数据然后读出结果_transform,_flush自定义流时你只需要实现对应的方法例如继承Readable并实现_read()、继承Writable并实现_write()框架会负责与底层资源交互、缓冲与事件分发。对象模式objectMode通过 Node API 创建的流默认只能操作字符串或 Buffer 对象。但流的实现其实可以基于其他 JavaScript 类型null 除外它在流中有特殊含义——代表流的结束这种流就处于对象模式objectMode中。在创建流对象时可以通过提供objectMode: true参数来生成对象模式的流。注意试图将现有的流转换为对象模式是不安全的因此是否开启对象模式必须在构造时决定。内部缓冲区与 highWaterMarkNode.js 中 stream 的缓冲区设计可以沿用开头 C 语言拷贝文件的模板来理解抛开异步区别从src读出数据到buf后并没有直接写入dest而是先放在一个比较大的缓冲区中等待写入消费到dest。也就是说借助缓冲区可以实现读与写的分离。Readable 和 Writable 流都会把数据存储在内部缓冲区中分别可以通过writable._writableState.getBuffer()和readable._readableState.buffer访问。缓冲区大小由构造 stream 时的highWaterMark标志指定普通流中它表示可容纳的字节数默认值通常为 16KB即 16384 字节而对于objectMode流该标志表示可以容纳的对象个数。可读流Readable当一个可读实例调用stream.push()方法时数据会被推入缓冲区如果数据没有被消费即没有调用stream.read()方法读取数据会一直留在缓冲队列中。当缓冲区中的数据到达highWaterMark指定的阈值时可读流将停止从底层汲取数据直到当前缓冲的数据被成功消费为止。这就是背压back-pressure在可读端的表现。可写流Writable与 drain在一个可写实例上不停地调用writable.write(chunk)时数据会被写入可写流的缓冲区。规则如下如果当前缓冲的数据量低于highWaterMark设定的值调用writable.write()会返回true表示数据已经写入缓冲区否则当缓冲的数据量达到阈值、数据无法再写入缓冲区时write()会返回false此后必须等到drain事件触发才能继续调用write写入。下面是官方文档风格的经典示例——向可写流写入一百万条数据并正确处理背压// Write the data to the supplied writable stream one million times. // Be attentive to back-pressure. function writeOneMillionTimes(writer, data, encoding, callback) { let i 1000000; write(); function write() { var ok true; do { i--; if (i 0) { // last time! writer.write(data, encoding, callback); } else { // see if we should continue, or wait // dont pass the callback, because were not done yet. ok writer.write(data, encoding); } } while (i 0 ok); if (i 0) { // had to stop early! // write some more once it drains writer.once(drain, write); } } }这段代码的巧妙之处在于do...while循环在write()返回true时持续写入一旦返回false就立即跳出循环并通过writer.once(drain, write)在drain事件到来后继续写。监听drain是写端背压处理的标志性手法面试中常被要求现场写出类似结构。Duplex 与 TransformDuplex 流和 Transform 流都是同时可读可写的它们会在内部维持两个缓冲区分别对应读取和写入从而允许两边同时独立操作维持高效的数据流。例如net.Socket就是一个 Duplex 流Readable 端允许从 socket 获取、消耗数据Writable 端允许向 socket 写入数据。数据写入的速度很有可能与消耗的速度存在差距所以两端可以独立操作和缓冲非常重要。pipe流的连接器stream 的.pipe()方法将一个可写流附到可读流上同时将可写流切换到流模式并把所有数据推给可写流。在 pipe 传递数据的过程中objectMode流是传递引用非objectMode流则是拷贝一份数据传递下去。pipe方法最主要的目的就是将数据的流动缓冲到一个可接受的水平不让不同速度的数据源之间的差异导致内存被占满。这也是为什么readable.pipe(writable)能自动处理背压——这正是我们前面手写writeOneMillionTimes想解决的同一问题。关于 pipe 的逐行实现原理文档推荐了 David Cai 的通过源码解析 Node.js 中导流pipe的实现一文建议进阶读者配合 Node 源码lib/stream/Readable.js中的pipe()方法一起阅读。Consoleconsole.log 是同步还是异步这是 Node 面试中最经典的问题之一。原文档引用的结论是console.log 同步还是异步取决于它与谁相连以及当前操作系统——一般情况下输出到 TTY/终端大多数实现是异步写出的但当你通过new Console(stdout[, stderr])指定一个文件作为目标时行为会有所不同。Node v6.x 中console.log的核心实现如下// As of v8 5.0.71.32, the combination of rest param, template string // and .apply(null, args) benchmarks consistently faster than using // the spread operator when calling util.format. Console.prototype.log function(...args) { this._stdout.write(${util.format.apply(null, args)}\n); };注意其中的this._stdout默认就是process.stdout。也就是说console.log的本质就是向 stdout 这个可写流写入一行格式化后的字符串先由util.format处理占位符%s、%d、%j等与多参数拼接再调用流的write()。仓库的 OS 章节sections/zh-cn/os.md还记录了两个与之强相关的细节可以在面试中作为加分点环境变量NODE_TTY_UNSAFE_ASYNC1当设为 1 时stdio 操作将同步进行例如console.log变成同步写入process.stdout.isTTY用于判断当前进程的标准输出是否连接到了终端TTY。文档给出了一个直观的验证node -p -e Boolean(process.stdout.isTTY)在终端下输出true而通过| cat管道接管后输出false。自己实现一个 console.log理解了上面的源码实现一个极简版就很容易let print (str) process.stdout.write(str \n); print(hello world);注意这段代码没有处理多参数也没有处理占位符即util.format的功能它只演示了输出函数 向 stdout 写字符串这一本质。console.log.bind(console) 问题另一个高频考点是为什么推荐写成console.log.bind(console)答案藏在Console类的构造器中——Node 在初始化Console实例时会把原型上的所有方法绑定到当前实例// 源码出处Node.js v6.x lib/console.js function Console(stdout, stderr) { // ... init ... // bind the prototype functions to this Console instance var keys Object.keys(Console.prototype); for (var v 0; v keys.length; v) { var k keys[v]; this[k] this[k].bind(this); } }因为 Node 的console全局对象内部持有的是_stdout、_stderr等实例字段把console.log作为回调传给其他函数时例如arr.forEach(console.log)this会丢失指向导致内部无法访问_stdout。所以从别处取出的console.log需要先.bind(console)才能保证this正确。能讲清这条源码逻辑通常意味着候选人真正读过 Node 核心模块代码。File一切皆文件与文件描述符一切皆是文件是 Unix/Linux 的基本哲学之一不仅普通文件目录、字符设备、块设备、套接字等在 Unix/Linux 中都以文件的方式被对待。也就是说这些资源的操作对象都是fd文件描述符都可以通过同一套系统调用system call来读写。在 Linux 中可以使用ulimit对 fd 资源进行一定程度的管理限制仓库 sections/zh-cn/os.md 中给出了完整的ulimit参数表例如-n设置同一时间可打开的文件描述符上限并特别提醒open socket 也占用 fd如果ulimit -n太小不仅打不开文件也无法建立 socket 连接——这一点在压测和部署高并发服务时尤其重要。Node.js 封装了标准 POSIX 文件 I/O 操作的集合通过require(fs)即可加载。该模块中所有方法都有异步执行和同步执行两个版本如readFile/readFileSync、writeFile/writeFileSync、open/openSync并可通过fs.open获取一个文件的文件描述符。stdio标准输入输出流stdiostandard input/output包括三个部分输入流 stdin、输出流 stdout、错误流 stderr。在 Node.js 中分别对应三个 streamprocess.stdinReadableprocess.stdoutWritableprocess.stderrWritable输出函数是每个人学习任何一门编程语言时学会的第一个函数C 语言的printf(hello, world!);、Python/Ruby 的print hello, world!、JavaScript 的console.log(hello, world!);。以 C 语言伪代码来看这类输出函数的实现思路如下int printf(FILE *stream, 要打印的内容) { // ... // 1. 申请一个临时内存空间 char *s malloc(4096); // 2. 处理好要打印的内容其值存储在 s 中 // ... // 3. 将 s 上的内容写入到 stream 中 fwrite(s, stream); // 4. 释放临时空间 free(s); // ... }关键在第 3 步stream 指的是 stdout输出流。这就引出了一条重要的系统知识链在 shell 上运行一个应用程序时shell 做的第一个操作是fork 当前 shell 的进程所以用ps查看从 shell 启动的进程其父进程 PID 就是当前 shell 的 PID并在这个过程中把 shell 的 stdio继承给了应用进程因此你在当前进程中把数据写入 stdout实际上就是写入 shell 的 stdout即显示在当前 shell 上输入同理当前进程继承了 shell 的 stdin从 stdin 读取数据实际上就是获取你在 shell 中输入的数据shell 可以是 Windows 下的 cmd、PowerShell也可以是 Linux 下的 bash、zsh。更进阶的场景是ssh当你用 ssh 在远程服务器上运行命令时服务器上的命令输出虽然也写入服务器 shell 的 stdout但这个远程 shell 是从sshd 服务上 fork 出来的其 stdout 是继承自 sshd 的一个 fd——而这个 fd 实际上是一个socket数据最终被写入 socket再通过网络传回你本地计算机的 shell stdout。所以远程输出在底层也是一条 socket 流。理解了上述内容就能明白守护进程为什么必须关闭 stdio如果切到后台的守护进程不关闭 stdio那么你在 shell 操作的过程中屏幕上会莫名其妙地多出一些输出。仓库的进程章节sections/zh-cn/process.md给出了守护进程的 C 语言实现其中对应的正是这段关闭全部 fd的逻辑for (; i getdtablesize(); i) { close(i); // 关闭打开的 fd }fd从 0 开始的整数Linux/Unix 的 fd 被设计为从 0 开始的整型数字前三个被系统约定保留给标准流。可以运行如下代码验证console.log(process.stdin.fd); // 0 console.log(process.stdout.fd); // 1 console.log(process.stderr.fd); // 2理解了fd 就是一个整数再回头看进程间通信就非常直白了仓库 sections/zh-cn/process.md 中的在 IPC 通道建立之前父进程与子进程是怎么通信的问题答案是——主进程先创建 IPC 通道然后把该通道的fd 通过环境变量NODE_CHANNEL_FD传给子进程子进程再凭这个 fd 连接回父进程。因为 fd 本质上只是整数通过环境变量传递完全可行。如何同步地获取用户输入理解了上面的内容后放到 Node.js 中来看获取用户的输入其实就是读取process.stdin这个输入流的数据。要同步读取不能使用异步的read接口而要使用同步的readSync接口。下面的完整实现来自 StackOverflow 上的经典回答作者 mklement0至今仍值得仔细研读var fs require(fs); var BUFSIZE 256; var buf new Buffer(BUFSIZE); var bytesRead; module.exports function() { var fd (win32 process.platform) ? process.stdin.fd : fs.openSync(/dev/stdin, rs); bytesRead 0; try { bytesRead fs.readSync(fd, buf, 0, BUFSIZE); } catch (e) { if (e.code EAGAIN) { // resource temporarily unavailable // Happens on OS X 10.8.3 (not Windows 7!), if theres no // stdin input - typically when invoking a script without any // input (for interactive stdin input). // If you were to just continue, youd create a tight loop. console.error(ERROR: interactive stdin input not supported.); process.exit(1); } else if (e.code EOF) { // Happens on Windows 7, but not OS X 10.8.3: // simply signals the end of *piped* stdin input. return ; } throw e; // unexpected exception } if (bytesRead 0) { // No more stdin input available. // OS X 10.8.3: regardless of input method, this is how the end // of input is signaled. // Windows 7: this is how the end of input is signaled for // *interactive* stdin input. return ; } // Process the chunk read. var content buf.toString(null, 0, bytesRead - 1); return content; };这段代码值得注意的工程细节在 Windows 上直接复用process.stdin.fd在类 Unix 系统上则通过fs.openSync(/dev/stdin, rs)以同步方式打开标准输入用fs.readSync一次读取BUFSIZE256 字节妥善处理两种边界错误EAGAIN无输入可读常见于无输入启动脚本的场景和EOF管道输入结束常见于 Windows 7bytesRead 0同样视为输入结束。如果你在编写脚本时更习惯 C 语言scanf式的同步输入社区也有node-scanf这类支持 TypeScript 的模块可以直接选用原文档在此处做了推荐。Readline按行读取流数据readline模块提供了一个从 Readable 流中一次读取一行的接口例如从process.stdin。当然你也可以用它读取文件流、net 流或 http 流。例如逐行读取文件const readline require(readline); const fs require(fs); const rl readline.createInterface({ input: fs.createReadStream(sample.txt) }); rl.on(line, (line) { console.log(Line from file: ${line}); });在实现原理上readline对两种输入源采用了不同的换行判定策略读取TTY终端数据时通过input.on(keypress, onkeypress)监听用户按下回车键以此判断是否产生了一个新行读取普通流文件、网络流等时则是缓存收到的数据再用正则的.test方法判断是否遇到了换行符。这一TTY 走按键事件、普通流走正则切分的双轨实现可以顺带联想到仓库 OS 章节中关于 TTY 的讨论process.stdout.isTTY的检测示例面试时可以把两者串起来讲。REPLRead-Eval-Print-LoopREPL 是 **Read-Eval-Print-Loop读取-求值-打印-循环**的缩写即交互式命令行环境读取你输入的一行表达式求值打印结果然后进入下一轮循环。这是学习与调试 Node.js 最直接的入口。原文档在该小节标注为整理中 / Coming soon此处不再展开。作为铺垫可以记住一点在 shell 中不带任何参数直接执行node就会启动 REPL这一点与仓库 OS 章节总结的四种 CLI 使用方式一致。此外Node 的 REPL 还支持 Tab 补全、历史命令记录可用环境变量NODE_REPL_HISTORY指定持久化文件路径等功能。面试复习清单将本文内容浓缩成一份可自查的面试清单Buffer为什么new Buffer()被废弃Buffer.from/alloc/allocUnsafe的区别Buffer.from(arr)与Buffer.from(arr.buffer)是拷贝还是共享内存一个汉字在 UTF-8 下占几个字节用Buffer.byteLength验证String Decoder为什么比Buffer.toString更适合流式解码残缺的多字节字符如何被暂存并在end()时拼接Stream四种流类型分别重写哪些方法objectMode是什么highWaterMark控制什么可读流达到阈值后会怎样可写流write()返回false之后怎么办drainnet.Socket为什么是 Duplexpipe如何自动处理背压Consoleconsole.log是同步还是异步它的本质是什么this._stdout.write(util.format(...) \n)为什么会出现console.log.bind(console)问题File/stdio为什么说一切皆文件fd 与 ulimit 的关系守护进程为什么要关闭 stdioprocess.stdin/stdout/stderr的 fd 分别是多少父进程如何通过NODE_CHANNEL_FD传递 fd 给子进程如何用readSync同步读取 stdinReadline/REPLreadline对 TTY 与普通流的换行判定有何不同REPL 是什么延伸阅读建议本文所属的 node-interview 仓库是一个成体系的 Node.js 高级面试目录IO 并非孤立主题——进程章节sections/zh-cn/process.md中的守护进程实现、IPC 与NODE_CHANNEL_FD、child_process的 stdio 配置事件与异步章节sections/zh-cn/event-async.md中的 EventEmitter 与事件循环以及 OS 章节sections/zh-cn/os.md中的 ulimit、TTY 与命令行选项都与本文内容互为印证。建议按事件循环 → Stream → IO → 进程的顺序串联复习构建完整的后端知识闭环。仓库本身使用 docsify 管理文档在本地执行npm install与npm run serve即可启动文档站点开始阅读。赞分享文档教程后端【免费下载链接】node-interviewHow to pass the Node.js interview of ElemeFE.项目地址https://gitcode.com/gh_mirrors/no/node-interview点击查看免费下载相关推荐如何从国家中小学智慧教育平台下载电子课本 PDFtchMaterial-parser 三步上手指南如何从国家中小学智慧教育平台下载电子课本 PDFtchMaterial parser 三步上手指南 想打印课本或备课批注国家中小学智慧教育平台却只允许在线预文档教程后端CANN/asc-devkit ListTensorDesc APIListTensorDesca nameZH CN_TOPIC_0000001714160421 /a 产品支持情况a namesection1人工智能深度学习算子库CANNAscendNode.js文件系统操作同步与异步IO深度解析Node.js文件系统操作同步与异步IO深度解析 本文深入解析Node.js文件系统操作中的同步与异步IO机制通过learnyounode项目的实践练习详教程CLI上一篇微信聊天记录导出全搞定5 分钟上手 WeChatMsg 本地备份、存档与统计指南下一篇渔人的直感给FF14钓鱼配一个只看鱼竿的贴身助理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表