前言

《深入浅出Node.js》这本书写得好,建议阅读。以下摘录自原书。

what

Buffer对象是Node处理二进制数据的一个接口。

why

在Node中,应用需要处理网络协议、操作数据库、处理图片、接收上传文件等操作,Buffer应运而生,用于操作二进制数据。

detail

内存分配

Buffer的内存分配是由c++模块申请内存,在Javascript层用slab机制分配内存。原因是如果过小且频繁的内存申请会对操作系统有压力。

slab机制主要分为两种情况,以8k为界限区分Buffer为大对象或是小对象。8k也是每个slab的大小,是javascript中分配的基础单位。

同时,多个Buffer对象会共用一个slab单元(如果这个单元还未被占满的话)。


深入浅出Node.js图示

如果是大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); 

参考

  1. 深入浅出Node.js第六章
  2. node-buffer-str模块