ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Buffer 新手入门【Node.js】

Buffer 新手入门【Node.js】 目录Node.js Buffer 新手入门一、Buffer 是什么二、为什么 Node.js 需要 Buffer三、可以把 Buffer 理解成什么四、Buffer 的几个重要特点1. Buffer 的长度固定2. Buffer 每一个元素占 1 个字节3. Buffer 可以直接操作二进制数据五、Buffer 的创建方式六、Buffer.alloc()1. 创建指定长度的 Buffer2. 为什么是 00七、Buffer.allocUnsafe()为什么叫 Unsafe八、Buffer.from()1. 根据字符串创建九、ASCII 和 Buffer十、Buffer.toString()十一、字符串 → Buffer → 字符串十二、Buffer 可以使用 [] 访问十三、修改 Buffer十四、Buffer 中的数字为什么最大是 255十五、超过 255 会发生什么十六、为什么 buf[0].toString(2) 可以看到二进制toString(2) / toString(10) / toString(16)十七、为什么 Buffer 输出经常是十六进制十八、中文为什么占 3 个字节十九、字符数量和字节数量一定一样吗二十、Buffer.length 到底表示什么二十一、Buffer 常用属性和方法二十二、Buffer.concat()二十三、Buffer.slice() / subarray()二十四、Buffer 和 Array 有什么区别ArrayBuffer二十五、Buffer 在 Node.js 中到底用在哪里1. 文件2. 网络3. HTTP4. 图片、音频、视频二十六、Buffer 和 Stream 有什么关系二十七、Buffer、二进制、编码三者是什么关系二十八、最重要的几个概念一定要分清字符字节 Byte二进制编码十六进制二十九、一张图理解 Buffer三十、新手现阶段记住这几个就够了Node.js Buffer 新手入门一、Buffer 是什么在 Node.js 中经常会看到Buffer很多刚接触 Node.js 的人第一次看到它会觉得Buffer到底是什么为什么 Node.js 要专门设计一个Buffer其实可以先把它简单理解成Buffer 就是一块专门用来存放二进制数据的内存空间。例如constbufBuffer.from(hello);console.log(buf);输出Buffer 68 65 6c 6c 6f这里的68 65 6c 6c 6f并不是普通的字符串而是hello在 UTF-8 编码下对应的二进制字节。二、为什么 Node.js 需要 BufferJavaScript 最开始主要是运行在浏览器里的。浏览器中的 JavaScript 经常处理字符串 数字 对象 数组 DOM但是 Node.js 不一样。Node.js 经常需要直接处理计算机底层的数据例如文件 TCP 网络数据 HTTP 请求 图片 音频 视频 压缩文件 加密数据 WebSocket 数据这些数据本质上都是一串二进制数据也就是一串 0 和 1。例如01001000 01100101 01101100 01101100 01101111如果我们全部使用普通 JavaScript 字符串来处理会比较麻烦。所以 Node.js 提供了Buffer专门用于处理二进制数据。三、可以把 Buffer 理解成什么可以把 Buffer 想象成一个固定大小的字节数组。例如constbufBuffer.alloc(5);可以理解成┌────┬────┬────┬────┬────┐ │ 00 │ 00 │ 00 │ 00 │ 00 │ └────┴────┴────┴────┴────┘ 1B 1B 1B 1B 1B每一个位置1 byte 8 bit所以一个 Buffer 的 5 个位置5 byte也就是40 bit四、Buffer 的几个重要特点1. Buffer 的长度固定例如constbufBuffer.alloc(5);console.log(buf.length);输出5创建的时候指定了5 byte这个 Buffer 的长度就是固定的。2. Buffer 每一个元素占 1 个字节例如constbufBuffer.from([10,20,30]);console.log(buf);可以理解成┌────┬────┬────┐ │ 10 │ 20 │ 30 │ └────┴────┴────┘ 1B 1B 1B所以buf.length得到33. Buffer 可以直接操作二进制数据例如constbufBuffer.from([65,66,67]);console.log(buf);得到Buffer 41 42 43转换成字符串console.log(buf.toString());得到ABC因为65 → A 66 → B 67 → C五、Buffer 的创建方式Node.js 中最常见的创建 Buffer 的方法主要有Buffer.alloc() Buffer.allocUnsafe() Buffer.from()六、Buffer.alloc()1. 创建指定长度的 Bufferconstbuf1Buffer.alloc(10);console.log(buf1);输出Buffer 00 00 00 00 00 00 00 00 00 00也就是创建10 byte的 Buffer。默认全部初始化为002. 为什么是 00因为Buffer.alloc()会对新分配的内存进行初始化。所以Buffer.alloc(5)相当于00 00 00 00 00这样更加安全因为你拿到的内存不会直接暴露之前的数据。七、Buffer.allocUnsafe()还可以constbuf2Buffer.allocUnsafe(10);console.log(buf2);它同样创建10 byte的 Buffer。但是allocUnsafe()不会像alloc()一样保证把内存全部清零。因此理论上可能看到之前内存中遗留的数据。例如可能出现Buffer 00 7a 31 9f ...具体内容是不确定的。为什么叫 Unsafe因为Unsafe 不安全它的主要特点是速度更快 但是不会保证初始化为 0所以Buffer.alloc()更适合普通场景。而Buffer.allocUnsafe()通常用于你非常清楚自己在做什么并且会立即覆盖全部数据的场景。例如constbufBuffer.allocUnsafe(10);buf.fill(0);console.log(buf);八、Buffer.from()Buffer.from()是我们平时非常常用的方法。它可以根据不同的数据创建 Buffer。1. 根据字符串创建constbuf3Buffer.from(hello);console.log(buf3);输出Buffer 68 65 6c 6c 6f为什么是这些数字因为字符串hello经过 UTF-8 编码以后h → 68 e → 65 l → 6c l → 6c o → 6f这里显示的是十六进制所以68 65 6c 6c 6f对应hello九、ASCII 和 BufferBuffer.from([105,108,111,118,101,121,111,117])就是一个很好的例子。这些数字按照 ASCII105 → i 108 → l 111 → o 118 → v 101 → e 121 → y 111 → o 117 → u所以105 108 111 118 101 121 111 117就是i love you创建 BufferconstbufBuffer.from([105,108,111,118,101,121,111,117]);console.log(buf.toString());输出iloveyou这里要注意Buffer 本身并不知道“这是 ASCII 还是 UTF-8 字符”。它本质上只是保存字节。编码决定了这些字节应该如何解释。十、Buffer.toString()Buffer 和字符串之间可以相互转换。例如constbufBuffer.from(hello);console.log(buf);得到Buffer 68 65 6c 6c 6f转换成字符串console.log(buf.toString());得到hello默认使用utf8也可以明确写console.log(buf.toString(utf8));十一、字符串 → Buffer → 字符串这是非常重要的一条链路字符串 ↓ 编码 ↓ Buffer ↓ 解码 ↓ 字符串例如conststrhello;constbufBuffer.from(str);console.log(buf);constresultbuf.toString(utf8);console.log(result);结果hello → Buffer → hello可以理解为hello ↓ UTF-8 编码 Buffer ↓ UTF-8 解码 hello十二、Buffer 可以使用 [] 访问Buffer 和数组很像。例如constbuf4Buffer.from([105,108,111,118,101,121,111,117]);可以通过下标读取console.log(buf4[0]);得到105再比如console.log(buf4[1]);得到108十三、修改 BufferBuffer 也可以通过下标修改。例如constbufBuffer.from(hello);console.log(buf.toString());得到hello修改buf[0]72;再转换console.log(buf.toString());得到Hello因为72 → H十四、Buffer 中的数字为什么最大是 255这是 Buffer 非常重要的一个知识点。一个字节1 byte 8 bit8 个 bit 最多能够表示11111111转换成十进制255所以一个 Buffer 元素的正常范围是0 ~ 255也就是0buf[i]255十五、超过 255 会发生什么例如constbufBuffer.from(hello);buf[0]361;361 的二进制101101001Buffer 一个字节只能保存 8 bit。因此最终只保留低 8 位01101001转换成十进制105105 对应 ASCIIe所以console.log(buf[0]);得到105而console.log(buf.toString());会变成eello可以记住Buffer 的一个元素只能保存一个字节也就是 0255。十六、为什么 buf[0].toString(2) 可以看到二进制例如constbufBuffer.from(hello);console.log(buf[0]);得到104因为h → 104然后console.log(buf[0].toString(2));得到1101000如果希望按照一个完整字节显示可以补成01101000这里的toString(2)意思是把数字转换成二进制字符串。toString(2) / toString(10) / toString(16)例如constnum104;console.log(num.toString(2));console.log(num.toString(10));console.log(num.toString(16));分别是1101000 104 68也就是二进制 → 2 十进制 → 10 十六进制 → 16十七、为什么 Buffer 输出经常是十六进制例如constbufBuffer.from(hello);console.log(buf);Node.js 输出Buffer 68 65 6c 6c 6f这里68 65 6c 6c 6f是十六进制表示法而不是十进制。Buffer 存的是字节而 Node.js 为了让二进制数据更方便人类阅读默认使用十六进制来展示这些字节。例如十进制 104 十六进制 68所以h ↓ ASCII / UTF-8 ↓ 十进制 104 ↓ 十六进制 68 ↓ Buffer 中显示 68十八、中文为什么占 3 个字节例如constbufBuffer.from(你好);console.log(buf);可能得到Buffer e4 bd a0 e5 a5 bd然后console.log(buf.length);得到6但是你好明明只有2 个字符为什么 Buffer 长度是6原因是JavaScript 字符串中的“字符数量”和 UTF-8 编码后的“字节数量”不是一回事。在 UTF-8 中你 → 3 byte 好 → 3 byte所以你 好 ↓ 3 3 ↓ 6 byte十九、字符数量和字节数量一定一样吗不一定。例如英文constbufBuffer.from(hello);console.log(buf.length);得到5因为h → 1 byte e → 1 byte l → 1 byte l → 1 byte o → 1 byte所以5 个字符 5 个字节但是中文constbufBuffer.from(你好);console.log(buf.length);得到6所以2 个字符 ≠ 6 个字节这就是字符 ≠ 字节一定要把这两个概念分开。二十、Buffer.length 到底表示什么例如constbufBuffer.from(你好);console.log(buf.length);得到6这里的length表示Buffer 中有多少个字节。不是有多少个汉字也不是有多少个字符而是多少 byte二十一、Buffer 常用属性和方法实际开发中最常见的可以先记住这些API作用Buffer.alloc(size)创建指定大小并初始化为 0 的 BufferBuffer.allocUnsafe(size)创建指定大小但不保证初始化的 BufferBuffer.from(data)根据数据创建 Bufferbuf.lengthBuffer 的字节长度buf[index]读取指定位置的字节buf[index] value修改指定位置的字节buf.toString()Buffer 转字符串buf.toString(hex)转成十六进制字符串buf.toString(base64)转成 Base64buf.slice()获取 Buffer 的一部分buf.subarray()获取 Buffer 的一部分Buffer.concat()拼接多个 Bufferbuf.equals()判断两个 Buffer 是否相同buf.includes()判断 Buffer 是否包含某个数据buf.indexOf()查找数据位置buf.fill()填充 Buffer二十二、Buffer.concat()Buffer 还可以进行拼接。例如constbuf1Buffer.from(hello );constbuf2Buffer.from(world);constresultBuffer.concat([buf1,buf2]);console.log(result.toString());得到hello world可以理解成Buffer 1 hello Buffer 2 world ↓ Buffer.concat() ↓ hello world这个在处理网络数据流的时候非常常见。二十三、Buffer.slice() / subarray()例如constbufBuffer.from(hello);constresultbuf.subarray(0,2);console.log(result.toString());得到he可以理解成h e l l o ↑ ↑ 取 0 ~ 2注意subarray(0,2)结束位置2不包含。和数组的切片规则比较类似[start, end)二十四、Buffer 和 Array 有什么区别Buffer 看起来很像数组constarr[65,66,67];constbufBuffer.from([65,66,67]);都可以arr[0]buf[0]但是它们的定位完全不同。Array普通 JavaScript 数组constarr[65,66,67];主要用于存储和操作 JavaScript 数据数组里面甚至可以放[1,hello,true,{},null]BufferBufferconstbufBuffer.from([65,66,67]);主要用于存储和处理二进制字节数据每一个元素本质上都是一个字节范围0 ~ 255所以可以简单记Array ↓ JavaScript 数据容器 Buffer ↓ 二进制数据容器二十五、Buffer 在 Node.js 中到底用在哪里Buffer 在 Node.js 中非常重要。例如1. 文件读取文件importfsfromnode:fs;constdatafs.readFileSync(./test.png);console.log(data);读取二进制文件时就可能得到 Buffer。例如图片 ↓ Buffer ↓ 二进制数据2. 网络例如 TCP客户端 ↓ 二进制数据 ↓ Node.js ↓ BufferNode.js 网络 API 经常会使用 Buffer。3. HTTP请求/响应的数据底层也可能以字节流的形式处理。例如HTTP 数据 ↓ 字节 ↓ Buffer ↓ 字符串 / JSON / 文件4. 图片、音频、视频例如jpg png mp3 mp4 zip pdf这些文件本质上都是二进制数据。因此 Buffer 非常适合处理。二十六、Buffer 和 Stream 有什么关系这是学习 Node.js 时非常重要的一组概念。假设你要读取一个10GB的视频文件。如果一次性读取10GB ↓ 全部放进内存可能会造成很大的内存压力。所以 Node.js 经常使用Stream流把数据分成一小块一小块处理。例如大文件 ↓ ┌──────┐ │ chunk│ → Buffer ├──────┤ │ chunk│ → Buffer ├──────┤ │ chunk│ → Buffer ├──────┤ │ chunk│ → Buffer └──────┘也就是说Stream 负责“流式传输”Buffer 经常负责“承载每一小块二进制数据”。这个关系非常重要。二十七、Buffer、二进制、编码三者是什么关系可以把它们理解成字符串 ↓ 编码UTF-8 ↓ 字节 ↓ Buffer反过来Buffer ↓ 字节 ↓ 解码UTF-8 ↓ 字符串例如conststr你好;constbufBuffer.from(str,utf8);console.log(buf);console.log(buf.toString(utf8));整个过程你好 ↓ UTF-8 编码 ↓ e4 bd a0 e5 a5 bd ↓ Buffer ↓ UTF-8 解码 ↓ 你好二十八、最重要的几个概念一定要分清学习 Buffer 的时候最容易混淆的是字符 字节 二进制 编码 十进制 十六进制可以这样理解字符人看到的hello 你好字节 Byte计算机实际处理的一小块数据1 byte 8 bit二进制计算机底层使用0 1编码规定一个字符应该如何转换成字节。例如UTF-8 ASCII UTF-16十六进制只是为了方便人类阅读二进制数据的一种表示方式。例如104可以表示成0x68所以十进制 104 ↓ 十六进制 68 ↓ 二进制 01101000它们表示的是同一个数。二十九、一张图理解 Buffer最终可以把 Buffer 理解成JavaScript │ │ 字符串 ↓ ┌───────────┐ │ 编码 │ │ UTF-8 │ └─────┬─────┘ ↓ 二进制字节数据0/1组合 ↓ ┌──────────────────┐ │ Buffer │ │ │ │e4 bd a0 e5 a5 bd │ └──────────────────┘ │ │ 解码 ↓ 你好所以 Buffer 的核心就是在 Node.js 中Buffer 是处理二进制字节数据的重要工具。三十、新手现阶段记住这几个就够了如果你刚开始学习 Node.js不需要一次把 Buffer 所有 API 都记住。先掌握// 创建 BufferBuffer.alloc()Buffer.allocUnsafe()Buffer.from()// Buffer 长度buf.length// 读取buf[0]// 修改buf[0]100// Buffer → 字符串buf.toString()// Buffer 拼接Buffer.concat()然后理解下面这几个概念1 byte 8 bit 1 byte 0 ~ 255 Buffer 是二进制数据容器 Buffer.length 表示字节数 字符串和 Buffer 可以相互转换 Buffer.from() 可以创建 Buffer buf.toString() 可以把 Buffer 转回字符串 英文字符通常占 1 个字节 UTF-8 中文字符通常占 3 个字节最后把这一条记住JavaScript 字符串 ↓ 编码 ↓ 字节 ↓ Buffer ↓ Node.js 处理 ↓ Buffer ↓ 解码 ↓ JavaScript 字符串这就是 Buffer 最核心的工作原理。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进