摘要:且傳入數據的大小必須小于。那些固定位數字讀寫當你在閱讀的文檔時,看到諸如,這樣的時,可能會想到規范中的類提供的那些方法。
在 Node.js 中,Buffer 常常用來存儲一些潛在的大體積數據,例如,文件和網絡 I/O 所獲取來的數據,若不指定編碼,則都以 Buffer 的形式來提供,可見其地位非同一般。你或許聽說過,Buffer 的創建,是可能會經過內部的一個 8KB 池的,那么具體的規則是什么呢?可以創建一個新 Buffer 實例的 API 那么多,到底哪些 API 會經過,哪些又不會經過呢?或許你在閱讀文檔時,還看到過許多形如 Buffer#writeUInt32BE , Buffer#readUInt32BE 等等這類固定位的數字的讀寫操作,它們具體是如何實現的呢?
現在讓我們一起跟著 Node.js 項目中 lib/buffer.js 中的代碼,來一探究竟。
8KB 池分配規則統計一下,當前版本的 Node.js (v6.0)中可以創建一個新 Buffer 類實例的 API 有:
new Buffer() (已不推薦使用,可能會泄露內存中潛在的敏感信息,具體例子可以看這里)
Buffer.alloc()
Buffer.allocUnsafe()(雖然也有泄露內存中敏感信息的可能,但語義上非常明確)
Buffer.from()
Buffer.concat()
跟著代碼追溯,這些 API 最后都會走進兩個內部函數中的一個,來創建 Buffer 實例,這兩個內部函數分別是 createBuffer() 和 allocate():
// lib/buffer.js // ... Buffer.poolSize = 8 * 1024; var poolSize, poolOffset, allocPool; function createPool() { poolSize = Buffer.poolSize; allocPool = createBuffer(poolSize, true); poolOffset = 0; } createPool(); function createBuffer(size, noZeroFill) { flags[kNoZeroFill] = noZeroFill ? 1 : 0; try { const ui8 = new Uint8Array(size); Object.setPrototypeOf(ui8, Buffer.prototype); return ui8; } finally { flags[kNoZeroFill] = 0; } } function allocate(size) { if (size === 0) { return createBuffer(size); } if (size < (Buffer.poolSize >>> 1)) { if (size > (poolSize - poolOffset)) createPool(); var b = allocPool.slice(poolOffset, poolOffset + size); poolOffset += size; alignPool(); return b; } else { return createBuffer(size, true); } }
通過代碼可以清楚的看到,若最后創建時,走的是 createBuffer() 函數,則不經過 8KB 池,若走 allocate() 函數,當傳入的數據大小小于 Buffer.poolSize 有符號右移 1 位后的結果(相當于將該值除以 2 再向下取整,在本例中,為 4 KB),才會使用到 8KB 池(若當前池剩余空間不足,則創建一個新的,并將當前池指向新池)。
那么現在讓我們來看看,這些 API 都走的是哪些方法:
// lib/buffer.js // ... Buffer.alloc = function(size, fill, encoding) { // ... return createBuffer(size); }; Buffer.allocUnsafe = function(size) { assertSize(size); return allocate(size); }; Buffer.from = function(value, encodingOrOffset, length) { // ... if (value instanceof ArrayBuffer) return fromArrayBuffer(value, encodingOrOffset, length); if (typeof value === "string") return fromString(value, encodingOrOffset); return fromObject(value); }; function fromArrayBuffer(obj, byteOffset, length) { byteOffset >>>= 0; if (typeof length === "undefined") return binding.createFromArrayBuffer(obj, byteOffset); length >>>= 0; return binding.createFromArrayBuffer(obj, byteOffset, length); } function fromString(string, encoding) { // ... if (length >= (Buffer.poolSize >>> 1)) return binding.createFromString(string, encoding); if (length > (poolSize - poolOffset)) createPool(); var actual = allocPool.write(string, poolOffset, encoding); var b = allocPool.slice(poolOffset, poolOffset + actual); poolOffset += actual; alignPool(); return b; } Buffer.concat = function(list, length) { // ... var buffer = Buffer.allocUnsafe(length); // ... return buffer; };
挺一目了然的,讓我們來總結一下,當在以下情況同時都成立時,創建的新的 Buffer 類實例才會經過內部 8KB 池:
通過 Buffer.allocUnsafe,Buffer.concat,Buffer.from(參數不為一個 ArrayBuffer 實例)和 new Buffer(參數不為一個 ArrayBuffer 實例)創建。
傳入的數據大小不為 0 。
且傳入數據的大小必須小于 4KB 。
那些固定位數字讀寫 API當你在閱讀 Buffer 的文檔時,看到諸如 Buffer#writeUInt32BE,Buffer#readUInt32BE 這樣的 API 時,可能會想到 ES6 規范中的 DateView 類提供的那些方法。其實它們做的事情十分相似,Node.js 項目中甚至還有將這些 API 的底層都替換成原生的 DateView 實例來操作的 PR ,但該 PR 目前已被標記為 stalled ,具體原因大致是:
沒有顯著的性能提升。
會在實例被初始化后又增加新的屬性。
noAssert 參數將會失效。
先不管這個 PR ,其實,這些讀寫操作,若數字的精度在 32 位以下,則對應方法都是由 JavaScript 實現的,十分優雅,利用了 TypeArray 下那些類(Buffer 中使用的是 Uint8Array)的實例中的元素,在位溢出時,會拋棄溢出位的機制。以 writeUInt32LE 和 writeUInt32BE (LE 和 BE 即小端字節序和大端字節序,可以參閱這篇文章)為例,一個 32 位無符號整數需要 4 字節存儲,大端字節序時,則第一個元素為直接將傳入的 32 位整數無符號右移 24 位,獲取到原最左的 8 位,拋棄當下左邊的所有位。以此類推,第二個元素為無符號右移 16 位,第三個元素為 8 位,第四個元素無需移動(小端字節序則相反):
Buffer.prototype.writeUInt32BE = function(value, offset, noAssert) { value = +value; offset = offset >>> 0; if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0); this[offset] = (value >>> 24); this[offset + 1] = (value >>> 16); this[offset + 2] = (value >>> 8); this[offset + 3] = value; return offset + 4; };
讀操作與之對應,使用了無符號左移后騰出空位再進行 | 操作合并:
Buffer.prototype.readUInt32BE = function(offset, noAssert) { offset = offset >>> 0; if (!noAssert) checkOffset(offset, 4, this.length); return (this[offset] * 0x1000000) + ((this[offset + 1] << 16) | (this[offset + 2] << 8) | this[offset + 3]); };
其中的 (this[offset] * 0x1000000) + 相當于 this[offset] << 24 | 。
最后參考:
https://github.com/nodejs/node/blob/master/lib/buffer.js
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/79349.html
摘要:閑談系列不涉及具體的講解,只會勾勾畫畫一些自己認為比較重要的特性。我們一般認為用兩個字節位表示,并且完全囊括了字符集。將其轉換成進制就是只是表示它們是碼。三的讀取和寫入相關重要的只有能夠讀寫,才能夠顯示其存在的價值。 原文地址:http://www.cnblogs.com/DeanCh... 在剛接觸Nodejs的時候,有些概念總讓學前端的我感到困惑(雖然大學的時候也是在搞后端,世界上...
摘要:在創建時大小已經被確定且是無法調整的,在內存分配這塊是由層面提供而不是具體后面會講解。在這里不知道你是否認為這是很簡單的但是上面提到的一些關鍵詞二進制流緩沖區,這些又都是什么呢下面嘗試做一些簡單的介紹。 showImg(https://segmentfault.com/img/remote/1460000019894717?w=1280&h=850); 多數人都擁有自己不了解的能力和機...
摘要:端輸入數據到端,對就是輸入流,得到的對象就是可讀流對就是輸出端得到的對象是可寫流。在中,這四種流都是的實例,它們都有事件,可讀流具有監聽數據到來的事件等,可寫流則具有監聽數據已傳給低層系統的事件等,和都同時實現了和的事件和接口。 原文地址在我的博客 node中的Buffer和Stream會給剛接觸Node的前端工程師們帶來困惑,原因是前端并沒有類似概念(or 有我們也沒意識到)。然而,...
摘要:主要用來檢測對象是否泄漏。子類實現相關的方法是否支持數組,判斷緩沖區的實現是否基于字節數組如果緩沖區的實現基于字節數組,返回字節數組 ByteBuf ByteBuf需要提供JDK ByteBuffer的功能(包含且不限于),主要有以下幾類基本功能: 7種Java基礎類型、byte[]、ByteBuffer(ByteBuf)的等的讀寫 緩沖區自身的copy和slice 設置網絡字節序 ...
摘要:回調函數提供兩個參數和,表示有沒有錯誤發生,是文件內容。文件關閉第一個參數文件時傳遞的文件描述符第二個參數回調函數回調函數有一個參數錯誤,關閉文件后執行。 showImg(//img.mukewang.com/5d3f890d0001836113660768.jpg); 人所缺乏的不是才干而是志向,不是成功的能力而是勤勞的意志。 —— 部爾衛 文章同步到github博客:https:/...
閱讀 3627·2023-04-26 02:32
閱讀 3905·2021-11-23 10:05
閱讀 2291·2021-10-08 10:04
閱讀 2711·2021-09-22 16:06
閱讀 3612·2021-09-22 15:27
閱讀 764·2019-08-30 15:54
閱讀 1698·2019-08-30 13:50
閱讀 2704·2019-08-29 13:56