NodeJs中的Buffer缓冲

Buffer,官方是这么说的:

JavaScript 语言没有读取或操作二进制数据流的机制。 Buffer 类被引入作为 Node.js API 的一部分,使其可以在 TCP 流或文件系统操作等场景中处理二进制数据流。

JavaScript 起初为浏览器而设计,没有读取或操作二进制数据流的机制。Buffer类的引入,则让NodeJS拥有操作文件流或网络二进制流的能力,它用来创建一个专门存放二进制数据的缓存区。

在使用Node.js做服务端开发时,http、tcp、udp、文件io等等类型的操作,都会用到Buffer。

Buffer 对象的内存分配不是在V8的堆内存中,而是Node在C++层面进行内存申请,可以理解为在内存中单独开辟了一部分空间,但是使用时分配内存则是由Node层面完成的,释放也是由Node中v8的gc机制自动控制。

Buffer代表一个缓冲区,存储二进制数据,是字节流。我们在网络传输时,就传输的这种字节流。写文件时,也是写的字节流。

我们知道数据流(stream of data)是从一个地方向另一个地方传输数据的过程,但是这个具体是怎么样的一个过程?

通常情况下,我们传输数据往往是为了处理它,或者读它,或者基于这些数据做处理等。但是,在每次传输过程中,有一个数据量的问题。因此当数据到达的时间比数据理出的时间快的时候,这个时候我们处理数据就需要等待了。

如果处理数据的时间比到达的时间快,这一时刻仅仅到达了一小部分数据,那这小部分数据需要等待剩下的数据填满,然后再送过去统一处理。

这个”等待区域”就是buffer! 它是你电脑上的一个很小的物理地址,一般在RAM中,在这里数据暂时的存储、等待,最后在流(stream)中,发送过去并处理。

我们可以把整个流(stream)和buffer的配合过程看作公交站。在一些公交站,公车在没有装满乘客前是不会发车的,或者在特定的时刻才会发车。当然,乘客也可能在不同的时间,人流量大小也会有所不同,有人多的时候,有人少的时候,乘客或公交站都无法控制人流量。

不论何时,早到的乘客都必须等待,直到公车接到指令可以发车。当乘客到站,发现公车已经装满,或者已经开走,他就必须等待下一班车次。

总之,这里总会有一个等待的地方,这个等待的区域就是Node.js中的Buffer Node.js不能控制数据什么时候传输过来,传输速度,就好像公交车站无法控制人流量一样。他只能决定什么时候发送数据。如果时间还不到,那么Node.js就会把数据放入buffer–”等待区域”中,一个在RAM中的地址,直到把他们发送出去进行处理。

一个关于buffer很典型的例子,就是你在线看视频的时候。如果你的网络足够快,数据流(stream)就可以足够快,可以让buffer迅速填满然后发送和处理,然后处理另一个,再发送,再另一个,再发送,然后整个stream完成。

但是当你网络连接很慢,当处理完当前的数据后,你的播放器就会暂停,或出现”缓冲”(buffer)字样,意思是正在收集更多的数据,或者等待更多的数据到来,才能下一步处理。当buffer装满并处理好,播放器就会显示数据,也就是播放视频了。在播放当前内容的时候,更多的数据也会源源不断的传输、到达和在buffer等待。

如果播放器已经处理完或播放完前一个数据,buffer仍然没有填满,”buffering”(缓冲)字符就会再次出现,等待和收集更多的数据。

在Node.js里,Buffer在转换为字符串时,toString方法的第一个参数就是编码类型,支持常见的编码格式:

  • utf8,多字节编码的Unicode字符,大多数文档和网页采用这种编码格式
  • ascii,8bit编码,一个字符占1个字节
  • utf16le,小端编码的unicode字符
  • utf16be,大端编码的unicode
  • ucs2,unicode编码,每个字符占两个字节
  • base64,Base-64字符串编码
  • hex,每个字节编码为两个十六进制字符

假如你不确认某个编码格式是否正确,可以使用Buffer.isEncoding(encoding)方法来测试。

在使用Buffer的toString方法时,如果你不指定编码格式,则默认使用utf8来转换。toString原型:

buf.toString([encoding][, start][, end])

第一个参数是编码格式,第二个是开始位置(0到buf.length-1),第三个是结束位置(不包含这个索引位置的数据)。

创建一个Buffer实例

使用new操作符,有四种方法创建一个Buffer实例:

new Buffer(size),创建一个指buffer定大小的buffer
new Buffer(array),根据一个字节数组来创建一个buffer
new Buffer(str[,encoding]),根据一个字符串和编码格式创建buffer,不指定编码时默认使用utf8
new Buffer(buffer),根据buffer实例创建一个新的buffer

比如下面的代码可以创建Buffer的实例:

var buf1 = new Buffer(256);
var buf2 = new Buffer("Hello Buffer");
var buf3 = new Buffer([0x65,0x66,0x67]);
var buf4 = new Buffer(buf2);

有一点需要说明,使用new Buffer(size)分配的缓冲区,是未初始化的哦。那块内存里,可能是脏的,什么玩意儿都有。试试下面的代码:

var buf1 = new Buffer(256);
buf1.write('abc');
console.log("buf1\'s content: ", buf1.toString());

上面的代码企图使用toString转换Buffer,可是你会看到控制台输出了很多乱码……修改一下,使用buf.fill()方法填充一下就好了:

var buf1 = new Buffer(256);
buf1.fill(0);
buf1.write('abc');
console.log("buf1\'s content: ", buf1.toString());

往缓冲区写数据

write的原型如下:

buf.write(string[, offset][, length][, encoding])

buf.write用来向缓冲区中写入一个字符串,返回实际写入的字节数。参数含义如下:

  • string,待写入的字符串对象
  • offset,缓冲区偏移量,指定的话就从这个位置开始写入,不指定就默认为0
  • length,要写入的字节数
  • encoding,代谢如字符串的编码格式,默认为utf8 

从缓冲区读数据

buf.toString其实是一种读数据的方式。当然,还有其它的,比如buf[index]可以根据下标读取字节,buf.readIntXXX,buf.readUIntXXX……

buf.toJSON()可以把一个Buffer对象转换为JSON格式。当你针对一个Buffer对象调用JSON.stringify方法时,buf.toJSON()就会被调用。比如:

var buf = new Buffer('test');
var json = JSON.stringify(buf);

console.log(json);
// '{"type":"Buffer","data":[116,101,115,116]}'

缓冲区的长度

一个Buffer对象的大小,在创建时就固定下来,创建之后不可改变。嘿嘿,这可是容易引起误解的,尤其是你觉得Buffer里保存的是字符串时。试试下面的代码有助于理解这一点:

var buf1 = new Buffer(256);
buf1.fill(0);
buf1.write('abc');
console.log("buf1\'s length - %d, not 3\n", buf1.length);
buf1.write('abcdef');
console.log("buf1\'s length - %d, not 6\n", buf1.length);

另外当你要从确定字符串在缓冲区中占用的字节长度时,不能使用字符串的length属性,因为String.length返回的是字符长度。而对于采用UTF8等编码格式编码的字符串,一个字符可能占用多个字节。所以,String.length所代表的字符串长度和字节长度就不一致。注意,Buffer.length返回的不是缓冲区的字节长度,而且是创建时的那个长度,不会随着缓冲内容变化而变化。

要想衡量一个字符串占用的字节长度,可以使用Buffer.byteLength(string[,encoding])这个方法,它会测量一个字符串在指定编码格式下占用的字节长度。

var name = new String('who is \u5F20\u4E09\u4E30?');
console.log('name.length = %d', name.length);
console.log('byteLength = %d', Buffer.byteLength(name, 'utf8'));

缓冲区操作

Buffer还支持切片、拷贝、拼接、比较等操作。

buf.slice([start[, end]])可以根据起止位置(不包含结束位置对应的数据)对一个缓冲区进行切片,返回一个新的Buffer对象,方便我们操作缓冲区的某个区域。但值得注意的是,这个切片是对原有缓冲区的引用,而不是副本,你对切片内容的修改,实际上修改的是原始的缓冲区。这个方法返回一个代表切片的Buffer对象。

buf.copy(targetBuffer[, targetStart][, sourceStart][, sourceEnd])可以将一个缓冲区指定区域的内容拷贝到另一个缓冲的指定区域。类似C语言里的memcpy。targetStart指定目标缓冲区的起始偏移,sourceStart指定源缓冲区的起始偏移,它们默认都是0;sourceEnd指定源缓冲区的结束位置,默认是源缓冲区的长度。实际复制时,会比较目标缓冲区的长度和待复制区域的长度,哪个小按哪个来,不会越界。

buf.concat(list[,totalLength]),可以将一串缓冲区拼接成一个。第一个参数list是一个缓冲区数组,第二是待拼接的缓冲区的总长度。如果你不提供totalLength,concat会自己遍历list中的缓冲区计算总长度,会有一点性能损失。这个方法返回拼接后的缓冲区。

看下面的示例代码:

var buf1 = new Buffer('1234');
var buf2 = new Buffer('12567');
var bufList = [buf1, buf2];
var buf3 = Buffer.concat(bufList);
console.log('buf3 - %s', buf3.toString());
var buf4 = buf3.slice(3, 8);
console.log('buf4 - %s', buf4.toString());
var buf5 = new Buffer(5);
buf3.copy(buf5, 0, 1);
console.log('buf5 - %s', buf5.toString());

buf.equals(otherBuffer)判断当前缓冲区是否和另一个相等,相等时返回true。

buf.compare(otherBuffer)比较当前缓冲区和另一个缓冲区的大小,相等返回0,小于返回-1,大于返回1。

看下面的示例代码:

var buf1 = new Buffer('1234');
var buf2 = new Buffer('12567');
var buf3 = new Buffer('1234');
var buf4 = new Buffer('0123');
console.log('buf1.compare(buf2) = ', buf1.compare(buf2));
console.log('buf1.compare(buf3) = ', buf1.compare(buf3));
console.log('buf1.compare(buf4) = ', buf1.compare(buf4));

 

posted @ 2022-06-10 18:12  李小菜丶  阅读(547)  评论(0编辑  收藏  举报