详解nodeJS之二进制buffer对象
前面的话
在ES6引入TypedArray之前,JavaScript语言没有读取或操作二进制数据流的机制。Buffer类被引入作为Nodejs的API的一部分,使其可以在TCP流和文件系统操作等场景中处理二进制数据流。现在TypedArray已经被添加进ES6中,Buffer类以一种更优与更适合Node.js用例的方式实现了Uint8Array。本文将详细介绍buffer对象
概述
由于应用场景不同,在Node中,应用需要处理网络协议、操作数据库、处理图片、接收上传文件等,在网络流和文件的操作中,还要处理大量二进制数据,JavaScript自有的字符串远远不能满足这些需求,于是Buffer对象应运而生
Buffer是一个典型的JavaScript与C++结合的模块,它将性能相关部分用C++实现,将非性能相关的部分用JavaScript实现。Buffer类的实例类似于整数数组,除了其是大小固定的、且在V8堆外分配物理内存。Buffer的大小在其创建时就已确定,且不能调整大小
由于Buffer太过常见,Node在进程启动时就已经加载了它,并将其放在全局对象(global)上。所以在使用Buffer时,无须通过require()即可直接使用
/* {[Function:Buffer] poolSize:8192, from:[Function], alloc:[Function], allocUnsafe:[Function], allocUnsafeSlow:[Function], isBuffer:[Function:isBuffer], compare:[Function:compare], isEncoding:[Function], concat:[Function], byteLength:[Function:byteLength]} */ console.log(Buffer);
创建
在Node.jsv6之前的版本中,Buffer实例是通过Buffer构造函数创建的,它根据提供的参数返回不同的Buffer,而新版本的nodejs则提供了对应的方法
1、newBuffer(size)。传一个数值作为第一个参数给Buffer()(如newBuffer(10)),则分配一个指定大小的新建的Buffer对象
分配给这种Buffer实例的内存是未初始化的(没有用0填充)。虽然这样的设计使得内存的分配非常快,但已分配的内存段可能包含潜在的敏感旧数据
这种Buffer实例必须手动地被初始化,可以使用buf.fill(0)或写满这个Buffer。虽然这种行为是为了提高性能而有意为之的,但开发经验表明,创建一个快速但未初始化的Buffer与创建一个慢点但更安全的Buffer之间需要有更明确的区分
varbuf=newBuffer(5); console.log(buf);//buf.fill(0); console.log(buf);//
[注意]当我们为一个Buffer对象分配空间大小后,其长度就是固定的,不能更改
varbuf=newBuffer(5); console.log(buf);//buf[0]=1; console.log(buf);// buf[10]=1; console.log(buf);//
【Buffer.allocUnsafe(size)】
在新版本中,由Buffer.allocUnsafe(size)方法替代,来分配一个大小为size字节的新建的没有用0填充的Buffer。可以使用buf.fill(0)初始化Buffer实例为0
varbuf=Buffer.allocUnsafe(10); console.log(buf);//buf.fill(0); console.log(buf);//
【Buffer.alloc(size[,fill[,encoding]])】
在新版本中,使用Buffer.alloc(size)方法可以生成一个安全的buffer对象,参数size
分配一个大小为size字节的新建的Buffer。如果fill为undefined,则该Buffer会用0填充
varbuf=Buffer.alloc(5); console.log(buf);//
2、newBuffer(array或buffer)。传一个数组或Buffer作为第一个参数,则将所传对象的数据拷贝到Buffer
varbuf1=newBuffer([1,2,3,4,5]); console.log(buf1);//varbuf2=newBuffer(buf1); console.log(buf2);//
【Buffer.from(array或buffer)】
在新版本中,由Buffer.from(array或buffer)方法替代
varbuf1=Buffer.from([1,2,3,4,5]); console.log(buf1);//varbuf2=Buffer.from(buf1); console.log(buf2);//
3、newBuffer(string[,encoding])。第一个参数是字符串,第二个参数是编码方式,默认是'utf-8'
varbuf1=newBuffer('thisisatést'); console.log(buf1.toString());//thisisatést console.log(buf1.toString('ascii'));//thisisatC)st varbuf2=newBuffer('7468697320697320612074c3a97374','hex'); console.log(buf2.toString());//thisisatést
Node.js目前支持的字符编码包括:
'ascii'-仅支持7位ASCII数据。如果设置去掉高位的话,这种编码是非常快的。 'utf8'-多字节编码的Unicode字符。许多网页和其他文档格式都使用UTF-8。 'utf16le'-2或4个字节,小字节序编码的Unicode字符。支持代理对(U+10000至U+10FFFF)。 'ucs2'-'utf16le'的别名。 'base64'-Base64编码。当从字符串创建Buffer时,这种编码可接受“URL与文件名安全字母表”。 'latin1'-一种把Buffer编码成一字节编码的字符串的方式。 'binary'-'latin1'的别名。 'hex'-将每个字节编码为两个十六进制字符。
【Buffer.from(string[,encoding])】
在新版本中,由Buffer.from(string[,encoding]方法替代
varbuf1=Buffer.from('thisisatést'); console.log(buf1.toString());//thisisatést console.log(buf1.toString('ascii'));//thisisatC)st varbuf2=Buffer.from('7468697320697320612074c3a97374','hex'); console.log(buf2.toString());//thisisatést
4、newBuffer(arrayBuffer[,byteOffset[,length]])。参数arrayBuffer
vararr=newUint16Array(2); arr[0]=5000; arr[1]=4000; varbuf=newBuffer(arr.buffer); console.log(buf);//arr[1]=6000; console.log(buf);//
【Buffer.from(arrayBuffer[,byteOffset[,length]])】
在新版本中,由Buffer.from(arrayBuffer[,byteOffset[,length]])方法替代
vararr=newUint16Array(2); arr[0]=5000; arr[1]=4000; varbuf=Buffer.from(arr.buffer); console.log(buf);//arr[1]=6000; console.log(buf);//
类数组
Buffer对象类似于数组,它的元素为16进制的两位数,即0到255的数值
console.log(Buffer.from('test'));//
【长度】
不同编码的字符串占用的元素个数各不相同,中文字在UTF-8编码下占用3个元素,字母和半角标点符号占用1个元素
varbuf=Buffer.from('match'); console.log(buf.length);//5 varbuf=Buffer.from('火柴'); console.log(buf.length);//6
【下标】
Buffer受Array类型的影响很大,可以访问length属性得到长度,也可以通过下标访问元素
varbuf=Buffer.alloc(10); console.log(buf.length);//=>10
上述代码分配了一个长10字节的Buffer对象。我们可以通过下标对它进行赋值
buf[0]=100; console.log(buf[0]);//=>100
要注意的是,给元素的赋值如果小于0,就将该值逐次加256,直到得到一个0到255之间的整数。如果得到的数值大于255,就逐次减256,直到得到0~255区间内的数值。如果是小数,舍弃小数部分,只保留整数部分
buf[0]=-100; console.log(buf[0]);//156 buf[1]=300; console.log(buf[1]);//44 buf[2]=3.1415; console.log(buf[2]);//3
【fromcharcode】
通常地,创建的buffer对象的内容是其uft-8字符编码
varbuf=Buffer.from('match'); console.log(buf);//
如果要访问其对应的字符,则需要使用字符串的fromCharCode()方法
console.log(String.fromCharCode(buf[0]));//'m'
内存分配
Buffer对象的内存分配不是在V8的堆内存中,而是在Node的C++层面实现内存的申请的。因为处理大量的字节数据不能采用需要一点内存就向操作系统申请一点内存的方式,这可能造成大量的内存申请的系统调用,对操作系统有一定压力。为此Node在内存的使用上应用的是在C++层面申请内存、在JavaScript中分配内存的策略
为了高效地使用申请来的内存,Node采用了slab分配机制。slab是一种动态内存管理机制,最早诞生于SunOS操作系统(Solaris)中,目前在一些*nix操作系统中有广泛的应用,如FreeBSD和Linux。简单而言,slab就是一块申请好的固定大小的内存区域。slab具有如下3种状态:full:完全分配状态;partial:部分分配状态;empty:没有被分配状态
当我们需要一个Buffer对象,可以通过以下方式分配指定大小的Buffer对象:
newBuffer(size);//旧 Buffer.alloc(size);//新
【poolSize】
poolSize属性是用于决定预分配的、内部Buffer实例池的大小的字节数。默认地,Node以8KB为界限来区分Buffer是大对象还是小对象:
Buffer.poolSize=8*1024;
这个8KB的值也就是每个slab的大小值,在JavaScript层面,以它作为单位单元进行内存的分配
1、分配小Buffer对象
如果指定Buffer的大小少于8KB,Node会按照小对象的方式进行分配。Buffer的分配过程中主要使用一个局部变量pool作为中间处理对象,处于分配状态的slab单元都指向它。以下是分配一个全新的slab单元的操作,它会将新申请的SlowBuffer对象指向它:
varpool; functionallocPool(){ pool=newSlowBuffer(Buffer.poolSize); pool.used=0; }
构造小Buffer对象时的代码如下:
newBuffer(1024);//旧 Buffer.alloc(1024);//新
这次构造将会去检查pool对象,如果pool没有被创建,将会创建一个新的slab单元指向它:
if(!pool||pool.length-pool.used同时当前Buffer对象的parent属性指向该slab,并记录下是从这个slab的哪个位置(offset)开始使用的,slab对象自身也记录被使用了多少字节,代码如下:
this.parent=pool; this.offset=pool.used; pool.used+=this.length; if(pool.used&7)pool.used=(pool.used+8)&~7;这时候的slab状态为partial。当再次创建一个Buffer对象时,构造过程中将会判断这个slab的剩余空间是否足够。如果足够,使用剩余空间,并更新slab的分配状态。下面的代码创建了一个新的Buffer对象,它会引起一次slab分配:
newBuffer(3000);//旧 Buffer.alloc(3000);//新如果slab剩余的空间不够,将会构造新的slab,原slab中剩余的空间会造成浪费。例如,第一次构造1字节的Buffer对象,第二次构造8192字节的Buffer对象,由于第二次分配时slab中的空间不够,所以创建并使用新的slab,第一个slab的8KB将会被第一个1字节的Buffer对象独占。下面的代码一共使用了两个slab单元:
newBuffer(1);//旧 Buffer.alloc(1);//新 newBuffer(8192);//旧 Buffer.alloc(8192);//新要注意的是,由于同一个slab可能分配给多个Buffer对象使用,只有这些小Buffer对象在作用域释放并都可以回收时,slab的8KB空间才会被回收。尽管创建了1个字节的Buffer对象,但是如果不释放它,实际可能是8KB的内存没有释放
2、分配大Buffer对象
如果需要超过8KB的Buffer对象,将会直接分配一个SlowBuffer对象作为slab单元,这个slab单元将会被这个大Buffer对象独占
//Bigbuffer,justallocone this.parent=newSlowBuffer(this.length); this.offset=0;这里的SlowBuffer类是在C++中定义的,虽然引用buffer模块可以访问到它,但是不推荐直接操作它,而是用Buffer替代
上面提到的Buffer对象都是JavaScript层面的,能够被V8的垃圾回收标记回收。但是其内部的parent属性指向的SlowBuffer对象却来自于Node自身C++中的定义,是C++层面上的Buffer对象,所用内存不在V8的堆中
综上,真正的内存是在Node的C++层面提供的,JavaScript层面只是使用它。当进行小而频繁的Buffer操作时,采用slab的机制进行预先申请和事后分配,使得JavaScript到操作系统之间不必有过多的内存申请方面的系统调用。对于大块的Buffer而言,则直接使用C++层面提供的内存,而无需细腻的分配操作
转换
Buffer对象可以与字符串之间相互转换。目前支持的字符串编码类型有如下几种:ASCII、UTF-8、UTF-16LE/UCS-2、Base64、Binary、Hex
【write()】
一个Buffer对象可以存储不同编码类型的字符串转码的值,调用write()方法可以实现该目的
buf.write(string,[offset],[length],[encoding])string
要写入buf的字符串 offset
开始写入string的位置。默认:0 length
要写入的字节数。默认:buf.length-offset encoding
string的字符编码。默认:'utf8';返回: 写入的字节数 根据encoding的字符编码写入string到buf中的offset位置。length参数是写入的字节数。如果buf没有足够的空间保存整个字符串,则只会写入string的一部分。只部分解码的字符不会被写入
varbuf=Buffer.alloc(5); console.log(buf);//varlen=buf.write('test',1,3); console.log(buf);// console.log(len);/3 由于可以不断写入内容到Buffer对象中,并且每次写入可以指定编码,所以Buffer对象中可以存在多种编码转化后的内容。需要小心的是,每种编码所用的字节长度不同,将Buffer反转回字符串时需要谨慎处理
【toString()】
实现Buffer向字符串的转换也十分简单,Buffer对象的toString()可以将Buffer对象转换为字符串
buf.toString([encoding],[start],[end])encoding-使用的编码。默认为'utf8'
start-指定开始读取的索引位置,默认为0
end-结束位置,默认为缓冲区的末尾
返回-解码缓冲区数据并使用指定的编码返回字符串
varbuf=Buffer.alloc(26); for(vari=0;i<26;i++){ buf[i]=i+97; } console.log(buf.toString('ascii'));//abcdefghijklmnopqrstuvwxyz console.log(buf.toString('ascii',0,5));//abcde console.log(buf.toString('utf8',0,5));//abcde console.log(buf.toString(undefined,0,5));//abcde【toJSON()】
将NodeBuffer转换为JSON对象
buf.toJSON()返回buf的JSON格式
varbuf=Buffer.from('test'); varjson=buf.toJSON(buf); console.log(json);//{type:'Buffer',data:[116,101,115,116]}【isEncoding()】
目前比较遗憾的是,Node的Buffer对象支持的编码类型有限,只有少数的几种编码类型可以在字符串和Buffer之间转换。为此,Buffer提供了一个isEncoding()函数来判断编码是否支持转换
Buffer.isEncoding(encoding)将编码类型作为参数传入上面的函数,如果支持转换返回值为true,否则为false。很遗憾的是,在中国常用的GBK、GB2312和BIG-5编码都不在支持的行列中
console.log(Buffer.isEncoding('utf8'));//true console.log(Buffer.isEncoding('gbk'));//false类方法
【Buffer.byteLength(string[,encoding])】
Buffer.byteLength()方法返回一个字符串的实际字节长度。这与String.prototype.length不同,因为那返回字符串的字符数
string
| | | | 要计算长度的值 encoding
如果string是字符串,则这是它的字符编码。默认:'utf8' 返回:
string包含的字节数 varstr='火柴'; varbuf=Buffer.from(str); console.log(str.length);//2 console.log(buf.length);//6 console.log(buf.byteLength);//6【Buffer.compare(buf1,buf2)】
该方法用于比较buf1和buf2,通常用于Buffer实例数组的排序。相当于调用buf1.compare(buf2)
buf1
buf2
Returns:
varbuf1=Buffer.from('1234'); varbuf2=Buffer.from('0123'); vararr=[buf1,buf2]; varresult=Buffer.compare(buf1,buf2); console.log(result);//1 console.log(arr.sort());//[, ] 【Buffer.concat(list[,totalLength])】
该方法返回一个合并了list中所有Buffer实例的新建的Buffer
list
要合并的Buffer实例的数组 totalLength
合并时list中Buffer实例的总长度 返回:
如果list中没有元素、或totalLength为0,则返回一个新建的长度为0的Buffer。如果没有提供totalLength,则从list中的Buffer实例计算得到。为了计算totalLength会导致需要执行额外的循环,所以提供明确的长度会运行更快
varbuf1=Buffer.alloc(10); varbuf2=Buffer.alloc(14); varbuf3=Buffer.alloc(18); vartotalLength=buf1.length+buf2.length+buf3.length; console.log(totalLength);//42 varbufA=Buffer.concat([buf1,buf2,buf3],totalLength); console.log(bufA);//console.log(bufA.length);//42 【Buffer.isBuffer(obj)】
如果obj是一个Buffer则返回true,否则返回false
varbuf=Buffer.alloc(5); varstr='test'; console.log(Buffer.isBuffer(buf));//true console.log(Buffer.isBuffer(str));//false实例方法
【buf.slice([start[,end]])】
该方法返回一个指向相同原始内存的新建的Buffer,但做了偏移且通过start和end索引进行裁剪
start
新建的Buffer开始的位置。默认:0 end
新建的Buffer结束的位置(不包含)。默认:buf.length 返回:
varbuffer1=Buffer.from('test'); console.log(buffer1);//varbuffer2=buffer1.slice(1,3); console.log(buffer2);// console.log(buffer2.toString());//'es' [注意]修改这个新建的Buffer切片,也会同时修改原始的Buffer的内存,因为这两个对象所分配的内存是重叠的
varbuffer1=Buffer.from('test'); console.log(buffer1);//varbuffer2=buffer1.slice(1,3); console.log(buffer2);// buffer2[0]=0; console.log(buffer1);// console.log(buffer2);// 【buf.copy(target[,targetStart[,sourceStart[,sourceEnd]]])】
该方法用于拷贝buf的一个区域的数据到target的一个区域,即便target的内存区域与buf的重叠
target
| 要拷贝进的Buffer或Uint8Array targetStart
target中开始拷贝进的偏移量。默认:0 sourceStart
buf中开始拷贝的偏移量。当targetStart为undefined时忽略。默认:0 sourceEnd
buf中结束拷贝的偏移量(不包含)。当sourceStart为undefined时忽略。默认:buf.length 返回:
被拷贝的字节数 varbuffer1=Buffer.from('test'); varbuffer2=Buffer.alloc(5); varlen=buffer1.copy(buffer2,1,3); console.log(buffer1);//console.log(buffer2);// console.log(len);//1 【buf.compare(target[,targetStart[,targetEnd[,sourceStart[,sourceEnd]]]])】
该方法比较buf与target,返回表明buf在排序上是否排在target之前、或之后、或相同。对比是基于各自Buffer实际的字节序列
target
要比较的Buffer targetStart
target中开始对比的偏移量。默认:0 targetEnd
target中结束对比的偏移量(不包含)。当targetStart为undefined时忽略。默认:target.length sourceStart
buf中开始对比的偏移量。当targetStart为undefined时忽略。默认:0 sourceEnd
buf中结束对比的偏移量(不包含)。当targetStart为undefined时忽略。默认:buf.length 返回:
如果target与buf相同,则返回0
如果target排在buf前面,则返回1
如果target排在buf后面,则返回-1
varbuf1=Buffer.from([1,2,3,4,5,6,7,8,9]); varbuf2=Buffer.from([5,6,7,8,9,1,2,3,4]); //输出:0(buf2中的1234对比buf2中的1234) console.log(buf1.compare(buf2,5,9,0,4)); //输出:-1(buf2中的567891对比buf1中的56789) console.log(buf1.compare(buf2,0,6,4)); //输出:1(buf2中的1对比buf2中的6789) console.log(buf1.compare(buf2,5,6,5));【buf.equals(otherBuffer)】
如果buf与otherBuffer具有完全相同的字节,则返回true,否则返回false
otherBuffer
要比较的Buffer 返回:
varbuf1=Buffer.from('ABC'); varbuf2=Buffer.from('ABC'); varbuf3=Buffer.from('abc'); console.log(buf1.equals(buf2));//true console.log(buf1.equals(buf3));//false【buf.fill(value[,offset[,end]][,encoding])】
value
| | 用来填充buf的值 offset
开始填充buf的位置。默认:0 end
结束填充buf的位置(不包含)。默认:buf.length encoding
如果value是一个字符串,则这是它的字符编码。默认:'utf8' 返回:
buf的引用 如果未指定offset和end,则填充整个buf。这个简化使得一个Buffer的创建与填充可以在一行内完成
varb=Buffer.allocUnsafe(10).fill('h'); console.log(b.toString());//hhhhhhhhhh【buf.indexOf(value[,byteOffset][,encoding])】
value
| | 要搜索的值 byteOffset
buf中开始搜索的位置。默认:0 encoding
如果value是一个字符串,则这是它的字符编码。默认:'utf8' 返回:
buf中value首次出现的索引,如果buf没包含value则返回-1 如果value是字符串,则value根据encoding的字符编码进行解析;如果value是Buffer,则value会被作为一个整体使用。如果要比较部分Buffer可使用buf.slice();如果value是数值,则value会解析为一个0至255之间的无符号八位整数值
varbuf=Buffer.from('thisisabuffer'); //输出:0 console.log(buf.indexOf('this')); //输出:2 console.log(buf.indexOf('is')); //输出:8 console.log(buf.indexOf(Buffer.from('abuffer'))); //输出:8 //(97是'a'的十进制ASCII值) console.log(buf.indexOf(97)); //输出:-1 console.log(buf.indexOf(Buffer.from('abufferexample'))); //输出:8 console.log(buf.indexOf(Buffer.from('abufferexample').slice(0,8)));【buf.lastIndexOf(value[,byteOffset][,encoding])】
与buf.indexOf()类似,除了buf是从后往前搜索而不是从前往后
varbuf=Buffer.from('thisbufferisabuffer'); //输出:0 console.log(buf.lastIndexOf('this')); //输出:17 console.log(buf.lastIndexOf('buffer')); //输出:17 console.log(buf.lastIndexOf(Buffer.from('buffer'))); //输出:15 //(97是'a'的十进制ASCII值) console.log(buf.lastIndexOf(97)); //输出:-1 console.log(buf.lastIndexOf(Buffer.from('yolo'))); //输出:5 console.log(buf.lastIndexOf('buffer',5)); //输出:-1 console.log(buf.lastIndexOf('buffer',4));【buf.includes(value[,byteOffset][,encoding])】
该方法相当于buf.indexOf()!==-1
value
| | 要搜索的值 byteOffset
buf中开始搜索的位置。默认:0 encoding
如果value是一个字符串,则这是它的字符编码。默认:'utf8' 返回:
如果buf找到value,则返回true,否则返回false varbuf=Buffer.from('thisisabuffer'); //输出:true console.log(buf.includes('this')); //输出:true console.log(buf.includes('is')); //输出:true console.log(buf.includes(Buffer.from('abuffer'))); //输出:true //(97是'a'的十进制ASCII值) console.log(buf.includes(97)); //输出:false console.log(buf.includes(Buffer.from('abufferexample'))); //输出:true console.log(buf.includes(Buffer.from('abufferexample').slice(0,8))); //输出:false console.log(buf.includes('this',4));以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。