溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Node中的Buffer類怎么使用

發(fā)布時(shí)間:2022-12-13 09:34:33 來(lái)源:億速云 閱讀:128 作者:iii 欄目:web開(kāi)發(fā)

這篇文章主要介紹了Node中的Buffer類怎么使用的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇Node中的Buffer類怎么使用文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。

TypedArray出來(lái)之前,JavaScript這門語(yǔ)言是不能很好地處理原始二進(jìn)制數(shù)據(jù)(raw binary data)的,這是因?yàn)橐婚_(kāi)始的時(shí)候JavaScript主要還是應(yīng)用在瀏覽器中作為腳本語(yǔ)言使用,所以需要處理原生二進(jìn)制數(shù)據(jù)的場(chǎng)景是少之又少。而Node出來(lái)后,由于服務(wù)端的應(yīng)用需要處理大量的二進(jìn)制流例如文件讀寫(xiě),TCP連接等,所以Node在JavaScript(V8)之外,定義了一種新的數(shù)據(jù)類型Buffer。由于Buffer在Node應(yīng)用中使用十分廣泛,所以只有真正掌握了它的用法,你才能寫(xiě)出更好的Node應(yīng)用。

二進(jìn)制基礎(chǔ)

在正式介紹Buffer的具體用法之前,我們先來(lái)簡(jiǎn)單回顧一下有關(guān)二進(jìn)制的知識(shí)。

身為程序員,我們應(yīng)該都不會(huì)對(duì)二進(jìn)制感到陌生,因?yàn)?strong>計(jì)算機(jī)所有的數(shù)據(jù)底層都是以二進(jìn)制(binary)的格式儲(chǔ)存的。換句話來(lái)說(shuō)你電腦里面的文件,不管是純文本還是圖片還是視頻,在計(jì)算機(jī)的硬盤里面都是由01這兩個(gè)數(shù)字組成的。在計(jì)算機(jī)科學(xué)中我們把0或者1單個(gè)數(shù)字叫做一個(gè)比特(bit),8個(gè)比特可以組成一個(gè)字節(jié)(byte)。十進(jìn)制(decimal)數(shù)字16如果用1個(gè)字節(jié)來(lái)表示的話,底層存儲(chǔ)結(jié)構(gòu)是:Node中的Buffer類怎么使用我們可以看到16用二進(jìn)制表示的話相比于十進(jìn)制的表示一下子多了6位數(shù)字,如果數(shù)字再大點(diǎn)的話二進(jìn)制的位數(shù)會(huì)更多,這樣我們無(wú)論是閱讀還是編寫(xiě)起來(lái)都很不方便。因?yàn)檫@個(gè)原因,程序員一般喜歡用十六進(jìn)制(hexadecimal)來(lái)表示數(shù)據(jù)而不是直接使用二進(jìn)制,例如我們?cè)趯?xiě)CSS的時(shí)候color的值用的就是16進(jìn)制(例如#FFFFFF)而不是一堆0和1。

字符編碼

既然所有數(shù)據(jù)底層都是二進(jìn)制,網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)也是二進(jìn)制的話,為什么我們現(xiàn)在閱讀的文章是中文而不是一堆01呢?這里就要介紹一下字符編碼的概念了。所謂的字符編碼簡(jiǎn)單來(lái)說(shuō)就是一個(gè)映射關(guān)系表,它表示的是字符(中文字符、英文字符或者其它字符)是如何和二進(jìn)制數(shù)字(包含若干個(gè)字節(jié))對(duì)應(yīng)起來(lái)的。舉個(gè)例子,如果使用我們熟悉的ascii來(lái)編碼,a這個(gè)英文字符的二進(jìn)制表示是0b01100001(0b是二進(jìn)制數(shù)字的前綴)。因此當(dāng)我們的電腦從某個(gè)以ascii編碼的文件中讀取到0b01100001這串二進(jìn)制數(shù)據(jù)時(shí),就會(huì)在屏幕中顯示a這個(gè)字符,同樣a這個(gè)字符保存到計(jì)算機(jī)中或者在網(wǎng)絡(luò)上傳輸都是0b01100001這個(gè)二進(jìn)制數(shù)據(jù)。除了ascii碼,常見(jiàn)的字符編碼還有utf-8utf-16等。

Buffer

掌握了基本的二進(jìn)制知識(shí)字符編碼的概念后,我們終于可以正式學(xué)習(xí)Buffer了。我們先來(lái)看一下Buffer的官方定義:

The Buffer class in Node.js is designed to handle raw binary data. Each buffer corresponds to some raw memory allocated outside V8. Buffers act somewhat like arrays of integers, but aren't resizable and have a whole bunch of methods specifically for binary data. The integers in a buffer each represent a byte and so are limited to values from 0 to 255 inclusive. When using console.log() to print the Buffer instance, you'll get a chain of values in hexadecimal values.

簡(jiǎn)單來(lái)說(shuō)所謂的Buffer就是Node在V8堆內(nèi)存之外分配的一塊固定大小的內(nèi)存空間。當(dāng)Buffer被用console.log打印出來(lái)時(shí),會(huì)以字節(jié)為單位,打印出一串以十六進(jìn)制表示的值。

創(chuàng)建Buffer

了解完Buffer的基本概念后,我們?cè)賮?lái)創(chuàng)建一個(gè)Buffer對(duì)象。創(chuàng)建Buffer的方式有很多種,常見(jiàn)的有Buffer.alloc,Buffer.allocUnsafeBuffer.from。

Buffer.alloc(size[, fill[, encoding]])

這是最常見(jiàn)的創(chuàng)建Buffer的方式,只需要傳入Buffer的大小即可

const buff = Buffer.alloc(5)

console.log(buff)
// Prints: <Buffer 00 00 00 00 00>

上面的代碼中我創(chuàng)建了一個(gè)大小為5個(gè)字節(jié)的Buffer區(qū)域,console.log函數(shù)會(huì)打印出五個(gè)連續(xù)的十六進(jìn)制數(shù)字,表示當(dāng)前Buffer儲(chǔ)存的內(nèi)容。我們可以看到當(dāng)前的Buffer被填滿了0,這是Node默認(rèn)的行為,我們可以設(shè)置后面兩個(gè)參數(shù)fillencoding來(lái)指定初始化的時(shí)候填入另外的內(nèi)容。

這里值得一提的是我在上面的代碼中使用的是Node全局的Buffer對(duì)象,而沒(méi)有從node:buffer包中顯式導(dǎo)入,這完全是因?yàn)榫帉?xiě)方便,在實(shí)際開(kāi)發(fā)中應(yīng)該采用后者的寫(xiě)法:

import { Buffer } from 'node:buffer'

Buffer.allocUnsafe(size)

Buffer.allocUnsafeBuffer.alloc的最大區(qū)別是使用allocUnsafe函數(shù)申請(qǐng)到的內(nèi)存空間是沒(méi)有被初始化的,也就是說(shuō)可能還殘留了上次使用的數(shù)據(jù),因此會(huì)有數(shù)據(jù)安全的問(wèn)題。allocUnsafe函數(shù)接收一個(gè)size參數(shù)作為buffer區(qū)域的大小:

const buff = Buffer.allocUnsafe(5)

console.log(buff)
// Prints (實(shí)際內(nèi)容可能有出入): <Buffer 8b 3f 01 00 00>

從上面的輸出結(jié)果來(lái)看我們是控制不了使用Buffer.allocUnsafe分配出來(lái)的buffer內(nèi)容的。也正是由于不對(duì)分配過(guò)來(lái)的內(nèi)存進(jìn)行初始化所以這個(gè)函數(shù)分配Buffer的速度會(huì)比Buffer.alloc更快,我們?cè)趯?shí)際開(kāi)發(fā)中應(yīng)該根據(jù)自己實(shí)際的需要進(jìn)行取舍。

Buffer.from

這個(gè)函數(shù)是我們最常用的創(chuàng)建Buffer的函數(shù),它有很多不同的重載,也就是說(shuō)傳入不同的參數(shù)會(huì)有不同的表現(xiàn)行為。我們來(lái)看幾個(gè)常見(jiàn)的重載:

Buffer.from(string[, encoding])

當(dāng)我們傳入的第一個(gè)參數(shù)是字符串類型時(shí),Buffer.from會(huì)根據(jù)字符串的編碼(encoding參數(shù),默認(rèn)是utf8)生成該字符串對(duì)應(yīng)的二進(jìn)制表示。看個(gè)例子:

const buff = Buffer.from('你好世界')

console.log(buff)
// Prints: <Buffer e4 bd a0 e5 a5 bd e4 b8 96 e7 95 8c>
console.log(buff.toString())
// Prints: '你好世界'
console.log(buff.toString('ascii'))
// Prints: ''d= e%=d8\x16g\x15\f''

在上面例子中,我使用"你好世界"這個(gè)字符串完成了Buffer的初始化工作,由于我沒(méi)有傳入第二個(gè)encoding參數(shù),所以默認(rèn)使用的是utf8編碼。后面我們通過(guò)查看第一個(gè)console.log的輸出可以發(fā)現(xiàn),雖然我們傳入的字符串只有四個(gè)字符,可是初始化的Buffer卻有12個(gè)字節(jié),這是因?yàn)?strong>utf8編碼中一個(gè)漢字會(huì)使用3個(gè)字節(jié)來(lái)表示。接著我們通過(guò)buff.toString() 方法來(lái)查看buff的內(nèi)容,由于toString方法的默認(rèn)編碼輸出格式是utf8,所以我們可以看到第二個(gè)console.log可以正確輸出buff儲(chǔ)存的內(nèi)容。不過(guò)在第三個(gè)console.log中我們指定了字符的編碼類型是ascii,這個(gè)時(shí)候我們會(huì)看到一堆亂碼??吹竭@里我想你對(duì)我之前提到的字符編碼一定有更深的認(rèn)識(shí)了。

Buffer.from(buffer)

當(dāng)Buffer.from接收的參數(shù)是一個(gè)buffer對(duì)象時(shí),Node會(huì)創(chuàng)建一個(gè)新的Buffer實(shí)例,然后將傳進(jìn)來(lái)的buffer內(nèi)容拷貝到新的Buffer對(duì)象里面。

const buf1 = Buffer.from('buffer')
const buf2 = Buffer.from(buf1)

console.log(buf1)
// Prints: <Buffer 62 75 66 66 65 72>
console.log(buf2)
// Prints: <Buffer 62 75 66 66 65 72>

buf1[0] = 0x61

console.log(buf1.toString())
// Prints: auffer
console.log(buf2.toString())
// Prints: buffer

在上面的例子中,我們先創(chuàng)建了一個(gè)Buffer對(duì)象buf1,里面存儲(chǔ)的內(nèi)容是"buffer"這個(gè)字符串,然后通過(guò)這個(gè)Buffer對(duì)象初始化了一個(gè)新的Buffer對(duì)象buf2。這個(gè)時(shí)候我們將buf1的第一個(gè)字節(jié)改為0x61(a的編碼),我們發(fā)現(xiàn)buf1的輸出變成了auffer,而buf2的內(nèi)容卻沒(méi)有發(fā)生變化,這也就印證了Buffer.from(buffer)是數(shù)據(jù)拷貝的觀點(diǎn)。

?注意:當(dāng)Buffer的數(shù)據(jù)很大的時(shí)候,Buffer.from拷貝數(shù)據(jù)的性能是很差的,會(huì)造成CPU占用飆升,主線程卡死的情況,所以在使用這個(gè)函數(shù)的時(shí)候一定要清楚地知道Buffer.from(buffer)背后都做了什么。筆者就在實(shí)際項(xiàng)目開(kāi)發(fā)中踩過(guò)這個(gè)坑,導(dǎo)致線上服務(wù)響應(yīng)緩慢!

Buffer.from(arrayBuffer[, byteOffset[, length]])

說(shuō)完了buffer參數(shù),我們?cè)賮?lái)說(shuō)一下arrayBuffer參數(shù),它的表現(xiàn)和buffer是有很大的區(qū)別的。ArrayBuffer是ECMAScript定義的一種數(shù)據(jù)類型,它簡(jiǎn)單來(lái)說(shuō)就是一片你不可以直接(或者不方便)使用的內(nèi)存,你必須通過(guò)一些諸如Uint16ArrayTypedArray對(duì)象作為View來(lái)使用這片內(nèi)存,例如一個(gè)Uint16Array對(duì)象的.buffer屬性就是一個(gè)ArrayBuffer對(duì)象。當(dāng)Buffer.from函數(shù)接收一個(gè)ArrayBuffer作為參數(shù)時(shí),Node會(huì)創(chuàng)建一個(gè)新的Buffer對(duì)象,不過(guò)這個(gè)Buffer對(duì)象指向的內(nèi)容還是原來(lái)ArrayBuffer的內(nèi)容,沒(méi)有任何的數(shù)據(jù)拷貝行為。我們來(lái)看個(gè)例子:

const arr = new Uint16Array(2)

arr[0] = 5000
arr[1] = 4000

const buf = Buffer.from(arr.buffer)

console.log(buf)
// Prints: <Buffer 88 13 a0 0f>

// 改變?cè)瓉?lái)數(shù)組的數(shù)字
arr[1] = 6000

console.log(buf)
// Prints: <Buffer 88 13 70 17>

從上面例子的輸出我們可以知道,arrbuf對(duì)象會(huì)共用同一片內(nèi)存空間,所以當(dāng)我們改變?cè)瓟?shù)組的數(shù)據(jù)時(shí),buf的數(shù)據(jù)也會(huì)發(fā)生相應(yīng)的變化。

其它Buffer操作

看完了創(chuàng)建Buffer的幾種做法,我們接著來(lái)看一下Buffer其它的一些常用API或者屬性

buf.length

這個(gè)函數(shù)會(huì)返回當(dāng)前buffer占用了多少字節(jié)

// 創(chuàng)建一個(gè)大小為1234字節(jié)的Buffer對(duì)象
const buf1 = Buffer.alloc(1234)
console.log(buf1.length)
// Prints: 1234

const buf2 = Buffer.from('Hello')
console.log(buf2.length)
// Prints: 5

Buffer.poolSize

這個(gè)字段表示Node會(huì)為我們預(yù)創(chuàng)建的Buffer池子有多大,它的默認(rèn)值是8192,也就是8KB。Node在啟動(dòng)的時(shí)候,它會(huì)為我們預(yù)創(chuàng)建一個(gè)8KB大小的內(nèi)存池,當(dāng)用戶用某些API(例如Buffer.alloc)創(chuàng)建Buffer實(shí)例的時(shí)候可能會(huì)用到這個(gè)預(yù)創(chuàng)建的內(nèi)存池以提高效率,下面是一個(gè)具體的例子:

const buf1 = Buffer.from('Hello')
console.log(buf1.length)
// Prints: 5

// buf1的buffer屬性會(huì)指向其底層的ArrayBuffer對(duì)象對(duì)應(yīng)的內(nèi)存
console.log(buf1.buffer.byteLength)
// Prints: 8192

const buf2 = Buffer.from('World')
console.log(buf2.length)
// Prints: 5

// buf2的buffer屬性會(huì)指向其底層的ArrayBuffer對(duì)象對(duì)應(yīng)的內(nèi)存
console.log(buf2.buffer.byteLength)
// Prints: 8192

在上面的例子中,buf1buf2對(duì)象由于長(zhǎng)度都比較小所以會(huì)直接使用預(yù)創(chuàng)建的8KB內(nèi)存池。其在內(nèi)存的大概表示如圖:Node中的Buffer類怎么使用這里值得一提的是只有當(dāng)需要分配的內(nèi)存區(qū)域小于4KB(8KB的一半)并且現(xiàn)有的Buffer池子還夠用的時(shí)候,新建的Buffer才會(huì)直接使用當(dāng)前的池子,否則Node會(huì)新建一個(gè)新的8KB的池子或者直接在內(nèi)存里面分配一個(gè)區(qū)域(FastBuffer)。

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

這個(gè)函數(shù)可以按照一定的偏移量(offset)往一個(gè)Buffer實(shí)例里面寫(xiě)入一定長(zhǎng)度(length)的數(shù)據(jù)。我們來(lái)看一下具體的例子:

const buf = Buffer.from('Hello')

console.log(buf.toString())
// Prints: "Hello"

// 從第3個(gè)位置開(kāi)始寫(xiě)入'LLO'字符
buf.write('LLO', 2)
console.log("HeLLO")
// Prints: "HeLLO"

這里需要注意的是當(dāng)我們需要寫(xiě)入的字符串的長(zhǎng)度超過(guò)buffer所能容納的最長(zhǎng)字符長(zhǎng)度(buf.length)時(shí),超過(guò)長(zhǎng)度的字符會(huì)被丟棄:

const buf = Buffer.from('Hello')

buf.write('LLO!', 2)
console.log(buf.toString())
// Print:s "HeLLO"

另外,當(dāng)我們寫(xiě)入的字符長(zhǎng)度超過(guò)buffer的最長(zhǎng)長(zhǎng)度,并且最后一個(gè)可以寫(xiě)入的字符不能全部填滿時(shí),最后一個(gè)字符整個(gè)不寫(xiě)入:

const buf = Buffer.from('Hello')

buf.write('LL你', 2)
console.log(buf.toString())
// Prints "HeLLo"

在上面的例子中,由于"你"是中文字符,需要占用三個(gè)字節(jié),所以不能全部塞進(jìn)buf里面,因此整個(gè)字符的三個(gè)字節(jié)都被丟棄了,buf對(duì)象的最后一個(gè)字節(jié)還是保持"o"不變。

Buffer.concat(list[, totalLength])

這個(gè)函數(shù)可以用來(lái)拼接多個(gè)Buffer對(duì)象生成一個(gè)新的buffer。函數(shù)的第一個(gè)參數(shù)是待拼接的Buffer數(shù)組,第二個(gè)參數(shù)表示拼接完的buffer的長(zhǎng)度是多少(totalLength)。下面是一個(gè)簡(jiǎn)單的例子:

const buf1 = Buffer.from('Hello')
const buf2 = Buffer.from('World')

const buf = Buffer.concat([buf1, buf2])
console.log(buf.toString())
// Prints "HelloWorld"

上面的例子中,因?yàn)槲覀儧](méi)有指定最終生成Buffer對(duì)象的長(zhǎng)度,所以Node會(huì)計(jì)算出一個(gè)默認(rèn)值,那就是buf.totalLength = buf1.length + buf2.length。而如果我們指定了totalLength的值的話,當(dāng)這個(gè)值比buf1.lengh + buf2.length小時(shí),Node會(huì)截?cái)?/strong>最后生成的buffer;如果指定的值比buf1.length + buf2.length大時(shí),生成buf對(duì)象的長(zhǎng)度還是totalLength,多出來(lái)的位數(shù)填充的內(nèi)容是0。

這里還有一點(diǎn)值得指出的是,Buffer.concat最后拼接出來(lái)的Buffer對(duì)象是通過(guò)拷貝原來(lái)Buffer對(duì)象得出來(lái),所以改變?cè)瓉?lái)的Buffer對(duì)象的內(nèi)容不會(huì)影響到生成的Buffer對(duì)象,不過(guò)這里我們還是需要考慮拷貝的性能問(wèn)題就是了。

Buffer對(duì)象的垃圾回收

在文章剛開(kāi)始的時(shí)候我就說(shuō)過(guò)Node所有的Buffer對(duì)象分配的內(nèi)存區(qū)域都是獨(dú)立于V8堆空間的,屬于堆外內(nèi)存。那么是否這就意味著B(niǎo)uffer對(duì)象不受V8垃圾回收機(jī)制的影響需要我們手動(dòng)管理內(nèi)存了呢?其實(shí)不是的,我們每次使用Node的API創(chuàng)建一個(gè)新的Buffer對(duì)象的時(shí)候,每個(gè)Buffer對(duì)象都在JavaScript的空間對(duì)應(yīng)著一個(gè)對(duì)象(Buffer內(nèi)存的引用),這個(gè)對(duì)象是受V8垃圾回收控制的,而Node只需要在這個(gè)引用被垃圾回收的時(shí)候掛一些鉤子來(lái)釋放掉Buffer指向的堆外內(nèi)存就可以了。簡(jiǎn)單來(lái)說(shuō)Buffer分配的空間我們不需要操心,V8的垃圾回收機(jī)制會(huì)幫我們回收掉沒(méi)用的內(nèi)存。

關(guān)于“Node中的Buffer類怎么使用”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“Node中的Buffer類怎么使用”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI