Skip to content

基础模块 之 Buffer 模块 #36

Description

@fengshi123

前言

在前端,我们只需做字符串级别的操作,很少接触字节、进制等底层操作,一方面这足以满足日常需求,另一方面 在 ECMAScript 2015 (ES6) 引入 TypedArray 之前,JavaScript 语言没有读取或操作二进制数据流的机制;然而在后端,处理文件、网络协议、图片、视频等是非常常见的,尤其像文件、网络流等操作处理的都是二进制数据。为了让 javascript 能够处理二进制数据,node 封装了一个 Buffer 类,主要用于操作字节,处理二进制数据。

一个 Buffer 类似于一个整数数组,可以取下标,有 length 属性,有剪切复制操作等,很多 API 也类似数组,但 Buffer 的大小在被创建时确定,且无法调整。Buffer 处理的是字节,两位十六进制,因此在整数范围就是0~255。

Buffer 可以与string 互相转化,还可以设置字符集编码。Buffer 用来处理文件 I/O、网络 I/O 传输的二进制数据,string 用来呈现。在处理文件 I/O、网络 I/O 传输的二进制数据时,应该尽量以 Buffer 形式直接传输,速度会得到很好的提升。

Buffer 内存分配与性能优化:Buffer 是一个典型的 javascript 与 C++ 结合的模块,与性能有关的用 C++ 来实现,javascript 负责衔接和提供接口。Buffer 所占的内存不是 V8 分配的,是独立于 V8 堆内存之外的内存,通过 C++ 层面实现内存申请、javascript 分配内存。值得一提的是,每当我们使用 Buffer.alloc(size) 请求一个 Buffer 内存时,Buffer 会以 8KB 为界限来判断分配的是大对象还是小对象,小对象存入剩余内存池,不够再申请一个 8KB 的内存池;大对象直接采用 C++ 层面申请的内存。因此,对于一个大尺寸对象,申请一个大内存比申请众多小内存池快很多。
注意点:
1、Buffer 对象类似于数组,它的元素为 16 进制的两位数,即 0 到 255 的数值;不同编码的字符串占用的元素个数各不相同,例如在 UTF-8 编码下:中文占用 3 个字符,字母和半角标点符号占用 1 个字符;
2、如果给 Buffer 元素赋值如果小于 0,就将该值逐次加 256,直到得到一个 0 到 255 之间的整数;如果得到的数值大于 255,就逐次减 256,直到得到 0-255 区间的数值;如果是小数,则舍弃小数部分,只保留整数部分;

1、创建

  • new Buffer(array)
  • Buffer.alloc(length)
  • Buffer.allocUnsafe(length)
  • Buffer.from(array)

1.1、new Buffer

注意这个用法存在安全问题,已被废弃;

const buf1 = new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
console.log(buf1.toString());  // buffer

1.2、Buffer.alloc

const buf2 = Buffer.alloc(10); // 长度为 10 的 buffer,初始值为 0x0?
console.log(buf2); // <Buffer 00 00 00 00 00 00 00 00 00 00>

const buf3 = Buffer.alloc(10, 1); // 长度为 10 的 buffer,初始值为 0x1?
console.log(buf3); // <Buffer 01 01 01 01 01 01 01 01 01 01>

1.3、Buffer.from 用法

1.3.1、Buffer.from(array)

const buf4 = Buffer.from([1, 2, 3]) 
console.log(buf4.toString()); // <Buffer 01 02 03>

const buf5 = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
console.log(buf5);  // <Buffer 62 75 66 66 65 72>

1.3.2、Buffer.from(string[, encoding])

默认编码 utf8

const buf6 = Buffer.from('buffer');
console.log(buf6);  // <Buffer 62 75 66 66 65 72>
console.log(buf6.toString()); // buffer

1.3.3、Buffer.from(buffer)

创建新的 Buffer 实例,并将 buffer 的数据拷贝到新的实例中去

let buff = Buffer.from('buffer');
let buff2 = Buffer.from(buff);

console.log(buff.toString());  // 输出:buffer
console.log(buff2.toString());  // 输出:buffer

buff2[0] = 0x61;

console.log(buff.toString());  // 输出:buffer
console.log(buff2.toString());  // 输出:auffer

2、比较

2.1、buf.equals(otherBuffer)

// 例子一:编码一样,内容相同 => true
var buf1 = Buffer.from('A');
var buf2 = Buffer.from('A');
console.log( buf1.equals(buf2) );  // true

// 例子二:编码一样,内容不同  => false
var buf3 = Buffer.from('A');
var buf4 = Buffer.from('B');
console.log( buf3.equals(buf4) );  // false

// 例子三:编码不一样,内容相同  => false
var buf5 = Buffer.from('ABC');  
var buf6 = Buffer.from('ABC', 'hex');
console.log(buf5.equals(buf6));  // false

2.2、buf.compare(target[, targetStart[, targetEnd[, sourceStart[, sourceEnd]]]])

const buf1 = Buffer.from('ABC');
const buf2 = Buffer.from('BCD');
const buf3 = Buffer.from('ABCD');
const buf4 = Buffer.from('ABC');

//  0
console.log(buf1.compare(buf4));
// -1
console.log(buf1.compare(buf2));
// -1
console.log(buf1.compare(buf3));
// 1
console.log(buf2.compare(buf3));
// 0
console.log(buf2.compare(buf3, 1, 3, 0, 2));

2.3、Buffer.compare(buf1, buf2)

跟 buf.compare(target) 大同小异,一般用于排序

const buf1 = Buffer.from('1234');
const buf2 = Buffer.from('0123');
const arr = [buf1, buf2];
// [ <Buffer 30 31 32 33>, <Buffer 31 32 33 34> ]
console.log(arr.sort(Buffer.compare));

3、其它

3.1、连接

Buffer.concat(list[, totalLength])
其中 totalLength 有两点需要注意。假设 list 里面所有 buffer 的长度累加和为 length

  • totalLength > length:返回长度为 totalLength 的 Buffer 实例,超出长度的部分填充 0;
  • totalLength < length:返回长度为 totalLength 的 Buffer 实例,后面部分舍弃;
const buff1 = Buffer.from([1, 2]);
const buff2 = Buffer.from([3, 4]);

const length = buff1.length + buff2.length;
const buff3 = Buffer.concat([buff1, buff2], length);
console.log(buff3.length); // 4
console.log(buff3); // <Buffer 01 02 03 04>


const buff4 = Buffer.concat([buff1, buff2], 3);
console.log(buff4.length);  // 3
console.log(buff4);  // <Buffer 01 02 03>

const buff5 = Buffer.concat([buff1, buff2], 5);
console.log(buff5.length);  // 5
console.log(buff5);  // <Buffer 01 02 03 04 00>

3.2、拷贝

buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])

var buff1 = Buffer.from([1, 2]);
var buff2 = Buffer.from([3, 4, 5]);

// 1、不传后面 3 个参数的情况
buff1.copy(buff2);
console.log(buff2);  // <Buffer 01 02 05>

// 2、传后面 3 个参数的情况
buff1.copy(buff2, 1, 0, buff1.length);
console.log(buff2);  // <Buffer 03 01 02>

3.3、查找

buf.indexOf(value[, byteOffset][, encoding])
跟数组的查找差不多,需要注意的是,value 可能是 String、Buffer、Integer中的任意类型。

  • String:如果是字符串,那么 encoding 就是其对应的编码,默认是 utf8。
  • Buffer:如果是 Buffer 实例,那么会将 value 中的完整数据,跟 buf 进行对比。
  • Integer:如果是数字,那么 value 会被当做无符号的 8 位整数,取值范围是 0 到 255。

另外,可以通过 byteOffset 来指定起始查找位置;

const buf = Buffer.from('this is a buffer');
console.log(buf.indexOf('this')); // 0
console.log(buf.indexOf(Buffer.from('a buffer'))); // 8
console.log(buf.indexOf(Buffer.from('a buffer example'))); // -1
// (97 is the decimal ASCII value for 'a')
console.log(buf.indexOf(97)); // 8

console.log(buf.indexOf('is', 3)); // 5

3.4、写入

buf.write(string[, offset[, length]][, encoding])
将 sring 写入 buf 实例,同时返回写入的字节数;
参数如下:

  • string:写入的字符串;
  • offset:从 buf 的第几位开始写入,默认是 0;
  • length:写入多少个字节,默认是 buf.length - offset;
  • encoding:字符串的编码,默认是 utf8;
var buff1 = Buffer.alloc(4);
const num1 = buff1.write('a');  // 返回 1
console.log(buff1);  // 打印 <Buffer 61 00 00 00>

var buff2 = Buffer.alloc(4);
const num2 = buff2.write('abc', 1, 2);  // 返回 2
console.log(buff2);  // 打印 <Buffer 00 61 62 00>

3.5、填充

buf.fill(value[, offset[, end]][, encoding])
用 value 填充 buf,常用于初始化 buf。参数说明如下:

  • value:用来填充的内容,可以是 Buffer、String 或 Integer;
  • offset:从第几位开始填充,默认是 0;
  • end:停止填充的位置,默认是 buf.length;
  • encoding:如果 value 是 String,那么为 value 的编码,默认是 utf8;
var buff = Buffer.alloc(10).fill('a', 1, 3);
console.log(buff);  // <Buffer 00 61 61 00 00 00 00 00 00 00>

3.6、转换成字符串

buf.toString([encoding[, start[, end]]])

var buff = Buffer.from('hello');
console.log( buff.toString() );  // hello
console.log( buff.toString('utf8', 0, 2) );  // he

3.7、遍历

buf.values()、buf.keys()、buf.entries()

var buff = Buffer.from('abcde');

for(const key of buff.keys()){
    console.log('key is %d', key);
}

for(const value of buff.values()){
  console.log('value is %d', value);
}

for(const pair of buff.entries()){
  console.log('buff[%d] === %d', pair[0], pair[1]);
}

3.8、截取

buf.slice([start[, end]])
用于截取 buf,并返回一个新的 Buffer 实例。需要注意的是,这里返回的 Buffer 实例,指向的仍然是 buf 的内存地址,所以对新 Buffer 实例的修改,也会影响到 buf。

var buff1 = Buffer.from('abcde');
console.log(buff1);  // <Buffer 61 62 63 64 65>

var buff2 = buff1.slice(1, 3);
console.log(buff2);  // <Buffer 62 63>

buff2[0] = 128; 
console.log(buff2);  //  <Buffer 80 63>
console.log(buff1);  // <Buffer 61 80 63 64 65>

4、参考文献

(1)Nodejs进阶:核心模块Buffer常用API使用总结
(2)认识node核心模块--从Buffer、Stream到fs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions