徹底理解Node.js中的Buffer

lvwxx發表於2019-04-22

每當在Node.js中遇到Buffer,Streambinary data之類的單詞時,是否總是像我一樣感到困惑? 認為它們並不是常用的,而只適合Node.js專家和包開發人員去使用。

實際上,這些單詞是非常重要的,尤其對於用Node.js進行web開發而沒有任何CS學位的人員。

當然,如果你選擇繼續做一個普通的Node.js開發人員,你可能永遠不會直接使用它們。但是如果你想對Node.js的理解提升到下一個級別,那麼你確實需要更深入地瞭解Node的許多核心特性。比如Buffer。這正是我寫這篇文章的目的——幫助我們揭開其中一些特性的神祕面紗,並將Node.js的學習帶到下一個層次。

在開始前,先看一下Node.js官方文件對Buffer的說明

… 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類作為Node.js API的一部分引入,使操作二進位制資料流或與之互動成為可能。
複製程式碼

接下來我們更深入的去了解Buffer,streams,binary data。

什麼是二進位制資料

你可能已經知道計算機以二進位制檔案儲存和表示資料。二進位制就是1和0的集合。例如,下面是5個不同的二進位制檔案,5組不同的1和0:

10, 01, 001, 1110, 00101011
複製程式碼

二進位制中每個1和0都稱為Bit,這是二進位制數字的一種簡短形式。

為了儲存或表示一段資料,計算機需要將該資料轉換為二進位制表示。例如,要儲存數字12,計算機需要將12轉換成二進位制表示,即1100。

但是在工作中,number並不是唯一的資料型別。通常上還會有string,images,videos。計算機知道如何用二進位制表示所有的資料型別。比如計算機如何用二進位制表示string型別的“L”呢?要將任何字元儲存在二進位制檔案中,計算機首先將該字元轉換為數字,然後將該數字轉換為二進位制表示形式。對於字串“L”,計算機首先將L轉換成表示L的數字。

開啟瀏覽器控制檯,輸入“L”. charcodeat(0)。這時控制檯會顯示出數字76,這是字元“L”的數字表示。但是計算機又是如何知道每個字元表示的確切數字呢?它怎麼知道用76來表示L?

字符集

字符集已經定義好的表示每個字元的確切數字的規則。我們對這些規則有不同的定義,最流行的包括Unicode和ASCII。JavaScript可以很好地處理Unicode字符集。所以,瀏覽器中的Unicode規定76應該表示L

我們已經看到計算機是如何用數字表示字元的。轉換成數字之後計算機再把76轉換它的二進位制表示。

字元編碼

正如有一些字符集規則定義數字應該怎麼樣表示字元一樣,也有一些規則定義了數字應該如何在二進位制檔案中表示。具體來說,就是用多少位來表示數字。這叫做字元編碼。

字元編碼的一個規則是UTF-8。UTF-8宣告字元應該以bytes編碼。一個byte是8位(bit)的集合 —— 8個1和0。因此,UTF-8規定應該使用8個1和0來表示二進位制中任何字元。

之前的例子提到,數字12用二進位制表示為 1100,但是用UTF-8表示應該是8位才對。所以UTF-8規定,計算機需要在不滿8位的二進位制數字左邊新增更多的位,以使其成為一個位元組。所以12應該儲存為00001100

因此 76 在UTF-8規則下儲存表示為:01001100

這就是計算機在二進位制檔案中儲存字串或字元的方式。同樣,計算機也規定了圖片和視訊應該如何轉換、編碼和儲存在二進位制檔案中的規則。計算機將所有資料型別儲存在二進位制檔案中。

現在我們瞭解了什麼是二進位制資料,接下來我們介紹一下什麼是二進位制資料流。

js中的Stream只是表示隨著時間的推移從一個點移動到另一個點的資料序列。整個概念是,你有大量的資料要處理,但是你不需要等到所有的資料都可用後才開始處理它。基本上,這個大資料被分解並以塊的形式傳送。因此,從Buffer的原始定義來看,這僅僅意味著二進位制資料正在檔案系統中移動。例如,將儲存在file1.txt中的文字移動到file2.txt。

但是Buffer究竟如何幫助我們在流與二進位制資料進行互動或操作呢?Buffer到底是什麼?

Buffer

我們已經提到,資料流是資料從一個點移動到另一個點,但是它們究竟是如何移動的呢?

通常資料的移動是為了處理或讀取資料,並根據資料做出決策。在這個過程中,可能需要資料到達一個最小量或者最大量才能進行處理。因此,如果資料到達的速度快於程式消耗資料的速度,那麼多餘的資料需要在某個地方的等待來處理。另一方面,如果程式消耗資料的速度快於資料到達的速度,那麼早到達的少數資料需要等待一定數量的資料到達,然後再傳送出去進行處理。

那個“等候區”就是Buffer!它是計算機中的一個小物理位置,通常位於RAM中,資料在RAM中被臨時收集、等待,並最終發在流過程中送出去進行處理。

我們可以把整個stream和buffer過程看做一個汽車站。在某個汽車站,汽車直到有一定數量的乘客或者是一個特殊的時間才可以發車。此外,乘客可能在不同的時間以不同的速度到達。無論是旅客還是汽車站都不能控制旅客到達車站的時間。提前到達的乘客需要等汽車發車。當有些乘客到達時,乘客已經滿員或者汽車已經離開,需要等待下一輛汽車。

無論什麼情況,總有一個等待的地方。這就是Node.js的Buffer! js不能控制資料到達的速度或時間,也不能控制流的速度。它只能決定何時傳送資料。如果還沒有到時間,Node.js將把它們放在buffer中,即RAM中的一個小位置,直到將它們傳送出去進行處理為止。

一個典型的例子是,當你在觀看流媒體視訊時,可以看到buffer在工作。如果你的網際網路連線足夠快,流的速度將足夠快,可以立即填滿Buffer併傳送出去進行處理,然後再填入另一個Buffer,然後傳送出去,再傳送一個,再傳送一個,直到流完成為止。

但是如果你的連線很慢,在處理了第一組到達的資料後,視訊會被卡主,這意味著程式正在收集更多的資料,或者等待更多的資料到達。當buffer被填滿並處理後,播放器會繼續播放視訊。在播放的同時,更多的資料將繼續到達並在buffer中等待。

與Buffer互動

Node.js在處理流期間會自動建立buffer,我們也可以通過Nodejs提供的API自己建立buffer。根據你的需求,這裡有幾種不同的方法可以建立buffer。

// Create an empty buffer of size 10.
// A buffer that only can accommodate 10 bytes
const buf1 = Buffer.alloc(10)

// Create a buffer with content
const buf2 = Buffer.from("hello buffer")
複製程式碼

當建立成功buffer後,你就可以開始和它進行互動了。

// 檢視buffer的結構
buf1.toJSON()
// { type: 'Buffer', data: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }

buf2.toJSON()
//{ type: 'Buffer',data: [ 104, 101, 108, 108, 111, 32, 98, 117, 102, 102, 101, 114 ]}

buf1.length // 10
buf2.length // 12

// 寫操作
buf1.write("Buffer really rocks!")

// decode
buf1.toString() // 'Buffer rea'
// 因為buf1建立時只分配了10byte的空間。超過的將不會被儲存。
複製程式碼

更多的互動API,可以檢視官方文件

希望這篇介紹能幫助您更好地理解Node.js Buffer。

更多有關js的文章,請關注github

相關文章