在Node.js中使用缓冲区

作者选择了COVID-19救助基金作为Write for DOnations计划的捐赠对象。

介绍

A buffer is a space in memory (typically RAM) that stores binary data. In Node.js, we can access these spaces of memory with the built-in Buffer class. Buffers store a sequence of integers, similar to an array in JavaScript. Unlike arrays, you cannot change the size of a buffer once it is created.

如果你已经编写过Node.js代码,那么你可能已经隐式地使用过缓冲区。例如,当你使用fs.readFile()从文件中读取数据时,回调函数或Promise返回的数据是一个缓冲区对象。此外,当在Node.js中进行HTTP请求时,如果客户端无法一次性处理流,则返回的数据流会暂时存储在内部缓冲区中。

缓冲区在与二进制数据交互时非常有用,通常用于较低级别的网络操作。它们还为你提供了在Node.js中进行精细数据操作的能力。

在本教程中,您将使用Node.js REPL运行各种缓冲区示例,例如创建缓冲区、从缓冲区读取、向缓冲区写入和复制,以及使用缓冲区在二进制数据和编码数据之间进行转换。到教程结束时,您将学会如何使用Buffer类处理二进制数据。

先决条件

步骤 1 —— 创建缓冲区

这第一步将向您展示在 Node.js 中创建缓冲区对象的两种主要方法。

要决定使用哪种方法,您需要回答这个问题:您是想创建一个新的缓冲区,还是从现有数据中提取一个缓冲区?如果您要将尚未接收到的数据存储在内存中,那么您将需要创建一个新的缓冲区。在 Node.js 中,我们使用Buffer alloc()函数来实现这一点。

让我们打开Node.js REPL来亲自看看。在您的终端中,输入node命令:

  1. node

您将看到提示符以>开始。

alloc()函数以缓冲区的大小作为其唯一的必需参数。大小是一个整数,表示缓冲区对象将使用多少字节的内存。例如,如果我们想创建一个大小为 1KB(千字节)的缓冲区,相当于 1024 字节,我们可以在控制台中输入以下内容:

  1. const firstBuf = Buffer.alloc(1024);

要创建一个新的缓冲区,我们使用了全局可用的Buffer类,该类具有alloc()方法。通过将1024作为alloc()的参数,我们创建了一个大小为1KB的缓冲区。

默认情况下,当您使用alloc()初始化缓冲区时,缓冲区将填充二进制零,作为以后数据的占位符。但是,如果我们愿意的话,我们可以更改默认值。如果我们想要创建一个新的填充有1而不是0的缓冲区,我们将设置alloc()函数的第二个参数fill

在您的终端中,在REPL提示符下创建一个新的填充有1的缓冲区:

  1. const filledBuf = Buffer.alloc(1024, 1);

我们刚刚创建了一个新的缓冲区对象,该对象引用存储了1KB的1的内存空间。虽然我们输入了一个整数,但缓冲区中存储的所有数据都是二进制数据。

二进制数据可以以许多不同的格式出现。例如,让我们考虑一个表示一个字节数据的二进制序列:01110110。如果这个二进制序列使用ASCII编码标准表示一个英语字符串,那么它将是字母v。然而,如果我们的计算机正在处理一幅图像,那么这个二进制序列可能包含有关像素颜色的信息。

计算机知道如何以不同的方式处理它们,因为字节的编码方式不同。字节编码是字节的格式。如果使用字符串数据初始化,则Node.js中的缓冲区默认使用UTF-8编码方案。UTF-8中的一个字节代表一个数字、一个字母(英文和其他语言)或一个符号。UTF-8是ASCII的超集,美国信息交换标准代码。ASCII可以编码大写和小写英文字母、数字0-9以及几个其他符号,如感叹号(!)或和符号(&)。

如果我们要编写的程序只能处理ASCII字符,我们可以使用alloc()函数的第三个参数—encoding来更改我们缓冲区使用的编码方式。

让我们创建一个新的缓冲区,长度为五个字节,只存储ASCII字符:

  1. const asciiBuf = Buffer.alloc(5, 'a', 'ascii');

缓冲区用ASCII表示初始化为五个字节的字符a

注意:默认情况下,Node.js支持以下字符编码:

  • ASCII,表示为ascii
  • UTF-8,表示为utf-8utf8
  • UTF-16,表示为utf-16leutf16le
  • UCS-2,表示为ucs-2ucs2
  • Base64,表示为base64
  • 十六进制,表示为hex
  • ISO/IEC 8859-1,表示为latin1binary

所有这些值都可以用于接受encoding参数的Buffer类函数。因此,这些值对于alloc()方法都是有效的。

到目前为止,我们一直在使用alloc()函数创建新的缓冲区。但有时我们可能希望从已经存在的数据(如字符串或数组)创建缓冲区。

要从预先存在的数据创建缓冲区,我们使用from()方法。我们可以使用该函数从以下内容创建缓冲区:

  • 整数数组:整数值可以介于0255之间。
  • ArrayBuffer:这是一个存储固定长度字节的JavaScript对象。
  • A string.
  • 另一个缓冲区。
  • 具有Symbol.toPrimitive属性的其他JavaScript对象。该属性告诉JavaScript如何将对象转换为原始数据类型:booleannullundefinednumberstringsymbol。您可以在Mozilla的JavaScript文档中了解更多关于Symbols的信息。

让我们看看如何从字符串创建缓冲区。在Node.js提示符中输入:

  1. const stringBuf = Buffer.from('My name is Paul');

现在我们已经从字符串My name is Paul创建了一个缓冲区对象。让我们从之前制作的另一个缓冲区创建一个新的缓冲区:

  1. const asciiCopy = Buffer.from(asciiBuf);

我们现在创建了一个包含与asciiBuf相同数据的新缓冲区asciiCopy

现在我们已经体验了创建缓冲区,我们可以深入探讨读取其数据的示例。

步骤2 —— 从缓冲区中读取

有许多方法可以访问缓冲区中的数据。我们可以访问缓冲区中的单个字节,或者我们可以提取整个内容。

要访问缓冲区的一个字节,我们传递我们想要的字节的索引或位置。缓冲区按顺序存储数据,就像数组一样。它们还像数组一样对它们的数据进行索引,从0开始。我们可以使用数组表示法在缓冲区对象上获取单个字节。

让我们通过在REPL中从字符串创建一个缓冲区来看看它的效果:

  1. const hiBuf = Buffer.from('Hi!');

现在让我们读取缓冲区的第一个字节:

  1. hiBuf[0];

当您按下ENTER时,REPL将显示:

Output
72

整数72对应于字母H的UTF-8表示。

注意:字节的值可以是介于0255之间的数字。一个字节是8位的序列。一个位是二进制的,因此只能有两个值之一:01。如果我们有一个包含两个可能值的位的8位序列,那么我们最多可以有2⁸个字节的可能值。这意味着最多有256个值。由于我们从零开始计数,这意味着我们的最高数字是255。

让我们对第二个字节做同样的操作。在REPL中输入以下内容:

  1. hiBuf[1];

REPL返回105,代表小写字母i

最后,让我们获取第三个字符:

  1. hiBuf[2];

您将在REPL中看到33显示出来,对应于!

让我们尝试从无效索引中检索一个字节:

  1. hiBuf[3];

REPL将返回:

Output
undefined

这就像我们尝试使用不正确的索引访问数组中的元素一样。

现在我们已经了解了如何读取缓冲区的单个字节,让我们看看一次检索缓冲区中存储的所有数据的选项。缓冲区对象带有toString()toJSON()方法,它们以两种不同的格式返回缓冲区的全部内容。

如其名,toString() 方法将缓冲区的字节转换为字符串并将其返回给用户。如果我们在hiBuf上使用这个方法,我们将得到字符串Hi!。让我们试试吧!

在提示符中输入:

  1. hiBuf.toString();

REPL 将返回:

Output
'Hi!'

该缓冲区是从一个字符串创建的。让我们看看如果我们在一个不是由字符串数据创建的缓冲区上使用toString()会发生什么。

让我们创建一个新的空缓冲区,大小为10字节:

  1. const tenZeroes = Buffer.alloc(10);

现在,让我们使用toString()方法:

  1. tenZeroes.toString();

我们会看到以下结果:

'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'

字符串\u0000NULL的 Unicode 字符。它对应于数字0。当缓冲区的数据未编码为字符串时,toString()方法返回字节的 UTF-8 编码。

toString()有一个可选参数encoding。我们可以使用这个参数来改变返回的缓冲区数据的编码。

例如,如果您想要hiBuf的十六进制编码,您可以在提示符中输入以下内容:

  1. hiBuf.toString('hex');

该语句将计算为:

Output
'486921'

486921是代表字符串Hi!的字节的十六进制表示。在 Node.js 中,当用户想要将数据的编码从一种形式转换为另一种形式时,他们通常将字符串放入缓冲区,并使用所需的编码调用toString()

toJSON()方法的行为不同。无论缓冲区是由字符串创建还是其他方式,它始终将数据作为字节的整数表示返回。

让我们重复使用hiBuftenZeroes缓冲区来练习使用toJSON()。在提示符处输入:

  1. hiBuf.toJSON();

REPL将返回:

Output
{ type: 'Buffer', data: [ 72, 105, 33 ] }

JSON对象具有一个type属性,它将始终是Buffer。这样程序可以区分这些JSON对象和其他JSON对象。

data属性包含字节的整数表示的数组。您可能已经注意到,7210533对应于我们单独提取字节时收到的值。

让我们尝试对tenZeroes使用toJSON()方法:

  1. tenZeroes.toJSON();

在REPL中,您将看到以下内容:

Output
{ type: 'Buffer', data: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }

type与之前的相同。但是,数据现在是一个包含十个零的数组。

现在我们已经介绍了从缓冲区读取的主要方法,让我们看看如何修改缓冲区的内容。

第3步 —— 修改缓冲区

我们可以使用多种方法修改现有的缓冲区对象。与读取类似,我们可以使用数组语法逐个修改缓冲区字节。我们还可以向缓冲区写入新内容,以替换现有数据。

让我们首先看看如何更改缓冲区的单个字节。回想一下我们的缓冲变量hiBuf,其中包含字符串Hi!。让我们更改每个字节,使其包含Hey

在 REPL 中,让我们首先尝试将hiBuf的第二个元素设置为e

  1. hiBuf[1] = 'e';

现在,让我们将该缓冲区视为字符串,以确认它是否存储了正确的数据。随后调用toString()方法:

  1. hiBuf.toString();

它将被评估为:

Output
'H\u0000!'

我们收到了奇怪的输出,因为缓冲区只能接受整数值。我们不能将其分配给字母e;相反,我们必须将其分配给其二进制等价表示e的数字:

  1. hiBuf[1] = 101;

现在当我们再次调用toString()方法时:

  1. hiBuf.toString();

在 REPL 中,我们得到了这个输出:

Output
'He!'

要更改缓冲区中的最后一个字符,我们需要将第三个元素设置为对应于y字节的整数:

  1. hiBuf[2] = 121;

让我们再次使用toString()方法来确认:

  1. hiBuf.toString();

您的 REPL 将显示:

Output
'Hey'

如果我们尝试写入超出缓冲区范围的字节,它将被忽略,缓冲区的内容不会更改。例如,让我们尝试将缓冲区的不存在的第四个元素设置为o

  1. hiBuf[3] = 111;

我们可以使用toString()方法确认缓冲区未更改:

  1. hiBuf.toString();

输出仍然是:

Output
'Hey'

如果我们想要更改整个缓冲区的内容,我们可以使用write()方法。

write()方法接受一个字符串,该字符串将替换缓冲区的内容。

  1. hiBuf.write('Hi!');

让我们使用write()方法将hiBuf的内容更改回Hi!。在你的Node.js shell中,在提示符处键入以下命令:

REPL中write()方法返回3。这是因为它写入了三个字节的数据。每个字母大小为一个字节,因为该缓冲区使用UTF-8编码,每个字符使用一个字节。如果缓冲区使用UTF-16编码,每个字符至少占两个字节,则write()函数会返回6

  1. hiBuf.toString();

现在通过使用toString()来验证缓冲区的内容:

Output
'Hi!'

REPL将生成:

这比逐个字节更改每个元素要快。

  1. const petBuf = Buffer.alloc(3);

如果尝试写入的字节数超过缓冲区的大小,则缓冲区对象将只接受适合的字节。为了说明这一点,让我们创建一个存储三个字节的缓冲区:

  1. petBuf.write('Cats');

现在让我们尝试将Cats写入其中:

  1. petBuf.toString();

当评估write()调用时,REPL返回3,表示仅将三个字节写入缓冲区。现在确认缓冲区包含前三个字节:

Output
'Cat'

write()函数按顺序添加字节,因此只有前三个字节放入了缓冲区。

相比之下,让我们创建一个存储四个字节的Buffer

  1. const petBuf2 = Buffer.alloc(4);

将相同的内容写入其中:

  1. petBuf2.write('Cats');

然后添加一些新内容,其占用的空间小于原始内容:

  1. petBuf2.write('Hi');

由于缓冲区是按顺序写入的,从0开始,如果我们打印缓冲区的内容:

  1. petBuf2.toString();

我们会看到:

Output
'Hits'

前两个字符被覆盖,但缓冲区的其余部分保持不变。

有时,我们希望在现有缓冲区中的数据不是字符串,而是存在于另一个缓冲区对象中。在这些情况下,我们可以使用copy()函数来修改我们缓冲区的存储内容。

让我们创建两个新的缓冲区:

  1. const wordsBuf = Buffer.from('Banana Nananana');
  2. const catchphraseBuf = Buffer.from('Not sure Turtle!');

wordsBufcatchphraseBuf缓冲区都包含字符串数据。我们想要修改catchphraseBuf,使其存储Nananana Turtle!而不是Not sure Turtle!。我们将使用copy()wordsBuf复制NanananacatchphraseBuf

要将数据从一个缓冲区复制到另一个缓冲区,我们将使用源信息的缓冲区上的copy()方法。因此,由于wordsBuf具有我们要复制的字符串数据,我们需要这样复制:

  1. wordsBuf.copy(catchphraseBuf);

在这种情况下,target参数是catchphraseBuf缓冲区。

当我们将其输入 REPL 时,它返回 15,表示写入了 15 个字节。字符串 Nananana 仅使用了 8 个字节的数据,所以我们立即知道我们的复制并没有按预期进行。使用 toString() 方法来查看 catchphraseBuf 的内容:

  1. catchphraseBuf.toString();

REPL 返回:

Output
'Banana Nananana!'

默认情况下,copy()wordsBuf 的整个内容放入 catchphraseBuf 中。我们需要更具选择性地实现我们的目标,只复制 Nananana。在继续之前,让我们重新编写 catchphraseBuf 的原始内容:

  1. catchphraseBuf.write('Not sure Turtle!');

copy() 函数还有一些更多参数,允许我们自定义复制到另一个缓冲区的数据。下面是此函数的所有参数列表:

  • target – 这是 copy() 的唯一必需参数。正如我们之前的使用所示,它是我们想要复制到的缓冲区。
  • targetStart – 这是目标缓冲区中我们应该开始复制的字节的索引。默认为 0,表示从缓冲区的开头开始复制数据。
  • sourceStart – 这是源缓冲区中我们应该复制的字节的索引。
  • sourceEnd – 这是源缓冲区中我们应该停止复制的字节的索引。默认为缓冲区的长度。

所以,要从wordsBuf复制NanananacatchphraseBuf,我们的target应该像之前一样是catchphraseBuftargetStart应该是0,因为我们希望Nananana出现在catchphraseBuf的开头。 sourceStart应该是7,因为这是wordsBufNananana开始的索引。 sourceEnd应该继续是缓冲区的长度。

在REPL提示符下,像这样复制wordsBuf的内容:

  1. wordsBuf.copy(catchphraseBuf, 0, 7, wordsBuf.length);

REPL确认已写入8字节。注意如何使用wordsBuf.length作为sourceEnd参数的值。像数组一样,length属性给我们提供了缓冲区的大小。

现在让我们看看catchphraseBuf的内容:

  1. catchphraseBuf.toString();

REPL返回:

Output
'Nananana Turtle!'

成功!我们能够通过复制wordsBuf的内容来修改catchphraseBuf的数据。

如果您愿意,您可以退出Node.js REPL。请注意,当您这样做时,所有创建的变量将不再可用:

  1. .exit

结论

在本教程中,您学到了缓冲区是内存中存储二进制数据的固定长度分配。您首先通过在内存中定义它们的大小并使用预先存在的数据进行初始化来创建缓冲区。然后,您通过检查其各个字节并使用 toString()toJSON() 方法从缓冲区中读取数据。最后,您通过更改其各个字节并使用 write()copy() 方法来修改缓冲区中存储的数据。

缓冲区使您能够深入了解 Node.js 如何操作二进制数据。现在您可以与缓冲区交互,观察字符编码影响数据存储的不同方式。例如,您可以从非 UTF-8 或 ASCII 编码的字符串数据创建缓冲区,并观察它们在大小上的差异。您还可以使用 toString() 将 UTF-8 缓冲区转换为其他编码方案。

要了解 Node.js 中的缓冲区,您可以阅读 Node.js 文档 上关于 Buffer 对象的内容。如果您想继续学习 Node.js,可以返回 如何在 Node.js 中编码系列,或者浏览我们的 Node 主题页面 上的编程项目和设置。

Source:
https://www.digitalocean.com/community/tutorials/using-buffers-in-node-js