彻底理解Node.js中的Buffer
发布于 5 年前 作者 lvwxx 3770 次浏览 来自 分享

彻底理解Node.js中的Buffer

每当在Node.js中遇到Buffer,Streambinary data之类的单词时,是否总是像我一样感到困惑? 认为它们并不是常用的,而只适合Node.js专家和包开发人员去使用。

实际上,这些单词是非常重要的,尤其对于用Node.js进行web开发而没有任何CS学位的人员。

当然,如果你选择继续做一个普通的Node.js开发人员,你可能永远不会直接使用它们。但是如果你想对Node.js的理解提升到下一个级别,那么你确实需要更深入地了解Node的许多核心特性。比如Buffer。这正是我写这篇文章的目的——帮助我们揭开其中一些特性的神秘面纱,并将Node.js的学习带到下一个层次。

在开始前,先看一下Node.js官方文档对Buffer的说明

… mechanism for reading or manipulating streams of binary data. The Buffer class was introduced as part of the Node.js API to make it possible to interact with octet streams in the context of things like TCP streams and file system operations.

让我们用简单易懂的语言来重新描述它:

Buffer类作为Node.js API的一部分引入,使操作二进制数据流或与之交互成为可能。

接下来我们更深入的去了解Buffer,streams,binary data。

什么是二进制数据

你可能已经知道计算机以二进制文件存储和表示数据。二进制就是1和0的集合。例如,下面是5个不同的二进制文件,5组不同的1和0:

10, 01, 001, 1110, 00101011

二进制中每个1和0都称为Bit,这是二进制数字的一种简短形式。

为了存储或表示一段数据,计算机需要将该数据转换为二进制表示。例如,要存储数字12,计算机需要将12转换成二进制表示,即1100。

但是在工作中,number并不是唯一的数据类型。通常上还会有string,images,videos。计算机知道如何用二进制表示所有的数据类型。比如计算机如何用二进制表示string类型的“L”呢?要将任何字符存储在二进制文件中,计算机首先将该字符转换为数字,然后将该数字转换为二进制表示形式。对于字符串“L”,计算机首先将L转换成表示L的数字。

打开浏览器控制台,输入“L”. charcodeat(0)。这时控制台会显示出数字76,这是字符“L”的数字表示。但是计算机又是如何知道每个字符表示的确切数字呢?它怎么知道用76来表示L?

字符集

字符集已经定义好的表示每个字符的确切数字的规则。我们对这些规则有不同的定义,最流行的包括Unicode和ASCII。JavaScript可以很好地处理Unicode字符集。所以,浏览器中的Unicode规定76应该表示L

我们已经看到计算机是如何用数字表示字符的。转换成数字之后计算机再把76转换它的二进制表示。

字符编码

正如有一些字符集规则定义数字应该怎么样表示字符一样,也有一些规则定义了数字应该如何在二进制文件中表示。具体来说,就是用多少位来表示数字。这叫做字符编码。

字符编码的一个规则是UTF-8。UTF-8声明字符应该以bytes编码。一个byte是8位(bit)的集合 —— 8个1和0。因此,UTF-8规定应该使用8个1和0来表示二进制中任何字符。

之前的例子提到,数字12用二进制表示为 1100,但是用UTF-8表示应该是8位才对。所以UTF-8规定,计算机需要在不满8位的二进制数字左边添加更多的位,以使其成为一个字节。所以12应该存储为00001100

因此 76 在UTF-8规则下存储表示为:01001100

这就是计算机在二进制文件中存储字符串或字符的方式。同样,计算机也规定了图片和视频应该如何转换、编码和存储在二进制文件中的规则。计算机将所有数据类型存储在二进制文件中。

现在我们了解了什么是二进制数据,接下来我们介绍一下什么是二进制数据流。

js中的Stream只是表示随着时间的推移从一个点移动到另一个点的数据序列。整个概念是,你有大量的数据要处理,但是你不需要等到所有的数据都可用后才开始处理它。基本上,这个大数据被分解并以块的形式发送。因此,从Buffer的原始定义来看,这仅仅意味着二进制数据正在文件系统中移动。例如,将存储在file1.txt中的文本移动到file2.txt。

但是Buffer究竟如何帮助我们在流与二进制数据进行交互或操作呢?Buffer到底是什么?

Buffer

我们已经提到,数据流是数据从一个点移动到另一个点,但是它们究竟是如何移动的呢?

通常数据的移动是为了处理或读取数据,并根据数据做出决策。在这个过程中,可能需要数据到达一个最小量或者最大量才能进行处理。因此,如果数据到达的速度快于进程消耗数据的速度,那么多余的数据需要在某个地方的等待来处理。另一方面,如果进程消耗数据的速度快于数据到达的速度,那么早到达的少数数据需要等待一定数量的数据到达,然后再发送出去进行处理。

那个“等候区”就是Buffer!它是计算机中的一个小物理位置,通常位于RAM中,数据在RAM中被临时收集、等待,并最终发在流过程中送出去进行处理。

我们可以把整个stream和buffer过程看做一个汽车站。在某个汽车站,汽车直到有一定数量的乘客或者是一个特殊的时间才可以发车。此外,乘客可能在不同的时间以不同的速度到达。无论是旅客还是汽车站都不能控制旅客到达车站的时间。提前到达的乘客需要等汽车发车。当有些乘客到达时,乘客已经满员或者汽车已经离开,需要等待下一辆汽车。

无论什么情况,总有一个等待的地方。这就是Node.js的Buffer! js不能控制数据到达的速度或时间,也不能控制流的速度。它只能决定何时发送数据。如果还没有到时间,Node.js将把它们放在buffer中,即RAM中的一个小位置,直到将它们发送出去进行处理为止。

一个典型的例子是,当你在观看流媒体视频时,可以看到buffer在工作。如果你的互联网连接足够快,流的速度将足够快,可以立即填满Buffer并发送出去进行处理,然后再填入另一个Buffer,然后发送出去,再发送一个,再发送一个,直到流完成为止。

但是如果你的连接很慢,在处理了第一组到达的数据后,视频会被卡主,这意味着程序正在收集更多的数据,或者等待更多的数据到达。当buffer被填满并处理后,播放器会继续播放视频。在播放的同时,更多的数据将继续到达并在buffer中等待。

与Buffer交互

Node.js在处理流期间会自动创建buffer,我们也可以通过Nodejs提供的API自己创建buffer。根据你的需求,这里有几种不同的方法可以创建buffer。

// Create an empty buffer of size 10.
// A buffer that only can accommodate 10 bytes
const buf1 = Buffer.alloc(10)

// Create a buffer with content
const buf2 = Buffer.from("hello buffer")

当创建成功buffer后,你就可以开始和它进行交互了。

// 查看buffer的结构
buf1.toJSON()
// { type: 'Buffer', data: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }

buf2.toJSON()
//{ type: 'Buffer',data: [ 104, 101, 108, 108, 111, 32, 98, 117, 102, 102, 101, 114 ]}

buf1.length // 10
buf2.length // 12

// 写操作
buf1.write("Buffer really rocks!")

// decode
buf1.toString() // 'Buffer rea'
// 因为buf1创建时只分配了10byte的空间。超过的将不会被存储。

更多的交互API,可以查看官方文档

希望这篇介绍能帮助您更好地理解Node.js Buffer。

回到顶部