身为Node开发人员这些年来,你是否从未遇到过Node缓冲区(Buffer)这个概念呢?也许这个术语你见过几次,但每次都不想一探究竟?你可能的确没遇到过要使用缓冲区的场景,毕竟Node.js并不是那种要求程序员直接和程序管理内存的操作打交道的语言。但是,如果你想要成为专业Node开发人员,愿意为此付出更多的努力,那么你就必须深入探索缓冲区之类的概念,从而理解Node的底层工作机制。
本文最初发布于 livecodestram.dev 网站,经网站授权由 InfoQ 中文站翻译并分享。
初看上去,你会觉得 Node 缓冲区是一个很难理解的主题,但事实并非如此。问题在于你看过的所有在线教程上来就会创建一些 Node 缓冲区,然后开始操作它们,却没有提前解释到底这是什么东西。为了避免在这篇文章中犯同样的错误,我会首先解释什么是缓冲区。但在此之前,我们必须搞明白伴随缓冲区出现的其他一些概念。
为了正确理解缓冲区,我们应该了解二进制数据、字符编码(character encoding)和流(stream)。你可能还不明白它们和缓冲区有什么关系,先别急,搞懂它们后就能知道缓冲区是什么意思了。
什么是二进制数据?
如果你已经知道了什么是二进制数据,则可以直接跳到下一个主题。否则就请读下去,了解什么是二进制数据。
二进制数字系统是类似我们常用的十进制的另一个数字系统。十进制使用 0-9 的数表示数字,而二进制仅使用 0 和 1 表示数字。下面是二进制数的一些示例。
在计算机科学中,二进制数中的每个数字均被视为一个位。8 个位合称一个字节。那么计算机科学与二进制有什么关系?计算机使用二进制数字来存储和表示数据。因此,存储在计算机中的每种数据最终都将存储为一组二进制数。我们称这些数据为二进制数据。
为了将所有类型的数据都存储为二进制数据,计算机应该知道如何将它们转换为二进制数。计算机为实现这一目的有很多种机制,下面具体介绍。
计算机如何将数字转换为二进制数据?
将数字转换为二进制数据只是一种数学处理。十进制数字 9 可以用二进制表示为 101,其他整数也有自己的对应。计算机具备自行做这种转换的能力。
计算机如何将字符转换为二进制数据?
对这个问题的简单解释是“每个字符都有一个与之关联的唯一二进制数”。这种唯一编号称为字符的代码点或字符码。你可以在 Javascript 中使用 charCodeAt 函数来查找每个字符的字符码。
有两大标准用来为每个字符分配字符码:ASCII 和 Unicode。无论使用哪种编程语言,它们各自赋予字符的字符码都是一样的。ASCII 最多使用 7 位来表示字符,而 Unicode 最多使用 16 位。所以 Unicode 提供了比 ASCII 更大的范围,可以表示更多的字符,进而成为了最受欢迎的标准。
计算机将字符转换为二进制数据时,需要做的唯一工作就是查找每个字符的字符点吗?答案是否定的。你还需要执行一个将字符转换为二进制数据的步骤。那就是字符编码。
什么是字符编码?
我之前提到过 ASCII 最多可以使用 7 位,而 Unicode 最多可以使用 16 位来表示字符。但是计算机用不着一直使用 Unicode 的全部 16 位来表示字符。例如,字符“A”可以使用至少 7 位来表示。如果计算机用前导 0 填充二进制数,使用全部 16 位来存储“A”,就是在浪费系统资源。
这里就轮到字符编码登场了。字符编码标准决定了计算机用来表示字符的位数。UTF-8、UTF-16 和 UTF-32 就是其中一些字符编码标准。
UTF-8 使用 8 位(一字节)的块表示字符。它可以使用 1-4 个字节对所有 Unicode 字符进行编码。现在,如果计算机使用 UTF-8 标准对“A”进行编码,则存储的二进制值为 01000001,带一个前导 0。
这样就完成了将字符转换为二进制数据的过程。将字符串转换为二进制数据时,无非就是将每个字符都转换为二进制数据。计算机将图像、音频和视频数据转换为二进制数据时会用到更多的标准和方法。
现在出现了流的概念。我们来看看它又是什么。
什么是流?
流是从一处移到另一处的数据的集合。这里我们谈论的是二进制数据流,它是从一个地方移动到另一个地方的二进制数据的集合。
一条流中会包含大量数据。但是计算机不必等待流中的所有数据到位也可以开始处理它们。将流发送到某个目的地时,由于数据太多了,因此不会一次发送完流中的全部数据,而是将流分为许多较小的数据块。目标会接收这些块并归拢起来,并在有足够的块可用时开始处理它们。
接收流的目标会以某种方式处理数据——可以是读取、操作或写入数据。但是目标处的数据处理器的能力,限制了它一次可以处理的数据量的上下限。那么,当目标接收到不符合此限制的数据块时会发生什么呢?目标无法丢弃它们。然而,目标可以使用一种机制来存储接收到的块,直到它们可以被处理器处理为止。这里就引入了缓冲区的概念。但首先我们应该知道缓冲区到底是什么,才能进一步理解它们如何帮助存储数据块。
什么是缓冲区,它们要做什么?
一个缓冲区是计算机内存(通常是 RAM)中一处较小的存储空间。在目标处理器准备好处理来自流的数据块之前,缓冲区充当后者的等待区域。
如果目标从流中接收数据的速度快于其处理数据的速度,则这些多余的数据将在缓冲区中“等待”,直到处理器可以处理新的数据为止。如果目标从流中接收数据的速度慢于其处理数据的速度,换句话说,如果当前可用的块数量低于处理器可接受的最小数据量,则这些数据块将在一个缓冲区中“等待”,直到有足够数量的数据可用为止。
所以缓冲区指的就是一处等待区域,流数据在这里等待处理器,直到后者准备好处理数据为止。只要是有流存在的地方,你都会看到后台存在缓冲区来存储尚未处理的数据块。
你可能已经听说过缓冲(buffering)这个概念。当你观看 YouTube 视频时,有时视频会加载一段时间,然后才会继续播放。这是因为你的浏览器正在等待视频流的更多数据块就位。在浏览器收到足够的数据块之前,它们会存储在这些缓冲区中,并等待处理器处理。这就是“缓冲”这个名词的来历。这也正是 Node.js 中二进制流会遇到的情况。
当我们尝试在 Node 程序中读取大文件时,也会发生同样的事情。这里使用的缓冲区会存储通过文件流发送的数据块,直到有足够的数据可用,然后再将其传递给程序。此过程也称为缓冲。
但是 Node.js 如何使用缓冲区?
现在,你已经了解了缓冲区的基本概念以及为什么需要它们。但是你可能还想知道 Node 为什么需要缓冲区。
答案很简单。当你将 HTTP 请求发送到 Web 服务器时,该请求会作为 TCP 流通过网络发送,这是一个二进制数据流。因此,你构建的所有 Node 服务器都必须处理流和缓冲区。
当你使用 fs.readFile()方法读取文件时,它将通过回调或 promise 返回一个缓冲区对象。
简而言之,Node.js 中一些最重要的模块会不断处理缓冲区和缓冲区操作。你可能已经在不知不觉中用过了缓冲区。
Node.js 中的缓冲区创建和操作
Node.js 提供了一个 Buffer 类,可让你轻松创建和操作缓冲区。我们来看看用它能做什么。
这将创建一个大小为 100 的缓冲区,这意味着该缓冲区会存储 100 个字节的零。
你还可以从字符串和整数数组创建缓冲区。
你可以使用索引访问缓冲区中的每个字节。
现在我们来看看如何写入缓冲区。
write 方法将覆盖缓冲区中的现有内容,并将其更改为你提供的值。
你可以查看 Node.js 文档(https://nodejs.org/api/buffer.html),了解还可以使用缓冲区做些什么事情。
小结
如你在本文中所见,缓冲区对于 Node.js 的工作机制而言至关重要。了解这些概念可以帮助你成为更优秀的 Node 开发人员。这些知识可帮助你编写优化的 Node 程序,并了解这种语言的局限性,知道如何解决它们。因此,下次你遇到与 Node.js 相关的令人生畏的术语时,请不要犹豫,应该像对待缓冲区一样直接面对它。
原文链接:https://livecodestream.dev/post/2020-06-06-a-complete-introduction-to-node-buffers
评论 1 条评论