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 00 00 00 00 00>
// 如果不加这个参数则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: <Buffer 88 a0>
console.log(buf1);
// Prints: <Buffer 88 13 a0 0f>
console.log(buf2);
arr[1] = 6000;
// Prints: <Buffer 88 a0>
console.log(buf1);
// Prints: <Buffer 88 13 70 17>
console.log(buf2);
通过TypedArray.buffer
创建Buffer
时,可以通过byteOffset
和length
参数使用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
进行总结的,如您在阅读的过程中发现问题,请联系作者,最后感谢您的支持!
简书作者 小菜荔枝 转载请联系作者获得授权