之前用JavaScript主要是處理常規的數字、字符串、數組對象等數據,基本沒有試過用JavaScript處理二進制數據塊,最近的項目中涉及到這方面的東西,就花一段時間學了下這方面的API,在此總結一下。
爲了方便處理二進制數據,nodejs特意封裝了一個Buffer模塊。文檔地址:http://nodejs.cn/doc/node/buffer.html
能夠經過下面的方式來初始化一個Buffer對象,傳入參數50,這樣就在內存中申請了一個50byte,400bit的區域來備用,這塊區域的大小一旦申請就不能改變,而後經過Buffer對象的fill方法來填充這塊內存區域,。
能夠看到,Buffer對象在debug工具中顯示的是一個長度爲50的Uint8Array數組,這個Uint8Array對象是啥後邊會解釋。
數組的每一位上都存儲着一個無符號8位整數,也就是一個字節,0~255。
Buffer對象初始化方法還包括:
Buffer對象還有一些比較、拼接、複製、填充等方法,在上面的文檔中都有
nodejs的fs模塊
不光手動初始化能夠獲取一個Buffer對象,經過fs模塊來讀取文件,也能夠獲取一個buffer對象。
dnf.exe是一個存放於硬盤中的31733096字節的文件
經過fs模塊的readFileSync方法讀取此文件,能夠獲得一個Buffer對象,此Buffer對象
運行以後結果以下:
這樣dnf.exe文件就被讀取到了內存中,nodejs中有了一個Buffer對象,其佔用的內存空間是31733096byte。
Buffer對象能夠被寫入本地文件系統,或者經過網絡寫入遠程的機器中,或者轉換爲字符串來作更多的操做,或者不作任何處理。
上面例子中的場景,咱們能夠在當前目錄下用次buffer寫入一個新的文件
運行完畢後:
這個例子能夠行得通,然而事情不會一直這麼簡單。
個人電腦內存大小是8G,當我想用這段代碼copy一個12G的文件時,會發生什麼事情呢? 在第一步讀取文件的時候,咱們須要建立一個佔用12G大小內存空間的Buffer對象,這樣顯然是行不通的,內存會爆掉。那怎麼辦呢?
Nodejs中的 Stream
什麼是Stream,爲何要有Stream
上一小節的例子中,咱們遇到了內存不夠用的狀況,顯然咱們就須要
把數據分紅一小塊一小塊,一塊一塊的放到內存中去處理,這樣內存就不會爆掉了~
在這裏我想比喻一下,方便理解:
CPU至關於一個
工人
工人須要操做工具加熱水(CPU須要運行代碼執行計算)
硬盤至關於一個
水池
水池裏邊能夠蓄水,容量很大,可是不能在水池裏邊直接加熱水,須要把水放進鍋中(硬盤能夠存放數據,容量很大,可是不能直接在硬盤中利用數據執行計算,須要把數據讀進內存中)
內存至關於一口
鍋
鍋能夠盛水並加熱,可是容量不大(內存中能夠存放數據用於計算,可是容量不大)
加熱-導入水(計算-導入數據)
那麼上面小節中咱們遇到的問題就是:池子中的水太多,一次倒鍋裏去就溢出來了,加熱不了了。因而咱們採起一種措施:
從水池中鏈接一個管道到鍋中,這根
管道(stream.Readable類)能夠把水從池子中導入鍋中
管子一開始是封閉的,咱們能夠把開關打開(綁定 Event: 'data',此事件綁定以後即刻觸發)
也能夠暫停導入(readable.pause())
能夠恢復導入(readable.resume())
還能夠手動導入(readable.read())
加熱完畢—導出水(計算完畢-導出數據)
當導入的一鍋水燒好之後,須要把這一鍋水倒出去才能處理下一鍋,因而
從鍋中鏈接一根
管道(stream.Writable類)到另外的容器中(多是硬盤中的另一塊區域,也多是遠程的另一臺機器)
和上面的readStream同樣,
管子能夠向另一個地方導出水(writable.write)
不計算只中轉
當鍋只是起一箇中轉的做用時,能夠把導入管接到導出管上去(readable.pipe(destination[, options]))
過程當中也能夠把他們分開(readable.unpipe([destination]))
既能夠導出也能夠導入的Stream
有的管道既能夠導入,也能夠導出(stream.Transform)
Stream模塊基本用法
把前邊的例子改寫:
、
或者更簡單的
或者
readstream中的內容默認是Buffer對象,在讀取以前設置字符編碼,讀取的時候便可獲取字符串。
Stream的應用場景
用到Stream的地方有
剛纔說的file system
http模塊的request對象和response對象
net模塊的數據傳輸
當咱們須要持續的向一個地址寫入二進制數據or從一個地址讀出二進制數據時,咱們也能夠根據nodejs官方提供的API實現咱們本身的讀寫stream
當咱們用nodejs作http代理的時候,對於客戶端,nodejs是服務器,response對象是一個writeStream;對於目標服務器,nodejs是客戶端,res對象是一個readStream,
所以直接res.pipe(response)就把目標服務器的數據轉發給客戶端了。
http服務端向客戶端寫入文件的時候,也能夠直接把文件的readStream對接到response上
瀏覽器中的 ArrayBuffer
上面nodejs中的Buffer對象在ws控制檯中顯示出來的是Uint8Array,對於這一點我查了下,發現es5實際上是有二進制處理的API的,只是在瀏覽器端用的實在很少,因此以前並無關注到。
下面是msdn文檔裏邊對ArrayBuffer的解釋:
ArrayBuffer 對象表示用於存儲不一樣類型化數組的數據的原始數據緩衝區。沒法直接讀取或寫入 ArrayBuffer,但能夠將它傳遞給類型化數組或 DataView 對象 來解釋原始緩衝區。可使用 ArrayBuffer 來存儲任何類型的數據(或混合類型的數據)。html
、
存儲二進制數據,定義時length以byte來計數,看起來是否是和nodejs環境下的Buffer很像?
可是看API這東西並不能直接寫入和讀取數據,因此,接着往下看吧!
ArrayBuffer的查看和編輯視圖——類型化數組和DataView
什麼是類型化數組
剛剛說到的ArrayBuffer,看起來有點像nodejs中的Buffer對象,可是又沒有Buffer對象的讀寫API,這個不太科學嘛,因此確定須要一種辦法來操做它。
實際上,ArrayBuffer對象就是一塊靜止的二進制數據存儲區:
00000001 00000010 000000011 00000100
當須要寫入或者操做這段內存區時,若是直接開幾個API讓你寫入01010101這種數據,我想你的心裏必定是崩潰的。
因此es5提供了一些視圖來表示和操做這些二進制數據,好比,每8位二進制數轉換成一個10進制的8位無符號整數,存入一個特殊數組中,就是
Uint8Array [1,2,3,4]
這種數組的原型並無指向Array.prototype,它不具備JavaScript普通數組的那些操做方法,同時這種數組裏邊全部的元素必定是一個0~255的整數。
當咱們操做 arr[3]=1以後,Uint8Array就變成了
[1,2,3,1]
而ArrayBuffer的內容就變成了
00000001 00000010 000000011 00000001
要注意的是,ArrayBuffer是原始數據,根據原始數據能夠建立多份不一樣類型的數據視圖,當任何一個視圖改變了原始數據後,其餘視圖所看到的數據都會發生變化,下面會給出例子。
這個Uint8Array 是否是很眼熟呢? 看起來nodejs中的Buffer和瀏覽器環境下的Uint8Array有某種聯繫啊!
類型化數組類型
這種視圖的類型有不少:
每種數組中的數據類型不一樣,看名字能夠明白他們存放的數據類型,例如Float32Array,就是把每32個二進制數轉換成一個無符號浮點數,存入這種類型化數組中。
類型化數組操做API
初始化方法:
屬性解釋:
操做方法:
DataView
DataView是一個能夠獲取ArrayBuffer數據和編輯ArrayBuffer數據的對象,它取ArrayBuffer中的一個片斷,提供一些方法來獲取或編輯這些片斷中的數據,不像類型化數組同樣把數據放入一個數組結構中。
一種特殊的類型化數組
Uint8ClampedArray和Uint8Array相似,惟一不一樣的是,當插入數組的值不在0~255之間的時候,取值策略不一樣,Uint8Array是將輸入的數字取模,而Uint8ClampedArray是大於255取255,小於0取0
Uint8ClampedArray在canvas的getImageData時用到,由於顏色值#ffffff,恰好用3個字節來表示,比較合適~
類型化數組理解時要注意的特色
初始化時字節取值範圍
ArrayBuffer初始化時的length是以字節(byte)爲單位的而不是位(bit),
經過緩衝區初始化類型化數組的時候,offerset表明的緩衝區中的偏移量,也
而類型化數組初始化時的length是類型化數組中的length表明的並非這次初始化要使用的緩衝區中的字節長度,而是類型化數組中的元素個數。
Uint32Array = new Uint32Array ( buffer, byteOffset, length);
因此例如Uint32Array實際被使用的緩衝區中的字節應該是從第byteOffset個到第byteOffset+4*length個字節,而不是從第byteOffset個字節到byteOffset+length個字節
類型化數組只是視圖,原始數據存在ArrayBuffer中,原始數據發生變化,全部視圖都變化
瀏覽器中的使用實例
1.ajax接收ArrayBuffer數據
var req = new XMLHttpRequest();
req.open('GET', "http://www.example.com");
req.responseType = "arraybuffer";
req.send();
req.onreadystatechange = function () {
if (req.readyState === 4) {
var buffer = req.response;
var dataview = new DataView(buffer);
var ints = new Uint8Array(buffer.byteLength);
for (var i = 0; i < ints.length; i++) {
ints[i] = dataview.getUint8(i);
}
alert(ints[10]);
}
}
2.websocket也支持ArrayBuffer類型的數據接收
var websocket = new WebSocket(sUri);
websocket.binaryType = "arraybuffer" ;
3.用於在前端建立blob對象,file對象下載or上傳
4.瀏覽器雖然無權限讀取本地文件,可是有權限向本地寫入文件,寫入文件到本地時可能會用到
5.視頻音頻播放(寫文件和播放視音頻我沒有驗證)
後面我會學習這些使用形式,到時候再作總結~
nodejs中的buffer和瀏覽器中的Uint8Array的關係
可見,nodejs中實現的Buffer其實是Uint8Arr數組的一個子類,是nodejs爲了進一步提高JavaScript的二進制數據處理能力而封裝的一個類。