Buffer
穩(wěn)定性:2-Stable
0x01 為什么要用Buffer
眾所周知,JavaScript與C/C++不同,沒有讀寫二進制流的機制也無法操作內(nèi)存。在ECMAScript 2015(ES6)中,TypedArray可以有效的解決這個問題,它允許開發(fā)者以數(shù)組下標的形式操作內(nèi)存,大大增強了JavaScript處理二進制數(shù)據(jù)的能力。其實Buffer的功能與TypedArray相同,它為node.js提供了處理內(nèi)存的功能,如操作TCP流,文件系統(tǒng)等。相比之下,Buffer的一些功能在V8引擎的助力下性能可以發(fā)揮得更好。因此推薦在node.js的開發(fā)中使用Buffer(當然,使用ES6的TypedArray也可以)
-
Buffer的實例與整型數(shù)組類似,最底層的內(nèi)存分配是在V8 heap的外部,分配內(nèi)存的大小在Buffer實例創(chuàng)建后不可更改。 -
Buffer類是使用八位字節(jié)流,所以一個字節(jié)的大小是0~255,與C語言中的unsigned char和ES6的Uint8Array的范圍是相同的。 -
Buffer類是全局的,直接使用即可,不需要使用require('buffer').Buffer
以下是官網(wǎng)的一些簡單事例:
// 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');
不吹不黑,作者親身經(jīng)歷,掌握好以下兩種技能可以幫助你更快的理解這篇文章和官方文檔:
- C語言基礎(chǔ)
- ECMAScript 2015 ArrayBuffer TypedArray
0x02 Buffer.from(), Buffer.alloc(), and Buffer.allocUnsafe()
Node.js V6版本之前,Buffer實例是通過Buffer構(gòu)造函數(shù)創(chuàng)建的,構(gòu)造函數(shù)的傳參不同,創(chuàng)建的實例也是不同的
- 如果第一個參數(shù)是一個
number(e.g. new Buffer(10)),會創(chuàng)建一個大小是number值的Buffer,它分配的內(nèi)存段是沒有進行初始化的,可能會包含敏感的數(shù)據(jù),這種實例必須通過buf.fill(0)方法或者其他寫操作使其內(nèi)存進行初始化。然而,這種情況就是為了提升性能和速度, 因為創(chuàng)建一個fast-but-uninitialized Buffer(內(nèi)存快速分配但是沒有進行初始化)與slower-but-safer Buffer(分配的內(nèi)存進行初始化,速度慢但更安全)差別還是很大的 - 如果第一個參數(shù)是一個
String或者Buffer,則Buffer實例會拷貝參數(shù)的內(nèi)存 - 如果第一個參數(shù)是
ArrayBuffer,則Buffer實例共享ArrayBuffer的內(nèi)存
以上可知,new Buffer()根據(jù)第一個參數(shù)的類型不同,分配的內(nèi)存有很大的差別,且程序也不會對new Buffer()的參數(shù)進行正確性校驗,或是當初始化Buffer內(nèi)存數(shù)據(jù)失敗時,這些都會不經(jīng)意地給您的代碼帶來安全性和可靠性問題。為了使創(chuàng)建Buffer實例的過程更加安全和可靠,各種形式的new Buffer()都是反對使用的,開發(fā)人員可能要改造有關(guān)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()創(chuàng)建的Buffer實例可能會分配共享內(nèi)存池的內(nèi)存,如果它的大小小于等于Buffer.poolSize的一半。通過Buffer.allocUnsafeSlow()創(chuàng)建的內(nèi)存則永遠不會使用共享內(nèi)存池。
0x03 命令行參數(shù):--zero-fill-buffers
--zero-fill-buffer參數(shù)的作用是:new Buffer(size),Buffer.allocUnsafe(),Buffer.allocUnsafeSlow()或者new SlowBuffer(size)創(chuàng)建的實例的內(nèi)存都會被0填滿。使用這個參數(shù)將改變這些方法的執(zhí)行結(jié)果和性能,只有在需要強制創(chuàng)建沒有敏感數(shù)據(jù)的Buffer實例時推薦使用
$ node --zeor-file-buffers
> Buffer.allocUnsafe(5);
// <Buffer 00 00 00 00 00>
// 如果不加這個參數(shù)則Buffer的數(shù)據(jù)可能是別的
0x04 為什么 Buffer.allocUnsafe() 和 Buffer.allocUnsafeSlow() 不安全
當調(diào)用Buffer.allocUnsafe()和Buffer.allocUnsafeSlow()時,分配的內(nèi)存段沒有初始化(內(nèi)存沒有清零),這個設(shè)計使得分配內(nèi)存的過程非常快,但分配的內(nèi)存可能潛在地包含敏感數(shù)據(jù)。所以調(diào)用Buffer.allocUnsafe()創(chuàng)建的Buffer實例如果沒有對分配的內(nèi)存段的數(shù)據(jù)進行重寫,當對這個Buffer進行讀操作時,敏感的數(shù)據(jù)就會泄露。當考慮性能方面的優(yōu)勢使用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() 也可以創(chuàng)建iterators (遍歷器)
0x06 Buffers and Character Encodings
Buffer實例通常用來表述字符編碼的序列,例如UTF-8,UCS2,Base64,甚至十六進制的數(shù)據(jù)。通過指定的字符編碼,Buffer和JavaScript字符串可以相互轉(zhuǎn)換。
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),編碼速度很快且如果有超出范圍的數(shù)據(jù)將會被截取掉 -
'utf8'多字節(jié)的Unicode編碼字符,多數(shù)網(wǎng)頁和文檔的格式都是UTF-8 -
'utf16le'2或者4字節(jié),小端的Unicode編碼字符,支持的代理項對(U+10000 to U+10FFFF) -
'ucs2''utf16le'的別名 -
'base64'Base64編碼,當通過字符串創(chuàng)建Buffer時,將會使用URL and FIlename Safe Alphabet -
'latin1'一字節(jié)字符串的編碼方式(詳見RFC1345第63頁) -
'binary''latin1'的別名 -
'hex'一個字節(jié)兩個十六進制字符的編碼方式,如0x2f
注意:當今瀏覽器遵循的是WHATWG spec標準,'latin1'和'ISO-8859-1'都屬于'win-1252'編碼的一種('win-1252'還包含其他的編碼)。這意味著如果有些操作如http.get(),它返回的字符是WHATWG spec的'win-1252'編碼的數(shù)據(jù),使用'latin1'進行解碼得到的數(shù)據(jù)可能是不正確的。
0x07 Buffers and TypedArray
Buffer實例也是Unit8Array視圖。但是它與ECMAScript 2015的TypedArray還有些細微的不同。例如,ArrayBuffer#slice()創(chuàng)建實例的內(nèi)存是slice方法拷貝的內(nèi)存,而Buffer#slice()是在Buffer的基礎(chǔ)上創(chuàng)建了視圖來操作內(nèi)存,所以Buffer#slice()的效率更高一些
通過Buffer創(chuàng)建二進制數(shù)組時要注意下面幾點:
-
TypedArray是拷貝Buffer對象的內(nèi)存,但不共享內(nèi)存 -
Buffer對象的數(shù)組形式與TypedArry的不同。例如,new Uint32Array(Buffer.from([1,2,3,4]))所創(chuàng)建的Uint32Array是多元素數(shù)組[1,2,3,4],而new Uint32Array(TypedArray)創(chuàng)建的是只有一個元素的數(shù)組[0x01020304]或[0x04030201]
通過TypedArray對象的.buffer屬性創(chuàng)建的Buffer實例與TypedArray實例共享同一個內(nèi)存
const arr = new Uint16Array(2);// arr是TypedArray
arr[0] = 5000;
arr[1] = 4000;
// 拷貝`arr`的內(nèi)存
const buf1 = Buffer.from(arr);
// 共享`arr`的內(nèi)存
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創(chuàng)建Buffer時,可以通過byteOffset和length參數(shù)使用ArrayTyped的部分內(nèi)存
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()的第二個參數(shù)是一個mapping遍歷的函數(shù):
TypedArray.from(source[, mapFn[, thisArg]])
但Buffer.from()并不支持這個mapping函數(shù):
Buffer.from(array)Buffer.from(buffer)Buffer.from(arrayBuffer[, byteOffset [, length]])Buffer.from(string[, encoding])
總結(jié):這篇文章首先回答了為什么要使用Buffer,然后詳述了Buffer的功能、效率、安全性及一些使用上的問題,使讀者對其概念有一個初步的了解,下一章將會帶大家深入了解Class Buffer的具體使用方法,敬請期待。
本文檔是根據(jù)Node.js目前穩(wěn)定版本的文檔Node.js v6.10.2 Documentation進行總結(jié)的,如您在閱讀的過程中發(fā)現(xiàn)問題,請聯(lián)系作者,最后感謝您的支持!
簡書作者 小菜荔枝 轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)