事情要从一段代码说起
http.get(options, function(res){
var data = '';
res.on('data', function(chunk){
data += chunk;
});
res.on('end'), function(){
...
});
})
@jifeng 同学在一个回复里提醒我,“这样遇到中文时可能会遇到问题”,今天认真的看了 @朴灵 写的《小心buffer的拼接问题》,其中提到的 BufferHelper 已经很好的解决了这个问题。
不过这和我第一反应想到的解决方法还是略有不同的,所以我在这里也来谈谈 Buffer 的拼接。
第一反应
我的第一反应是实现一个让 Buffer 对象“相加”的方法。就像这样:
http.get(options, function(res){
var data = new Buffer(0);
res.on('data', function(chunk){
data = concat1(data, chunk);
});
res.on('end'), function(){
...
});
});
这么做,就能把拼接的问题留在 res.on('data', ...)
里。 concat1 函数也很简单。
var concat1 = function(buf1, buf2) {
var tmp = new Buffer(buf1.length + buf2.length);
buf1.copy(tmp, 0);
buf2.copy(tmp, buf1.length);
return tmp;
}
在 chunk 数量较多的情况下,这个方法在性能上应该会略逊于 @朴灵 的 BufferHelper,不过我想理解起来应该会更容易。
进一步扩展
上面也提到了,因为每次只能让两个 Buffer 对象 “相加”,所以对象数量较多时,就需要频繁的构建对象和复制内容,这会让性能下降,所以我本想扩展一下,实现多个 Buffer 相加的功能,不过在写的过程中,我发现比起提高性能,自己更希望让代码保持简单,于是就成了下面这样(你可以结合 @朴灵 的文章修改 concat2 来提高性能)。
下面这个函数实现了任意个 Buffer 相加:
var concat2 = function() {
var tmp = arguments[0];
for (var i = 1; i < arguments.length; i++) {
tmp = concat1(tmp, arguments[i]);
}
return tmp;
}
在只有 2 个 Buffer 对象时,concat2 是完全可以代替 concat1 的。
现在要再进一步,实现用数组(其元素都是 Buffer 对象)作为参数:
var concat = function() {
if (!Buffer.isBuffer(arguments[0])) {
return concat2.apply(this, arguments[0]);
}
return concat2.apply(this, arguments);
}
当第一个参数不是 Buffer 对象时,函数把其认为是一个数组,并通过 apply 方法调用 concat2 来完成拼接。
当然,上面的函数都是没有上保险的,如果你不按预期输入参数,不会得到任何出错提示,只会得到不可预计的结果。
最终代码和简单测试
index.js:
var concat1 = function(buf1, buf2) {
var tmp = new Buffer(buf1.length + buf2.length);
buf1.copy(tmp, 0);
buf2.copy(tmp, buf1.length);
return tmp;
}
var concat2 = function() {
var tmp = arguments[0];
for (var i = 1; i < arguments.length; i++) {
tmp = concat1(tmp, arguments[i]);
}
return tmp;
}
var concat = function() {
if (!Buffer.isBuffer(arguments[0])) {
return concat2.apply(this, arguments[0]);
}
return concat2.apply(this, arguments);
}
exports.concat1 = concat1;
exports.concat2 = concat2;
exports.concat = concat;
测试完毕后,只要留下 exports.concat
就够了,正常使用下,功能上它是完全能替代 concat1 和 concat2 的。
test.js:
var bc = require('./index.js');
var buf = [
new Buffer('ABC'),
new Buffer('123'),
new Buffer('DEF')
];
console.log(bc.concat1(buf[0], buf[1]).toString());
console.log('--------');
console.log(bc.concat2(buf[0], buf[1], buf[2]).toString());
console.log('--------');
console.log(bc.concat(buf[0], buf[1], buf[2]).toString());
console.log('--------');
console.log(bc.concat(buf).toString());
console.log('--------');
优化一下
响应一下大家的回复,给出一个优化后的版本:
exports.concat = function() {
var args = (arguments.length === 1) ? arguments[0] : arguments;
var i;
var sumlen = 0;
for (i = 0; i < args.length; i++) {
sumlen += args[i].length;
}
var buf = new Buffer(sumlen);
var pos = 0;
for (i = 0; i < args.length; i++) {
args[i].copy(buf, pos);
pos += args[i].length;
}
return buf;
};
欢迎原创
这样似乎内存拷贝太多,可以写个buffer队列管理类,在需要的时候才拼接,也不限于两个拼接。
concat1
每次都需要重新new Buffer ,这样不好啊。
同意myy的说法,循环中不断new buffer是非常不理智的做法, 建议使用队列。 不断new buffer需要注意的是一定不要做太多计算, 太多计算会让进程没有时间GC, 造成内存飙升。 基本这样的进程是memory faults非常高。
Buffer继承于SlowBuffer, SlowBuffer默认会有个大小。