node 核心模組學習之 Buffer

JackZhouMine發表於2019-02-16

node 核心模組學習之Buffer

何為 Buffer

在ES6引入 TypeArray 之前,JS沒有能讀取和操作二進位制資料流的機制,Buffer 作為 Node API 引入,以便能和 TCP 網路流、檔案流等進行互動。 目前 ES6 中有 TypeArray 了,Buffer 類以更加優化和適用的於 Node 操作的方式實現了 Unit8Array API。
總之,Buffer 就是用來操作二進位制資料的,位於全域性變數中,無需引入即可使用。
Buffer 例項類似於 整型陣列,緩衝區大小在建立是確定,不能調整,記憶體有C++申請,JS 分配。

Instances of the Buffer class are similar to arrays of integers but correspond to fixed-sized, raw memory allocations outside the V8 heap. The size of the Buffer is established when it is created and cannot be changed.

…mechanism for reading or manipulating streams of binary data. The Buffer class was introduced as part of the Node.js API to make it possible to interact with octet streams in the context of things like TCP streams and file system operations。

Buffer 快取區,計算機讀取速度和處理不匹配,讀取速度高於處理速度時,會開闢一段記憶體區域存放待處理的資料,這個段記憶體就叫緩衝區

例項化 Buffer

在v6.0之前建立Buffer物件直接使用new Buffer()建構函式來建立物件例項,但是Buffer對記憶體的許可權操作相比很大,可以直接捕獲一些敏感資訊,存在安全隱患,之後的版本,用下面幾個函式例項畫一個Buffer:

  • Buffer.from()
  • Buffer.alloc()
  • Buffer.allocUnsafe()
函式 引數 返回值
from arry 包含array的位元組副本的Buffer,陣列中的每一項表示一個8位位元組的數字,故值在0--255以內,否則取餘
from buffer 從buffer複製一個新的buffer
from arrayBuffer[,byteOffet,[,length]] 與arrayBuffer共享記憶體的Buffer
from string[,encoding] string初始化的Buffer
alloc size[,fill[encoding]] 指定大小的Buffer例項額,省略 fill,預設用0填充
allocUnsafe size 指定大小的buffer,不被初始化,可能包含敏感資訊

allocUnsafe分配的記憶體不被初始化,即歸零,記憶體速度快,但是可能包含舊資料,不覆蓋這些資料,就可能造成記憶體洩漏。

編碼

支援以下編碼:

  • utf
  • ascii
  • base64
  • binary
  • utf16le
  • hex

讀寫緩衝區

buffer.write(string[offset,[length]][,encoding])

  • string – 寫入緩衝區的字串;
  • offset – 開始寫入的位置,預設 0;
  • length – 寫入位元組,預設 buf.length
  • encoding – 編碼,預設 utf8。

返回值:int:寫入的實際大小,沒有足夠的空間儲存,只會寫入一部分。
buffer.toString([endcoding[,start=0[,end=buffer.length]]]) 解碼指定緩衝區的資料,並按 endcoding 編碼格式返回字串。

let buf = Buffer.alloc(10);//分配 10 個位元組的空間
console.log(buf)//<Buffer 00 00 00 00 00 00 00 00 00 00> 沒 fill ,用 0 填充

let len = buf.write(`this is a buffer`);// 16個位元組
console.log(buf)//<Buffer 74 68 69 73 20 69 73 20 61 20>
console.log(len)//10 

// 上面的程式碼和下面的一樣
let buffer = Buffer.from(`this is a buffer`.substring(0, 10))
console.log(buffer)//<Buffer 74 68 69 73 20 69 73 20 61 20>
console.log(buffer.length)//10

let buf2 = Buffer.alloc(8,10);// 分配 8 個位元組的記憶體,用 10 填充
console.log(buf2)//<Buffer 0a 0a 0a 0a 0a 0a 0a 0a>
let size = buf2.write(`this a buffer`, 2, 2);//從索引 2 開始寫,寫入2位元組的資料
console.log(buf2)//<Buffer 00 00 74 68 00 00 00 00>
console.log(size)//2
console.log(buf.toString(`utf16le`,2,8));//獩槧⁳
console.log(buf.toString(`base64`,2,8));//aXMgaXMg
console.log(buf.toString(`ascii`,2,8));//is is
console.log(buf.toString(`utf8`,2,8));//is is
console.log(buf.toString(`hex`,2,8));//697320697320

填充 buffer.fill(value[,offset=0[,end=buffer.length]][,endcoding])

value 可以是 BufferStringInt

const buf1 = Buffer.alloc(10).fill(`abcd`)//空間足夠,迴圈填充
console.log(buf1.toString())//abcdabcdab  迴圈填充,知道空間滿
const buf2 = Buffer.alloc(3).fill(`abcdef`);//空間不夠,截斷
console.log(buf2.toString());//abc
const buf3 = Buffer.alloc(10).fill(`abc`, 3);//從索引 3 開始填充
console.log(buf3);//<Buffer 00 00 00 61 62 63 61 62 63 61>
console.log(buf3.toString());//abcabcabca
const buf4 = Buffer.alloc(10).fill(`abc`, 3, 7);//從索引 3 開始填充,到索引 7 結束
console.log(buf4);//<Buffer 00 00 00 61 62 63 61 00 00 00>
console.log(buf4.toString());// abca
let buffer = Buffer.alloc(10).fill(`abcd`)
console.log(buffer.toString())
buffer=Buffer.alloc(10).fill(34) // 改變原來的 buffer
console.log(buffer.toString())

buffer 比較

buffer.equals(buffer)

比較兩個 buffer 的資料是否相同。

// 例子一:編碼一樣,內容相同
var buf1 = Buffer.from(`A`);
var buf2 = Buffer.from(`A`);

console.log( buf1.equals(buf2) );  // true

// 例子二:編碼一樣,內容不同
var buf3 = Buffer.from(`A`);
var buf4 = Buffer.from(`B`);

console.log( buf3.equals(buf4) );  // false

// 例子三:編碼不一樣,內容相同
var buf5 = Buffer.from(`ABC`);  // <Buffer 41 42 43>
var buf6 = Buffer.from(`414243`, `hex`);//<Buffer 41 42 43>
var buf7 = Buffer.from(`414243`, `utf16le`);//<Buffer 34 00 31 00 34 00 32 00 34 00 33 00>
console.log(buf5.equals(buf6));//true
console.log(buf7.equals(buf6));//false

buf.compare(target[, targetStart[, targetEnd[, sourceStart[, sourceEnd]]]])

compare 可規定比較的範圍,返回一個數字。

const buf1 = Buffer.from(`ABC`);
const buf2 = Buffer.from(`BCD`);
const buf3 = Buffer.from(`ABCD`);

console.log(buf1.compare(buf1));//0

console.log(buf1.compare(buf2));//-1

console.log(buf1.compare(buf3));//-1

console.log(buf2.compare(buf1));//1

console.log(buf2.compare(buf3));//1

//  ABC BCD ABCD 
console.log([buf1, buf2,buf3 ].sort(Buffer.compare));//[ <Buffer 41 42 43>, <Buffer 41 42 43 44>, <Buffer 42 43 44> ]  ABC ABCD BCD

arr.sort(Buffer.compare) — buffer 陣列排序,按比較,第一位能比出結果的,就確定了。

const buf1 = Buffer.from(`81234`);
const buf2 = Buffer.from(`80234`);
const arr = [buf1, buf2];
console.log(arr);//[ <Buffer 38 31 32 33 34>, <Buffer 38 30 32 33 34> ]
console.log(arr.sort(Buffer.compare));//[ <Buffer 38 30 32 33 34>, <Buffer 38 31 32 33 34> ] 第一位,38=38,不能得出順序,第二位,30 < 31,buf2 排在前面來。

檢查 buffer

Buffer.isBuffer(object)

計算需要分配的記憶體

Buffer.byteLength(string, encoding=‘utf8’)

buffer 大小 buffer.length

console.log(Buffer.byteLength(`☃☃`))// 6 需要 6 個位元組儲存 兩個☃☃ 
let buffer = Buffer.alloc(10).fill(`☃`,4)// 從索引 4 開始存,剛好能存 2 個 ☃
console.log(buffer.length)// 10 給 buffer 分配的記憶體空間
console.log(`☃☃`.length)//2  字串長度
console.log(buffer.toString())// `☃☃` 
console.log(buffer.toString().length)// 6 單位是位元組
console.log(Buffer.byteLength(`☃☃`))// 6 需要 6 個位元組儲存兩個☃

let buffer2 = Buffer.alloc(10).fill(`☃`,5)// 從索引 5 開始存,剛好能存 2 個 ☃ 還差 1 位元組空間
console.log(buffer2.toString())// `☃�` 有一個亂碼 
console.log(buffer2.toString().length)// 7 

buffer 連線 Buffer.concat(bufferList[,totalLength])

totalLength 是所有bufferList 元素長度的累加。
totalLength > 實際累加長度,用 0 填充;
totalLength < 實際累計長度,後面的捨棄。

//接著上面的程式碼
let buf3=Buffer.concat(arr,4);
console.log(buf3);//<Buffer 38 30 32 33 34 38> 捨棄四位
let buf4=Buffer.concat(arr,12);
console.log(buf4);//<Buffer 38 30 32 33 34 38 31 32 33 34 00 00> 0 填充兩位

複製 bufSource.copy(bufTarget[,targetStart[,ssourceStart[,sourceEnd]]])

複製 bufSource 的 sourceStart — sourceEnd-1 的位元組到 bufTarget 的 target 位置開始存放。
返回值int:實際存入的位元組數。目標 buffer 空間不夠,複製源會被階段。

const buf1 = Buffer.alloc(10);//分配 10 個位元組的空間
const buf2 = Buffer.from(`copyFunction`);
console.log(`複製前 buf1`, buf1);//複製前 buf1 <Buffer 00 00 00 00 00 00 00 00 00 00>

console.log(`複製前 buf2`, buf2);//複製前 buf2 <Buffer 63 6f 70 79 46 75 6e 63 74 69 6f 6e>

let result = buf2.copy(buf1, 4, 1, 5);//複製 buf1 1--5 位元組到 buf2 的  第 4 個索引位置開始存放,用 6 個位元組來存放4個位元組的資料,空間足夠。
console.log(`複製後 buf1`, buf1);//複製後 buf1 <Buffer 00 00 00 00 6f 70 79 46 00 00>
console.log(buf1.toString());//opyF
console.log(`複製後 buf2`, buf2);//複製後 buf2 <Buffer 63 6f 70 79 46 75 6e 63 74 69 6f 6e>
console.log(buf2.toString());//copyFunction
console.log(`複製後 result`, result);// 4

擷取 buf.slice([start=0[, end=buf.length]])

從 buf 中擷取一部分,組成新的 buffer , 兩者記憶體是共享的,所以修改時,會相互影響。

let buf1 = Buffer.alloc(5).fill(`abcd`)
let buf2 = buf1.slice()
console.log(buf2.toString())//abcda
let buf3 = buf1.slice(2,4)
console.log(buf3)//cd
console.log(buf3.toString())//cd
// 測試共享記憶體
console.log(buf3[0]=`100`)// 100 修改 buf3 的第一個值 為 d,返回修改後的值
console.log(buf3[0].toString())//100
console.log(buf3)//<Buffer 64 64>
console.log(buf3.toString())//dd  修改了
console.log(buf1)//<Buffer 61 62 64 64 61>
console.log(buf1.toString())//abdd buf1 也修改了

查詢 buf.indexOf(value,byteOffset=0)

從 buf 的 byteOffset 位置開始查詢 value,找到一個 value,返回其索引,否則返回 -1。value 可以是 StringIntBuffer

const buf2 = Buffer.from(`copyFunction`);
let result = buf2.indexOf(`c`, 3, `utf8`);
let result2 = buf2.indexOf(`c`);
let result3 = buf2.indexOf(`C`);
console.log(result);// 7 索引 3 之後第一個 c 的索引
console.log(result2);// 0 第一個 c
console.log(result3);// -1
buf2.indexOf(Buffer.from(`copy`),2,`utf8`);//-1
buf2.indexOf(9,4);//-1
let buffer = Buffer.alloc(10).fill(`abcd`);
console.log(buffer.toString());// abcdabcdab

// 遞迴查詢所有buffer
let indexs = [];//這裡很關鍵 儲存查詢到的下標
function recursiveIndexOf(buffer, char, start) {
    if (start < 0) { start = 0; }
    if (start > buffer.length - 1) { return -1; }// 開始下標大於 buffer 最大下標,返回 -1,也是遞迴出口
    let index = buffer.indexOf(char, start);
    if (index !== -1) {
        indexs.push(index);
        recursiveIndexOf(buffer, char, index + 1);
    }
    return indexs;
}
let result = recuisiveIndexOf(buffer, `a`, 0);
console.log(result);//[0,4,8]

buffer 轉 String 和 Object

buf.toString([encoding=utf8[,start=0[,end=buf.length]]])buf.toJSON()
toJSON 返回一個物件。{type:`Buffer`,data:[]} data 是 buffer 的值。

let buffer = Buffer.alloc(10).fill(`abcd`);
console.log(buffer.toString())//abcd
console.log(buffer.toJSON())//{ type: `Buffer`,data: [ 97, 98, 99, 100, 97, 98, 99, 100, 97, 98 ] }
console.log(Object.getPrototypeOf(buffer.toJSON()))// {}  可見 toJSON 返回的是物件
console.log(buffer[0])//97
console.log(buffer.toJSON().data[0])//97
console.log(buffer.toJSON().data)//[ 97, 98, 99, 100, 97, 98, 99, 100, 97, 98 ]
console.log(JSON.stringify(buffer.toJSON()))// 變成 json 字串

buffer 遍歷

buffer.keys()buffer.values()buffer.entries()

let buffer = Buffer.alloc(10).fill(`abcd`);
for (let key of buffer.keys()) {
    process.stdout.write(`${key}`)//輸出不換行,write 只能接收 String 和 Buffer 作為引數,可用模板字串轉換
    // console.log(key) 這樣輸出會換行
}
//0123456789
console.log()
for (let value of buffer.values()) {
    process.stdout.write(`${value}`);//9798991009798991009798
}
console.log(``)
for (let entriy of buffer.entries()) {
    console.log(`buffer[%d]==%d`, entriy[0], entriy[1])
}
/* 
buffer[0]==97
buffer[1]==98
buffer[2]==99
buffer[3]==100
buffer[4]==97
buffer[5]==98
buffer[6]==99
buffer[7]==100
buffer[8]==97
buffer[9]==98 
*/

TODO

  • TypeArray vs Buffer vs ArrayBuffer

最後

第一次在 SG 寫用 markdown 寫文章,體驗並不怎麼好,必須字型很小,看的眼疼,程式碼顯示不太友好,比如會出現滾動條,不帶行號等。SG 大佬眾多,歡迎指教。

參考文章:

相關文章