Node.js Buffer(一、初学者的角度了解Buffer)

Buffer

稳定性:2-Stable

0x01 为什么要用Buffer

众所周知,JavaScript与C/C++不同,没有读写二进制流的机制也无法操作内存。在ECMAScript 2015(ES6)中,TypedArray可以有效的解决这个问题,它允许开发者以数组下标的形式操作内存,大大增强了JavaScript处理二进制数据的能力。其实Buffer的功能与TypedArray相同,它为node.js提供了处理内存的功能,如操作TCP流,文件系统等。相比之下,Buffer的一些功能在V8引擎的助力下性能可以发挥得更好。因此推荐在node.js的开发中使用Buffer(当然,使用ES6的TypedArray也可以)

  • Buffer的实例与整型数组类似,最底层的内存分配是在V8 heap的外部,分配内存的大小在Buffer实例创建后不可更改。
  • Buffer类是使用八位字节流,所以一个字节的大小是0~255,与C语言中的unsigned char和ES6的Uint8Array的范围是相同的。
  • Buffer类是全局的,直接使用即可,不需要使用require('buffer').Buffer

以下是官网的一些简单事例:

// Creates a zero-filled Buffer of length 10.
const buf1 = Buffer.alloc(10);

// Creates a Buffer of length 10, filled with 0x1.
const buf2 = Buffer.alloc(10, 1);

// Creates an uninitialized buffer of length 10.
// This is faster than calling Buffer.alloc() but the returned
// Buffer instance might contain old data that needs to be
// overwritten using either fill() or write().
const buf3 = Buffer.allocUnsafe(10);

// Creates a Buffer containing [0x1, 0x2, 0x3].
const buf4 = Buffer.from([1, 2, 3]);

// Creates a Buffer containing ASCII bytes [0x74, 0x65, 0x73, 0x74].
const buf5 = Buffer.from('test');

// Creates a Buffer containing UTF-8 bytes [0x74, 0xc3, 0xa9, 0x73, 0x74].
const buf6 = Buffer.from('tést', 'utf8');

不吹不黑,作者亲身经历,掌握好以下两种技能可以帮助你更快的理解这篇文章和官方文档:

  • C语言基础
  • ECMAScript 2015 ArrayBuffer TypedArray

0x02 Buffer.from(), Buffer.alloc(), and Buffer.allocUnsafe()

Node.js V6版本之前,Buffer实例是通过Buffer构造函数创建的,构造函数的传参不同,创建的实例也是不同的

  • 如果第一个参数是一个number(e.g. new Buffer(10)),会创建一个大小是number值的Buffer,它分配的内存段是没有进行初始化的,可能会包含敏感的数据,这种实例必须通过buf.fill(0)方法或者其他写操作使其内存进行初始化。然而,这种情况就是为了提升性能和速度, 因为创建一个fast-but-uninitialized Buffer(内存快速分配但是没有进行初始化)与slower-but-safer Buffer(分配的内存进行初始化,速度慢但更安全)差别还是很大的
  • 如果第一个参数是一个String或者Buffer,则Buffer实例会拷贝参数的内存
  • 如果第一个参数是ArrayBuffer,则Buffer实例共享ArrayBuffer的内存

以上可知,new Buffer()根据第一个参数的类型不同,分配的内存有很大的差别,且程序也不会对new Buffer()的参数进行正确性校验,或是当初始化Buffer内存数据失败时,这些都会不经意地给您的代码带来安全性和可靠性问题。为了使创建Buffer实例的过程更加安全和可靠,各种形式的new Buffer()都是反对使用的,开发人员可能要改造有关new Buffer()的代码,用Buffer.from()Buffer.alloc()Buffer.allocUnsafe()等方法来代替。

  • Buffer.from(array)
  • Buffer.from(arrayBuffer[, buteOffset [, length]])
  • Buffer.from(buffer)
  • Buffer.from(string)
  • Buffer.alloc(size[, fill[, encoding]])
  • Buffer.allocUnsafe(size)Buffer.allocUnsafeSlow(size)

通过Buffer.allocUnsafe()创建的Buffer实例可能会分配共享内存池的内存,如果它的大小小于等于Buffer.poolSize的一半。通过Buffer.allocUnsafeSlow()创建的内存则永远不会使用共享内存池。

0x03 命令行参数:--zero-fill-buffers

--zero-fill-buffer参数的作用是:new Buffer(size)Buffer.allocUnsafe()Buffer.allocUnsafeSlow()或者new SlowBuffer(size)创建的实例的内存都会被0填满。使用这个参数将改变这些方法的执行结果和性能,只有在需要强制创建没有敏感数据的Buffer实例时推荐使用

$ node --zeor-file-buffers
> Buffer.allocUnsafe(5); 
// 
// 如果不加这个参数则Buffer的数据可能是别的

0x04 为什么 Buffer.allocUnsafe() 和 Buffer.allocUnsafeSlow() 不安全

当调用Buffer.allocUnsafe()Buffer.allocUnsafeSlow()时,分配的内存段没有初始化(内存没有清零),这个设计使得分配内存的过程非常快,但分配的内存可能潜在地包含敏感数据。所以调用Buffer.allocUnsafe()创建的Buffer实例如果没有对分配的内存段的数据进行重写,当对这个Buffer进行读操作时,敏感的数据就会泄露。当考虑性能方面的优势使用Buffer.allocUnsafe()时,一定要避免这个安全漏洞。

0x05 Buffers and ES6 iteration

Buffer实例可以使用ECMAScript 2015 (ES6) for...of语法

const buf = Buffer.from([1, 2, 3]);

// Prints:
//   1
//   2
//   3
for (const b of buf) {
  console.log(b);
}

另外说明的是,buf.values()buf.keys()buf.entries() 也可以创建iterators (遍历器)

0x06 Buffers and Character Encodings

Buffer实例通常用来表述字符编码的序列,例如UTF-8,UCS2,Base64,甚至十六进制的数据。通过指定的字符编码,Buffer和JavaScript字符串可以相互转换。

const buf = Buffer.from('hello world', 'ascii');

// Prints: 68656c6c6f20776f726c64
console.log(buf.toString('hex'));

// Prints: aGVsbG8gd29ybGQ=
console.log(buf.toString('base64'));

目前Node.js支持的字符编码有:

  • 'ascii' 仅用于7-bit ASCII(ASCII码是0~127),编码速度很快且如果有超出范围的数据将会被截取掉
  • 'utf8' 多字节的Unicode编码字符,多数网页和文档的格式都是UTF-8
  • 'utf16le' 2或者4字节,小端的Unicode编码字符,支持的代理项对(U+10000 to U+10FFFF)
  • 'ucs2' 'utf16le'的别名
  • 'base64' Base64编码,当通过字符串创建Buffer时,将会使用URL and FIlename Safe Alphabet
  • 'latin1' 一字节字符串的编码方式(详见RFC1345第63页)
  • 'binary' 'latin1'的别名
  • 'hex' 一个字节两个十六进制字符的编码方式,如0x2f

注意:当今浏览器遵循的是WHATWG spec标准,'latin1''ISO-8859-1'都属于'win-1252'编码的一种('win-1252'还包含其他的编码)。这意味着如果有些操作如http.get(),它返回的字符是WHATWG spec的'win-1252'编码的数据,使用'latin1'进行解码得到的数据可能是不正确的。

0x07 Buffers and TypedArray

Buffer实例也是Unit8Array视图。但是它与ECMAScript 2015的TypedArray还有些细微的不同。例如,ArrayBuffer#slice()创建实例的内存是slice方法拷贝的内存,而Buffer#slice()是在Buffer的基础上创建了视图来操作内存,所以Buffer#slice()的效率更高一些
通过Buffer创建二进制数组时要注意下面几点:

  • TypedArray是拷贝Buffer对象的内存,但不共享内存
  • Buffer对象的数组形式与TypedArry的不同。例如,new Uint32Array(Buffer.from([1,2,3,4]))所创建的Uint32Array是多元素数组[1,2,3,4],而new Uint32Array(TypedArray)创建的是只有一个元素的数组[0x01020304][0x04030201]

通过TypedArray对象的.buffer属性创建的Buffer实例与TypedArray实例共享同一个内存

const arr = new Uint16Array(2);// arr是TypedArray

arr[0] = 5000;
arr[1] = 4000;

// 拷贝`arr`的内存
const buf1 = Buffer.from(arr);

// 共享`arr`的内存
const buf2 = Buffer.from(arr.buffer);

// Prints: 
console.log(buf1);

// Prints: 
console.log(buf2);

arr[1] = 6000;

// Prints: 
console.log(buf1);

// Prints: 
console.log(buf2);

通过TypedArray.buffer创建Buffer时,可以通过byteOffsetlength参数使用ArrayTyped的部分内存

const arr = new Uint16Array(20);
const buf = Buffer.from(arr.buffer, 0, 16);

// Prints: 16
console.log(buf.length);

Buffer.from()TypedArray.from()方法是不同的,TypedArray.from()的第二个参数是一个mapping遍历的函数:

  • TypedArray.from(source[, mapFn[, thisArg]])

Buffer.from()并不支持这个mapping函数:

  • Buffer.from(array)
  • Buffer.from(buffer)
  • Buffer.from(arrayBuffer[, byteOffset [, length]])
  • Buffer.from(string[, encoding])

总结:这篇文章首先回答了为什么要使用Buffer,然后详述了Buffer的功能、效率、安全性及一些使用上的问题,使读者对其概念有一个初步的了解,下一章将会带大家深入了解Class Buffer的具体使用方法,敬请期待。

本文档是根据Node.js目前稳定版本的文档Node.js v6.10.2 Documentation进行总结的,如您在阅读的过程中发现问题,请联系作者,最后感谢您的支持!

作者 小菜荔枝 转载请联系作者获得授权

你可能感兴趣的:(Node.js Buffer(一、初学者的角度了解Buffer))