JavaScript ArrayBuffer淺析

時隔一年半,再次來到博客園。回首剛接觸前端時所寫的兩篇隨筆,沒法直視啊~javascript

----------------------------------------------------------------------------♠前端

 

簡介:java

  ArrayBuffer又稱類型化數組chrome

  javascript數組(Array)長什麼樣子,相信你們都清楚,那麼我說說差異應該就能夠了解這到底是個什麼了!編程

  1. 數組裏面能夠放數字、字符串、布爾值以及對象和數組等,ArrayBuffer放0和1組成的二進制數據
  2. 數組放在堆中,ArrayBuffer則把數據放在棧中(因此取數據時後者快)
  3. ArrayBuffer初始化後固定大小,數組則能夠自由增減。(準確的說,視圖才應該跟數組來比較這個特色)

構造函數:  數組

// new ArrayBuffer(Bytelength);
var arraybuffer = new ArrayBuffer(8);

//類方法ArrayBuffer.isView() 判斷某對象是否爲 視圖(這是什麼?往下看)
var int8a = new Int8Array(arraybuffer);
ArrayBuffer.isView(int8a)  //return true

//類屬性ArrayBuffer.length 默認值1,暫未發現用處
ArrayBuffer.length //return 1

//返回的對象具備byteLength屬性 值爲參數Bytelength
arraybuffer.byteLength //return 8

如上所訴:實例化一個對象的時候,僅須要傳入一個參數,即字節數。瀏覽器

字節(Byte):存儲空間的基本計量單位。一個字節等於8位(bit),每一位用0或1表示。服務器

以下爲兩個字節(16個格子):網絡

1 0 1 1 0 0 0 1 0 1 0 0 1 0 1 0

 

 視圖:app

  ArrayBuffer對象並無提供任何讀寫內存的方法,而是容許在其上方創建「視圖」,從而插入與讀取內存中的數據。如上:咱們在內存中分配了16個格子也就是兩個字節,若是咱們要劃分出A視圖與B視圖來瓜分這16個格子的話,代碼是這樣的:

var arraybuffer = new ArrayBuffer(8);

var aView = new Int8Array(arraybuffer,0,1);
var bView = new Int8Array(arraybuffer,1,1);

aView[0] = 1;  //二進制00000001
bView[0] = 2;  //二進制00000010

格子變成這樣了:

0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0

 

前8位表示數字1,後8位表示數字2

視圖類型

 視圖類型 數據類型  佔用位數  佔用字節  有無符號 
 Int8Array  整數  8  1  有
 Uint8Array  整數  8  1  無
 Uint8ClampedArray  整數  8  1  無
 Int16Array  整數  16  2  有
 Uint16Array  整數  16  2  無
 Int32Array  整數  32  4  有
 Uint32Array  整數  32  4  無
 Float32Array  浮點數  32  4  \
 Float64Array  浮點數  64  8  \

 

 

 

 

 

 

 

 

 

 

納尼?連最經常使用的字符串都沒有?悄悄告訴你,字符串自己也就用二進制保存的,後面細說。

佔用位數就至關於佔用了多少「格子」,等同於佔用字節數,能夠經過訪問視圖類型的靜態屬性:BYTES_PER_ELEMENT來獲取這個值,如:

Int8Array.BYTES_PER_ELEMENT          // 1
Uint16Array.BYTES_PER_ELEMENT        // 2
Int32Array.BYTES_PER_ELEMENT         // 4
Float32Array.BYTES_PER_ELEMENT       // 4
Float64Array.BYTES_PER_ELEMENT       // 8

 有無符號則表示該類數據類型是否包含負數,如:Int8Array表明8位有符號整數,其範圍爲 -128~127,而Uint8Array表明8位無符號整數,範圍是 0~255。

視圖構造函數

(一)

var view = new Int16Array([1,653,700,-90,88]);

如上:直接傳入必定特定範圍內的數組

(二)

var view = new Uint8Array(8);

view[0] = 10;
view[1] = 58;
view[2] = 156;
         .
         .
         .
view[7] = 255;

如上:傳入一個數組長度值,佔用的字節數 = 長度 X 該類型的BYTES_PER_ELEMENT

(三)

//new Int8Array(arraybuffer,start,length);

//參數
//arraybuffer爲ArrayBuffer的實例     必填
//start表示從第幾個字節開始            可選(默認從0開始)
//length表示數據個數                  可選(默認到分配的內存末尾)

var arraybuffer = new ArrayBuffer(32);

var aView = new Int16Array(arraybuffer,0,4);    //佔用0-7

var bView = new Float32Array(arraybuffer,8,5);  //佔用8-27

var cView = new Uint8Array(arraybuffer,28,8)    //僅剩4個,報錯Invalid typed array length

如上:首先分配了32字節的空間,A視圖使用Int16Array類型從0開始4個數據,每一個數據佔2個字節,因此A視圖一共佔用了8(0-7)個字節,後面的以此類推,最後留給C視圖的空間僅有4字節,然而傳入的length爲8,因此就超出了所分配內存的範圍而報錯。

萬一在分配視圖空間的時候,兩個試圖空間重疊了會發生什麼呢?舉個例子:

var arraybuffer = new ArrayBuffer(4);

var aView = new Int8Array(arraybuffer);  //從0開始到內存末尾

var bView = new Int8Array(arraybuffer,2); //從2開始到末尾

aView[0] = 1;
aView[1] = 2;
aView[2] = 3;
aView[3] = 4;

bView[0] = 9;
bView[1] = 8;

console.log(aView[2] );      //return   9
console.log(aView[3] );      //return   8

兩個相互重疊的視圖所佔據的內存空間,存在其中的值以最後一次寫進去的爲主。

假如咱們寫進去的數據類型不同又會發生什麼呢?↓

var arraybuffer = new ArrayBuffer(4);

var aView = new Int8Array(arraybuffer);  //從0開始到內存末尾

var bView = new Int16Array(arraybuffer,2); //從2開始到末尾

aView[0] = 1;
aView[1] = 2;
aView[2] = 3;
aView[3] = 4;

bView[0] = 500;
bView[1] = 8;

console.log(aView[2] );      //return   -12
console.log(aView[3] );      //return   1

咱們的B視圖從第二個字節開始,恰好能放一個16位的數據,然而咱們在下面又寫

bView[1] = 8;

並無報錯。說明在實例化視圖時超出內存空間不容許,而對內存讀寫時超出則沒有問題。不過bView[1]並無值,返回undefined

接下來咱們看看爲何返回-12與1呢?

500的二進制值爲(16位表示):00000001 11110100

1的二進制值爲(8位表示):     00000001

-12的二進制值表示(8位表示): 11110100

負數二進制轉化法(展開):

//先取負數的絕對值 

|-12| = 12

//12的二進制8位爲:  

 00001100

//對上一部的二進制取反,即1換成0,0換成1

11110011

//最後補碼,即對該值加 1

11110100
負數二進制轉換

原來如此,把500的16位分紅兩個8位就是1和-12。可是爲何-12在前面的呢?

這就要提到字節序這個東西了,詳細內容點擊連接看百科,這裏簡單說一下就是:500這個數字CPU-A認爲我應該存爲500,CPU-B認爲我應該存005,他們各有各的理由,不巧的是我的計算機就是將數字倒着存的,因此放在第三和第四字節裏面的東西分別是 11110100   00000001

 經過實驗(在chrome44裏),我總結了以下幾種狀況會獲得的結果:

  1. 若是在A類型中設置了超過A類型範圍的值,則將該值二進制後,取得對應範圍類型的結果做爲最終值;
  2. 設置某個字節的值爲String字符串,則該值爲0;
  3. 設置字節的值爲boolean值,則true爲1,false爲0;
  4. 若是在整型中設置了浮點型,則將浮點型取整(沒有四捨五入)後二進制轉化再取對應範圍的值;

其中第一點和第四點在設置最終值的時候都跟字節序有關,而爲了解決這個問題javascript引入了能夠設置字節序的新類型DataView,詳細狀況後面再說。

視圖的方法與屬性

var arraybuffer = new ArrayBuffer(8);

var view = new Int8Array(arraybuffer);


view.buffer           //return  arraybuffer      readonly

view.byteLength       //return   8               readonly

view.byteOffset       //return   0               readonly       

view.length           //return   0               readonly

view.entries()         //return Array Iterator object  包含鍵值對

view.keys()            //return Array Iterator object  只包含鍵

view.set([1,2,3],3)   //return [0,0,0,1,2,3,0,0]

view.subarray(1,4)   //return  [0,0,1] 根據上面set後的值 從位置1開始到4但不包括第4位      

 如上:前四個屬性都是隻讀的:

  buffer      返回ArrayBuffer的引用

  byteLength  返回字節長度

  byteOffset   返回視圖在該ArrayBuffer中佔用內存區域的起點位置

  length     返回視圖數據的個數

  set()       第一個參數爲已有的視圖或者數組,第二個參數表明從第幾個字節開始設置值

  subarray     返回一個新的視圖,若是第二個參數省略,則取剩餘的所有

entries和keys兩個方法目前僅在chrome和FireFox上面支持,返回一個數組迭代對象,你能夠經過該對象的next()方法依次取得相應的值,或者使用for...of循環進行迭代。

在寫這篇隨便的時候,我查看了 Mozilla開發者網絡 實際上這幾種視圖類型的原型TypedArray還有不少方法,諸如join、indexOf、forEach、map等,但惋惜其餘瀏覽器並不支持,或許未來會有所改善。

 DataView視圖

爲了解決各類硬件設備、數據傳輸等對默認字節序的設定不一而致使解碼時候會發生的混亂問題,javascript提供了DataView類型的視圖來讓開發者在對內存進行讀寫時手動設定字節序的類型。

(一)DataView構造函數

//new DataView(arraybuffer,byteOffset [, byteLength])

var arraybuffer = new ArrayBuffer(8);

var dv1 = new DataView(arraybuffer);    //0-7

var dv2 = new DataView(arraybuffer,2);    //2-7

var dv3 = new DataView(arraybuffer,3,2);    //3-4

(二)DataView實例化後的對象所具備的功能

Read Write
getInt8() setInt8()
getUint8() setUint8()
getInt16() setInt16()
getUint16() setUint16()
getInt32() setInt32()
getUint32() setUint32()
getFloat32() setFloat32()
getFloat64() setFloat64()

 

 

 

 

 

 

 

 

 

以上這些方法均遵循以下的語法

//讀取數據
var num  =  dataview.getUint32(byteOffset [, littleEndian]);

//寫入數據
dataview.setUint32(byteOffset,value [, littleEndian]);

//參數
//byteOffset   表示從內存的哪一個字節開始
//value           該對應字節將被設置的值
//littleEndian  字節序,true爲小端字節序,false或者不填爲大端字節序

值得注意的是,在DataView視圖中,讀寫超出其實例化時的範圍的值時,都會發生錯誤,這跟以前的固定類型的視圖不同,在使用時更加謹慎。

你能夠經過以下的方式來判斷運行當前javascript的機器使用哪種字節序

var littleEndian = (function() {
  var buffer = new ArrayBuffer(2);
  new DataView(buffer).setInt16(0, 256, true);
  return new Int16Array(buffer)[0] === 256;
})();
console.log(littleEndian); // true ---->littleEndian 
                           //false ---->BigEndian

ArrayBuffer與字符串

javascript的字符串使用UTF-16編碼的方式,因此咱們能夠這樣來作:

function Uint162Str(arraybuffer){
    return String.fromCharCode.apply(null,new Uint16Array(arraybuffer));
}

function Str2Uint16(str){
    //假設字符串」abc「 length=3,使用16位,則每個字母佔據2字節,總字節爲length乘以2
    var arraybuffer =new ArrayBuffer(str.length*2);
    var view = new Uint16Array(arraybuffer);
    for(var i=0,l=str.length;i<l;i++){
        view[i] = str.charCodeAt(i);
    }
    return view;
}    

在實際開發中,咱們可能會遇到從服務器端拿來的二進制數據的字符串使用的是UTF-8編碼的,這時咱們就須要先將UTF-8的二進制編碼還原成爲unicode對應的二進制,目前在有意義的unicode範圍內,已經能夠恰好用兩個字節來容納這個二進制值了,至關於UTF-8三個字節來表示的字符,固然也包括了咱們最關心的中文字符。然而關於unicode的那些事也比較繁瑣,就不在此討論了,你能夠參考這個:Decode UTF-8 with Javascript


參考連接

TypedArray(MDN)

Javascript TypedArray 解惑:Uint8Array 與 Uint8ClampedArray 的區別

後記:

  雖然兩三年前別人就寫過這個東西了,可是我仍是寫了一遍,之前寫了兩篇就放棄了,由於看到不少大神的文章,以爲本身實在太菜,連寫的資格都沒有。這一年半的時間都在看別人的,現在才恍然,我不過是要記錄本身的編程之路,經過寫做才能對問題研究得相對透徹,又不是要出教程,勇敢的告訴你們,鄙人才疏學淺,請各位看官多多指正!o(∩_∩)o 

相關文章
相關標籤/搜索