NodeJS中Buffer模块详解
⼀,开篇分析
所谓缓冲区Buffer,就是 "临时存贮区" 的意思,是暂时存放输⼊输出数据的⼀段内存。
JS语⾔⾃⾝只有字符串数据类型,没有⼆进制数据类型,因此NodeJS提供了⼀个与String对等的全局构造函数Buffer来提供对⼆进制数据的操作。除了可以读取⽂件得到Buffer的实例外,还能够直接构造,例如:
复制代码代码如下:
 var buffer = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]) ;
Buffer与字符串类似,除了可以⽤.length属性得到字节长度外,还可以⽤[index]⽅式读取指定位置的字节,例如:
复制代码代码如下:
buffer[0] ; // 0x68;
Buffer与字符串能够互相转化,例如可以使⽤指定编码将⼆进制数据转化为字符串:
复制代码代码如下:
var str = String("utf-8");  // hello
将字符串转换为指定编码下的⼆进制数据:
复制代码代码如下:
var buffer= new Buffer("hello", "utf-8") ; // <Buffer 68 65 6c 6c 6f>
⼀点⼉区别:
Buffer与字符串有⼀个重要区别。字符串是只读的,并且对字符串的任何修改得到的都是⼀个新字符串,原字符串保持不变。⾄于Buffer,更像是可以做指针操作的C语⾔数组。例如,可以⽤[index]⽅式直接修改某个位置的字节。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
slice⽅法也不是返回⼀个新的Buffer,⽽更像是返回了指向原Buffer中间的某个位置的指针,如下所⽰。
[ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]
^          ^
|          |
bin    bin.slice(2)
因此对slice⽅法返回的Buffer的修改会作⽤于原Buffer,例如:
复制代码代码如下:
var buffer= new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]) ;
var sub = bin.slice(2) ;
sub[0] = 0x65 ;
console.log(buffer) ; //  <Buffer 68 65 65 6c 6f>
如果想要拷贝⼀份Buffer,得⾸先创建⼀个新的Buffer,并通过.copy⽅法把原Buffer中的数据复制过去。
这个类似于申请⼀块新的内存,并把已有内存中的数据复制过去。以下是⼀个例⼦。
复制代码代码如下:
var buffer= new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]) ;
var dup = new Buffer(bin.length) ;
dup[0] = 0x48 ;
console.log(buffer) ;  // <Buffer 68 65 6c 6c 6f>
console.log(dup) ;  // <Buffer 48 65 65 6c 6f>
总之,Buffer将JS的数据处理能⼒从字符串扩展到了任意⼆进制数据。
以上简单让⼤家了解⼀下什么是Buffer,下⾯具体说说如何使⽤和具体使⽤场景。
⼆,聊聊Buffer
JavaScript对字符串处理⼗分友好,⽆论是宽字节还是单字节字符串,都被认为是⼀个字符串。Node中需要处理⽹络协议、操作数据库、处理图⽚、⽂件上传等,还需要处理⼤量⼆进制数据,⾃带的字符串远不能满⾜这些要求,因此Buffer应运⽽⽣。
Buffer结构
Buffer是⼀个典型的Javascript和C++结合的模块,性能相关部分⽤C++实现,⾮性能相关部分⽤javascript实现。
Node在进程启动时Buffer就已经加装进⼊内存,并将其放⼊全局对象,因此⽆需require
Buffer对象:类似于数组,其元素是16进制的两位数。
Buffer内存分配
Buffer对象的内存分配不是在V8的堆内存中,在Node的C++层⾯实现内存的申请。
为了⾼效的使⽤申请来得内存,Node中采⽤slab分配机制,slab是⼀种动态内存管理机制,应⽤各种*nix操作系统。slab有三种状态:
(1) full:完全分配状态
(2) partial:部分分配状态
(3) empty:没有被分配状态
Buffer的转换
Buffer对象可以和字符串相互转换,⽀持的编码类型如下:
ASCII、UTF-8、UTF-16LE/UCS-2、Base64、Binary、Hex
字符串转Buffer
new Buffer(str, [encoding]),默认UTF-8
buf.write(string, [offset], [length], [encoding])
Buffer转字符串
Buffer不⽀持的编码类型
通过Buffer.isEncoding(encoding)判断是否⽀持
iconv-lite:纯JavaScript实现,更轻量,性能更好⽆需C++到javascript的转换
iconv:调⽤C++的libiconv库完成
Buffer的拼接
注意 "('data', function(chunk) {})",其中的参数chunk是Buffer对象,直接⽤+拼接会⾃动转换为字符串,对于宽字节字符可能会导致乱码产⽣,
解决⽅法:
js的基本数据类型
(1) 通过可读流中的setEncoding()⽅法,该⽅法可以让data事件传递不再是Buffer对象,⽽是编码后的字符串,其内部使⽤了StringEncoder模块。
(2) 将Buffer对象暂存到数组中,最后在组装成⼀个⼤Buffer让后编码转换为字符串输出。
Buffer在⽂件I/O和⽹络I/O中⼴泛应⽤,其性能举⾜轻重,⽐普通字符串性能要⾼出很多。
Buffer的使⽤除了与字符串的转换有性能损耗外,在⽂件读取时候,有⼀个highWaterMark设置对性能影
响⾄关重要。
a,highWaterMark设置对Buffer内存的分配和使⽤有⼀定影响。
b, highWaterMark设置过⼩,可能导致系统调⽤次数过多。
什么时候该⽤buffer,什么时候不该⽤  ------ 纯粹的javascript⽀持unicode码⽽对⼆进制不是很⽀持,当解决TCP流或者⽂件流的时候,处理流是有必要的,我们保存⾮utf-8字符串,2进制等等其他格式的时候,我们就必须得使⽤ ”Buffer“ 。
三,实例引⼊
复制代码代码如下:
var buf = new Buffer("this is text concat test !") ,str = "this is text concat test !" ;
console.time("buffer concat test !");
var list = [] ;
var len = 100000 * buf.length ;
for(var i=0;i<100000;i++){
list.push(buf) ;
len += buf.length ;
}
var s1 = at(list, len).toString() ;
console.timeEnd("buffer concat test !") ;
console.time("string concat test !") ;
var list = [] ;
for (var i = 100000; i >= 0; i--) {
list.push(str) ;
}
var s2 = list.join("") ;
console.timeEnd("string concat test !") ;
以下是运⾏结果:
读取速度肯定string更快,buffer还需要toString()的操作。所以我们在保存字符串的时候,该⽤string还是要⽤string,就算⼤字符串拼接string的速度也不会⽐buffer慢。
那什么时候我们⼜需要⽤buffer呢?没办法的时候,当我们保存⾮utf-8字符串,2进制等等其他格式的时候,我们就必须得使⽤了。
四,总结⼀下
(1),JavaScript适合处理Unicode编码数据,但对⼆进制数据的处理并不友好。
(2),所以处理TCP流或⽂件系统时,对⼋位字节流的处理很有必要。
(3),Node有⼏个⽤于处理,创建和消耗⼋位字节流的⽅法。
(4),原始数据存放在⼀个Buffer实例中,⼀个Buffer类似⼀个整数数组,但是它的内存,分配在V8堆栈外。⼀个Buffer的⼤⼩是不能更改的。
(5),处理的编码类型有:ascii,utf8,utf16le,ucs2(utf16le的别名),base64,binary,hex。
(6),Buffer为全局元素,直接new Buffer()就得到⼀个Buffer实例。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。