前言
《深入浅出Node.js》这本书写得好,建议阅读。以下摘录自原书。
what
Buffer对象是Node处理二进制数据的一个接口。
why
在Node中,应用需要处理网络协议、操作数据库、处理图片、接收上传文件等操作,Buffer应运而生,用于操作二进制数据。
detail
内存分配
Buffer的内存分配是由c++模块申请内存,在Javascript层用slab机制分配内存。原因是如果过小且频繁的内存申请会对操作系统有压力。
slab机制主要分为两种情况,以8k为界限区分Buffer为大对象或是小对象。8k也是每个slab的大小,是javascript中分配的基础单位。
同时,多个Buffer对象会共用一个slab单元(如果这个单元还未被占满的话)。
如果是大Buffer对象,由javascript分配slowbuffer对象,独占一个slab单元,单元大小为所需大小。并且直接由c++申请内存
how
转化
字符串转buffer
let b2 = Buffer.from('abc')
let b3 = new Buffer('abc') // 弃用
let b4 = Buffer.alloc(3, 'abc') // 速度比allocUnsafe慢
let b4 = Buffer.allocUnsafe(3, 'abc') // 可能包含敏感数据
let b4 = Buffer.alloc(3, 'abc')
console.log(b2) // <Buffer 61 62 63> 注意是十六进制。对应的十进制是96
console.log(b3) // <Buffer 61 62 63> 对应的十进制是97
console.log(b4) // <Buffer 61 62 63> 对应的十进制是98
console.log(b2[0]) // 96
buffer转字符串
let b2 = Buffer.from('abc')
b2.toString('utf-8', 0, 1) // a
存在的问题是内置支持转换编码格式有限,需要依据第三方模块来帮助转化,比如iconv-lite。
乱码问题
中文为《静夜思》
var fs = require('fs');
// highWaterMark:11的意思是流每次读取11个字节的内容。
var rs = fs.createReadStream('test.md', {highWaterMark: 11});
var data = '';
rs.on("data", function (chunk){
data += chunk; // 这里相当于data = data.toString() + chunk.toString()
});
rs.on("end", function () {
console.log(data);
});
出现乱码的原因是中文占3个字节,流每次取11个字节的话,11 - 3*3 还剩2个字节无法识别,所以这两个字节就是乱码。包括下一次的第一个字节也无法识别,也是乱码。如果是纯英文就不会有这个问题。
解决方法一
rs.setEncoding('utf8');
本质操作就是识别为utf-8编码时,会保存之前识别不了的两个字节,在下一波流中进行拼接自动完成字符串转化。在'data'回调函数中获得的已经是字符串了。这种方案的缺陷在于适用编码类型较少。
解决方法二
var chunks = [];
var size = 0;
res.on('data', function (chunk) {
chunks.push(chunk);
size += chunk.length;
});
res.on('end', function () {
var buf = Buffer.concat(chunks, size);
var str = iconv.decode(buf, 'utf8');
console.log(str);
});
Buffer.concat实现原理
Buffer.concat = function(list, length) {
if (!Array.isArray(list)) {
throw new Error('Usage: Buffer.concat(list, [length])');
}
if (list.length === 0) {
return new Buffer(0);
} else if (list.length === 1) {
return list[0];
}
if (typeof length !== 'number') {
length = 0;
for (var i = 0; i < list.length; i++) {
var buf = list[i];
length += buf.length;
}
}
var buffer = new Buffer(length);
var pos = 0;
for (var i = 0; i < list.length; i++) {
var buf = list[i];
buf.copy(buffer, pos);
pos += buf.length;
}
return buffer;
};
Buffer.prototype.copy原理
// copy 其实就是把Buffer对象中都每一个元素都拷贝过去
Buffer.prototype.copy = function (target, targetStart, sourceStart = 0, sourceEnd = this.length) {
for (let i = 0, len = sourceEnd - sourceStart; i < len; i++) {
target[targetStart + i] = this[sourceStart + i]
}
console.log('hia')
}
let targetBuf = Buffer.alloc(15) // 五个中文,15个字节
let sourceBuf = Buffer.from('我是')
let sourceBuf2 = Buffer.from('吴晗君')
sourceBuf.copy(targetBuf, 0, 0, 6)
sourceBuf2.copy(targetBuf, 6, 0, 9)
console.log(targetBuf.toString())
console.log(targetBuf.length)
实现buffer-split
// [1,2,3,2,5,2]
// current 0 -> 1 -> 3 -> 5
function bufferSplit (buf, splitBuf) {
let pos = 0 // 寻找到的位置
let current = 0 // 初始寻找位置
let result = []
const len = splitBuf.length
while ((pos = buf.indexOf(splitBuf, current)) > -1) {
result.push(buf.slice(current, pos))
current = pos + len // 找到后,更新开始寻找的位置
}
result.push(buf.slice(current))
return result
}
// [1,2,3,2,5,2] -> [3,2,5,2] -> [5,2] -> []
function bufferSplit2 (buf, splitBuf) {
var search = -1
var lines = []
while ((search = buf.indexOf(splitBuf)) > -1) {
lines.push(buf.slice(0, search))
buf = buf.slice(search + splitBuf.length, buf.length)
}
lines.push(buf)
return lines
}
function bufferSplit3 (str, splitStr) {
const buf = Buffer.from(str)
const splitBuf = Buffer.from(splitStr)
return bufferSplit(buf, splitBuf)
}
const buf = Buffer.from('我是哈哈我是我是老司机')
const splitBuf = Buffer.from('我是')
const result = bufferSplit(buf, splitBuf)
console.log(result.toString())
其实利用了buffer.copy,一部分一部分拼接起来。拼完之后用iconv-lite这种第三方库统一转化成字符串。
where
在http请求前将静态资源预先转为Buffer数据,要不然原来的数据是字符串的话,还要进行转化,消耗cpu。这样在网络传输时速度就会更快。提高吞吐量。
let helloworld = '';
for (var i = 0; i < 1024 * 10; i++) {
helloworld += "a";
}
helloworld = new Buffer(helloworld);
http.createServer(function (req, res) {
res.writeHead(200);
res.end(helloworld);
}).listen(8001);