Node.js模块之Buffer
簡言
在沒有出現Node.js之前,JavaScript還是運行在瀏覽器端,對于處理Unicode編碼的字符串數據很容易,但是對于處理二進制以及非Unicode編碼的數據無能為力,但是對于Server端操作TCP以及文件I/O的處理是必須的。在Node.js里面提供了Buffer類處理二進制的數據,可以處理各種類型的數據。并且在Node.js里面一些重要模塊net、http、fs中的數據傳輸以及處理都有Buffer的身影,因為一些基礎的核心模塊都要依賴Buffer,所以在node啟動的時候,就已經加載了Buffer,我們可以在全局下面直接使用Buffer。
創建Buffer對象:new Buffer不安全?
在v6.0之前創建Buffer對象直接使用new Buffer()構造函數來創建對象實例,但是Buffer對內存的權限操作相比很大,可以直接捕獲一些敏感信息,所以在v6.0以后,官方文檔里面建議使用Buffer.from()接口去創建Buffer對象。直接對比buffer.js的源碼,看看兩者有什么區別
// 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接口去分類內存的時候,分配出來的內容空間是沒有被初始化(數據沒被重置),很有可能會攜帶該緩存區之前的數據,如果緩存里面的內容是一些私鑰、密碼等敏感信息的話就可有可能被泄漏出去,下面舉個例子:
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這樣設計的會使得內存的分配非常快,因為不用每次都不用去初始化重置分配到的內容空間,雖然有一定的性能優勢,但是也有一定的安全風險,下面是具體的性能耗時對比:
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: 2439msv6.0之后的版本都建議使用Buffer.alloc()接口去分配內存,以及使用Buffer.from()接口去創建Buffer實例,與此同時,新版也維持Buffer.allocUnsafe()接口,但是語義上面已經說的明確,此外,在開啟安全方面,我們業務–zero-fill-buffers來默認啟用內存初始化,最后以下是總結:
使用new Buffer()構造函數創建Buffer對象實例并非絕對的不安全
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
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 - 將每個字節編碼為兩個十六進制字符。
總結
以上是生活随笔為你收集整理的Node.js模块之Buffer的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dubbo源码分析二:服务发布
- 下一篇: HTTP协议之http状态码详解