最近的幾篇文章中都涉及到了Buffer
, 雖然用過幾個API, 仍是系統總結一下, 主要針對API, 具體使用會少一點web
爲何使用Buffer
?bash
在引入 TypedArray 以前,JavaScript 語言沒有用於讀取或操做二進制數據流的機制。 Buffer 類是做爲 Node.js API 的一部分引入的,用於在 TCP 流、文件系統操做、以及其餘上下文中與八位字節流進行交互。網絡
最經常使用的場景在於:函數
TCP
流: 目前遇到用在封包和解包階段, 提到的三篇文章中都有涉及源碼分析
WebSocket
數據幀RPC
通信協議文件系統操做學習
參考ui
單純建立大小爲size
字節的新Buffer
Buffer.alloc(size[, fill[, encoding]])
分配的buffer
會經過buf.fill
進行初始化, 能確保永遠不會包含敏感數據
由於有初始化操做, 所以速度相較於allocUnsafe
慢得多
Buffer.allocUnsafe(size)
buffer
內存是未初始化的, 可能包含敏感數據Buffer.allocUnsafeSlow(size)
分配的buffer
內存是未初始化的, 可能包含敏感數據
建立一個非內存池的buffer
, 避免垃圾回收機制因建立太多獨立的buffer
而過分使用
三者選擇:
// 須要一個全新的buffer
Buffer.alloc
// 須要一個buffer, 不在乎有沒有敏感數據, 反正都會被覆蓋, 更經常使用
Buffer.allocUnsafe
// 須要一個須要再內存池中保留的buffer
Buffer.allocUnsafeSlow
複製代碼
Buffer.from(string[, encoding])
buffer
, 是最經常使用的Buffer.from(buffer)
buffer
建立一個buffer
, 經常使用於複製Buffer.from(array)
buffer
, 若是不是數字, 自動被0填充Buffer.from(arrayBuffer[, byteOffset[, length]])
: 由於沒用過, 記了也沒價值
Buffer.from(object[, offsetOrEncoding[, length]])
從對象建立一個buffer
, 實際相似於Buffer.from(string)
本質上是調用對象的Symbol.toPrimitive
或valueOf
方法
栗子1: Buffer.from(object)
class A {
[Symbol.toPrimitive]() {
return 'A'
}
}
Buffer.from(new A(), 'utf-8')
// 打印 <Buffer 41>
複製代碼
栗子2: Buffer.from(array)
Buffer.from(['a', '1', 2, 'b', '3'])
// 打印 <Buffer 00 01 02 00 03>
複製代碼
Buffer
實例可使用for..of
語法迭代, 相關的函數還有:
buf.keys()
buf.values()
buf.entries()
不過這樣很是無聊
const buf = Buffer.from([1, 2, 3]);
for (const b of buf) {
console.log(b);
}
// 打印:
// 1
// 2
// 3
複製代碼
索引操做符都比這有趣: 索引操做符 [index] 可用於獲取或設置 buf 中指定位置的字節
const buf = Buffer.from('Calabash')
console.log(buf[0]) // 67
複製代碼
buf.compare
buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])
buf
中的某個區域的數據到target
中的某個區域buf.equals(otherBuffer)
buffer
是否具備徹底相同的字節Buffer.concat(list[, totalLength])
Buffer
數組考慮一個w位(bit)的整數,位表示爲[Xw-1, Xw-2, ... ,X1, X0],其中Xw-1是最高有效位, 而X0是最低有效位。假設w是8的倍數,這些位就能被分組成爲字節,其中最高有效字節包含位[Xw-1, Xw-2, ... ,Xw-8], 而最低有效字節包含位[X7, X6, ... ,Xw-8], 大小端指的就是有效字節的排列方式
小端法(little endian): 最低有效字節在最前面
大端法(big endian): 最高有效字節在最前面
大端 vs 小端對比
// 假設後面兩個字節二進制值爲
1111 1111 0000 0001
// 轉爲十六進制爲
0xff 0x01
// 大端輸出 65281
console.log(Buffer.from([0xff, 0x01]).readUInt16BE(0).toString(10))
// 小端輸出 511
console.log(Buffer.from([0xff, 0x01]).readUInt16LE(0).toString(10))
複製代碼
TCP/IP爲任意整數數據項定義了統一的網絡字節順序(network byte order):大端字節順序
額外補充一點~
網絡序就是大端法字節順序
本地序依據機器類型,多是大端法字節順序或者小端法字節順序
讀字節流一共22個API, 其中包含以下兩個只須要一個字節就能表示, 不涉及大小端的API
buf.readInt8
: 讀取一個有符號的8位整數, 1個字節
buf.readUint8
: 讀取一個無符號的8位整數, 1個字節
其餘的數字類型包括:
讀取BigInt
類型, 8個字節:
readBigInt64BE
readBigInt64LE
readBigUInt64BE
readBigUInt64LE
讀取64位雙精度浮點數, 8個字節:
readDoubleBE
readDoubleLE
讀取32位浮點數, 4個字節:
readFloatBE
readFloatLE
讀取16位整數, 2個字節
readInt16BE
readInt16LE
readUInt16BE
readUInt16LE
讀取32位整數, 4個字節
readInt32BE
readInt32LE
readUInt32BE
readUInt32LE
讀取指定的字節數, 最高支持48位精度, 0~6個字節
readIntBE
readIntLE
readUIntBE
readUIntLE
寫字節流的API與上方一致, 只須要將read
換成write
一共涉及三個方法, 都會將buf
解析成無符號的x位整數的數組, 並以字節的順序原地進行交換
swap16()
: buf.length
必須是2的倍數
swap32()
: buf.length
必須是4的倍數
swap64()
: buf.length
必須是8的倍數
例如:
// 65281
Buffer.from([0xff, 0x01]).readUInt16BE(0).toString(10)
// 511
Buffer.from([0xff, 0x01]).readUInt16LE(0).toString(10)
// 511
Buffer.from([0xff, 0x01]).swap16().readUInt16BE(0).toString(10)
複製代碼
buf.toJSON
: 返回buf
的JSON格式, 當字符串化Buffer
時, JSON.stringify()
會調用該函數const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);
const json = JSON.stringify(buf);
console.log(json);
// 打印: {"type":"Buffer","data":[1,2,3,4,5]}
複製代碼
buf.slice(start, end)
: 建立一個指向與原始Buffer
同一內存得新Buffer
, 但進行了裁剪, 注意兩個對象所分配的內存是重疊的, 修改一個會影響另外一個
base64
和string
的轉換
// Y2FsYWJhc2g=
const base64Str = Buffer.from('calabash').toString('base64')
// calabash
Buffer.from(base64Str, 'base64').toString()
複製代碼
到這裏基本完成Buffer API
的學習, 不過以我目前的水平, 沒有須要本身封包解包的場景, 只有看別人封包解包了TAT