寫完上一篇文章想學Node.js,stream先有必要搞清楚
留下了懸念,stream
對象數據流轉的具體內容是什麼?本篇文章將爲你們進行深刻講解。javascript
看一段以前使用stream
操做文件的例子:java
var fileName = path.resolve(__dirname, 'data.txt'); var stream=fs.createReadStream(fileName); console.log('stream內容',stream); stream.on('data',function(chunk){ console.log(chunk instanceof Buffer) console.log(chunk); })
看一下打印結果,發現第一個stream是一個對象 ,截圖部份內容。node
第二個和第三個打印結果,c++
Buffer對象,相似數組,它的元素爲16進制的兩位數,即0到255的數值。能夠看出stream中流動的數據是Buffer類型,二進制數據,接下來開始咱們的Buffer探索之旅。git
二進制是計算機最底層的數據格式,字符串,數字,視頻,音頻,程序,網絡包等,在最底層都是用二進制來進行存儲。這些高級格式和二進制之間,均可以經過固定的編碼格式進行相互轉換。程序員
例如,C語言中int32類型的十進制整數(無符號),就佔用32bit即4byte,十進制的3對應的二進制就是00000000 00000000 00000000 00000011
。字符串也是同理,能夠根據ASCII編碼規則或者unicode編碼規則(如utf-8)等和二進制進行相互轉換。總之,計算機底層存儲的數據都是二進制格式,各類高級類型都有對應的編碼規則和二進制進行相互轉換。github
在最初的javascript
生態中,javascript
還運行在瀏覽器端,對於處理Unicode編碼的字符串數據很容易,可是對於處理二進制以及非Unicode
編碼的數據無能爲力,可是對於Server
端操做TCP/HTTP
以及文件I/O
的處理是必須的。我想就是所以在Node.js
裏面提供了Buffer
類處理二進制的數據,能夠處理各類類型的數據。數據庫
Buffer模塊的一個說明。segmentfault
在Node.js裏面一些重要模塊net、http、fs中的數據傳輸以及處理都有Buffer的身影,由於一些基礎的核心模塊都要依賴Buffer,因此在node啓動的時候,就已經加載了Buffer,咱們能夠在全局下面直接使用Buffer,無需經過require()。且 Buffer 的大小在建立時肯定,沒法調整。
在 NodeJS v6.0.0
版本以前,Buffer
實例是經過 Buffer 構造函數建立的,即便用 new 關鍵字建立,它根據提供的參數返回不一樣的 Buffer,但在以後的版本中這種聲明方式就被廢棄了,替代 new 的建立方式主要有如下幾種。後端
用 Buffer.alloc
和 Buffer.allocUnsafe
建立 Buffer 的傳參方式相同,參數爲建立 Buffer 的長度,數值類型。
// Buffer.alloc 和 Buffer.allocUnsafe 建立 Buffer // Buffer.alloc 建立 Buffer,建立一個大小爲6字節的空buffer,通過了初始化 let buf1 = Buffer.alloc(6); // Buffer.allocUnsafe 建立 Buffer,建立一個大小爲6字節的buffer,未通過初始化 let buf2 = Buffer.allocUnsafe(6); console.log(buf1); // <Buffer 00 00 00 00 00 00> console.log(buf2); // <Buffer 00 e7 8f a0 00 00>
經過代碼能夠看出,用 Buffer.alloc
和 Buffer.allocUnsafe
建立 Buffer
是有區別的,Buffer.alloc
建立的 Buffer
是被初始化過的,即 Buffer
的每一項都用 00 填充,而 Buffer.allocUnsafe
建立的 Buffer 並無通過初始化,在內存中只要有閒置的 Buffer 就直接 「抓過來」 使用。
Buffer.allocUnsafe
建立 Buffer
使得內存的分配很是快,但已分配的內存段可能包含潛在的敏感數據,有明顯性能優點的同時又是不安全的,因此使用需格外 「當心」。
Buffer.from(str, )
支持三種傳參方式:
ASCII
、UTF-8
、Base64
等等。Buffer
的每一項。 Buffer
,會將 Buffer
的每一項做爲新返回 Buffer
的每一項。說明:Buffer
目前支持的編碼格式
// 傳入字符串和字符編碼 let buf = Buffer.from("hello", "utf8"); console.log(buf); // <Buffer 68 65 6c 6c 6f>
// 數組成員爲十進制數 let buf = Buffer.from([1, 2, 3]); console.log(buf); // <Buffer 01 02 03>
// 數組成員爲十六進制數 let buf = Buffer.from([0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd]); console.log(buf); // <Buffer e4 bd a0 e5 a5 bd> console.log(buf.toString("utf8")); // 你好
在 NodeJS
中不支持 GB2312
編碼,默認支持 UTF-8
,在 GB2312
中,一個漢字佔兩個字節,而在 UTF-8
中,一個漢字
佔三個字節,因此上面 「你好」 的 Buffer
爲 6 個十六進制數組成。
// 數組成員爲字符串類型的數字 let buf = Buffer.from(["1", "2", "3"]); console.log(buf); // <Buffer 01 02 03>
傳入的數組成員能夠是任何進制的數值,當成員爲字符串的時候,若是值是數字會被自動識別成數值類型,若是值不是數字或成員爲是其餘非數值類型的數據,該成員會被初始化爲 00。
建立的 Buffer
能夠經過 toString
方法直接指定編碼進行轉換,默認編碼爲 UTF-8
。
// 傳入一個 Buffer let buf1 = Buffer.from("hello", "utf8"); let buf2 = Buffer.from(buf1); console.log(buf1); // <Buffer 68 65 6c 6c 6f> console.log(buf2); // <Buffer 68 65 6c 6c 6f> console.log(buf1 === buf2); // false console.log(buf1[0] === buf2[0]); // true buf1[1]=12; console.log(buf1); // <Buffer 68 0c 6c 6c 6f> console.log(buf2); // <Buffer 68 65 6c 6c 6f>
當傳入的參數爲一個 Buffer
的時候,會建立一個新的 Buffer
並複製上面的每個成員。
Buffer
爲引用類型,一個 Buffer
複製了另外一個 Buffer 的成員,當其中一個 Buffer 複製的成員有更改,另外一個 Buffer 對應的成員不會跟着改變,說明傳入buffer
建立新的Buffer
的時候是一個深拷貝的過程。
buffer對應於 V8 堆內存以外的一塊原始內存
Buffer
是一個典型的javascript
與C++
結合的模塊,與性能有關的用C++來實現,javascript
負責銜接和提供接口。Buffer
所佔的內存不是V8
堆內存,是獨立於V8
堆內存以外的內存,經過C++
層面實現內存申請(能夠說真正的內存是C++
層面提供的)、javascript
分配內存(能夠說JavaScript
層面只是使用它)。Buffer
在分配內存最終是使用ArrayBuffer
對象做爲載體。簡單點而言, 就是Buffer
模塊使用v8::ArrayBuffer
分配一片內存,經過TypedArray
中的v8::Uint8Array
來去寫數據。
說道Buffer的內存分配就不得不說Buffer
的8KB
的問題,對應buffer.js
源碼裏面的處理以下:
Buffer.poolSize = 8 * 1024; function allocate(size) { if(size <= 0 ) return new FastBuffer(); if(size < Buffer.poolSize >>> 1 ) if(size > poolSize - poolOffset) createPool(); var b = allocPool.slice(poolOffset,poolOffset + size); poolOffset += size; alignPool(); return b } else { return createUnsafeBuffer(size); } }
源碼直接看來就是以8KB做爲界限,若是寫入的數據大於8KB一半的話直接則直接去分配內存,若是小於4KB的話則從當前分配池裏面判斷是否夠空間放下當前存儲的數據,若是不夠則從新去申請8KB的內存空間,把數據存儲到新申請的空間裏面,若是足夠寫入則直接寫入數據到內存空間裏面,下圖爲其內存分配策略。
看內存分配策略圖,若是當前存儲了2KB的數據,後面要存儲5KB大小數據的時候分配池判斷所需內存空間大於4KB,則會去從新申請內存空間來存儲5KB數據而且分配池的當前偏移指針也是指向新申請的內存空間,這時候就以前剩餘的6KB(8KB-2KB)內存空間就會被擱置。至於爲何會用8KB
做爲存儲單元
分配,爲何大於8KB
按照大內存分配策略,在下面Buffer
內存分配機制優勢有說明。
仍是看上面那張內存分配圖,若是須要超過8KB
的Buffer
對象,將會直接分配一個SlowBuffer
對象做爲基礎單元,這個基礎單元將會被這個大Buffer
對象獨佔。
// Big buffer,just alloc one this.parent = new SlowBuffer(this.length); this.offset = 0;
這裏的SlowBUffer
類實在C++
中定義的,雖然引用buffer模塊能夠訪問到它,可是不推薦直接操做它,而是用Buffer
替代。這裏內部parent
屬性指向的SlowBuffer
對象來自Node
自身C++
中的定義,是C++
層面的Buffer
對象,所用內存不在V8
的堆中
此外,Buffer
單次的內存分配也有限制,而這個限制根據不一樣操做系統而不一樣,而這個限制能夠看到node_buffer.h
裏面
static const unsigned int kMaxLength = sizeof(int32_t) == sizeof(intptr_t) ? 0x3fffffff : 0x7fffffff;
對於32位的操做系統單次可最大分配的內存爲1G,對於64位或者更高的爲2G。
Buffer
真正的內存實在Node
的C++
層面提供的,JavaScript
層面只是使用它。當進行小而頻繁的Buffer
操做時,採用的是8KB
爲一個單元的機制進行預先申請和過後分配,使得Javascript
到操做系統之間沒必要有過多的內存申請方面的系統調用。對於大塊的Buffer
而言(大於8KB
),則直接使用C++
層面提供的內存,則無需細膩的分配操做。
根據最初代碼的打印結果,stream
中流動的數據就是Buffer
類型,也就是二進制
。
緣由一:
node
官方使用二進制做爲數據流動確定是考慮過不少,好比在上一篇 想學Node.js,stream先有必要搞清楚文章已經說過,stream主要的設計目的——是爲了優化IO操做
(文件IO
和網絡IO
),對應後端不管是文件IO
仍是網絡IO
,其中包含的數據格式都是未知的,有多是字符串,音頻,視頻,網絡包等等,即便就是字符串,它的編碼格式也是未知的,可能ASC編碼
,也可能utf-8
編碼,對於這些未知的狀況,還不如直接使用最通用的格式二進制
.
緣由二:
Buffer
對於http
請求也會帶來性能提高。
舉一個例子:
const http = require('http'); const fs = require('fs'); const path = require('path'); const server = http.createServer(function (req, res) { const fileName = path.resolve(__dirname, 'buffer-test.txt'); fs.readFile(fileName, function (err, data) { res.end(data) // 測試1 :直接返回二進制數據 // res.end(data.toString()) // 測試2 :返回字符串數據 }); }); server.listen(8000);
將代碼中的buffer-test
文件大小增長到50KB
左右,而後使用ab
工具測試一下性能,你會發現不管是從吞吐量
(Requests per second)仍是鏈接時間上,返回二進制格式比返回字符串格式效率提升不少。爲什麼字符串格式效率低?—— 由於網絡請求的數據原本就是二進制格式傳輸,雖然代碼中寫的是 response
返回字符串,最終還得再轉換爲二進制進行傳輸,多了一步操做,效率固然低了。
咱們能夠把整個流(stream)
和Buffer
的配合過程看做公交站
。在一些公交站,公交車
在沒有裝滿乘客前是不會發車的,或者在特定的時刻纔會發車。固然,乘客也可能在不一樣的時間,人流量大小也會有所不一樣,有人多的時候,有人少的時候,乘客
或公交車站
都沒法控制人流量。
不論什麼時候,早到的乘客都必須等待,直到公交車
接到指令能夠發車。當乘客到站,發現公交車
已經裝滿,或者已經開走,他就必須等待下一班車次。
總之,這裏總會有一個等待的地方,這個等待的區域
就是Node.js
中的Buffer
,Node.js
不能控制數據何時傳輸過來,傳輸速度,就好像公交車站沒法控制人流量同樣。他只能決定何時發送數據(公交車發車)。若是時間還不到,那麼Node.js
就會把數據放入Buffer
等待區域中,一個在RAM中的地址,直到把他們發送出去進行處理。
注意點:
Buffer
雖好也不要瞎用,Buffer
與String
二者均可以存儲字符串類型的數據,可是,String
與Buffer
不一樣,在內存分配上面,String
直接使用v8堆存儲
,不用通過c++
堆外分配內存,而且Google
也對String
進行優化,在實際的拼接測速對比中,String
比Buffer
快。可是Buffer
的出現是爲了處理二進制以及其餘非Unicode
編碼的數據,因此在處理非utf8
數據的時候須要使用到Buffer
來處理。
今天就分享這麼多,若是對分享的內容感興趣,能夠關注公衆號「程序員成長指北」,或者加入技術交流羣,你們一塊兒討論。
Node系列原創文章:
require時,exports和module.exports的區別你真的懂嗎
以爲不錯點個Star,歡迎 加羣 互相學習。
做者簡介:koala,專一完整的 Node.js 技術棧分享,從 JavaScript 到 Node.js,再到後端數據庫,祝您成爲優秀的高級 Node.js 工程師。【程序員成長指北】做者,Github 博客開源項目 https://github.com/koala-coding/goodBlog