在沒有出現Node.js以前,JavaScript仍是運行在瀏覽器端,對於處理Unicode編碼的字符串數據很容易,可是對於處理二進制以及非Unicode編碼的數據無能爲力,可是對於Server端操做TCP以及文件I/O的處理是必須的。在Node.js裏面提供了Buffer類處理二進制的數據,能夠處理各類類型的數據。而且在Node.js裏面一些重要模塊net、http、fs中的數據傳輸以及處理都有Buffer的身影,由於一些基礎的核心模塊都要依賴Buffer,因此在node啓動的時候,就已經加載了Buffer,咱們能夠在全局下面直接使用Buffer。javascript
在v6.0以前建立Buffer對象直接使用new Buffer()
構造函數來建立對象實例,可是Buffer對內存的權限操做相比很大,能夠直接捕獲一些敏感信息,因此在v6.0之後,官方文檔裏面建議使用Buffer.from()
接口去建立Buffer對象。直接對比buffer.js的源碼,看看二者有什麼區別java
// Buffer構造函數的源碼 function Buffer(arg, encodingOrOffset, length) { if (typeof arg === 'number') { if (typeof encodingOrOffset === 'string') { throw new Error( 'If encoding is specified then the first argument must be a string' ); } return Buffer.allocUnsafe(arg); } return Buffer.from(arg, encodingOrOffset, length); }
// Buffer.from函數的源碼 Buffer.from = function(value, encodingOrOffset, length) { if (typeof value === 'number') throw new TypeError('"value" argument must not be a number'); if (isArrayBuffer(value) || isSharedArrayBuffer(value)) return fromArrayBuffer(value, encodingOrOffset, length); if (typeof value === 'string') return fromString(value, encodingOrOffset); return fromObject(value); };
由源碼裏面能夠看到Buffer構造函數裏面會判斷第一個參數是否爲數字類型而調用allocUnsafe
接口或者是直接調用from
接口去建立實例,而這兩種建立對象的惟一區別就在於Buffer構造函數方式的第一個參數是數字類型,那麼就是說,若是咱們使用構造函數方式建立時候第一個參數不傳數字類型就和from
接口建立的邏輯是一致的,相對會更加安全。接下來看爲啥若是第一個參數傳遞是數字的話會可能存在安全風險,若是第一個參數是數字,Buffer構造函數會去分配一個內存空間給到實例化的buffer使用,而調用allocUnsafe
接口去分類內存的時候,分配出來的內容空間是沒有被初始化(數據沒被重置),頗有可能會攜帶該緩存區以前的數據,若是緩存裏面的內容是一些私鑰、密碼等敏感信息的話就可有可能被泄漏出去,下面舉個例子:node
var password = 'thisIsMyPassword'; for( var i = 0, i < 100000; i++ ) { var buf = (new Buffer(200)).toString('ascii'); if (buf.indexOf(token) !== -1) { console.log('Found at i ' + i + ': ' + buf); } } // password內存申請的存儲可能在new Buffer裏面泄漏出去
而最初new Buffer()
API這樣設計的會使得內存的分配很是快,由於不用每次都不用去初始化重置分配到的內容空間,雖然有必定的性能優點,可是也有必定的安全風險,下面是具體的性能耗時對比:c++
console.time('new'); for( var i = 0;i< 1000000;i++) { new Buffer(2000); } console.timeEnd('new'); console.time('alloc'); for( var i = 0;i< 1000000;i++) { Buffer.alloc(2000); } console.timeEnd('alloc'); // 運行結果,不初始化比初始化更快 // new: 1498ms // alloc: 2439ms
v6.0以後的版本都建議使用Buffer.alloc()
接口去分配內存,以及使用Buffer.from()
接口去建立Buffer實例,與此同時,新版也維持Buffer.allocUnsafe()
接口,可是語義上面已經說的明確,此外,在開啓安全方面,咱們業務–zero-fill-buffers
來默認啓用內存初始化,最後如下是總結:git
使用new Buffer()
構造函數建立Buffer對象實例並不是絕對的不安全 github
alloc
接口分配內存空間會初始化內存,不會泄漏舊緩存 數組
allocUnsafe
接口分配內存空間速度更優,但有數據安全風險 瀏覽器
Buffer可直接操做二進制數據類型,這必然要有二進制數據的載體,而在JavaScript裏面已經實現了ArrayBuffer對象、TypedArray對象以及DataView對象在ES6的時候歸入了ECMAScript規格里面。其實這些數據結構也被應用在瀏覽器端,例如File API、WebGL、Canvas、WebSockets等一些API底層都是二進制數據的通訊,查看node_buffer.cc
源碼,Buffer在C++層面分配內存最終也是使用ArrayBuffer對象做爲載體,如今先區分一下ArrayBuffer、TypedArray以及DataView三者的區別。緩存
ArrayBuffer對象 : 內存中一段原始的二進制數據,能夠經過「視圖」進行操做。安全
TypedArray對象 : 用來生成內存的視圖,經過9個構造函數,能夠生成9種數據格式的視圖,好比Buffer裏面就使用到Uint8Array(無符號8位整形)數組視圖。
DataView對象 : 暫時與本文無關不作詳細介紹。
簡單點而言, 就是Buffer模塊使用v8::ArrayBuffer分配一片內存,經過TypedArray中的v8::Uint8Array來去寫數據 ,而說道Buffer的內存分配就不得不說Buffer的8KB的問題,對應buffer.js
源碼裏面的處理就是
Buffer.poolSize = 8 * 1024; function allocate(size) { if(size <= 0 ) return new FastBuffer(); if(size < Buffer.poolSize >>> 1 ) if(size > poolSize - poolOffset) createPool(); var b = allocPool.slice(poolOffset,poolOffset + size); poolOffset += size; alignPool(); return b } else { return createUnsafeBuffer(size); } }
源碼直接看來就是以8KB做爲界限,若是寫入的數據大於8KB一半的話直接則直接去分配內存,若是小於4KB的話則從當前分配池裏面判斷是否夠空間放下當前存儲的數據,若是不夠則從新去申請8KB的內存空間,把數據存儲到新申請的空間裏面,若是足夠寫入則直接寫入數據到內存空間裏面,下圖爲其內存分配策略。
如上圖,若是當前存儲了2KB的數據,後面要存儲5KB大小數據的時候分配池判斷所需內存空間大於4KB,則會去從新申請內存空間來存儲5KB數據而且分配池的當前偏移指針也是指向新申請的內存空間,這時候就以前剩餘的6KB(8KB-2KB)內存空間就會被擱置。至於爲何會用8KB做爲存儲單元分配,這裏還沒進一步深究。
此外,Buffer單次的內存分配也有限制,而這個限制根據不一樣操做系統而不一樣,而這個限制能夠看到node_buffer.h
裏面
static const unsigned int kMaxLength = sizeof(int32_t) == sizeof(intptr_t) ? 0x3fffffff : 0x7fffffff;
對於32位的操做系統單次可最大分配的內存爲1G,對於64位或者更高的爲2G
Buffer與String二者均可以存儲字符串類型的數據,可是,String與Buffer不一樣,在內存分配上面,String直接使用v8堆存儲,不用通過c++堆外分配內存,而且Google也對String進行優化,在實際的拼接測速對比中,String比Buffer快。可是Buffer的出現是爲了處理二進制以及其餘非Unicode編碼的數據,因此在處理非utf8數據的時候須要使用到Buffer來處理。
ascii - 僅支持7位ASCII數據。
utf8 - 多字節編碼的Unicode字符
utf16le - 2或4個字節,小端編碼的Unicode字符
base64 - Base64字符串編碼
binary - 二進制編碼。
hex - 將每一個字節編碼爲兩個十六進制字符。