在 Node.js 中使用缓冲区

作者选择了 COVID-19 救援基金作为 Write for Donations计划的一部分接受捐款。

介绍

Node.js,我们可以使用内置的Buffer类来访问这些内存空间。 Buffer 存储整数序列,类似于 arrayJavaScript中。

例如,当您从包含 fs.readFile()的文件中读取时,返回到 callback or Promise的数据是一个缓冲器 object

当您与二进制数据互动时,缓冲器非常有用,通常是在较低的网络层面,他们还为您提供在 Node.js 中进行细粒数据操纵的能力。

在本教程中,您将使用 Node.js REPL来运行各种缓冲器的示例,例如创建缓冲器,从缓冲器中阅读,从缓冲器中写入和复制,并使用缓冲器在二进制和编码数据之间进行转换。

前提条件

要在 macOS 或 Ubuntu 18.04 上安装此功能,请遵循 如何在 macOS 上安装 Node.js 并创建本地开发环境如何在 Ubuntu 18.04 上安装 Node.js 如何在 Ubuntu 18.04的部分中的步骤。

  • 在本教程中,您将与 Nodejs 中的缓冲器进行交互。 REPL (阅读评估打印循环) 如果您想要对 Nodejs 如何有效地使用 Nodejs REPL 进行更新,您可以阅读我们关于 如何在 Ubuntu 18.04 上使用 Node.js REPL的指南。 *对于本文,我们希望用户能够熟悉基本的 JavaScript 及其类型。

步骤 1 - 创建一个缓冲器

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

要决定使用哪种方法,您需要回答以下问题:您想要创建新的缓冲器或从现有数据中提取缓冲器吗? 如果您要将数据存储在您尚未接收的内存中,您需要创建新的缓冲器。

让我们打开 Node.js REPL,以便我们自己看到。 在您的终端中,输入节点命令:

1node

您将看到``开始的提示。

alloc()函数将缓冲器的大小作为其第一个和唯一所需的参数。大小是代表缓冲器对象将使用多少字节内存的整数.例如,如果我们想创建一个缓冲器,其大小为1KB(kilobyte),相当于1024字节,我们会在控制台中输入:

1const firstBuf = Buffer.alloc(1024);

为了创建一个新的缓冲,我们使用了全球可用的缓冲类,该类有alloc()方法. 通过提供1024作为alloc()的参数,我们创建了一个大小为 1KB 的缓冲。

默认情况下,当您用alloc()初始化缓冲器时,缓冲器将以二进制零件作为后续数据的位置保持器填充,但是,如果我们想要,我们可以更改默认值。

在您的终端中,在填写 `1 的 REPL 提示中创建一个新的缓冲:

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

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

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

计算机知道如何以不同的方式处理它们,因为字节是不同的编码。 字节编码是字节的格式。 Node.js 中的缓冲器默认使用 UTF-8编码方案,如果它是与字符串数据初始化的。 UTF-8 中的一个字节代表一个数字,一个字母(英语和其他语言)或一个符号。 UTF-8 是 ASCII的超组,美国信息交换标准代码。

如果我们正在编写一个只能使用ASCII字符的工作程序,我们可以用alloc()函数的第三个参数编码来改变我们的缓冲器使用的编码。

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

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

缓冲器以a字符的五个字节进行初始化,使用ASCII表示。

<$>[注] **注:默认情况下,Node.js 支持以下字符编码:

  • ASCII, represented as ascii
  • UTF-8, represented as utf-8 or utf8
  • UTF-16, represented as utf-16le or utf16le
  • UCS-2, represented as ucs-2 or ucs2
  • Base64, represented as base64
  • Hexadecimal, represented as hex
  • ISO/IEC 8859-1, represented as latin1 or binary

所有这些值都可以用于接受编码参数的缓冲符类函数,因此,这些值都适用于alloc()方法 <$>

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

要从现有数据创建缓冲器,我们使用 from() 方法,我们可以使用该函数创建缓冲器从:

  • 整数的数组:整数值可在 0 和 255 之间。
  • 一个 ArrayBuffer:这是一个存储固定字节长度的 JavaScript 对象
  • 一个字符串
  • 另一个缓冲
  • 其他具有 Symbol.toPrimitive 属性的 JavaScript 对象告诉 JavaScript 如何将对象转换为原始数据类型: boolean, null, undefined, number, stringsymbol

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

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

现在我们从字符串我的名字是保罗中创建了一个缓冲对象,让我们从我们之前创建的另一个缓冲中创建一个新的缓冲:

1const asciiCopy = Buffer.from(asciiBuf);

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

现在,我们已经有创建缓冲器的经验,我们可以深入研究阅读他们的数据的例子。

步骤 2 – 从缓冲器中阅读

我们可以访问缓冲中的一个个别字节,或者我们可以提取整个内容。

为了访问一个缓冲器的一个字节,我们通过我们想要的字节的索引或位置。 缓冲器将数据以序列的方式存储,就像数组一样。

让我们看看通过在 REPL 中创建一个字符串的缓冲器会是什么样子:

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

现在让我们阅读缓冲器的第一个字节:

1hiBuf[0];

當您按下「ENTER」, REPL 會顯示:

1[secondary_label Output]
272

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

<$>[注] **注:字节的值可以是0255之间的数字。一个字节是8位的序列。一个字节是二进制的,因此只能有两个值中的一个:01。如果我们有一个8位的序列和每位的两个可能的值,那么我们每位最多有28个可能的值。

让我们对第二个字节做同样的事情,在 REPL 中输入以下内容:

1hiBuf[1];

REPL 返回105,表示i

最后,让我们来看看第三个角色:

1hiBuf[2];

您将看到33显示在 REPL 中,它与! 相符。

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

1hiBuf[3];

Repl 将返回:

1[secondary_label Output]
2undefined

这就像我们试图访问具有错误索引的数组中的一个元素一样。

现在我们已经看到如何读取缓冲器的个别字节,让我们看看我们一次检索缓冲器中存储的所有数据的选项。

正如它的名字所暗示的那样,toString()方法将缓冲符串的字节转换为字符串,并将其返回给用户。如果我们在hiBuf上使用这种方法,我们将得到字符串Hi!

在快点中,进入:

1hiBuf.toString();

Repl 将返回:

1[secondary_label Output]
2'Hi!'

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

让我们创建一个新的,空的缓冲器,它是10字节大的:

1const tenZeroes = Buffer.alloc(10);

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

1tenZeroes.toString();

我们将看到以下结果:

1'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'

u0000字符串是NULL的Unicode字符串,它对应于数字0。当缓冲器的数据不被编码为字符串时,toString()方法会返回字节的UTF-8编码。

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

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

1hiBuf.toString('hex');

该声明将评估:

1[secondary_label Output]
2'486921'

486921是代表字节字符串Hi!的六进制表示。在Node.js中,当用户想要将数据的编码从一个表格转换到另一个表格时,他们通常会将字符串放入缓冲器中,并用他们想要的编码来调用toString()

toJSON()方法的行为不同,无论缓冲器是由字符串构成的还是不是,它总是返回数据作为字节的整数表示。

让我们重新使用hiBuftenZeroes缓冲器来练习使用toJSON()

1hiBuf.toJSON();

Repl 将返回:

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

JSON 对象有一个类型属性,它将始终是缓冲器,所以程序可以将这些 JSON 对象与其他 JSON 对象区分开来。

数据属性包含了字节的整数表示的数组,您可能已经注意到7210533与我们单独拉取字节时所获得的值相匹配。

让我们尝试使用tenZeroestoJSON()方法:

1tenZeroes.toJSON();

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

1[secondary_label Output]
2{ type: 'Buffer', data: [
3    0, 0, 0, 0, 0,
4    0, 0, 0, 0, 0
5  ] }

类型与之前所指出的相同,但是,数据现在是一个包含十个零的数组。

现在我们已经涵盖了从缓冲器中阅读的主要方式,让我们看看如何修改缓冲器的内容。

步骤 3 – 更改缓冲器

我们可以通过多种方式修改现有缓冲对象. 类似于阅读,我们可以使用数组语法单独修改缓冲字节. 我们也可以将新内容写入缓冲器中,取代现有数据。

让我们先看看我们如何改变一个缓冲器的个别字节。回想一下我们的缓冲器变量hiBuf,其中包含字符串Hi!

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

1hiBuf[1] = 'e';

现在,让我们把这个缓冲符作为一个字符串来确认它存储了正确的数据。

1hiBuf.toString();

它将被评估为:

1[secondary_label Output]
2'H\u0000!'

我们得到了这种奇怪的输出,因为缓冲器只能接受一个整数值,我们不能将其分配给字母e;相反,我们必须分配给它的数字,其二进制等价代表e:

1hiBuf[1] = 101;

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

1hiBuf.toString();

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

1[secondary_label Output]
2'He!'

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

1hiBuf[2] = 121;

让我们再一次用ToString()方法来确认:

1hiBuf.toString();

你的 REPL 會顯示:

1[secondary_label Output]
2'Hey'

如果我们试图写出在缓冲器范围之外的字节,它将被忽略,缓冲器的内容不会改变,例如,让我们尝试将缓冲器中不存在的第四个元素设置为o:

1hiBuf[3] = 111;

我们可以通过toString()方法确认缓冲器不变:

1hiBuf.toString();

产量仍然是:

1[secondary_label Output]
2'Hey'

如果我们想要更改整个缓冲器的内容,我们可以使用write()方法,而write()方法接受一个代替缓冲器内容的字符串。

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

1hiBuf.write('Hi!');

在 REPL 中,writ()方法返回了3,这是因为它写了三个字节的数据,每个字母大小为一个字节,因为这个缓冲器使用 UTF-8 编码,该缓冲器为每个字符使用一个字节。

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

1hiBuf.toString();

REPL将生产:

1[secondary_label Output]
2'Hi!'

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

如果您尝试写出比缓冲器的大小更多的字节,缓冲器对象只会接受适合的字节。

1const petBuf = Buffer.alloc(3);

现在让我们尝试写到它:

1petBuf.write('Cats');

write()调用被评估时,REPL返回3,表示只有三个字节被写入缓冲器。

1petBuf.toString();

REPL 返回:

1[secondary_label Output]
2'Cat'

Write()函数以连续顺序添加字节,所以只有前三个字节被放置在缓冲器中。

相比之下,让我们创建一个存储四个字节的缓冲器:

1const petBuf2 = Buffer.alloc(4);

写出相同的内容:

1petBuf2.write('Cats');

然后添加一些新的内容,比原始内容占用更少的空间:

1petBuf2.write('Hi');

由于缓冲器序列写,从0开始,如果我们打印缓冲器的内容:

1petBuf2.toString();

我们会受到欢迎:

1[secondary_label Output]
2'Hits'

第一两个字符被写过,但其余的缓冲器未被触及。

有时我们想要的数据在我们现有的缓冲器中不是在一个字符串中,而是居住在另一个缓冲器对象中,在这些情况下,我们可以使用复制()函数来修改我们的缓冲器存储的东西。

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

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

wordsBufcatchphraseBuf缓冲器都包含字符串数据. 我们希望修改catchphraseBuf以存储Nananana Turtle!而不是Not sure Turtle!

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

1wordsBuf.copy(catchphraseBuf);

在这种情况下,目标参数是catchphraseBuf缓冲器。

当我们输入到 REPL 时,它会返回15,表示已经写了 15 个字节. 字符串Nananana只使用 8 个字节的数据,所以我们立刻知道我们的副本没有按计划进行。

1catchphraseBuf.toString();

REPL 返回:

1[secondary_label Output]
2'Banana Nananana!'

默认情况下,‘copy()’ 收集了‘wordsBuf’的全部内容,并将其放入‘catchphraseBuf’中。我们需要对我们的目标更有选择性,只需复制‘Nananana’。

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

复制()函数有几个参数,允许我们定制将哪些数据复制到另一个缓冲器中。

正如我们从以前的使用中所看到的那样,它是我们想要复制的缓冲器

  • targetStart - 这是我们应该开始复制的目标缓冲器中的字节的索引。 默认情况下它是 0,这意味着它复制了从缓冲器开始的数据
  • sourceStart - 这是我们应该复制的源缓冲器中的字节的索引
  • sourceEnd - 这是我们应该停止复制的源缓冲器中的字节的索引。

因此,要将NanananawordsBuf复制到catchphraseBuf,我们的目标应该是catchphraseBuf就像以前一样。

在 REPL 提示中,复制wordsBuf的内容如下:

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

REPL 确认8字节已经写入,请注意wordsBuf.length是如何用作sourceEnd参数的值。

现在让我们来看看‘catchphraseBuf’的内容:

1catchphraseBuf.toString();

REPL 返回:

1[secondary_label Output]
2'Nananana Turtle!'

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

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

1.exit

结论

在本教程中,您了解到缓冲器是存储二进制数据的内存中的固定长度分配。您首先通过在内存中定义其大小并将其初始化为现有数据创建缓冲器,然后通过检查其个别字节并使用toString()toJSON()方法来读取缓冲器的数据。

缓冲器为您提供了对二进制数据如何被 Node.js 操纵的很好的见解. 现在您可以与缓冲器进行交互,您可以观察字符编码如何影响数据存储的方式。 例如,您可以从不是 UTF-8 或 ASCII 编码的串数据创建缓冲器,并观察其大小差异。

要了解 Node.js 中的缓冲器,您可以阅读缓冲器对象上的 Node.js 文档 如果您想继续学习 Node.js,您可以返回如何在 Node.js 系列中编码(https://www.digitalocean.com/community/tutorial_series/how-to-code-in-node-js),或者在我们的 Node 主题页面上浏览编程项目和设置。

Published At
Categories with 技术
comments powered by Disqus