ECMAScript6(7):二進制數組

這個部分若是沒有C語言和計算機基礎會比較難理解,若是實在理解不了能夠收藏它,往後再看。數組

二進制數組其實很早就有了,不過爲了 WebGL 中,數據能夠高效和顯卡交換數據。分爲3類:app

  • ArrayBuffer:表明內存中的一段二進制數據;
  • TypedArray:讀寫簡單的二進制數據,如 Uint8Array, Int16Array, Float32Array 等9類;
  • DataView:讀寫複雜的二進制數據,如 Uint8, Int16, Float32 等8類;
數據類型 字節長度 含義 對應 C 語言類型 TypedArray 類型 DataView 類型
Int8 1 8位有符號整數 char Int8Array Int8
Uint8 1 8位無符號整數 unsigned char Uint8Array Uint8
Uint8C 1 8位無符號整數(自動過濾溢出) unsigned char Uint8ClampedArray 不支持
Int16 2 16位有符號整數 short Int16Array Int16
Uint16 2 16位無符號整數 unsigned short Uint16Array Uint16
Int32 4 32位有符號整數 int Int32Array Int32
Uint32 4 32位無符號整數 unsigned int Uint32Array Uint32
Float32 4 32位浮點數 float Float32Array Float32
Float64 8 64位浮點數 double Float64Array Float64

ArrayBuffer

ArrayBuffer 表明內存中的一段二進制數據,咱們無法直接操做,須要利用視圖(TypedArray,DataView)按必定格式解讀二進制數據。但咱們依然能夠構造一段內存來存放二進制數據:函數

var buf = new ArrayBuffer(32);  //分配32個字節的內存存放數據, 默認全0
var dataview = new DataView(buf);   //將這段內存轉爲視圖
dataview.getUint8(0);   //獲得第一個8字節的值(無符號),0

這裏須要強調的是,分配內存空間不要太大!畢竟你的內存是有限的。
其次,不管使用什麼視圖,其實例化的內存若是共享,全部的寫入操做會修改每個視圖,由於內存共用的:this

var buf = new ArrayBuffer(32);
var view16 = new Int16Array(buf);
var viewu8 = new Uint8Array(buf);

console.log(viewu8[0]);   //0
view16[0]=-1;
console.log(viewu8[0]);   //255

這裏之因此獲得255,是由於內存共用致使的,但爲什麼不是-1?Int16Array 是有符號類型的,這樣二進制的最高位用做符號位,負數記爲1:1000 0000 0000 0001,以後的數字用移碼存儲,獲得-1的二進制爲:1111 1111 1111 1111, 以後利用Uint8Array讀取無符號的前8位,獲得1111 1111這個計算爲十進制爲 $2^8-1=255$。具體關於數制轉換和反碼補碼這裏再也不展開,不然就跑偏了。編碼

ArrayBuffer 對象也有幾個方法和屬性:code

  • byteLength: 獲得內存區域的字節長度
const N = 32;
var buf = new ArrayBuffer(N);
if(buf.byteLength === N){
  //分配成功
} else {
  //分配失敗
}
  • slice(start=0, end=this.byteLength): 分配新內存,並把先有內存 start 到 end 部分複製過去,返回這段新內存區域
var buf = new ArrayBuffer(32);
var newBuf = buf.slice(0,3);
  • isView(view): 判斷傳入的 view 是否當前 buffer 的視圖,是則返回 true, 不然 false。該方法暫沒法使用。
var buf1 = new ArrayBuffer(32);
var buf2 = new ArrayBuffer(32);
var buf1View = new Int8Array(buf1);
var buf2View = new Int8Array(buf2);

buf1.isView(buf1View);   //true
buf1.isView(buf2View);   //false

TypedArray

具備一個構造函數 DataView(), 接受一個ArrayBuffer參數,視圖化該段內存;或接受一個數組參數,實例化該數組爲二進制內容。獲得的值是一個數組,能夠直接使用[]訪問每一個位置的內容,有length屬性。其構造函數有9個:orm

數據類型 字節長度 含義 對應 C 語言類型 TypedArray 類型構造函數
Int8 1 8位有符號整數 char Int8Array()
Uint8 1 8位無符號整數 unsigned char Uint8Array()
Uint8C 1 8位無符號整數(自動過濾溢出) unsigned char Uint8ClampedArray()
Int16 2 16位有符號整數 short Int16Array()
Uint16 2 16位無符號整數 unsigned short Uint16Array()
Int32 4 32位有符號整數 int Int32Array()
Uint32 4 32位無符號整數 unsigned int Uint32Array()
Float32 4 32位浮點數 float Float32Array()
Float64 8 64位浮點數 double Float64Array()

以上9個會對內存進行不一樣位數的格式化,以獲得對應類型值的數組。這個數組不一樣於普通數組,它不支持稀疏數組,默認值爲0,並且同一個數組只能存放同一個類型的變量。對象

以上每一個構造函數都對應以下形式的參數:內存

(buffer, start=0, len=buffer.byteLength-start*8)

能夠指定序列化其中 start到 end部分的二進制數據。注意這裏指定的範圍必須和數組類型所匹配,不能出現相似new Int32Array(buffer,2,2)的狀況。若是你以爲這個不符合你的需求,可使用 DataView。字符串

若是你以爲上面的寫法複雜,能夠不寫 new ArrayBuffer,直接使用 TypedArray,但注意參數的意義不同:

var f64a = new Float64Array(4);    //分配32個字節,並做爲double類型使用。 32 = 64 / 8 * 4

TypedArray的構造函數還接受另外一個TypedArray做爲參數,開闢新內存複製其值並改變類型,對原視圖和buffer 不構成影響,也不共用內存。
TypeArray的構造函數還接受另外一個Array做爲參數,開闢新內存複製其值,對原數組不構成影響,也不共用內存。

固然利用一下方法,能夠把 TypedArray 轉換爲普通數組:

var arr = [].slice.call(typedArray);

TypedArray具備除了concat()之外的所有數組方法,固然,它也具備 iterator,能夠用 for...of 遍歷。
如下是 TypedArray 特有的屬性和方法:

  • buffer屬性:返回該視圖對於的二進制內存區域
  • BYTES_PER_ELEMENT屬性:是個常數,表示數組中每一個值的字節大小,不一樣視圖的返回值與上方表格一致
  • byteLength: 返回該視圖對於的內存大小,只讀
  • byteOffset: 返回該視圖從對應 buffer 的哪一個字節開始,只讀
  • set(arr_or_typeArray, start=0): 在內存層面,從arr_or_typeArray 的 start 下標開始複製數組到固然 typeArray
  • subarray(start=0,end=this.length),截取 start到 end部分子數組,可是和原數組共用內存
  • from(): 接受一個可遍歷參數,轉爲該視圖實例
  • of(): 將參數列表轉爲該視圖實例

小技巧,轉換字符串和 ArrayBuffer

//該方法僅限轉換 utf-16 的字符串
function ab2str(buf){
  return String.fromCharCode.apply(null, new Uint16Array(buf));
}
function str2ab(str){
  var len = str.length;
  var view = new Uint16Array(len);
  for(let i = 0; i < len; i++){
    view[i] = str.charCodeAt(i);
  }
  return view.buffer;
}
var str = "Hello world";
var buf = str2ab(str);
var view = new Uint16Array(buf);
for(var i = 0; i < view.length; i++){
  console.log(String.fromCharCode(view[i]));   //一次輸出"Hello world"的每一個字母
}
console.log(ab2str(buf));    //"Hello world"
這裏擴展一些編碼知識,咱們知道計算機裏面存儲的是二進制,而且存儲的最小單位是字節。可是不一樣的系統存儲方式不一樣,分爲高位優先和低位優先。好比 20170101 這個數字,其十六進制表示爲 0x0133C575, 在低位優先的系統中存儲方式爲 0x75 0xC5 0x33 0x01, 而在高位優先的系統中存儲方式爲 0x01 0x33 0xC5 0x75。因爲大多數計算機採用低位優先的方式,因此 ES6 採用是也是低位優先的方式,但遇到高位優先的數據時,就不能簡單的直接那來使用,具體使用會在 DataView 中介紹,這裏說明一種判斷低位優先(little endian)仍是高位優先(big endian)的方法:

還有須要注意的是數據溢出,這個也是須要數制方面基礎比較好理解,這裏不過多展開了。舉一個例子:
Uint8 只能表示8位無符號整數,最大是1111 1111, 也就是十進制的 0~255;Int8由於有了符號位,只能表示十進制-128~127,若是給它的值不在這個範圍內就會發生溢出,獲得一個你意想不到但情理之中的值

var view1 = new Uint8Array(2);
view1[0] = 256;   //256 二進制是 1 0000 0000 因爲數據只能容納8個值,進位1就丟了
view1[1] = -1;    //以前說過-1 二進制(補碼)爲 1111 1111(全1), 做爲無符號數8個1就是255

console.log(view1[0]);   //0
console.log(view1[1]);   //255

var view2 = new Int8Array(2);
view2[0] = 128;   //因爲符號位溢出,系統自動用32位計算這個數1 000 0000 0000 0000 0000 0000 1000 0000,取符號位和最後8位獲得-128
view2[1] = -128;  //因爲符號位溢出,系統自動用32位計算這個數0 111 1111 1111 1111 1111 1111 0111 1111,取符號位和最後8位獲得127
console.log(view2[0]);   //-128
console.log(view2[1]);   //127

爲了防止這樣的狀況,js 有一個 Unit8ClampedArray, 使整數方向的溢出值爲255,0方向的易楚志爲0。注意這是個無符號的類型;

var view = new Uint8ClampedArray(2);
view[0] = 256;
view[1] = -1;

console.log(view[0]);   //255
console.log(view[1]);   //0

複合視圖

劃分一塊 buffer 使用獲得 C 語言中的結構體

var buf = new ArrayBuffer(24);
var name = new Uint8Array(buf, 0, 16);
var gender = new Uint8Array(buf, 16, 1);
var age = new Uint16Array(buf, 18, 1);
var score = new Float32Array(buf,20,1);

至關於如下 C語言代碼

struct Person{
  char name[16];
  char gender;
  int age;
  float score;
}

共用一塊 buffer 使用獲得 C 語言中的聯合體

var buf = new ArrayBuffer(8);
var num = new Uint16Array(buf);
var dotNum = new Float64Array(buf);

至關於如下 C語言代碼

union Example{
  int num[4];
  double dotNum;
}

DataView

具備一個構造函數 DataView(), 接受一個ArrayBuffer參數,視圖化該段內存。畢竟當一段內存有多種數據時,複合視圖也不是那麼方便,這時適合使用 DataView 視圖。其次 DataView 能夠自定義高位優先和低位優先,這樣能夠讀取的數據就更多了。
DataView構造函數形式以下,這一點和 TypedArray 一致:

(buffer, start=0, len=buffer.byteLength-start*8)

它具備如下方法格式化讀取 buffer 中的信息:

  • getInt8(start, isLittleEndian): 從 start 字節處讀取 1 個字節,返回一個8位有符號整數, 第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • getUint8(start, isLittleEndian): 從 start 字節處讀取 1 個字節,返回一個8位無符號整數, 第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • getInt16(start, isLittleEndian): 從 start 字節處讀取 2 個字節,返回一個16位有符號整數, 第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • getUint16(start, isLittleEndian): 從 start 字節處讀取 2 個字節,返回一個16位無符號整數, 第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • getInt32(start, isLittleEndian): 從 start 字節處讀取 4 個字節,返回一個32位有符號整數, 第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • getUint32(start, isLittleEndian): 從 start 字節處讀取 4 個字節,返回一個32位無符號整數, 第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • getFloat32(start, isLittleEndian): 從 start 字節處讀取 4 個字節,返回一個32位浮點數, 第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • getFloat64(start, isLittleEndian): 從 start 字節處讀取 8 個字節,返回一個64位浮點數, 第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;

它具備如下方法格式化寫入 buffer 中的信息:

  • setInt8(start,value,isLittleEndian): 在 start位置寫入 1 個字節的8位有符號整數value;第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • setUint8(start,value,isLittleEndian): 在 start位置寫入 1 個字節的8位無符號整數value;第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • setInt16(start,value,isLittleEndian): 在 start位置寫入 2 個字節的16位有符號整數value;第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • setUint16(start,value,isLittleEndian): 在 start位置寫入 2 個字節的16位無符號整數value;第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • setInt32(start,value,isLittleEndian): 在 start位置寫入 4 個字節的32位有符號整數value;第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • setUint32(start,value,isLittleEndian): 在 start位置寫入 4 個字節的32位無符號整數value;第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • setFloat32(start,value,isLittleEndian): 在 start位置寫入 4 個字節的32位浮點數value;第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;
  • setFloat64(start,value,isLittleEndian): 在 start位置寫入 8 個字節的64位浮點數value;第二參默認爲 false 表示使用高位優先,爲 true 表示低位優先;

它具備如下屬性和方法:

  • buffer屬性:返回該視圖對於的二進制內存區域
  • byteLength: 返回該視圖對於的內存大小,只讀
  • byteOffset: 返回該視圖從對應 buffer 的哪一個字節開始,只讀

若是你不知道計算機使用的是高位優先仍是低位優先,也能夠自行判斷:

//方法1
const BIG_ENDIAN = Symbol('BIG_ENDIAN');
const LITTLE_ENDIAN = Symbol('LITTLE_ENDIAN');
function getPlatformEndianness(){
  let arr32 =  Uint32Array.of(0x12345678);
  let arr8 = new Uint8Array(arr32.buffer);
  switch((arr8[0]*0x1000000)+(arr8[1]*0x10000)+(arr8[2]*0x100)+arr8[3]){
    case 0x12345678: return BIG_ENDIAN;
    case 0x78563412: return LITTLE_ENDIAN;
    default: throw new Error("unknow Endianness");
  }
}

//方法2
window.isLittleEndian = (function(){
  var buffer = new ArrayBuffer(2);
  new DataView(buffer).setInt16(0, 256, true);
  return new Int16Array(buffer)[0] === 256;
}());
相關文章
相關標籤/搜索