背景
為了在 WebGL 中高效地操作二進制數(shù)據(jù),ES6 為瀏覽器引入了 TypedArray 和 DataView,兩個操作底層二進制數(shù)據(jù)的視圖。因為具有直接操作內存的能力而不用進行轉換,在處理 WebGL 二進制數(shù)據(jù)上性能遠高于 Array。
ArrayBuffer
Js中的數(shù)值類型都是float64進行存儲,在內存中占據(jù) 64bit 即 8byte,盡管只存儲 ‘1’ 這個值,同樣需要申請 8byte 的空間,相當于浪費 63bit :)
ArrayBuffer的作用是創(chuàng)建一個二進制數(shù)據(jù)的緩沖區(qū),創(chuàng)建之后不能直接對緩沖區(qū)進行操作,必須構建視圖,通過視圖進行數(shù)據(jù)操作。ArrayBuffer并不是一個新的類型,而是新的接口,通過構造,可以返回一段包含指定字節(jié)數(shù)量的內存地址。
const buffer = new ArrayBuffer(8); // 我們創(chuàng)建了一個包含8字節(jié)的數(shù)據(jù)緩沖區(qū)
console.log(buffer.byteLength); // 8
視圖
在創(chuàng)建緩沖區(qū)的時候,我們按照字節(jié)為單位,8個字節(jié)長度即64位,我們通過視圖可以對這64位的長度的內存空間進行指定類型地讀寫。
DataView
DataView接口提供了八種類型的數(shù)據(jù)的讀寫,包括 :
- int8
- unit8
- int16
- uint16
- int32
- uint32
- float32
- float64
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setInt8(0, 126);
view.setInt8(1, 100);
view.setInt8(2, -120);
view.setInt8(3, 128);
console.log(view.getInt8(0)); // 126
console.log(view.getInt8(1)); // 100
console.log(view.getInt8(2)); // -120
console.log(view.getInt8(3)); // -128,帶符號的8位數(shù)最大為127,溢出后再讀取從第三個字節(jié)段開始的帶符號8位數(shù)值,結果是-128
DataView.prototype.setInt8 可以從指定的字節(jié)段開始存儲數(shù)據(jù),方法包括3個參數(shù):byteOffset: number,value:number,littleEndian:boolean。第一個表示想從那一個字節(jié)段開始存儲數(shù)據(jù),第二個是存儲的值(10進制),第三個是大字節(jié)序或者小字節(jié)序,默認為 true。
DataView.prototype.getInt8可以獲取從指定字節(jié)段開始的int8型數(shù)據(jù),方法包括2個參數(shù):byteOffset: number,littleEndian:boolean
其他類型的操作和int8一樣。
Dataview是一種通用視圖,不僅僅可以對指定字節(jié)段進行讀寫,還可以在同一緩沖區(qū)內使用不同類型的數(shù)據(jù),這樣可以提高內存利用效率。
const buffer = new ArrayBuffer(8); // 8個字節(jié)段
const view = new DataView(buffer);
view.setUint8(0, 250);
view.setInt32(1, 78787878);
// 由于32位數(shù)值占四個字節(jié),所以下一個存儲操作應該從第五個字節(jié)段地址開始
view.setInt16(5, 1000);
view.setInt8(7, 126)
console.log(view.getUint8(0)); // 250
console.log(view.getInt32(1)); // 78787878
console.log(view.getInt16(5)); // 1000
console.log(view.getInt8(7)); //126
其實 set 和 get 的類型沒有強制對應,因為這些操作只是從一個內存地址開始,讀取或寫入一定長度的值。
view.setInt32(1, 78787878);
view.getInt8(1); // 4
得到4的原因是,32位存儲78787878且默認小字節(jié)序即:
0000 0100 1011 0010 0011 0101 0010 0110
讀取的時候只讀取8位且默認也是小字節(jié)序即從上面的32位?。?br>
0000 0100
得 4
TypeArray
TypedArray視圖和DataView的區(qū)別在于,TypedArray是特定類型的視圖,實際上并沒有一個構造器叫TypedArray,其使用主要分為9個不同的類型:
| 類型 | 占用字節(jié)長度 |
|---|---|
| Int8Array | 1 |
| Uint8Array | 1 |
| Uint8ClampedArray | 1 |
| Int16Array | 2 |
| Uint16Array | 2 |
| Int32Array | 4 |
| Uint32Array | 4 |
| Float32Array | 4 |
| Float64Array | 8 |
在創(chuàng)建TypedArray視圖前,仍然需要一個數(shù)據(jù)緩沖區(qū),不過有更加簡潔的寫法。
- 由
ArrayBuffer創(chuàng)建
const buffer = new ArrayBuffer(8);
const view = new Int8Array(buffer);
view[0] = 128;
view[1] = 2;
view[2] = 100;
console.log(view); // Int8Array [ -128, 2, 100, 0, 0, 0, 0, 0 ]
因為 view[0] 我們設置了128,但是有符號的8位數(shù),最大的正數(shù)為 0111 1111 即127,128 為 1000 0000 符號位1表示負數(shù),負數(shù)的數(shù)值需要取反加1,所以128在轉換為8位帶符號數(shù)后為 -128。
和DataView一樣,TypedArray也可以使一個buffer中有著不同類型的元素:
const buffer = new ArrayBuffer(12);
const view8 = new Int8Array(buffer, 0, 4);
view8[0] = 100;
view8[1] = 128;
view8[2] = -126;
view8[3] = 5;
console.log(view8); // Int8Array [ 100, -128, -126, 5 ]
const view16 = new Int16Array(buffer, 4, 2);
view16[0] = 32768;
view16[1] = 32767;
console.log(view16); // Int16Array [ -32768, 32767 ]
const view32 = new Int32Array(buffer, 8, 1);
view32[0] = 2147483648;
console.log(view32); // Int32Array [ -2147483648 ]
// 由于buffer已經被寫入,我們可以通過DataView來讀取
const view = new DataView(buffer);
console.log(view.getInt8(0, true)); // 100
console.log(view.getInt8(1, true)); // -128
console.log(view.getInt8(2, true)); // -126
console.log(view.getInt8(3, true)); // 5
console.log(view.getInt16(4, true)); // -32768
console.log(view.getInt16(6, true)); // 32767
console.log(view.getInt32(8, true)); // -2147483648
需要注意類型的字節(jié)長度和字節(jié)偏移。
- 指定元素數(shù)量
const view = new Int8Array(8); // 表示view包括8個元素,每個占用8字節(jié)
- 傳入數(shù)組
const arr = [127, 100, 128];
const view = new Int8Array(arr);
console.log(view); // Int8Array [ 127, 100, -128 ]
需要注意的是,傳入的數(shù)組元素需要符合要創(chuàng)建的視圖類型,這一點需要先保證,不然則會溢出。
-
Uint8ClampedArray
創(chuàng)建Uint8ClampedArray視圖的時候,不會發(fā)生元素溢出,會將溢出值固定在類型的最小值或者最大值。
const arr = [127, 100, -128, 65535, 256];
const view = new Uint8ClampedArray(arr);
console.log(view); // Uint8ClampedArray [ 127, 100, 0, 255, 255 ]
在canvas的 ImageData 中就是用Uint8ClampedArray來表示圖片的pixel信息。
end
另外TypedArray 和 DataView還有其他的特殊方法,可以參考:
TypedArray
DataView
ArrayBuffer
需要注意的是TypedArray和DataView只是底層數(shù)據(jù)緩沖區(qū)的視圖,若緩沖區(qū)數(shù)據(jù)發(fā)生改變,視圖也會改變。