这是我参与更文挑战的第11天,活动详情查看: 更文挑战
为什么有Buffer?
javascript对于字符串的操作十分友好,无论是宽子节字符串还是单子节字符串,都被认为是一个字符串。但是文件和网络I/O对于前端开发者而言都不曾有的应用场景,因为前端只需做一些简单的字符串操作或dom操作基本就能满足业务需求,在ES规范中,也没有对这些方面做任何定义,只有commonjs踵有部分的二进制定义。由于应用场景不同,在node踵,应用需哟啊处理的网络协议,操作数据库,处理图片,接受上传文件等,在网络流和文件的操作中,还要处理大量二进制数据,javascript自有的字符串远远不能满足这些需求,于是Buffer对象应运而生。
什么是Buffer?
Buffer是一个想Array的对象,但是他主要用于操作字节。下面我们从模块结构和对象结构的层面来认识它。
Buffer是一个典型的js与C++的结合的模块,它将性能相关的部分用C++实现,其余的用js实现。Buffer的内存不是通过V8分配的,他属于堆外内存,由于V8垃圾回收性能的影响,将常用的操作对象用更高效和转悠的内存分配回收策略来管理。一般来说,node进行启动时已经加载了他,在全局对象global上。
它的元素为16禁止的两位数(0-255)
var str = "nodejs";
var buf = new Buffer(str, 'utf-8');
console.log(buff);
//<Buffer 6e 6f 64 65 6a 73>
// =>
复制代码
Buffer受Array影响很大,可以通过length获取长度,也可以通过下标访问元素。
var buf = new Buffer(100);
console.log(buf.length); // => 100
console.log(buf[10]);
复制代码
Buffer对象的内存分配不是在V8的堆内存中,而是在Node的C++层面实现内存的申请的。因为处理大量的字节数据不能采用需要一点内存就向操作系统申请一点内存的方式,这可能造成大量的内存申请的系统调用,对操作系统有一定压力。为此Node在内存的使用上应用的是在C++层面申请内存、在JavaScript中分配内存的策略。
为了高效地使用申请来的内存,Node采用了slab分配机制。slab是一种动态内存管理机制简单而言,slab就是一块申请好的固定大小的内存区域。slab具有如下3种状态。
- full:完全分配状态。
- partial:部分分配状态。
- empty:没有被分配状态。
真正的内存是在Node的C++层面提供的,JavaScript层面只是使用它。当进行小而频繁的Buffer操作时,采用slab的机制进行预先申请和事后分配,使得JavaScript到操作系统之间不必有过多的内存申请方面的系统调用。对于大块的Buffer而言,则直接使用C++层面提供的内存,而无需细腻的分配操作。
Buffer的转换
目前支持下面几种:
- ASCII
- UTF-8
- UTF-16LE/UCS-2
- Base64
- Binary
- Hex
字符串转Buffer
new Buffer(str, [encoding]);
复制代码
Buffer转字符串
buf.toString([encoding], [start], [end]);
复制代码
Buffer不支持的编码类型
中国常用的DBK,DB2312和BIG-5编码都不在支持行列中,对于不支持的编码,可以接触node生态圈中的模块完成转换,icon车iconv-lite两个模块都可以支持更多的编码类型转换,当然还有其他的
const iconv = require('icon-lite');
// buffer转字符串
var str = iconv.decode(buf, 'win1251');
// 字符串转buffer
var buf = iconv.encode('abc', 'win1251');
复制代码
如何解决buffer乱码(setEncoding与string_decoder)
// setEncoding
var rs = fs.createReadStream('test.md', { highWaterMark: 11});
rs.setEncoding('utf8');
复制代码
可读流对象在内部设置了一个decoder对象,每次都通过decoder对象进行buffer到字符串的解码,然后传递给调用者,之后,data不再收到原始的Buffer对象,但是依旧无法解释为何设置编码后的轮吗问题被解决掉了,最终的原因还是来自于string_decoder模块的实例对象
// string_decoder
var StringDecoder = require('string_decoder').StringDecoder;
var decoder = new StringDecoder('utf8'); 4
var buf1 = new Buffer([0xE5, 0xBA, 0x8A, 0xE5, 0x89, 0x8D, 0xE6, 0x98, 0x8E, 0xE6, 0x9C]);
console.log(decoder.write(buf1));
// => 床前明
var buf2 = new Buffer([0x88, 0xE5, 0x85, 0x89, 0xEF, 0xBC, 0x8C, 0xE7, 0x96, 0x91, 0xE6]);
console.log(decoder.write(buf2));
// => 月光,疑
复制代码
正确的拼接过程
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;
};
复制代码
最后
体验过js友好的字符串操作后,有些开发者可能会形成思维定势,将Buffer当作字符串来理解,但字符串与Buffer之间有实质上的禅意,即buffer是二进制数据,字符串与Buffer之间存在编码关系,因此,理解Buffer的诸多细节十分必要,对于如何高效处理二进制数据十分有用。
今天就到这里啦,谢谢大家!!