您好,登錄后才能下訂單哦!
這篇文章主要講解了“Nodejs中的buffer緩存區(qū)的作用是什么”,文中的講解內(nèi)容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Nodejs中的buffer緩存區(qū)的作用是什么”吧!
涉及的知識點
Buffer 緩沖區(qū)
ECMAScript 6 入門 ArrayBuffer
深入淺出 Node.js
淺談malloc,calloc,realloc函數(shù)之間的區(qū)別
補碼
理解字節(jié)序
先說一下 JavaScript 中的 ArrayBuffer 的接口及其背景, 如下內(nèi)容來自于 ECMAScript 6 入門 ArrayBuffer 。
ArrayBuffer對象、TypedArray視圖和DataView視圖是 JavaScript 操作二進制數(shù)據(jù)的一個接口。這些對象早就存在,屬于獨立的規(guī)格(2011 年 2 月發(fā)布),ES6 將它們納入了 ECMAScript 規(guī)格,并且增加了新的方法。它們都是以數(shù)組的語法處理二進制數(shù)據(jù),所以統(tǒng)稱為二進制數(shù)組。
這個接口的原始設計目的,與 WebGL 項目有關。所謂 WebGL,就是指瀏覽器與顯卡之間的通信接口,為了滿足 JavaScript 與顯卡之間大量的、實時的數(shù)據(jù)交換,它們之間的數(shù)據(jù)通信必須是二進制的,而不能是傳統(tǒng)的文本格式。文本格式傳遞一個 32 位整數(shù),兩端的 JavaScript 腳本與顯卡都要進行格式轉化,將非常耗時。這時要是存在一種機制,可以像 C 語言那樣,直接操作字節(jié),將 4 個字節(jié)的 32 位整數(shù),以二進制形式原封不動地送入顯卡,腳本的性能就會大幅提升。
二進制數(shù)組就是在這種背景下誕生的。它很像 C 語言的數(shù)組,允許開發(fā)者以數(shù)組下標的形式,直接操作內(nèi)存,大大增強了 JavaScript 處理二進制數(shù)據(jù)的能力,使得開發(fā)者有可能通過 JavaScript 與操作系統(tǒng)的原生接口進行二進制通信。
看完我們知道, ArrayBuffer 系列接口使得 JavaScript 有了處理二進制數(shù)據(jù)的能力, 其使用方式主要是分為如下幾步
通過 ArrayBuffer 構造函數(shù), 創(chuàng)建長度為 10 的內(nèi)存區(qū)
通過 Uint8Array 構造函數(shù)傳參數(shù)使其指向 ArrayBuffer
向操作數(shù)組一樣向第一個字節(jié)寫入數(shù)據(jù) 123
const buf1 = new ArrayBuffer(10); const x1 = new Uint8Array(buf1); x1[0] = 123;
在 Node.js 中也完全可以使用 ArrayBuffer 相關的接口去處理二進制數(shù)據(jù), 仔細看完 ArrayBuffer 與 Buffer 的文檔可以發(fā)現(xiàn), Buffer 的進一步封裝能夠更簡單的上手與更好的性能, 接著讓我們?nèi)タ纯?Buffer 的使用例子
通過 alloc 方法創(chuàng)建長度為 10 的內(nèi)存區(qū)
通過 writeUInt8 向第一個字節(jié)寫入數(shù)據(jù) 123
通過 readUint8 讀取第一個字節(jié)的數(shù)據(jù)
const buf1 = Buffer.alloc(10); buf1.writeUInt8(123, 0) buf1.readUint8(0)
通過靜態(tài)方法 alloc 創(chuàng)建一個 Buffer 實例
Tips: 直接通過 Buffer 構造函數(shù)創(chuàng)建實例的方式由于安全性問題已經(jīng)廢棄
Buffer.alloc = function alloc(size, fill, encoding) { assertSize(size); if (fill !== undefined && fill !== 0 && size > 0) { const buf = createUnsafeBuffer(size); return _fill(buf, fill, 0, buf.length, encoding); } return new FastBuffer(size); }; class FastBuffer extends Uint8Array { constructor(bufferOrLength, byteOffset, length) { super(bufferOrLength, byteOffset, length); } }
發(fā)現(xiàn) Buffer 其實就是 Uint8Array, 這里再補充一下在 JavaScript 中也可以不通過 ArrayBuffer 對象, 直接使用 Uint8Array 操作內(nèi)存, 如以下的例子
通過 Uint8Array 構造函數(shù)創(chuàng)建長度為 10 的內(nèi)存區(qū)
向操作數(shù)組一樣向第一個字節(jié)寫入數(shù)據(jù) 123
const x1 = new Uint8Array(10); x1[0] = 123
那么 Node.js 中 Buffer 僅通過 Uint8Array 類, 如何模擬實現(xiàn)下面所有的視圖類型的行為, 以及 Buffer 又做了哪些其他的擴展了 ?
Int8Array:8 位有符號整數(shù),長度 1 個字節(jié)。
Uint8Array:8 位無符號整數(shù),長度 1 個字節(jié)。
Uint8ClampedArray:8 位無符號整數(shù),長度 1 個字節(jié),溢出處理不同。
Int16Array:16 位有符號整數(shù),長度 2 個字節(jié)。
Uint16Array:16 位無符號整數(shù),長度 2 個字節(jié)。
Int32Array:32 位有符號整數(shù),長度 4 個字節(jié)。
Uint32Array:32 位無符號整數(shù),長度 4 個字節(jié)。
Float32Array:32 位浮點數(shù),長度 4 個字節(jié)。
Float64Array:64 位浮點數(shù),長度 8 個字節(jié)。
提供了 alloc, allocUnsafe, allocUnsafeSlow 3個方法去創(chuàng)建一個 Buffer 實例, 上面講了 alloc 方法沒有什么特別, 下面講一下另外兩種方法
與 alloc 不同的是, allocUnsafe 并沒有直接返回 FastBuffer, 而是始終從 allocPool 中類似 slice 出來的內(nèi)存區(qū)。
Buffer.allocUnsafe = function allocUnsafe(size) { assertSize(size); return allocate(size); }; function allocate(size) { if (size <= 0) { return new FastBuffer(); } if (size < (Buffer.poolSize >>> 1)) { if (size > (poolSize - poolOffset)) createPool(); const b = new FastBuffer(allocPool, poolOffset, size); poolOffset += size; alignPool(); return b; } return createUnsafeBuffer(size); }
這塊內(nèi)容其實我也是很早之前在讀樸靈大佬的深入淺出 Node.js 就有所映像, 為什么這樣做了, 原因主要如下
為了高效地使用申請來的內(nèi)存,Node采用了slab分配機制。slab是一種動態(tài)內(nèi)存管理機制,最早
誕生于SunOS操作系統(tǒng)(Solaris)中,目前在一些*nix操作系統(tǒng)中有廣泛的應用,如FreeBSD和Linux。 簡單而言,slab就是一塊申請好的固定大小的內(nèi)存區(qū)域。slab具有如下3種狀態(tài)。
full:完全分配狀態(tài)。
partial:部分分配狀態(tài)。
empty:沒有被分配狀態(tài)。
當我們需要一個Buffer對象,可以通過以下方式分配指定大小的Buffer對象:
new Buffer(size); Node以8 KB為界限來區(qū)分Buffer是大對象還是小對象: Buffer.poolSize = 8 * 1024; 這個8 KB的值也就是每個slab的大小值,在JavaScript層面,以它作為單位單元進行內(nèi)存的分配。
比起 allocUnsafe 從預先申請好的 allocPool 內(nèi)存中切割出來的內(nèi)存區(qū), allocUnsafeSlow 是直接通過 createUnsafeBuffer 先創(chuàng)建的內(nèi)存區(qū)域。從命名可知直接使用 Uint8Array 等都是 Slow 緩慢的。
Buffer.allocUnsafeSlow = function allocUnsafeSlow(size) { assertSize(size); return createUnsafeBuffer(size); };
這個 Unsafe 不安全又是怎么回事了, 其實我們發(fā)現(xiàn)直接通過 Uint8Array 申請的內(nèi)存都是填充了 0 數(shù)據(jù)的認為都是安全的, 那么 Node.js 又做了什么操作使其沒有被填充數(shù)據(jù)了 ?
let zeroFill = getZeroFillToggle(); function createUnsafeBuffer(size) { zeroFill[0] = 0; try { return new FastBuffer(size); } finally { zeroFill[0] = 1; } }
那么我們只能去探究一下 zeroFill 在創(chuàng)建前后, 類似開關的操作的是如何實現(xiàn)這個功能
zeroFill 的值來自于 getZeroFillToggle 方法返回, 其實現(xiàn)在 src/node_buffer.cc 文件中, 整個看下來也是比較費腦。
簡要的分析一下 zeroFill 的設置主要是修改了 zero_fill_field 這個變量的值, zero_fill_field 值主要使用在 Allocate 分配器函數(shù)中。
void GetZeroFillToggle(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); NodeArrayBufferAllocator* allocator = env->isolate_data()->node_allocator(); Local<ArrayBuffer> ab; // It can be a nullptr when running inside an isolate where we // do not own the ArrayBuffer allocator. if (allocator == nullptr) { // Create a dummy Uint32Array - the JS land can only toggle the C++ land // setting when the allocator uses our toggle. With this the toggle in JS // land results in no-ops. ab = ArrayBuffer::New(env->isolate(), sizeof(uint32_t)); } else { uint32_t* zero_fill_field = allocator->zero_fill_field(); std::unique_ptr<BackingStore> backing = ArrayBuffer::NewBackingStore(zero_fill_field, sizeof(*zero_fill_field), [](void*, size_t, void*) {}, nullptr); ab = ArrayBuffer::New(env->isolate(), std::move(backing)); } ab->SetPrivate( env->context(), env->untransferable_object_private_symbol(), True(env->isolate())).Check(); args.GetReturnValue().Set(Uint32Array::New(ab, 0, 1)); }
內(nèi)存分配器的實現(xiàn)
從代碼實現(xiàn)可以看到如果 zero_fill_field 值為
真值的話會調(diào)用 UncheckedCalloc 去分配內(nèi)存
假值則調(diào)用 UncheckedMalloc 分配內(nèi)存
void* NodeArrayBufferAllocator::Allocate(size_t size) { void* ret; if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers) ret = UncheckedCalloc(size); else ret = UncheckedMalloc(size); if (LIKELY(ret != nullptr)) total_mem_usage_.fetch_add(size, std::memory_order_relaxed); return ret; }
接著 Allocate 函數(shù)的內(nèi)容
zero_fill_field 為真值的話會調(diào)用 UncheckedCalloc, 最后通過 calloc 去分配內(nèi)存
zero_fill_field 為假值則調(diào)用 UncheckedMalloc, 最后通過 realloc 去分配內(nèi)存
關于 calloc 與 realloc 函數(shù)
calloc: calloc 函數(shù)得到的內(nèi)存空間是經(jīng)過初始化的,其內(nèi)容全為0
realloc: realloc 函數(shù)得到的內(nèi)存空間是沒有經(jīng)過初始化的
至此讀到這里, 我們知道了 createUnsafeBuffer 創(chuàng)建未被初始化內(nèi)存的完整實現(xiàn), 在需要創(chuàng)建時設置 zero_fill_field 為 0 即假值即可, 同步創(chuàng)建成功再把 zero_fill_field 設置為 1 即真值就好了。
inline T* UncheckedCalloc(size_t n) { if (n == 0) n = 1; MultiplyWithOverflowCheck(sizeof(T), n); return static_cast<T*>(calloc(n, sizeof(T))); } template <typename T> inline T* UncheckedMalloc(size_t n) { if (n == 0) n = 1; return UncheckedRealloc<T>(nullptr, n); } template <typename T> T* UncheckedRealloc(T* pointer, size_t n) { size_t full_size = MultiplyWithOverflowCheck(sizeof(T), n); if (full_size == 0) { free(pointer); return nullptr; } void* allocated = realloc(pointer, full_size); if (UNLIKELY(allocated == nullptr)) { // Tell V8 that memory is low and retry. LowMemoryNotification(); allocated = realloc(pointer, full_size); } return static_cast<T*>(allocated); }
通過 Uint8Array 如何寫入讀取 Int8Array 數(shù)據(jù)? 如通過 writeInt8 寫入一個有符號的 -123 數(shù)據(jù)。
const buf1 = Buffer.alloc(10); buf1.writeInt8(-123, 0)
對寫入的數(shù)值范圍為 -128 到 127 進行了驗證
直接進行賦值操作
其實作為 Uint8Array 對應的 C 語言類型為 unsigned char, 可寫入的范圍為 0 到 255, 當寫入一個有符號的值時如 -123, 其最高位符號位為 1, 其二進制的原碼為 11111011, 最終存儲在計算機中所有的數(shù)值都是用補碼。所以其最終存儲的補碼為 10000101, 10 進制表示為 133。
此時如果通過 readUInt8 去讀取數(shù)據(jù)的話就會發(fā)現(xiàn)返回值為 133
如果通過 readInt8 去讀取的話, 套用代碼的實現(xiàn) 133 | (133 & 2 ** 7) * 0x1fffffe === -123 即滿足要求
function writeInt8(value, offset = 0) { return writeU_Int8(this, value, offset, -0x80, 0x7f); } function writeU_Int8(buf, value, offset, min, max) { value = +value; // `checkInt()` can not be used here because it checks two entries. validateNumber(offset, 'offset'); if (value > max || value < min) { throw new ERR_OUT_OF_RANGE('value', `>= ${min} and <= ${max}`, value); } if (buf[offset] === undefined) boundsError(offset, buf.length - 1); buf[offset] = value; return offset + 1; } function readInt8(offset = 0) { validateNumber(offset, 'offset'); const val = this[offset]; if (val === undefined) boundsError(offset, this.length - 1); return val | (val & 2 ** 7) * 0x1fffffe; }
計算機中的有符號數(shù)有三種表示方法,即原碼、反碼和補碼。三種表示方法均有符號位和數(shù)值位兩部分,符號位都是用0表示“正”,用1表示“負”,而數(shù)值位,三種表示方法各不相同。在計算機系統(tǒng)中,數(shù)值一律用補碼來表示和存儲。原因在于,使用補碼,可以將符號位和數(shù)值域統(tǒng)一處理;同時,加法和減法也可以統(tǒng)一處理。
通過 Uint8Array 如何寫入讀取 Uint16Array 數(shù)據(jù)?
從下面的代碼也是逐漸的看清了 Uint8Array 的實現(xiàn), 如果寫入 16 位的數(shù)組, 即會占用兩個字節(jié)長度的 Uint8Array, 每個字節(jié)存儲 8 位即可。
function writeU_Int16BE(buf, value, offset, min, max) { value = +value; checkInt(value, min, max, buf, offset, 1); buf[offset++] = (value >>> 8); buf[offset++] = value; return offset; } function readUInt16BE(offset = 0) { validateNumber(offset, 'offset'); const first = this[offset]; const last = this[offset + 1]; if (first === undefined || last === undefined) boundsError(offset, this.length - 2); return first * 2 ** 8 + last; }
BE 指的是大端字節(jié)序, LE 指的是小端字節(jié)序, 使用何種方式都是可以的。小端字節(jié)序寫用小端字節(jié)序讀, 端字節(jié)序寫就用大端字節(jié)序讀, 讀寫規(guī)則不一致則會造成亂碼, 更多可見 理解字節(jié)序。
大端字節(jié)序:高位字節(jié)在前,低位字節(jié)在后,這是人類讀寫數(shù)值的方法。
小端字節(jié)序:低位字節(jié)在前,高位字節(jié)在后,即以0x1122形式儲存。
對于 float32Array 的實現(xiàn), 相當于直接使用了 float32Array
寫入一個數(shù)值時直接賦值給 float32Array 第一位, 然后從 float32Array.buffe 中取出寫入的 4 個字節(jié)內(nèi)容
讀取時給 float32Array.buffe 4個字節(jié)逐個賦值, 然后直接返回 float32Array 第一位即可
const float32Array = new Float32Array(1); const uInt8Float32Array = new Uint8Array(float32Array.buffer); function writeFloatForwards(val, offset = 0) { val = +val; checkBounds(this, offset, 3); float32Array[0] = val; this[offset++] = uInt8Float32Array[0]; this[offset++] = uInt8Float32Array[1]; this[offset++] = uInt8Float32Array[2]; this[offset++] = uInt8Float32Array[3]; return offset; } function readFloatForwards(offset = 0) { validateNumber(offset, 'offset'); const first = this[offset]; const last = this[offset + 3]; if (first === undefined || last === undefined) boundsError(offset, this.length - 4); uInt8Float32Array[0] = first; uInt8Float32Array[1] = this[++offset]; uInt8Float32Array[2] = this[++offset]; uInt8Float32Array[3] = last; return float32Array[0]; }
感謝各位的閱讀,以上就是“Nodejs中的buffer緩存區(qū)的作用是什么”的內(nèi)容了,經(jīng)過本文的學習后,相信大家對Nodejs中的buffer緩存區(qū)的作用是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。