Buffer结构
模块结构
Buffer是一个典型的js与c++结合的模块。性能部分由c++实现,非性能部分用js实现
Buffer对象
Buffer对象类似于数组,它的元素为16进制的两位数,值域在[0,255]的数值。
var str="你好node.js";
var buf1=new Buffer(str,"UTF-8");
console.log(buf1);
//<Buffer e4 bd a0 e5 a5 bd 6e 6f 64 65 2e 6a 73>
- 在UTF-8的编码下汉字编码为3个元素,英文字母和符号编码1个元素。
console.log(buf1.length);
//13 2x3+7x1=13
- 类似于数组,可以通过.length属性直接访问元素的长度。
console.log(buf1[10]);
//46 2e:2x16+14=46(16进制转换成10进制)
//buffer的每个数的最大数是255
- 类似于数组,可以通过直接访问数组的index来获取数组某个位置上的值。这个值会转换成十进制。
buf1[10]=47
console.log(buf1.toString("utf-8"));
console.log(buf1);
console.log(buf1[10]);
//你好node/js
//<Buffer e4 bd a0 e5 a5 bd 6e 6f 64 65 2f 6a 73>(值被保存为2f)
//47
- 类似于数组,可以通过访问数组的index来对其重新赋值,所赋值会被识别为十进制,再转换为十六进制进行保存.
uf1[10]=-210;
console.log(buf1[10]);
//46 (-210+256=46)
console.log(buf1.toString("utf-8"));
//你好node.js
buf1[10]=46.25896;
console.log(buf1[10]);//46
//(小数舍弃小数部分)
//buf的每一个位置只能保存0-255(ff最大为255)
buf1[10]=257;
console.log(buf1[10]);//1
- 当所赋值的值域在[0,255]之外时,通过对其值加减256直到值域落在[0,255]再进行保存。当所赋值有小数时,忽略小数点及其后面的数字。
Buffer的内存分配
NOde采用了slab分配机制。在c++层面直接向申请一部分作为内存,通过js进行内存分配操作。避免频繁的内存申请的系统调用。
Buffer的转换
Buffer对象支持的编码类型包括:ASCII,UTF-8,Base64,Binary,Hex等。
字符串转Buffer
var str="你好nodejs";
var buf1=new Buffer(str,"UTF-8");
console.log(buf1.toString("UTF-8"));
//你好nodejs
console.log(buf1.toString("ASCII"));
//d= e%=nodejs
buf.write(string[, offset[, length]][, encoding])
- string {String} 需要被写入到 Buffer 的字节
- offset {Number} 默认:0
- length {Number} 默认:buffer.length - offset
- encoding {String} 默认:'utf8'
- 返回:{Number} 被写入的字节数
var buf1=Buffer.alloc(10);
console.log(buf1);//<Buffer 00 00 00 00 00 00 00 00 00 00>
var len1=buf1.write("node",0,"UTF-8");
console.log(buf1);//4
console.log(`len1:${len1}`);//len1:4
var len2=buf1.write(".js",len1,"UTF-8");
console.log(buf1);//<Buffer 6e 6f 64 65 2e 6a 73 00 00 00>
console.log(`len2:${len2}`);//len2:3
console.log(buf1.toString("UTF-8",0,(len1+len2))); //node.js
console.log(buf1.toString("UTF-8",1,(len1+len2-1)));//ode.j
Buffer转字符串
buf.toString([encoding],[start],[end]);
Buffer不支持的编码类型
检测node.js是否支持该编码:
var res1=Buffer.isEncoding('GBK');
console.log(res1);//false
var res2=Buffer.isEncoding('BIG-5');
console.log(res2);//false
使用iconv和iconv-lite模块来编码解码:
const iconv=require("iconv-lite");
var buf1=iconv.encode('node.js','win1251');
console.log(buf1);//<Buffer 6e 6f 64 65 2e 6a 73>
var str=iconv.decode(buf1,'win1251');
console.log(str);//node.js
Buffer的拼接
const fs = require("fs");
var rs = fs.createReadStream("C:/Users/Administrator/Desktop/English.txt");
var data = '';
rs.on('data', (chunk) => {
data += chunk;
});
rs.on("end", () => {
console.log(data);//会打印上面文本中的字符
});
上面的代码通常用于英文文本的流读取示范。'data'事件中获取的chunk对象即是buffer对象。而'data+=chunk'这句代码隐藏了'data=data.toString()+chunk.toString()'。也因此对于中文这种宽字节编码并不合适。
//将文件可读流的每次读取的Buffer长度限制为11
//文本中的内容为静夜思
const fs = require("fs");
var rs = fs.createReadStream("C:/Users/Administrator/Desktop/新建文本文档.txt", {
highWaterMark: 11
});
var data = '';
rs.on('data', (chunk) => {
data += chunk;
});
rs.on('end', () => {
console.log(data);//床前明��光,疑���地上霜。举头��明月,���头思故乡。
});
乱码的产生
文件可读流在读取时会逐个读取Buffer。
const fs=require("fs");
var rs=fs.createReadStream("C:/Users/Administrator/Desktop/新建文本文档.txt");
rs.on('data',(chunk)=>{
console.log(chunk);//<Buffer e5 ba 8a e5 89 8d e6 98 8e e6 9c 88 e5 85 89 ef bc 8c e7 96 91 e4 bc bc e5 9c b0 e4 b8 8a e9 9c 9c e3 80 82 e4 b8 be e5 a4 b4 e6 9c 9b e6 98 8e e6 9c ... 22 more bytes>
console.log(chunk.length);//72
});
因为上面限定了Buffer对象的长度为11,所以72个字符需要7次才能读完。刚好'月','似','望','低'分别对应(10-12),(22-24),(43-45),(55-57)刚好处于断档带。UTF-8编码无法识别剩下的字符,于是就以乱码的形式显示出来。
setEncoding()与string_decoder()
const fs=require("fs");
var rs=fs.createReadStream("C:/Users/Administrator/Desktop/新建文本文档.txt",{highWaterMark:11});
rs.setEncoding("utf-8");
rs.on("data",(chunk)=>{
console.log(chunk.toString());
});//床前明
月光,疑
似地上霜
。举头
望明月,
低头思故
乡。
setEncoding()能解决大部分的乱码问题,但它并不能从根源上解决问题。
正确拼接Buffer
const fs=require("fs");
const iconv=require("iconv-lite");
var chunks=[];
var size=0;
var rs=fs.createReadStream("C:/Users/Administrator/Desktop/新建文本文档.txt",{highWaterMark:11});
rs.on('data',(chunk)=>{
chunks.push(chunk);//以11为长度的buffer实例被保存为数组,回调函数会被重复7次
size+=chunk.length;
console.log(`chunk:${chunk}`);
console.log(`chunks:${chunks}`);
console.log(`size:${size}`);
});
//chunk:床前明�
//chunks:床前明�
//size:11
//chunk:�光,疑�
//chunks:床前明�,�光,疑�
//size:22
//chunk:��地上霜
//chunks:床前明�,�光,疑�,��地上霜
//size:33
//chunk:。举头�
//chunks:床前明�,�光,疑�,��地上霜,。举头�
//size:44
//chunk:�明月,�
//chunks:床前明�,�光,疑�,��地上霜,。举头�,�明月,�
//size:55
//chunk:��头思故
//chunks:床前明�,�光,疑�,��地上霜,。举头�,�明月,�,��头思故
//size:66
//chunk:乡。
//chunks:床前明�,�光,疑�,��地上霜,。举头�,�明月,�,��头思故,乡。
//size:72
rs.on('end',()=>{
var buf=Buffer.concat(chunks,size);
var str=iconv.decode(buf,'utf-8');
console.log(str);
});
正确的拼接方式是通过数组的方式来集合所有的buffer片段,再调用buffer.concat来将所有的buffer片段连接为一个buffer实例,再进行解码
Buffer与性能
在应用中我们通常操作字符串,但是在网络传输中都需要转换为Buffer,以进行二进制数据传输。在web应用中,字符串转换到Buffer是时时刻刻发生的,提高字符串到Buffer的转换效率可以很大程度提高网络吞吐率。
- 通过预先转换静态内容为Buffer对象,可以有效减少CPU的重复使用,节省服务器资源。在Node构建的web应用中。可以选择将页面中的动态内容与静态内容分离。静态内容的部分可以通过预先转换为Buffer的方式,使性能得到提升。由于文件是二进制数据,所以在不需要改变内容的场景下,尽量只读取Buffer,然后直接传输,不做额外的转换,避免损耗。
文件读取
暂略