目录
- Node.js Buffer 新手入门
- 一、Buffer 是什么?
- 二、为什么 Node.js 需要 Buffer?
- 三、可以把 Buffer 理解成什么?
- 四、Buffer 的几个重要特点
- 1. Buffer 的长度固定
- 2. Buffer 每一个元素占 1 个字节
- 3. Buffer 可以直接操作二进制数据
- 五、Buffer 的创建方式
- 六、Buffer.alloc()
- 1. 创建指定长度的 Buffer
- 2. 为什么是 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 有什么区别?
- Array
- Buffer
- 二十五、Buffer 在 Node.js 中到底用在哪里?
- 1. 文件
- 2. 网络
- 3. HTTP
- 4. 图片、音频、视频
- 二十六、Buffer 和 Stream 有什么关系?
- 二十七、Buffer、二进制、编码三者是什么关系?
- 二十八、最重要的几个概念一定要分清
- 字符
- 字节 Byte
- 二进制
- 编码
- 十六进制
- 二十九、一张图理解 Buffer
- 三十、新手现阶段记住这几个就够了
Node.js Buffer 新手入门
一、Buffer 是什么?
在 Node.js 中,经常会看到:Buffer;很多刚接触 Node.js 的人第一次看到它会觉得:Buffer到底是什么?为什么 Node.js 要专门设计一个Buffer?
其实可以先把它简单理解成:
Buffer 就是一块专门用来存放二进制数据的内存空间。
例如:
constbuf=Buffer.from('hello');console.log(buf);输出:
<Buffer 68 65 6c 6c 6f>这里的:
68 65 6c 6c 6f并不是普通的字符串,而是hello在 UTF-8 编码下对应的二进制字节。
二、为什么 Node.js 需要 Buffer?
JavaScript 最开始主要是运行在浏览器里的。
浏览器中的 JavaScript 经常处理:
字符串 数字 对象 数组 DOM但是 Node.js 不一样。
Node.js 经常需要直接处理计算机底层的数据,例如:
文件 TCP 网络数据 HTTP 请求 图片 音频 视频 压缩文件 加密数据 WebSocket 数据这些数据本质上都是:
一串二进制数据,也就是一串 0 和 1。
例如:
01001000 01100101 01101100 01101100 01101111如果我们全部使用普通 JavaScript 字符串来处理,会比较麻烦。
所以 Node.js 提供了:
Buffer专门用于处理二进制数据。
三、可以把 Buffer 理解成什么?
可以把 Buffer 想象成一个:
固定大小的字节数组。
例如:
constbuf=Buffer.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 的长度固定
例如:
constbuf=Buffer.alloc(5);console.log(buf.length);输出:
5创建的时候指定了:
5 byte这个 Buffer 的长度就是固定的。
2. Buffer 每一个元素占 1 个字节
例如:
constbuf=Buffer.from([10,20,30]);console.log(buf);可以理解成:
┌────┬────┬────┐ │ 10 │ 20 │ 30 │ └────┴────┴────┘ 1B 1B 1B所以:
buf.length得到:
33. Buffer 可以直接操作二进制数据
例如:
constbuf=Buffer.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. 创建指定长度的 Buffer
constbuf1=Buffer.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()
还可以:
constbuf2=Buffer.allocUnsafe(10);console.log(buf2);它同样创建:
10 byte的 Buffer。
但是:
allocUnsafe()不会像alloc()一样保证把内存全部清零。
因此理论上可能看到之前内存中遗留的数据。
例如可能出现:
<Buffer 00 7a 31 9f ...>具体内容是不确定的。
为什么叫 Unsafe?
因为:
Unsafe = 不安全它的主要特点是:
速度更快 但是不会保证初始化为 0所以:
Buffer.alloc()更适合普通场景。
而:
Buffer.allocUnsafe()通常用于你非常清楚自己在做什么,并且会立即覆盖全部数据的场景。
例如:
constbuf=Buffer.allocUnsafe(10);buf.fill(0);console.log(buf);八、Buffer.from()
Buffer.from()是我们平时非常常用的方法。
它可以根据不同的数据创建 Buffer。
1. 根据字符串创建
constbuf3=Buffer.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 和 Buffer
Buffer.from([105,108,111,118,101,121,111,117])就是一个很好的例子。
这些数字按照 ASCII:
105 → 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创建 Buffer:
constbuf=Buffer.from([105,108,111,118,101,121,111,117]);console.log(buf.toString());输出:
iloveyou这里要注意:
Buffer 本身并不知道“这是 ASCII 还是 UTF-8 字符”。它本质上只是保存字节。
编码决定了这些字节应该如何解释。
十、Buffer.toString()
Buffer 和字符串之间可以相互转换。
例如:
constbuf=Buffer.from('hello');console.log(buf);得到:
<Buffer 68 65 6c 6c 6f>转换成字符串:
console.log(buf.toString());得到:
hello默认使用:
utf8也可以明确写:
console.log(buf.toString('utf8'));十一、字符串 → Buffer → 字符串
这是非常重要的一条链路:
字符串 ↓ 编码 ↓ Buffer ↓ 解码 ↓ 字符串例如:
conststr='hello';constbuf=Buffer.from(str);console.log(buf);constresult=buf.toString('utf8');console.log(result);结果:
hello → Buffer → hello可以理解为:
'hello' ↓ UTF-8 编码 Buffer ↓ UTF-8 解码 'hello'十二、Buffer 可以使用 [] 访问
Buffer 和数组很像。
例如:
constbuf4=Buffer.from([105,108,111,118,101,121,111,117]);可以通过下标读取:
console.log(buf4[0]);得到:
105再比如:
console.log(buf4[1]);得到:
108十三、修改 Buffer
Buffer 也可以通过下标修改。
例如:
constbuf=Buffer.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也就是:
0<=buf[i]<=255十五、超过 255 会发生什么?
例如:
constbuf=Buffer.from('hello');buf[0]=361;361 的二进制:
101101001Buffer 一个字节只能保存 8 bit。
因此最终只保留低 8 位:
01101001转换成十进制:
105105 对应 ASCII:
e所以:
console.log(buf[0]);得到:
105而:
console.log(buf.toString());会变成:
eello可以记住:
Buffer 的一个元素只能保存一个字节,也就是 0~255。
十六、为什么 buf[0].toString(2) 可以看到二进制?
例如:
constbuf=Buffer.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)
例如:
constnum=104;console.log(num.toString(2));console.log(num.toString(10));console.log(num.toString(16));分别是:
1101000 104 68也就是:
二进制 → 2 十进制 → 10 十六进制 → 16十七、为什么 Buffer 输出经常是十六进制?
例如:
constbuf=Buffer.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 个字节?
例如:
constbuf=Buffer.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十九、字符数量和字节数量一定一样吗?
不一定。
例如英文:
constbuf=Buffer.from('hello');console.log(buf.length);得到:
5因为:
h → 1 byte e → 1 byte l → 1 byte l → 1 byte o → 1 byte所以:
5 个字符 = 5 个字节但是中文:
constbuf=Buffer.from('你好');console.log(buf.length);得到:
6所以:
2 个字符 ≠ 6 个字节这就是:
字符 ≠ 字节
一定要把这两个概念分开。
二十、Buffer.length 到底表示什么?
例如:
constbuf=Buffer.from('你好');console.log(buf.length);得到:
6这里的length表示:
Buffer 中有多少个字节。
不是:
有多少个汉字也不是:
有多少个字符而是:
多少 byte二十一、Buffer 常用属性和方法
实际开发中最常见的可以先记住这些:
| API | 作用 |
|---|---|
Buffer.alloc(size) | 创建指定大小并初始化为 0 的 Buffer |
Buffer.allocUnsafe(size) | 创建指定大小但不保证初始化的 Buffer |
Buffer.from(data) | 根据数据创建 Buffer |
buf.length | Buffer 的字节长度 |
buf[index] | 读取指定位置的字节 |
buf[index] = value | 修改指定位置的字节 |
buf.toString() | Buffer 转字符串 |
buf.toString('hex') | 转成十六进制字符串 |
buf.toString('base64') | 转成 Base64 |
buf.slice() | 获取 Buffer 的一部分 |
buf.subarray() | 获取 Buffer 的一部分 |
Buffer.concat() | 拼接多个 Buffer |
buf.equals() | 判断两个 Buffer 是否相同 |
buf.includes() | 判断 Buffer 是否包含某个数据 |
buf.indexOf() | 查找数据位置 |
buf.fill() | 填充 Buffer |
二十二、Buffer.concat()
Buffer 还可以进行拼接。
例如:
constbuf1=Buffer.from('hello ');constbuf2=Buffer.from('world');constresult=Buffer.concat([buf1,buf2]);console.log(result.toString());得到:
hello world可以理解成:
Buffer 1 hello + Buffer 2 world ↓ Buffer.concat() ↓ hello world这个在处理网络数据流的时候非常常见。
二十三、Buffer.slice() / subarray()
例如:
constbuf=Buffer.from('hello');constresult=buf.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];constbuf=Buffer.from([65,66,67]);都可以:
arr[0]buf[0]但是它们的定位完全不同。
Array
普通 JavaScript 数组:
constarr=[65,66,67];主要用于:
存储和操作 JavaScript 数据数组里面甚至可以放:
[1,'hello',true,{},null]Buffer
Buffer:
constbuf=Buffer.from([65,66,67]);主要用于:
存储和处理二进制字节数据每一个元素本质上都是:
一个字节范围:
0 ~ 255所以可以简单记:
Array ↓ JavaScript 数据容器 Buffer ↓ 二进制数据容器二十五、Buffer 在 Node.js 中到底用在哪里?
Buffer 在 Node.js 中非常重要。
例如:
1. 文件
读取文件:
importfsfrom'node:fs';constdata=fs.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='你好';constbuf=Buffer.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 最核心的工作原理。