Node閒談之Buffer

在剛接觸Nodejs的時候,有些概念總讓學前端的我感到困惑(雖然大學的時候也是在搞後端,世界上最好的語言,you know)。我能夠很快理解File System,Path等帶有明顯功能的模塊,卻一會兒不能理解Buffer這個玄而又玄的東西。由於,在前端的js實踐中,我不多去考慮什麼編碼方式,字符集之類的東西。二進制的理解僅限於大學課堂而已。本文與其說是在探討Node的Buffer模塊,倒不如說是來探討下如何從字符集,編碼的角度來理解Buffer這個模塊設立的意義。Node閒談系列不涉及具體的API講解,只會勾勾畫畫一些本身認爲比較重要的特性。javascript

1、基本知識

正如咱們學習編程的第一節課同樣,咱們明白,計算機就是一個二進制生物。它只能理解1和0,或者說有和沒有。「太極生兩儀,兩儀生四象,四象生八卦」。咱們在計算上看到的不管是任何東西,都是經過某種特殊的編碼方式,或者說是約定展示出來的。html

咱們很熟悉的ASCII碼就是這樣一種規範,和摩爾斯碼同樣,固定的值表示固定的含義。ASCII碼用1個字節8位來表示2^8=256種狀態。好比大寫字母A對應的是65,B對應66,是否是很簡單。ASCII碼並非佔滿了全部的256個位置,只用到了一半128位。在一個字節中,只佔用後面7位,最前面一位統一標識爲0。前端

可是咱們很容易就發現,ASCII碼遠遠是不夠的,最起碼咱們漢字就表示不了。後面考慮了許多其餘解決方案,咱們在此很少敘述,直接說最終解決方案——Unicode。Unicode想法很直接,就是想把全世界全部的字符都囊括進去。咱們通常認爲Unicode用兩個字節16位表示,而且徹底囊括了ASCII字符集。好比漢字「好」在unicode裏面二進制表示是 0101 1001 0111 1101。將其轉換成16進制就是U597D(U只是表示它們是unicode碼)。之因此我前面說是「通常認爲」,是由於這種想法是不許確的。Unicode一個平面(plane)是兩個字節。咱們常常談論的是它的一個基本平面,編碼是U+0000到U+FFFF,常見字符都在這個平面。Unicode還有16個輔助平面,碼點範圍是U+010000一直到U+10FFFF。通常而言,咱們只須要把關注點放在基本平面就好,而且要習慣Unicode的表示方式。由於,這是畢竟在各類編碼方式間轉化的「硬通貨」。java

咱們經常談到的utf-8,utf-16這些是什麼呢?這些都是具體的編碼方式,而Unicode是個字符集。以utf-8爲例,它在unicode碼的基礎上,進行從新編碼,把一些自己不須要佔滿2個字節的轉化爲1個字節。好比ASCII裏面的那些字符,在unicode裏面,第一個字節全是0,簡直是空間的浪費,也會把漢字編碼城3個字節。你盡能夠在控制檯試下Buffer.from('我','utf8')看下編碼後佔的字節數。node

javascript使用哪一種編碼方式?git

javascript採用Unicode字符集,可是只支持一種編碼方式。那就是USC-2。是否是沒有據說過?你能夠把它理解成utf-16。但它和utf-16究竟是什麼關係呢?github

二者的關係簡單說,就是UTF-16取代了UCS-2,或者說UCS-2整合進了UTF-16。因此,如今只有UTF-16,沒有UCS-2。編程

UCS-2只支持兩個字節,而在它後面纔出來的UTF-16在UCS-2的基礎上,利用輔助平面能夠支持4個字節。既然是UCS-2整合進UTF-16,那就存在有的字符UTF-16有,而UCS-2不存在的狀況。出現這種狀況怎麼辦?你們能夠參考下參考資料裏面阮老師的講述。後端

2、Buffer的生成

相關重要的APIapi

  • Buffer.alloc(size[, fill[, encoding]])
  • Buffer.allocUnsafe(size)
  • Buffer.allocUnsafeSlow(size)
  • Buffer.from(array)
  • buf.fill(value[, offset[, end]][, encoding])

在Buffer生成的過程當中,最大的關注點就是內存的申請和分配。原先new Buffer()生成Buffer的方法已經不建議再次使用,它和Buffer.allocUnsafe()方法同樣,可能包含敏感數據。

爲何會包含敏感數據呢?在生成buffer的過程當中,不是一步到位,要分爲兩步走,1,申請內存空間,2,申請的內存空間進行填充。Buffer.allocUnsafe()方法只完成了第一步。不完成第二步的後果就是,申請的空間可能「殘留了」之前內存上的數據。畢竟一起內存在計算機中老是申請了再釋放,釋放了再申請,不免就會致使一些數據沒有被及時清理乾淨。固然,因爲少了第二步操做,速度天然快了很多。

const a = Buffer.allocUnsafe(10);
console.log(a)
//<Buffer f0 4e a8 6f 01 02 00 00 00 20> 打印結果老是不同的,但咱們發現每一位上極可能不是00,這些數據就屬於敏感數據。

可使用buf.fill(0)進行後期的填充。但爲了不漏洞產生,應該避免使用Buffer.allocUnsafe()來分配內存。

Buffer.alloc()Buffer.allocUnsafe()安全的緣由在於它在第二步會把全部的舊數據清除掉,填充成0。

const a = Buffer.alloc(10);
console.log(a)
//<Buffer 00 00 00 00 00 00 00 00 00 00> 打印結果每一位都是0。

看到這裏,你是否是覺得Buffer.alloc()Buffer.allocUnsafe()的區別僅限於有沒有填充數據?其實並非的。真正與Buffer.alloc()差異在是否填充數據的是Buffer.allocUnsafeSlow()。原來,使用Buffer.allocUnsafe()分配內存須要藉助共享內存池(shared internal memory pool)。而Buffer.alloc()Buffer.allocUnsafeSlow()是直接在內存空間上開闢相應大小的內存空間。

Buffer.allocUnsafe() (與以前的 new Buffer(size) 機制相似)是三者中分配內存速度最快的方式,它採用了共享內存池(shared internal memory pool)這一方式,經過預先分配必定大小的一段內存,從中再向 JavaScript 分配相應大小的片斷,避免頻繁的向系統申請內存分配,來達到較高的效率。共享內存池的默認值 poolSize 爲 8KB(可從新賦值),只有當須要分配的內存小於等於 poolSize 的一半時,Buffer.allocUnsafe() 纔會從共享內存池從分配空間。

這裏的知識點到爲止,詳細的探討之後能夠能夠考慮專門寫一篇來講明,參考資料的內容也是至關不錯,建議閱讀。

3、Buffer的讀取和寫入

相關重要的API

  • buf.readInt8(offset[, noAssert])
  • buf.readDoubleBE(offset[, noAssert])
  • buf.readDoubleLE(offset[, noAssert])
  • buf.writeUInt8(value, offset[, noAssert])
  • buf.writeUInt16BE(value, offset[, noAssert])
  • buf.writeUInt16LE(value, offset[, noAssert])
  • ...

buffer只有可以讀寫,纔可以顯示其存在的價值。查看Buffer的文檔,Buffer的讀寫方法中有很是多以「BE」和「LE」結尾的方法。他們分別表明什麼呢?

大字序和小字序

「BE」表示的是「big endian」大端字節序,而「LE」天然表示「little endian」小端字節序。字節序是幹什麼的呢?咱們在描述一個字符的unicode碼的時候,習慣性地從左到右去寫。爲何不能夠從右右往左寫呢?多個字節不管是讀取仍是寫入,總要有一個順序,這就是「字節序」。大端序就是咱們常看到的高位字節在前,低位字節在後,小端序剛好相反。

爲何要區分大端序和小端序呢?不能都統一從一個方向讀取,寫入麼?

計算機電路先處理低位字節,效率比較高,由於計算都是從低位開始的。因此,計算機的內部處理都是小端字節序。
可是,人類仍是習慣讀寫大端字節序。因此,除了計算機的內部處理,其餘的場合幾乎都是大端字節序,好比網絡傳輸和文件儲存。

字節序的處理,就是一句話:"只有讀取的時候,才必須區分字節序,其餘狀況都不用考慮。"

好,下面咱們舉個實際的例子。

var buf = Buffer.from([1,3,5,7]);
//<Buffer 01 03 05 07>

buf.readInt16BE(0)
//259    
從buf中讀取16位的整數,因此讀取的第一個字符對應的碼點是 01 03轉化成10進制就是1*16^2+3 = 259。
buf.readInt16LE(0)
//756
// 小字序從右往左讀取,第一個字符對應的碼點是 03 01 轉化成10進制就是3*16^2+1 = 756

讀取和寫入是一個相反的過程,道理是同樣的。

//官方示例
const buf = Buffer.allocUnsafe(4);

buf.writeUInt8(0x3, 0);
buf.writeUInt8(0x4, 1);
buf.writeUInt8(0x23, 2);
buf.writeUInt8(0x42, 3);

// Prints: <Buffer 03 04 23 42>
console.log(buf);

Buffer還有不少頗有意思的方面須要進一步學習,待之後再進一步補充本文或者寫幾篇相關更爲深刻的文章。

未完待續。。。

參考資料

相關文章
相關標籤/搜索