作者选择了COVID-19 救济基金来接受捐赠,作为Write for DOnations计划的一部分。
介绍
甲缓冲是在存储器(RAM典型地)的空间,用于存储二进制数据。在Node.js 中,我们可以使用内置Buffer
类访问这些内存空间。缓冲器存储整数的序列,类似于阵列中的JavaScript。与数组不同,缓冲区一旦创建就无法更改其大小。
如果您已经编写了 Node.js 代码,您可能已经隐式使用了缓冲区。例如,当您从文件中读取时fs.readFile()
,返回给回调或 Promise的数据是一个缓冲区对象。此外,当在 Node.js 中发出 HTTP 请求时,它们会返回临时存储在内部缓冲区中的数据流,因为客户端无法一次处理所有流。
当您与二进制数据交互时,缓冲区很有用,通常是在较低的网络级别。它们还为您提供了在 Node.js 中进行细粒度数据操作的能力。
在本教程中,您将使用Node.js REPL来运行各种缓冲区示例,例如创建缓冲区、从缓冲区读取、写入和复制缓冲区,以及使用缓冲区在二进制数据和编码数据之间进行转换。在本教程结束时,您将学习如何使用Buffer
该类来处理二进制数据。
先决条件
- 您需要在开发机器上安装 Node.js。本教程使用版本 10.19.0。要在 macOS 或 Ubuntu 18.04 上安装它,请按照如何在 macOS 上安装 Node.js 和创建本地开发环境或如何在 Ubuntu 18.04 上安装 Node.js 的使用 PPA 安装部分中的步骤进行操作。
- 在本教程中,您将与 Node.js REPL(读取-评估-打印-循环)中的缓冲区进行交互。如果您想重新了解如何有效地使用 Node.js REPL,您可以阅读我们关于如何使用 Node.js REPL 的指南。
- 对于本文,我们希望用户熟悉基本的 JavaScript 及其数据类型。您可以通过我们的如何在 JavaScript 中编码系列来学习这些基础知识。
步骤 1 — 创建缓冲区
第一步将向您展示在 Node.js 中创建缓冲区对象的两种主要方法。
要决定使用哪种方法,您需要回答这个问题:您要创建新缓冲区还是从现有数据中提取缓冲区?如果要将尚未接收的数据存储在内存中,则需要创建一个新缓冲区。在 Node.js 中,我们使用类的alloc()
函数来做到这一点。Buffer
让我们打开Node.js REPL来亲眼看看。在您的终端中,输入node
命令:
- node
您将看到以 开头的提示>
。
该alloc()
函数将缓冲区的大小作为其第一个也是唯一必需的参数。大小是一个整数,表示缓冲区对象将使用多少字节的内存。例如,如果我们想创建一个 1KB(千字节)大的缓冲区,相当于 1024 字节,我们将在控制台中输入:
- const firstBuf = Buffer.alloc(1024);
为了创建一个新的缓冲区,我们使用了Buffer
具有alloc()
方法的全局可用类。通过提供1024
作为 的参数alloc()
,我们创建了一个 1KB 大的缓冲区。
默认情况下,当您使用 初始化缓冲区时alloc()
,缓冲区将填充二进制零作为以后数据的占位符。但是,如果我们愿意,我们可以更改默认值。如果我们想用1
s 而不是0
s创建一个新缓冲区,我们将设置alloc()
函数的第二个参数—— fill
。
在您的终端中,在 REPL 提示符处创建一个新的缓冲区,其中填充了1
s:
- const filledBuf = Buffer.alloc(1024, 1);
我们刚刚创建了一个新的缓冲区对象,它引用了内存中存储 1KB 1
s的空间。尽管我们输入了一个整数,但所有存储在缓冲区中的数据都是二进制数据。
二进制数据可以有多种不同的格式。例如,让我们考虑表示数据字节的二进制序列:01110110
。如果这个二进制序列使用ASCII 编码标准表示一个英文字符串,它就是字母。但是,如果我们的计算机正在处理图像,则该二进制序列可能包含有关像素颜色的信息。v
计算机知道以不同的方式处理它们,因为字节的编码方式不同。字节编码是字节的格式。如果使用字符串数据初始化,Node.js 中的缓冲区默认使用UTF-8编码方案。UTF-8 中的字节表示数字、字母(英语和其他语言)或符号。UTF-8 是美国信息交换标准码ASCII的超集。ASCII 可以用大写和小写英文字母、数字 0-9 和一些其他符号(如感叹号 ( ! ) 或与号 ( & ))对字节进行编码。
如果我们正在编写一个只能处理 ASCII 字符的程序,我们可以使用alloc()
函数的第三个参数更改缓冲区使用的编码—— encoding
。
让我们创建一个 5 个字节长的新缓冲区,并仅存储 ASCII 字符:
- const asciiBuf = Buffer.alloc(5, 'a', 'ascii');
缓冲区用字符的五个字节初始化a
,使用 ASCII 表示。
注意:默认情况下,Node.js 支持以下字符编码:
- ASCII,表示为
ascii
- UTF-8,表示为
utf-8
或utf8
- UTF-16,表示为
utf-16le
或utf16le
- UCS-2,表示为
ucs-2
或ucs2
- Base64,表示为
base64
- 十六进制,表示为
hex
- ISO/IEC 8859-1,表示为
latin1
或binary
所有这些值都可以在接受encoding
参数的Buffer 类函数中使用。因此,这些值对于该alloc()
方法都是有效的。
到目前为止,我们一直在使用该alloc()
函数创建新的缓冲区。但有时我们可能想从已经存在的数据创建一个缓冲区,比如字符串或数组。
为了从预先存在的数据创建缓冲区,我们使用from()
方法。我们可以使用该函数从以下位置创建缓冲区:
- 整数数组:整数值可以介于
0
和之间255
。 - An
ArrayBuffer
:这是一个存储固定长度字节的 JavaScript 对象。 - 一个字符串。
- 另一个缓冲区。
- 其他具有
Symbol.toPrimitive
属性的JavaScript 对象。该属性让JavaScript如何将对象转换为原始数据类型:boolean
,null
,undefined
,number
,string
,或symbol
。您可以在 Mozilla 的 JavaScript文档 中阅读有关 Symbols 的更多信息。
让我们看看如何从字符串创建缓冲区。在 Node.js 提示中,输入:
- const stringBuf = Buffer.from('My name is Paul');
我们现在有一个从字符串创建的缓冲区对象My name is Paul
。让我们从之前创建的另一个缓冲区创建一个新缓冲区:
- const asciiCopy = Buffer.from(asciiBuf);
我们现在创建了一个新的缓冲区asciiCopy
,其中包含与asciiBuf
.
现在我们已经体验了创建缓冲区,我们可以深入研究读取它们的数据的示例。
步骤 2 — 从缓冲区读取
有很多方法可以访问 Buffer 中的数据。我们可以访问缓冲区中的单个字节,也可以提取整个内容。
要访问缓冲区的一个字节,我们需要传递所需字节的索引或位置。缓冲区像数组一样按顺序存储数据。他们还像数组一样索引他们的数据,从0
. 我们可以在缓冲区对象上使用数组表示法来获取单个字节。
让我们通过从 REPL 中的字符串创建缓冲区来看看它的外观:
- const hiBuf = Buffer.from('Hi!');
现在让我们读取缓冲区的第一个字节:
- hiBuf[0];
当您按下 时ENTER
,REPL 将显示:
Output72
整数72
对应于字母 的 UTF-8 表示H
。
注:为字节的值之间可以进行数字0
和255
。一个字节是一个 8 位的序列。位是二进制的,因此只能具有以下两个值之一:0
或1
。如果我们有一个 8 位的序列,每位有两个可能的值,那么一个字节最多有 2⁸ 个可能的值。最多可以得到 256 个值。由于我们从零开始计数,这意味着我们的最高数字是 255。
让我们对第二个字节做同样的事情。在 REPL 中输入以下内容:
- hiBuf[1];
REPL 返回105
,代表小写i
。
最后,让我们得到第三个字符:
- hiBuf[2];
你会看到33
显示在 REPL 中,它对应于!
.
让我们尝试从无效索引中检索一个字节:
- hiBuf[3];
REPL 将返回:
Outputundefined
这就像我们试图访问一个索引不正确的数组中的元素一样。
现在我们已经了解了如何读取缓冲区的单个字节,让我们看看用于一次检索存储在缓冲区中的所有数据的选项。缓冲区对象带有toString()
和toJSON()
方法,它们以两种不同的格式返回缓冲区的全部内容。
顾名思义,该toString()
方法将缓冲区的字节转换为字符串并将其返回给用户。如果我们在 上使用这个方法hiBuf
,我们将得到字符串Hi!
。让我们试试吧!
在提示中,输入:
- hiBuf.toString();
REPL 将返回:
Output'Hi!'
该缓冲区是从字符串创建的。让我们看看如果我们toString()
在不是由字符串数据构成的缓冲区上使用 会发生什么。
让我们创建一个10
字节大的新的空缓冲区:
- const tenZeroes = Buffer.alloc(10);
现在,让我们使用该toString()
方法:
- tenZeroes.toString();
我们将看到以下结果:
'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'
该字符串\u0000
是 的 Unicode 字符NULL
。它对应于数字0
。当缓冲区的数据未编码为字符串时,该toString()
方法返回字节的 UTF-8 编码。
该toString()
有一个可选的参数encoding
。我们可以使用此参数来更改返回的缓冲区数据的编码。
例如,如果您想要十六进制编码,hiBuf
请在提示符下输入以下内容:
- hiBuf.toString('hex');
该语句将评估为:
Output'486921'
486921
是表示字符串的字节的十六进制表示Hi!
。在 Node.js 中,当用户想要将数据的编码从一种形式转换为另一种形式时,他们通常会将字符串放入缓冲区并toString()
使用他们想要的编码进行调用。
该toJSON()
方法的行为不同。无论缓冲区是否由字符串构成,它始终以字节的整数表示形式返回数据。
让我们重新使用hiBuf
和tenZeroes
缓冲区来练习使用toJSON()
. 在提示符下,输入:
- hiBuf.toJSON();
REPL 将返回:
Output{ type: 'Buffer', data: [ 72, 105, 33 ] }
JSON 对象具有一个type
始终为Buffer
. 这样程序就可以将这些 JSON 对象与其他 JSON 对象区分开来。
该data
属性包含字节的整数表示形式的数组。您可能已经注意到72
,105
、 和33
对应于我们单独拉取字节时收到的值。
让我们试试这个toJSON()
方法tenZeroes
:
- tenZeroes.toJSON();
在 REPL 中,您将看到以下内容:
Output{ type: 'Buffer', data: [
0, 0, 0, 0, 0,
0, 0, 0, 0, 0
] }
这type
与前面提到的相同。然而,数据现在是一个有十个零的数组。
现在我们已经介绍了从缓冲区读取的主要方法,让我们看看如何修改缓冲区的内容。
第 3 步 – 修改缓冲区
我们可以通过多种方式修改现有的缓冲区对象。与读取类似,我们可以使用数组语法单独修改缓冲区字节。我们还可以将新内容写入缓冲区,替换现有数据。
让我们先看看如何更改缓冲区的单个字节。回想一下我们的缓冲区变量hiBuf
,它包含字符串Hi!
。让我们更改每个字节,使其包含Hey
。
在 REPL 中,让我们首先尝试设置hiBuf
to的第二个元素e
:
- hiBuf[1] = 'e';
现在,让我们将此缓冲区视为一个字符串,以确认它正在存储正确的数据。通过调用该toString()
方法进行跟进:
- hiBuf.toString();
它将被评估为:
Output'H\u0000!'
我们收到了那个奇怪的输出,因为缓冲区只能接受一个整数值。我们不能将它分配给字母e
;相反,我们必须为其分配二进制等价物表示的数字e
:
- hiBuf[1] = 101;
现在,当我们调用该toString()
方法时:
- hiBuf.toString();
我们在 REPL 中得到这个输出:
Output'He!'
要更改缓冲区中的最后一个字符,我们需要将第三个元素设置为对应于 的字节的整数y
:
- hiBuf[2] = 121;
让我们toString()
再次使用该方法进行确认:
- hiBuf.toString();
您的 REPL 将显示:
Output'Hey'
如果我们尝试写入缓冲区范围之外的字节,它将被忽略并且缓冲区的内容不会改变。例如,让我们尝试将缓冲区中不存在的第四个元素设置为o
:
- hiBuf[3] = 111;
我们可以通过以下toString()
方法确认缓冲区没有变化:
- hiBuf.toString();
输出仍然是:
Output'Hey'
如果我们想改变整个缓冲区的内容,我们可以使用write()
方法。该write()
方法接受将替换缓冲区内容的字符串。
让我们使用write()
方法将 的内容改hiBuf
回Hi!
. 在您的 Node.js shell 中,在提示符处键入以下命令:
- hiBuf.write('Hi!');
REPL 中write()
返回的方法3
。这是因为它写入了三个字节的数据。每个字母的大小为一个字节,因为该缓冲区使用 UTF-8 编码,每个字符使用一个字节。如果缓冲区使用 UTF-16 编码,每个字符至少有两个字节,那么write()
函数将返回6
.
现在使用以下命令验证缓冲区的内容toString()
:
- hiBuf.toString();
REPL 将产生:
Output'Hi!'
这比逐字节更改每个元素要快。
如果您尝试写入比缓冲区大小更多的字节,则缓冲区对象将只接受适合的字节。为了说明这一点,让我们创建一个存储三个字节的缓冲区:
- const petBuf = Buffer.alloc(3);
现在让我们尝试写入Cats
它:
- petBuf.write('Cats');
当write()
调用被评估时,REPL 返回3
指示只有三个字节被写入缓冲区。现在确认缓冲区包含前三个字节:
- petBuf.toString();
REPL 返回:
Output'Cat'
该write()
函数按顺序添加字节,因此只有前三个字节被放置在缓冲区中。
相比之下,让我们制作一个Buffer
存储四个字节的 a :
- const petBuf2 = Buffer.alloc(4);
向其写入相同的内容:
- petBuf2.write('Cats');
然后添加一些比原内容占用更少空间的新内容:
- petBuf2.write('Hi');
由于缓冲区按顺序写入,从 开始0
,如果我们打印缓冲区的内容:
- petBuf2.toString();
迎接我们的是:
Output'Hits'
前两个字符被覆盖,但缓冲区的其余部分未受影响。
有时,我们想要在预先存在的缓冲区中的数据不在字符串中,而是驻留在另一个缓冲区对象中。在这些情况下,我们可以使用该copy()
函数来修改缓冲区存储的内容。
让我们创建两个新缓冲区:
- const wordsBuf = Buffer.from('Banana Nananana');
- const catchphraseBuf = Buffer.from('Not sure Turtle!');
在wordsBuf
和catchphraseBuf
缓冲区都包含字符串数据。我们想要修改,catchphraseBuf
以便它存储Nananana Turtle!
而不是Not sure Turtle!
. 我们将使用copy()
获得Nananana
的wordsBuf
到catchphraseBuf
。
要将数据从一个缓冲区复制到另一个缓冲区,我们将copy()
在作为信息源的缓冲区上使用该方法。因此,wordsBuf
对于我们想要复制的字符串数据,我们需要像这样复制:
- wordsBuf.copy(catchphraseBuf);
target
这种情况下的参数是catchphraseBuf
缓冲区。
当我们将其输入到 REPL 中时,它会返回15
指示已写入 15 个字节。该字符串Nananana
仅使用 8 个字节的数据,因此我们立即知道我们的副本没有按预期进行。使用toString()
方法查看内容catchphraseBuf
:
- catchphraseBuf.toString();
REPL 返回:
Output'Banana Nananana!'
默认情况下,copy()
将 的全部内容wordsBuf
放入catchphraseBuf
. 我们需要对我们的目标更有选择性,并且只复制Nananana
. catchphraseBuf
在继续之前,让我们重新编写原始内容:
- catchphraseBuf.write('Not sure Turtle!');
该copy()
函数还有一些参数,允许我们自定义将哪些数据复制到另一个缓冲区。下面是这个函数的所有参数的列表:
target
– 这是 的唯一必需参数copy()
。正如我们从之前的用法中看到的,它是我们想要复制到的缓冲区。targetStart
– 这是我们应该开始复制到的目标缓冲区中字节的索引。默认情况下它是0
,这意味着它从缓冲区的开头开始复制数据。sourceStart
– 这是我们应该从中复制的源缓冲区中字节的索引。sourceEnd
– 这是源缓冲区中我们应该停止复制的字节索引。默认情况下,它是缓冲区的长度。
所以,Nananana
要从wordsBuf
into复制catchphraseBuf
,我们target
应该catchphraseBuf
像以前一样。该targetStart
会是0
因为我们希望Nananana
出现之初catchphraseBuf
。本sourceStart
应该是7
因为这是在指数Nananana
中开始wordsBuf
。的sourceEnd
将继续是缓冲区的长度。
在 REPL 提示符下,复制如下内容wordsBuf
:
- wordsBuf.copy(catchphraseBuf, 0, 7, wordsBuf.length);
REPL 确认8
已写入字节。请注意 howwordsBuf.length
用作sourceEnd
参数的值。与数组一样,该length
属性为我们提供了缓冲区的大小。
现在让我们看看内容catchphraseBuf
:
- catchphraseBuf.toString();
REPL 返回:
Output'Nananana Turtle!'
成功!我们能够catchphraseBuf
通过复制 的内容来修改 的数据wordsBuf
。
如果您愿意,可以退出 Node.js REPL。请注意,当您执行以下操作时,创建的所有变量将不再可用:
- .exit
结论
在本教程中,您了解到缓冲区是内存中用于存储二进制数据的固定长度分配。您首先通过在内存中定义缓冲区大小并使用预先存在的数据对其进行初始化来创建缓冲区。然后通过检查缓冲区的各个字节并使用toString()
和toJSON()
方法从缓冲区读取数据。最后,您通过更改缓冲区的各个字节并使用write()
和copy()
方法修改了缓冲区存储的数据。
缓冲区让您深入了解 Node.js 如何操作二进制数据。现在您可以与缓冲区交互,您可以观察字符编码影响数据存储方式的不同方式。例如,您可以从非 UTF-8 或 ASCII 编码的字符串数据创建缓冲区,并观察它们的大小差异。您还可以使用 UTF-8 缓冲区toString()
并将其转换为其他编码方案。
要了解 Node.js 中的缓冲区,您可以阅读有关对象的Node.js 文档Buffer
。如果您想继续学习 Node.js,您可以返回到如何在 Node.js 中编码系列,或在我们的Node 主题页面上浏览编程项目和设置。