本文摘录自《Nodejs学习笔记》,更多章节及更新,请访问 github主页地址。欢迎加群交流,群号 197339705

模块概览

Buffer是node的核心模块,开发者可以利用它来处理二进制数据,比如文件流的读写、网络请求数据的处理等。

Buffer的API非常多,本文仅挑选 比较常用/容易理解 的API进行讲解,包括Buffer实例的创建、比较、连接、拷贝、查找、遍历、类型转换、截取、编码转换等。

创建

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

通过 new Buffer(array)

// Creates a new Buffer containing the ASCII bytes of the string 'buffer'
const buf = new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);

验证下:

var array = 'buffer'.split('').map(function(v){
return '0x' + v.charCodeAt(0).toString(16)
}); console.log( array.join() );
// 输出:0x62,0x75,0x66,0x66,0x65,0x72

通过 Buffer.alloc(length)

var buf1 = Buffer.alloc(10);  // 长度为10的buffer,初始值为0x0
var buf2 = Buffer.alloc(10, 1); // 长度为10的buffer,初始值为0x1
var buf3 = Buffer.allocUnsafe(10);  // 长度为10的buffer,初始值不确定
var buf4 = Buffer.from([1, 2, 3])  // 长度为3的buffer,初始值为 0x01, 0x02, 0x03

通过Buffer.from()

例子一:Buffer.from(array)

// [0x62, 0x75, 0x66, 0x66, 0x65, 0x72] 为字符串 "buffer"
// 0x62 为16进制,转成十进制就是 98,代表的就是字母 b
var buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
console.log(buf.toString());

例子二:Buffer.from(string[, encoding])

通过string创建buffer,跟将buffer转成字符串时,记得编码保持一致,不然会出现乱码,如下所示。

var buf = Buffer.from('this is a tést');  // 默认采用utf8

// 输出:this is a tést
console.log(buf.toString()); // 默认编码是utf8,所以正常打印 // 输出:this is a tC)st
console.log(buf.toString('ascii')); // 转成字符串时,编码不是utf8,所以乱码

对乱码的分析如下:

var letter = 'é';
var buff = Buffer.from(letter); // 默认编码是utf8,这里占据两个字节 <Buffer c3 a9>
var len = buff.length; // 2
var code = buff[0]; // 第一个字节为0xc3,即195:超出ascii的最大支持范围
var binary = code.toString(2); // 195的二进制:10101001
var finalBinary = binary.slice(1); // 将高位的1舍弃,变成:0101001
var finalCode = parseInt(finalBinary, 2); // 0101001 对应的十进制:67
var finalLetter = String.fromCharCode(finalCode); // 67对应的字符:C // 同理 0xa9最终转成的ascii字符为)
// 所以,最终输出为 this is a tC)st

例子三:Buffer.from(buffer)

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

var buff = Buffer.from('buffer');
var 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

buffer比较

buf.equals(otherBuffer)

判断两个buffer实例存储的数据是否相同,如果是,返回true,否则返回false。

// 例子一:编码一样,内容相同
var buf1 = Buffer.from('A');
var buf2 = Buffer.from('A'); console.log( buf1.equals(buf2) ); // true // 例子二:编码一样,内容不同
var buf3 = Buffer.from('A');
var buf4 = Buffer.from('B'); console.log( buf3.equals(buf4) ); // false // 例子三:编码不一样,内容相同
var buf5 = Buffer.from('ABC'); // <Buffer 41 42 43>
var buf6 = Buffer.from('414243', 'hex'); console.log(buf5.equals(buf6));

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

同样是对两个buffer实例进行比较,不同的是:

  1. 可以指定特定比较的范围(通过start、end指定)
  2. 返回值为整数,达标buf、target的大小关系

假设返回值为

  • 0:buf、target大小相同。
  • 1:buf大于target,也就是说buf应该排在target之后。
  • -1:buf小于target,也就是说buf应该排在target之前。

看例子,官方的例子挺好的,直接贴一下:

const buf1 = Buffer.from('ABC');
const buf2 = Buffer.from('BCD');
const buf3 = Buffer.from('ABCD'); // Prints: 0
console.log(buf1.compare(buf1)); // Prints: -1
console.log(buf1.compare(buf2)); // Prints: -1
console.log(buf1.compare(buf3)); // Prints: 1
console.log(buf2.compare(buf1)); // Prints: 1
console.log(buf2.compare(buf3)); // Prints: [ <Buffer 41 42 43>, <Buffer 41 42 43 44>, <Buffer 42 43 44> ]
// (This result is equal to: [buf1, buf3, buf2])
console.log([buf1, buf2, buf3].sort(Buffer.compare));

Buffer.compare(buf1, buf2)

buf.compare(target) 大同小异,一般用于排序。直接贴官方例子:

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

从Buffer.from([62])谈起

这里稍微研究下Buffer.from(array)。下面是官方文档对API的说明,也就是说,每个array的元素对应1个字节(8位),取值从0到255。

Allocates a new Buffer using an array of octets.

数组元素为数字

首先看下,传入的元素为数字的场景。下面分别是10进制、8进制、16进制,跟预期中的结果一致。

var buff = Buffer.from([62])
// <Buffer 3e>
// buff[0] === parseInt('3e', 16) === 62
var buff = Buffer.from([062])
// <Buffer 32>
// buff[0] === parseInt(62, 8) === parseInt(32, 16) === 50
var buff = Buffer.from([0x62])
// <Buffer 62>
// buff[0] === parseInt(62, 16) === 98

数组元素为字符串

再看下,传入的元素为字符串的场景。

  1. 0开头的字符串,在parseInt('062')时,可以解释为62,也可以解释为50(八进制),这里看到采用了第一种解释。
  2. 字符串的场景,跟parseInt()有没有关系,暂未深入探究,只是这样猜想。TODO(找时间研究下)
var buff = Buffer.from(['62'])
// <Buffer 3e>
// buff[0] === parseInt('3e', 16) === parseInt('62') === 62
var buff = Buffer.from(['062'])
// <Buffer 3e>
// buff[0] === parseInt('3e', 16) === parseInt('062') === 62
var buff = Buffer.from(['0x62'])
// <Buffer 62>
// buff[0] === parseInt('62', 16) === parseInt('0x62') === 98

数组元素大小超出1个字节

感兴趣的同学自行探究。

var buff = Buffer.from([256])
// <Buffer 00>

Buffer.from('1')

一开始不自觉的会将Buffer.from('1')[0]"1"划等号,其实"1"对应的编码是49。

var buff = Buffer.from('1')  // <Buffer 31>
console.log(buff[0] === 1) // false

这样对比就知道了,编码为1的是个控制字符,表示 Start of Heading。

console.log( String.fromCharCode(49) )  // '1'
console.log( String.fromCharCode(1) ) // '\u0001'

buffer连接:Buffer.concat(list[, totalLength])

备注:个人觉得totalLength这个参数挺多余的,从官方文档来看,是处于性能提升的角度考虑。不过内部实现也只是遍历list,将length累加得到totalLength,从这点来看,性能优化是几乎可以忽略不计的。

var buff1 = Buffer.alloc(10);
var buff2 = Buffer.alloc(20); var totalLength = buff1.length + buff2.length; console.log(totalLength); // 30 var buff3 = Buffer.concat([buff1, buff2], totalLength); console.log(buff3.length); // 30

除了上面提到的性能优化,totalLength还有两点需要注意。假设list里面所有buffer的长度累加和为length

  • totalLength > length:返回长度为totalLength的Buffer实例,超出长度的部分填充0。
  • totalLength < length:返回长度为totalLength的Buffer实例,后面部分舍弃。
var buff4 = Buffer.from([1, 2]);
var buff5 = Buffer.from([3, 4]); var buff6 = Buffer.concat([buff4, buff5], 5); console.log(buff6.length); //
console.log(buff6); // <Buffer 01 02 03 04 00> var buff7 = Buffer.concat([buff4, buff5], 3); console.log(buff7.length); // 3
console.log(buff7); // <Buffer 01 02 03>

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

使用比较简单,如果忽略后面三个参数,那就是将buf的数据拷贝到target里去,如下所示:

var buff1 = Buffer.from([1, 2]);
var buff2 = Buffer.alloc(2); buff1.copy(buff2); console.log(buff2); // <Buffer 01 02>

另外三个参数比较直观,直接看官方例子

const buf1 = Buffer.allocUnsafe(26);
const buf2 = Buffer.allocUnsafe(26).fill('!'); for (let i = 0 ; i < 26 ; i++) {
// 97 is the decimal ASCII value for 'a'
buf1[i] = i + 97;
} buf1.copy(buf2, 8, 16, 20); // Prints: !!!!!!!!qrst!!!!!!!!!!!!!
console.log(buf2.toString('ascii', 0, 25));

查找: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');

// Prints: 0
console.log(buf.indexOf('this')); // Prints: 2
console.log(buf.indexOf('is')); // Prints: 8
console.log(buf.indexOf(Buffer.from('a buffer'))); // Prints: 8
// (97 is the decimal ASCII value for 'a')
console.log(buf.indexOf(97)); // Prints: -1
console.log(buf.indexOf(Buffer.from('a buffer example'))); // Prints: 8
console.log(buf.indexOf(Buffer.from('a buffer example').slice(0, 8))); const utf16Buffer = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'ucs2'); // Prints: 4
console.log(utf16Buffer.indexOf('\u03a3', 0, 'ucs2')); // Prints: 6
console.log(utf16Buffer.indexOf('\u03a3', -4, 'ucs2'));

写:buf.write(string[, offset[, length]][, encoding])

将sring写入buf实例,同时返回写入的字节数。

参数如下:

  • string:写入的字符串。
  • offset:从buf的第几位开始写入,默认是0。
  • length:写入多少个字节,默认是 buf.length - offset。
  • encoding:字符串的编码,默认是utf8。

看个简单例子

var buff = Buffer.alloc(4);
buff.write('a'); // 返回 1
console.log(buff); // 打印 <Buffer 61 00 00 00> buff.write('ab'); // 返回 2
console.log(buff); // 打印 <Buffer 61 62 00 00>

填充: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(20).fill('a');

console.log(buff.toString());  // aaaaaaaaaaaaaaaaaaaa

转成字符串: buf.toString([encoding[, start[, end]]])

把buf解码成字符串,用法比较直观,看例子

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

console.log( buff.toString() );  // hello

console.log( buff.toString('utf8', 0, 2) );  // he

转成JSON字符串:buf.toJSON()

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

console.log( buff.toJSON() );  // { type: 'Buffer', data: [ 104, 101, 108, 108, 111 ] }

遍历:buf.values()、buf.keys()、buf.entries()

用于对buf进行for...of遍历,直接看例子。

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

for(const key of buff.keys()){
console.log('key is %d', key);
}
// key is 0
// key is 1
// key is 2
// key is 3
// key is 4 for(const value of buff.values()){
console.log('value is %d', value);
}
// value is 97
// value is 98
// value is 99
// value is 100
// value is 101 for(const pair of buff.entries()){
console.log('buff[%d] === %d', pair[0], pair[1]);
}
// buff[0] === 97
// buff[1] === 98
// buff[2] === 99
// buff[3] === 100
// buff[4] === 101

截取: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();
console.log(buff2); // <Buffer 61 62 63 64 65> var buff3 = buff1.slice(1, 3);
console.log(buff3); // <Buffer 62 63> buff3[0] = 97; // parseInt(61, 16) ==> 97
console.log(buff1); // <Buffer 62 63>

TODO

  1. 创建、拷贝、截取、转换、查找
  2. buffer、arraybuffer、dataview、typedarray
  3. buffer vs 编码
  4. Buffer.from()、Buffer.alloc()、Buffer.alocUnsafe()
  5. Buffer vs TypedArray

文档摘要

关于buffer内存空间的动态分配

Instances of the Buffer class are similar to arrays of integers but correspond to fixed-sized, raw memory allocations outside the V8 heap. The size of the Buffer is established when it is created and cannot be resized.

相关链接

unicode对照表

https://unicode-table.com/cn/#control-character

字符编码笔记:ASCII,Unicode和UTF-8

http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

Nodejs进阶:核心模块Buffer常用API使用总结的更多相关文章

  1. Nodejs进阶:核心模块net入门与实例讲解

    模块概览 net模块是同样是nodejs的核心模块.在http模块概览里提到,http.Server继承了net.Server,此外,http客户端与http服务端的通信均依赖于socket(net. ...

  2. NodeJS学习笔记 进阶 (12)Nodejs进阶:crypto模块之理论篇

    个人总结:读完这篇文章需要30分钟,这篇文章讲解了使用Node处理加密算法的基础. 摘选自网络 Nodejs进阶:crypto模块之理论篇 一. 文章概述 互联网时代,网络上的数据量每天都在以惊人的速 ...

  3. Nodejs进阶:crypto模块中你需要掌握的安全基础

    本文摘录自<Nodejs学习笔记>,更多章节及更新,请访问 github主页地址. 一. 文章概述 互联网时代,网络上的数据量每天都在以惊人的速度增长.同时,各类网络安全问题层出不穷.在信 ...

  4. Python第十一章-常用的核心模块01-collections模块

    python 自称 "Batteries included"(自带电池, 自备干粮?), 就是因为他提供了很多内置的模块, 使用这些模块无需安装和配置即可使用. 本章主要介绍 py ...

  5. Python第十一章-常用的核心模块03-json模块

    python 自称 "Batteries included"(自带电池, 自备干粮?), 就是因为他提供了很多内置的模块, 使用这些模块无需安装和配置即可使用. 本章主要介绍 py ...

  6. Python第十一章-常用的核心模块04-datetime模块

    python 自称 "Batteries included"(自带电池, 自备干粮?), 就是因为他提供了很多内置的模块, 使用这些模块无需安装和配置即可使用. 本章主要介绍 py ...

  7. Nodejs之express第三方核心模块的中间件——body-parser

    Node中的核心模块分两类:一类是自带的核心模块,如http.tcp等,第二类是第三方核心模块,express就是与http对应的第三方核心模块,用于处理http请求.express在3.0版本中自带 ...

  8. Java进阶 | IO流核心模块与基本原理

    一.IO流与系统 IO技术在JDK中算是极其复杂的模块,其复杂的一个关键原因就是IO操作和系统内核的关联性,另外网络编程,文件管理都依赖IO技术,而且都是编程的难点,想要整体理解IO流,先从Linux ...

  9. Nodejs核心模块

    (1)全局对象 在浏览器JS中,通常window是全局对象,而nodejs中的全局对象是global,所有全局变量都是global对象的属性. 在nodejs中能够直接访问到的对象通常都是global ...

随机推荐

  1. JS学习中遇到的一些题目

    1.找出所有的水仙花数: 水仙花数例如:153 的特点: 1^3+5^3+3^=153 而且水仙花数只会是三位数,所以可以利用循环的方式来解决问题,循环条件可以设为: var i = 1;i < ...

  2. 实现五种分组加密模式ECB,CBC,CFB,OFB,CTR

    没什么好说的,简单无脑! #include<iostream>using namespace std; int ECB(){ int duan[4]; int messageLen = 1 ...

  3. ios runtime部分事例方法说明

    一.场景--动态改变变量 unsigned ; Ivar *ivar = class_copyIvarList([self.person class], &count); ; i<cou ...

  4. PHP站内搜索

    1.SQL语句中的模糊查找 LIKE条件一般用在指定搜索某字段的时候, 通过"% 或_" 通配符的作用实现模糊查找功能,通配符可以在前面也可以在后面或前后都有. 搜索以PHP100 ...

  5. vue.js2.0新手笔记(一)——安装

    知道vue很长时间了,一直只是了解,没有深入学习,也没做什么具体的东西.现在有时间了,决定重头好好学一下,就从安装开始吧. 一.安装node vue是用npm安装,npm是node的一个包管理工具,所 ...

  6. IOC容器的依赖注入

    1.依赖注入发生的时间 当Spring IoC容器完成了Bean定义资源的定位.载入和解析注册以后,IoC容器中已经管理类Bean定义的相关数据,但是此时IoC容器还没有对所管理的Bean进行依赖注入 ...

  7. C语言学习第二章

    今天开始学习常量,变量,基本数据类型,printf()函数和scanf()函数,算术运算符. 首先常量:是在程序中保持不变的量 变量:编写程序时,常常需要将数据存储在内存中,方便后面使用这个数据或者修 ...

  8. IT行业能力细分

    在软件行业工作7年了,平时很懒,懒得做分享,今天特意分享一下软件行业,职业大的技术分类,同学们可根据自己职业规划补充学习知识块.

  9. 杜教筛 && bzoj3944 Sum

    Description Input 一共T+1行 第1行为数据组数T(T<=10) 第2~T+1行每行一个非负整数N,代表一组询问 Output 一共T行,每行两个用空格分隔的数ans1,ans ...

  10. Typecho - MyTagCloud标签云插件

    一.前言: 标签云是博客.CMS类系统的常见功能,读者可以根据标签快速的查找和浏览自己喜欢的文章.个人很喜欢Typecho的简洁,但对于后台不能控制前台标签栏目的显示还是略表遗憾.令人高兴的是Type ...