日常的學習筆記,包括 ES6、Promise、Node.js、Webpack、http 原理、Vue全家桶,后續可能還會繼續更新 Typescript、Vue3 和 常見的面試題 等等。
Buffer
參考文獻 buffer 緩沖區
緩沖區 Buffer
是暫時存放輸入輸出數據的一段內存。JS沒有二進制數據類型,而在處理TCP和文件流的時候,必須要處理二進制數據。所以 Node
提供了一個 Buffer
對象 來提供對二進制數據的操作。
Buffer
表示固定內存分配的全局對象,也就是說要放到緩存區中的字節數需要提前確定。而 Buffer
好比由一個 8位字節 組成的數組,可以有效的在JavasScript中表示二進制數據。
Buffer
簡單來說就是 node
中的 16進制 ,但 Buffer
在內存的標識也會全部使用 2進制 來進行表示。
(注:目前以無法使用 new Buffer()
創建 Buffer 實例,會存在安全性等問題。已被禁止使用。)
Buffer.alloc
Buffer
代表的是內存,一旦聲明好,就不能進行更改。
如果想要更改 Buffer
的大小,改小則對內存進行 截取 。 改大的話就需要創建一個更大的內存空間,將數據拷貝進行,也就是我們俗稱的 擴容 。
這時候就可以用到 Buffer
類的內置方法, Buffer.alloc()
。
Buffer.alloc(size[, fill[, encoding]])
,表示分配 size
個字節的新 Buffer
。 如果 fill
為 undefined
,則 Buffer
將以零填充。
- size:新的
Buffer
所需的長度。 - fill:用於預填充新
Buffer
的值,默認值為0
。 - encoding:如果
fill
是字符串,則這就是它的編碼。默認值為utf8
。
// 創建了一個指定長度的buffer實例
let buf1 = Buffer.alloc(3); // 最小單位是 3字節
console.log(buf1); // <Buffer 00 00 00>
let buf2 = Buffer.alloc(6); // 單位是 6
console.log(buf2); // <Buffer 00 00 00 00 00 00>
Buffer.from
上一篇文章中,我們曾經使用 Buffer.from
來創建過 base64 。
Buffer.from
方法用於創建包含指定 字符串,數組 或 buffer 的新 Buffer
實例。
Buffer.from
可以傳入的參數有很多,這里我們只擴展 字符串 和 數組 兩種。
Buffer.from(array)
Buffer.from(array)
使用 0
– 255
范圍內的字節 array
分配新的 Buffer
。 該范圍之外的數組條目將被截斷以符合它。
let buf1 = Buffer.from([0xe8, 0x8e, 0xab])
console.log(buf1); // <Buffer e8 8e ab>
let buf2 = Buffer.from([256, 0x8e, 0xab]) // 超過長度會自動取余
console.log(buf2); // <Buffer 00 8e ab>
let buf2 = Buffer.from(['aaa', 0x8e, 0xab]) // 不能在數組內存放其他數據類型
console.log(buf2); // <Buffer 00 8e ab>
(注:很少使用這種方法來定義 buffer
,因為需要指定存放的內容)
Buffer.from(string)
Buffer.from(string[, encoding])
創建包含 string
的新 Buffer
。 encoding
參數標識將 string
轉換為字節時要使用的字符編碼。
- string: 要編碼的字符串。
- encoding:
string
的編碼,默認值為utf8
。
let buf = Buffer.from('莫小尚');
console.log(buf); // <Buffer e8 8e ab e5 b0 8f e5 b0 9a>
Buffer.from(string)
是目前 Buffer
經常使用的方法。這個方法可以存儲數據,存儲的數據可以用 Buffer
進行表示。同時也可以和字符串之間進行相互轉化。
// 使用 .toString() 方法,將buffer轉換成字符串
console.log(buf.toString()); // 莫小尚
// 可以轉換成任意指定編碼
console.log(buf.toString('base64')); // 6I6r5bCP5bCa
buffer.toString()
默認值為 utf8
。
我們在進行讀寫操作時,如果不指定編碼,則所有讀取的文件內容都是 buffer
類型。
// test.txt
123456789
// index.js
const fs = require('fs');
let r = fs.readFileSync('./test.txt'); // 不指定 utf-8 編碼格式
console.log(r); // <Buffer 31 32 33 34 35 36 37 38 39>
Buffer的擴容
我們在操作 Buffer
時,會遇到原本規定的內存大小不夠的情況,這樣我們就需要對 Buffer
進行擴容。
const buf1 = Buffer.from('莫')
const buf2 = Buffer.from('小尚')
const bigBuf = Buffer.alloc(buf1.length + buf2.length);
console.log(bigBuf); // <Buffer 00 00 00 00 00 00 00 00 00>
buf.length
返回 buf
中的字節數。
這樣我們就創建了一個更大的 Buffer
對象,現在我們需要將內容拷貝到這個大 buffer 中。
buf1.copy(bigBuf, 0, 0, buf1.length);
buf2.copy(bigBuf, buf1.length, 0, buf2.length);
console.log(bigBuf.toString()); // 莫小尚
這里我們使用到了 buf.copy()
方法,稍后會進行講解。
這樣我們就完成了一個簡單的擴容操作。 (注:在實際工作中,此方法並不常用。我們一般會使用 buf.concat
來進行擴容操作。)
const buf1 = Buffer.from('莫');
const buf2 = Buffer.from('小尚');
const bigBuf = Buffer.concat([buf1, buf2]);
console.log(bigBuf.toString()); // 莫小尚
關於 buf.concat
的使用,稍后也會進行詳解。
Buffer.copy
buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])
將數據從 buf
的區域復制到 target
的區域,即使 target
內存區域與 buf
重疊。
- target:被拷貝的
Buffer
或Uint8Array
,也就是我們的 大容量Buffer
。 - targetStart:
target
內開始寫入的偏移量,默認值為0
。 - sourceStart:
buf
內開始復制的偏移量,默認值為0
。 - sourceEnd:
buf
內停止復制的偏移量(不包括),默認值為buf.length
。 - [callBack]:復制的字節數。
我們剛才使用了 buf.copy
進行了簡單的擴容操作,那么 buf.copy
的實現原理是什么呢。
方法實現
首先我們清楚, buf.copy
中一共接受四個參數,分別是 target
、targetStart
、 sourceStart
、 sourceEnd
。
現在我們來看一下完成后的代碼。
Buffer.prototype.copy = function (target, targetStart, sourceStart = 0, sourceEnd = this.length) {
for (let i = sourceStart; i < sourceEnd; i++) {
target[targetStart++] = this[i];
}
}
實現了 buf.copy
,我們就可以來看一下 buf.concat
方法了。
Buffer.concat
Buffer.concat(list[, totalLength])
會返回新的 Buffer
,它是將 list
中的所有 Buffer
實例連接在一起的結果。
- list:要拼接的
Buffer
或Uint8Array
實例的數組列表。 - totalLength:連接時
list
中Buffer
實例的總長度。 - callBack:返回一個新的
Buffer
。
在上面的例子中,我們使用 buf.concat
實現了一個 Buffer
擴容的例子。
下面我們就來詳解一下它的方法實現。
方法實現
根據 buf.concat
的使用方式,我們可以大概了解到一個思路。 那就是將傳入的 Buffer
實例通過 拷貝 的方式將其拼接成一個大的 Buffer
類。
這樣我們就可以大概手寫出其實現原理了。
Buffer.concat = function (bufferList, len = bufferList.reduce((a, b) => a + b.length, 0)) {
let buffer = Buffer.alloc(len);
// 記錄下一次 開始拼接的 位置
let offset = 0;
bufferList.forEach(buf => {
// 判斷是不是 Buffer
if (Buffer.isBuffer(buf)) {
buf.copy(buffer, offset);
offset += buf.length;
}
})
return buffer;
}
這樣我們就完成了 buf.concat
的實現。
本篇文章由 莫小尚 創作,文章中如有任何問題和紕漏,歡迎您的指正與交流。
您也可以關注我的 個人站點、博客園 和 掘金,我會在文章產出后同步上傳到這些平台上。
最后感謝您的支持!