Javascript的二進制數據處理學習 ——nodejs環境和瀏覽器環境分別分析

之前用JavaScript主要是處理常規的數字、字符串、數組對象等數據,基本沒有試過用JavaScript處理二進制數據塊,最近的項目中涉及到這方面的東西,就花一段時間學了下這方面的API,在此總結一下。
首先瀏覽器是沒有主動讀取本地文件的權限的,因此對JavaScript處理二進制數據能力的學習,應該從運行在服務器端的nodejs看起。

Nodejs 中的 Buffer

爲了方便處理二進制數據,nodejs特意封裝了一個Buffer模塊。文檔地址:http://nodejs.cn/doc/node/buffer.html

buffer模塊的基礎API

能夠經過下面的方式來初始化一個Buffer對象,傳入參數50,這樣就在內存中申請了一個50byte,400bit的區域來備用,這塊區域的大小一旦申請就不能改變,而後經過Buffer對象的fill方法來填充這塊內存區域,。
傳入的「abc」字符串,默認會按照utf8編碼的方式解碼爲二進制數據,存入到這一塊內存區域中對應的位置。
能夠看到,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模塊基本用法

基礎API固然仍是官方文檔: http://nodejs.cn/doc/node/stream.html
把前邊的例子改寫:
 
或者更簡單的
或者
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中的一個片斷,提供一些方法來獲取或編輯這些片斷中的數據,不像類型化數組同樣把數據放入一個數組結構中。
方法

下表列出了 DataView 對象的方法。前端

方法node

描述web

getInt8 方法ajax

在相對於視圖開始處的指定字節偏移量位置處獲取 Int8 值。canvas

getUint8 方法 (DataView)數組

在相對於視圖開始處的指定字節偏移量位置處獲取 Uint8 值。瀏覽器

getInt16 方法 (DataView)服務器

在相對於視圖開始處的指定字節偏移量位置處獲取 Int16 值。websocket

getUint16 方法 (DataView)

在相對於視圖開始處的指定字節偏移量位置處獲取 Uint16 值。

getInt32 方法 (DataView)

在相對於視圖開始處的指定字節偏移量位置處獲取 Int32 值。

getUint32 方法 (DataView)

在相對於視圖開始處的指定字節偏移量位置處獲取 Uint32 值。

getFloat32 方法 (DataView)

在相對於視圖開始處的指定字節偏移量位置處獲取 Float32 值。

getFloat64 方法 (DataView)

在相對於視圖開始處的指定字節偏移量位置處獲取 Float64 值。

setInt8 方法 (DataView)

在相對於視圖開始處的指定字節偏移量位置處存儲 Int8 值。

setUint8 方法 (DataView)

在相對於視圖開始處的指定字節偏移量位置處存儲 Uint8 值。

setInt16 方法 (DataView)

在相對於視圖開始處的指定字節偏移量位置處存儲 Int16 值。

setUint16 方法 (DataView)

在相對於視圖開始處的指定字節偏移量位置處存儲 Uint16 值。

setInt32 方法 (DataView)

在相對於視圖開始處的指定字節偏移量位置處存儲 Int32 值。

setUint32 方法 (DataView)

在相對於視圖開始處的指定字節偏移量位置處存儲 Uint32 值。

setFloat32 方法 (DataView)

在相對於視圖開始處的指定字節偏移量位置處存儲 Float32 值。

setFloat64 方法 (DataView)

在相對於視圖開始處的指定字節偏移量位置處存儲 Float64 值。

一種特殊的類型化數組

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的二進制數據處理能力而封裝的一個類。
實際上ArrayBuffer和類型化數組也並非瀏覽器環境下獨有的東西,他們是es5規範裏邊的內容,在nodejs環境下也可使用,例如:
 
————————————————end———————————————
以上內容,若有錯誤,歡迎斧正!
相關文章
相關標籤/搜索