Node.js Buffer 進階操作:切片、複製與記憶體管理
在掌握了 Buffer 的基礎建立與轉換後,本章將帶你深入探討 Buffer 的運算邏輯,特別是它們在記憶體中的表現形式與對效能的影響。
切片與視圖:subarray() 的真相
開發者最容易感到困惑的是 Buffer 的「切片」行為。與 JS 陣列的 slice() 不同,Buffer 的 subarray()(或舊稱 slice())不會複製資料,而是建立一個指向原始記憶體區塊的「映射/視圖」(View)。
const original = Buffer.from('Antigravity');
// 切割出前 4 個位元組
const sub = original.subarray(0, 4);
console.log(sub.toString()); // Anti
// 修改子 Buffer,會連帶修改原始數據!
sub.write('XXXX');
console.log(original.toString()); // XXXXgravity
如果你需要獨立的副本:請使用
Buffer.from(original.subarray(0, 4)) 或 Buffer.alloc() 建立新空間後再用 copy() 複製進去。手動複製內容:copy()
當你需要從一個 Buffer 部分移動資料到另一個 Buffer 時,copy() 是最精確的工具。
const source = Buffer.from('NodeJS-is-Cool');
const target = Buffer.alloc(4);
// source.copy(目標, 目標起始, 來源開始, 來源結束)
source.copy(target, 0, 0, 4);
console.log(target.toString()); // Node
內容比較與搜尋
在處理通訊協定或檔案格式時,比對二進位內容是日常任務:
buf.equals(otherBuf):快速檢查兩個 Buffer 的內容是否完全相同(二進位層級)。buf.indexOf(value):尋找特定內容(字串或另一個 Buffer)在檔案中的起始位置。Buffer.compare(buf1, buf2):用於排序 Buffer 陣列。
const b1 = Buffer.from('abc');
const b2 = Buffer.from('abcd');
console.log(b1.equals(b2)); // false
效能與記憶體管理建議
由於 Buffer 涉及到手動記憶體操作,有些性能上的最佳實務需要留意:
- 預分配大記憶體:如果你在迴圈中頻繁使用
Buffer.concat(),會導致大量的記憶體重新分配。建議預先alloc()一塊大的 Buffer,再用copy()依序填入。 - Buffer 池機制:Node.js 內部會維護一個小的 Buffer 池,用於處理較小的
Buffer.allocUnsafe請求,以減少系統頻繁向作業系統申請記憶體的開銷。 - 外部記憶體:Buffer 是在 V8 Heap 之外分配的。雖然這能避開 JS 的垃圾回收壓力,但如果建立了海量的 Buffer 而不釋放,依然會耗盡系統記憶體。
小知識:你可以用
process.memoryUsage() 來查看你的 Node.js 進程除了 Heap 之外,還佔用了多少系統記憶體 (rss) 以及 Buffer (external)。總結
Buffer 是 Node.js 處理二進位底層的基石。在下一章學習 Stream (串流) 時,你會更強烈地感受到:數據傳輸的本質其實就是一連串的「Buffer 片段在管道中流動」。