
Buffer 作為 nodejs 中重要的概念和功能,為開發者提供了操作二進制的能力。本文記錄了幾個問題,來加深對 Buffer 的理解和使用:
-
認識緩沖器 -
如何申請堆外內存 -
如何計算字節長度 -
如何計算字節長度 -
如何轉換字符編碼 -
理解共享內存與拷貝內存
認識 Buffer(緩沖器)
Buffer 是 nodejs 核心 API,它提供我們處理二進制數據流的功能。Buffer 的使用和 ES2017 的 Uint8Array 非常相似,但由於 node 的特性,專門提供了更深入的 api。
Uint8Array 的字面意思就是:8 位無符號整型數組。一個字節是 8bit,而字節的表示也是由兩個 16 進制(4bit)的數字組成的。
const buf = Buffer.alloc(1);
console.log(buf); // output: <Buffer 00>
如何申請堆外內存
Buffer 可以跳出 nodejs 對堆內內存大小的限制。nodejs12 提供了 4 種 api 來申請堆外內存:
-
Buffer.from()
-
Buffer.alloc(size[, fill[, encoding]])
-
Buffer.allocUnsafe(size)
-
Buffer.allocUnsafeSlow(size)
Buffer.alloc vs Buffer.allocUnsafe
在申請內存時,可能這片內存之前存儲過其他數據。如果不清除原數據,那么會有數據泄漏的安全風險;如果清除原數據,速度上會慢一些。具體用哪種方式,根據實際情況定。
-
Buffer.alloc:申請指定大小的內存,並且清除原數據,默認填充 0 -
Buffer.allocUnsafe:申請指定大小內存,但不清除原數據,速度更快
根據提供的 api,可以手動實現一個alloc
:
function pollifyAlloc(size, fill = 0, encoding = "utf8") {
const buf = Buffer.allocUnsafe(size);
buf.fill(fill, 0, size, encoding);
return buf;
}
Buffer.allocUnsafe vs Buffer.allocUnsafeSlow
從命名上可以直接看出效果,Buffer.allocUnsafeSlow
更慢。因為當使用 Buffer.allocUnsafe
創建新的 Buffer 實例時,如果要分配的內存小於 4KB,則會從一個預分配的 Buffer 切割出來。 這可以避免垃圾回收機制因創建太多獨立的 Buffer 而過度使用。
這種方式通過消除跟蹤和清理的需要來改進性能和內存使用。
如何計算字節長度
利用 Buffer,可以獲得數據的真實所占字節。例如一個漢字,它的字符長度是 1。但由於是 utf8 編碼的漢字,所以占用 3 個字節。
直接利用Buffer.byteLength()
可以獲得字符串指定編碼的字節長度:
const str = "本文原文地址: xxoo521.com";
console.log(Buffer.byteLength(str, "utf8")); // output: 31
console.log(str.length); // output: 19
也可以直接訪問 Buffer 實例的 length 屬性(不推薦):
console.log(Buffer.from(str, "utf8").length); // output: 31
如何轉換字符編碼
Nodejs 當前支持的編碼格式有:ascii、utf8、utf16le、ucs2、base64、latin1、binary、hex。其他編碼需要借助三方庫來完成。
下面,是用Buffer.from()
和buf.toString()
來封裝的 nodejs 平台的編碼轉換函數:
function trans(str, from = "utf8", to = "utf8") {
const buf = Buffer.from(str, from);
return buf.toString(to);
}
// output: 5Y6f5paH5Zyw5Z2AOiB4eG9vNTIxLmNvbQ==
console.log(trans("原文地址: xxoo521.com", "utf8", "base64"));
共享內存與拷貝內存
在生成 Buffer 實例,操作二進制數據的時候,千萬要注意接口是基於共享內存,還是基於拷貝底層內存。
例如對於生成 Buffer 實例的from()
,不同類型的參數,nodejs 底層的行為是不同的。
為了更形象地解釋,請看下面兩段代碼。
代碼 1:
const buf1 = Buffer.from("buffer");
const buf2 = Buffer.from(buf1); // 拷貝參數中buffer的數據到新的實例
buf1[0]++;
console.log(buf1.toString()); // output: cuffer
console.log(buf2.toString()); // output: buffer
代碼 2:
const arr = new Uint8Array(1);
arr[0] = 97;
const buf1 = Buffer.from(arr.buffer);
console.log(buf1.toString()); // output: a
arr[0] = 98;
console.log(buf1.toString()); // output: b
在第二段代碼中,傳入Buffer.from
的參數類型是arrayBuffer
。因此Buffer.from
僅僅是創建視圖,而不是拷貝底層內存。buf1 和 arr 的內存是共享的。
在操作 Buffer 的過程中,需要特別注意共享和拷貝的區別,發生錯誤比較難排查。
參考鏈接
-
Buffer 文檔 -
MDN Uint8Array -
How to use Buffers in Node.js -
Do you want a better understanding of Buffer in Node.js? Check this out. -
An Introduction to Buffer in Node.js
最后
-
覺得不錯, 幫忙點個推薦唄,您的支持是對我最大的激勵 -
歡迎我的公眾號: 「心譚博客」,只專注於 前端 + 算法的原創分享