Buffer 新手入门【Node.js】

目录

  • [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 最核心的工作原理。

相关推荐
jj_ccwgw5 小时前
从 0 到 1 搭建 pnpm + Workspaces + Turborepo Monorepo 保姆级指南
前端·node.js
梦想平凡5 小时前
棋牌游戏技术实战(一):用 Node.js 实现房间消息队列、请求幂等与断线恢复
游戏·node.js
*wait for?6 小时前
VScode使用uv-创建python虚拟环境
ide·vscode·编辑器
小天源6 小时前
GEO 品牌监测系统实战:基于 Node.js + Playwright 实现多平台采集与报告导出
人工智能·node.js·geo·品牌检测·ai诊断
damoluomu6 小时前
Directus 换掉 GPL 之后,Node.js CMS 到底怎么选?(一)
node.js
lzx_0028 小时前
Linux 开发工具(二) 简单认识一下编辑器 —— vim
linux·编辑器·vim
guslegend1 天前
脚手架入门:必要性、核心功能与执行原理
前端·架构·node.js·脚手架·前端工程化
此生决int1 天前
Linux系列(04)——基础开发工具(上)软件包管理器、vim、gcc/g++
linux·服务器·vim
HaipengYu2 天前
深度解密 OpenCode 2 插件加载机制:Dual-Version (V1/V2) 适配实战与官方文档的“隐形陷阱”
node.js