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 來存儲任何類型的數據(或混合類型的數據)。

 
存儲二進制數據,定義時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 對象的方法。

方法

描述

getInt8 方法

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

getUint8 方法 (DataView)

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

getInt16 方法 (DataView)

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

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———————————————
以上內容,如有錯誤,歡迎斧正!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM