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