JavaScript ArrayBuffer 二進制數組(一)


一、 ArrayBuffer 

ArrayBuffer對象、TypedArray視圖和DataView視圖是 JavaScript 操作二進制數據的一個接口。這些對象早就存在,屬於獨立的規格(2011 年 2 月發布),ES6 將它們納入了 ECMAScript 規格,並且增加了新的方法。它們都是以數組的語法處理二進制數據,所以統稱為二進制數組。

二進制數組由三類對象組成。

(1)ArrayBuffer對象:代表內存之中的一段二進制數據,可以通過“視圖”進行操作。“視圖”部署了數組接口,這意味着,可以用數組的方法操作內存。

(2)TypedArray視圖:共包括 9 種類型的視圖,比如Uint8Array(無符號 8 位整數)數組視圖, Int16Array(16 位整數)數組視圖, Float32Array(32 位浮點數)數組視圖等等。

(3)DataView視圖:可以自定義復合格式的視圖,比如第一個字節是 Uint8(無符號 8 位整數)、第二、三個字節是 Int16(16 位整數)、第四個字節開始是 Float32(32 位浮點數)等等,此外還可以自定義字節序。

簡單說,ArrayBuffer對象代表原始的二進制數據,TypedArray視圖用來讀寫簡單類型的二進制數據,DataView視圖用來讀寫復雜類型的二進制數據。

TypedArray視圖支持的數據類型一共有 9 種(DataView視圖支持除Uint8C以外的其他 8 種)。

數據類型 字節長度 含義 對應的 C 語言類型
Int8 1 8 位帶符號整數 signed char
Uint8 1 8 位不帶符號整數 unsigned char
Uint8C 1 8 位不帶符號整數(自動過濾溢出) unsigned char
Int16 2 16 位帶符號整數 short
Uint16 2 16 位不帶符號整數 unsigned short
Int32 4 32 位帶符號整數 int
Uint32 4 32 位不帶符號的整數 unsigned int
Float32 4 32 位浮點數 float
Float64 8 64 位浮點數 double

注意,二進制數組並不是真正的數組,而是類似數組的對象。

很多瀏覽器操作的 API,用到了二進制數組操作二進制數據,下面是其中的幾個。

ArrayBuffer 對象概述

ArrayBuffer對象代表儲存二進制數據的一段內存,它不能直接讀寫,只能通過視圖(TypedArray視圖和DataView視圖)來讀寫,視圖的作用是以指定格式解讀二進制數據。

ArrayBuffer也是一個構造函數,可以分配一段可以存放數據的連續內存區域。

const buf = new ArrayBuffer(32); 

上面代碼生成了一段 32 字節的內存區域,每個字節的值默認都是 0。可以看到,ArrayBuffer構造函數的參數是所需要的內存大小(單位字節)。

為了讀寫這段內容,需要為它指定視圖。DataView視圖的創建,需要提供ArrayBuffer對象實例作為參數。

const buf = new ArrayBuffer(32); const dataView = new DataView(buf); dataView.getUint8(0) // 0 

上面代碼對一段 32 字節的內存,建立DataView視圖,然后以不帶符號的 8 位整數格式,從頭讀取 8 位二進制數據,結果得到 0,因為原始內存的ArrayBuffer對象,默認所有位都是 0。

另一種TypedArray視圖,與DataView視圖的一個區別是,它不是一個構造函數,而是一組構造函數,代表不同的數據格式。

const buffer = new ArrayBuffer(12); const x1 = new Int32Array(buffer); x1[0] = 1; const x2 = new Uint8Array(buffer); x2[0] = 2; x1[0] // 2 

上面代碼對同一段內存,分別建立兩種視圖:32 位帶符號整數(Int32Array構造函數)和 8 位不帶符號整數(Uint8Array構造函數)。由於兩個視圖對應的是同一段內存,一個視圖修改底層內存,會影響到另一個視圖。

TypedArray視圖的構造函數,除了接受ArrayBuffer實例作為參數,還可以接受普通數組作為參數,直接分配內存生成底層的ArrayBuffer實例,並同時完成對這段內存的賦值。

const typedArray = new Uint8Array([0,1,2]); typedArray.length // 3 typedArray[0] = 5; typedArray // [5, 1, 2] 

上面代碼使用TypedArray視圖的Uint8Array構造函數,新建一個不帶符號的 8 位整數視圖。可以看到,Uint8Array直接使用普通數組作為參數,對底層內存的賦值同時完成。

ArrayBuffer.prototype.byteLength :返回所分配的內存區域的字節長度

ArrayBuffer.prototype.slice() :允許將內存區域的一部分,拷貝生成一個新的ArrayBuffer對象。

ArrayBuffer.isView()

ArrayBuffer有一個靜態方法isView,返回一個布爾值,表示參數是否為ArrayBuffer的視圖實例。這個方法大致相當於判斷參數,是否為TypedArray實例或DataView實例。

const buffer = new ArrayBuffer(8);
ArrayBuffer.isView(buffer) // false

const v = new Int32Array(buffer);
ArrayBuffer.isView(v) // true

 

二、TypedArray 視圖

ArrayBuffer對象作為內存區域,可以存放多種類型的數據。同一段內存,不同數據有不同的解讀方式,這就叫做“視圖”(view)。ArrayBuffer有兩種視圖,一種是TypedArray視圖,另一種是DataView視圖。前者的數組成員都是同一個數據類型,后者的數組成員可以是不同的數據類型。

目前,TypedArray視圖一共包括 9 種類型,每一種視圖都是一種構造函數。

  • 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 個字節。

這 9 個構造函數生成的數組,統稱為TypedArray視圖。它們很像普通數組,都有length屬性,都能用方括號運算符([])獲取單個元素,所有數組的方法,在它們上面都能使用。普通數組與 TypedArray 數組的差異主要在以下方面。

  • TypedArray 數組的所有成員,都是同一種類型。
  • TypedArray 數組的成員是連續的,不會有空位。
  • TypedArray 數組成員的默認值為 0。比如,new Array(10)返回一個普通數組,里面沒有任何成員,只是 10 個空位;new Uint8Array(10)返回一個 TypedArray 數組,里面 10 個成員都是 0。
  • TypedArray 數組只是一層視圖,本身不儲存數據,它的數據都儲存在底層的ArrayBuffer對象之中,要獲取底層對象必須使用buffer屬性。
// 創建一個8字節的ArrayBuffer
const b = new ArrayBuffer(8);

// 創建一個指向b的Int32視圖,開始於字節0,直到緩沖區的末尾
const v1 = new Int32Array(b);

// 創建一個指向b的Uint8視圖,開始於字節2,直到緩沖區的末尾
const v2 = new Uint8Array(b, 2);

// 創建一個指向b的Int16視圖,開始於字節2,長度為2
const v3 = new Int16Array(b, 2, 2);
  • TypedArray.prototype.copyWithin(target, start[, end = this.length])
  • TypedArray.prototype.entries()
  • TypedArray.prototype.every(callbackfn, thisArg?)
  • TypedArray.prototype.fill(value, start=0, end=this.length)
  • TypedArray.prototype.filter(callbackfn, thisArg?)
  • TypedArray.prototype.find(predicate, thisArg?)
  • TypedArray.prototype.findIndex(predicate, thisArg?)
  • TypedArray.prototype.forEach(callbackfn, thisArg?)
  • TypedArray.prototype.indexOf(searchElement, fromIndex=0)
  • TypedArray.prototype.join(separator)
  • TypedArray.prototype.keys()
  • TypedArray.prototype.lastIndexOf(searchElement, fromIndex?)
  • TypedArray.prototype.map(callbackfn, thisArg?)
  • TypedArray.prototype.reduce(callbackfn, initialValue?)
  • TypedArray.prototype.reduceRight(callbackfn, initialValue?)
  • TypedArray.prototype.reverse()
  • TypedArray.prototype.slice(start=0, end=this.length)
  • TypedArray.prototype.some(callbackfn, thisArg?)
  • TypedArray.prototype.sort(comparefn)
  • TypedArray.prototype.toLocaleString(reserved1?, reserved2?)
  • TypedArray.prototype.toString()
  • TypedArray.prototype.values()

BYTES_PER_ELEMENT 屬性

每一種視圖的構造函數,都有一個BYTES_PER_ELEMENT屬性,表示這種數據類型占據的字節數。

Int8Array.BYTES_PER_ELEMENT // 1 Uint8Array.BYTES_PER_ELEMENT // 1 Uint8ClampedArray.BYTES_PER_ELEMENT // 1 Int16Array.BYTES_PER_ELEMENT // 2 Uint16Array.BYTES_PER_ELEMENT // 2 Int32Array.BYTES_PER_ELEMENT // 4 Uint32Array.BYTES_PER_ELEMENT // 4 Float32Array.BYTES_PER_ELEMENT // 4 Float64Array.BYTES_PER_ELEMENT // 8 

 

三、DataView 視圖

如果一段數據包括多種類型(比如服務器傳來的 HTTP 數據),這時除了建立ArrayBuffer對象的復合視圖以外,還可以通過DataView視圖進行操作。

DataView視圖提供更多操作選項,而且支持設定字節序。本來,在設計目的上,ArrayBuffer對象的各種TypedArray視圖,是用來向網卡、聲卡之類的本機設備傳送數據,所以使用本機的字節序就可以了;而DataView視圖的設計目的,是用來處理網絡設備傳來的數據,所以大端字節序或小端字節序是可以自行設定的。

DataView視圖本身也是構造函數,接受一個ArrayBuffer對象作為參數,生成視圖。

new DataView(ArrayBuffer buffer [, 字節起始位置 [, 長度]]); 

下面是一個例子。

const buffer = new ArrayBuffer(24); const dv = new DataView(buffer); 

DataView實例有以下屬性,含義與TypedArray實例的同名方法相同。

  • DataView.prototype.buffer:返回對應的 ArrayBuffer 對象
  • DataView.prototype.byteLength:返回占據的內存字節長度
  • DataView.prototype.byteOffset:返回當前視圖從對應的 ArrayBuffer 對象的哪個字節開始

DataView實例提供 8 個方法讀取內存。

  • getInt8:讀取 1 個字節,返回一個 8 位整數。
  • getUint8:讀取 1 個字節,返回一個無符號的 8 位整數。
  • getInt16:讀取 2 個字節,返回一個 16 位整數。
  • getUint16:讀取 2 個字節,返回一個無符號的 16 位整數。
  • getInt32:讀取 4 個字節,返回一個 32 位整數。
  • getUint32:讀取 4 個字節,返回一個無符號的 32 位整數。
  • getFloat32:讀取 4 個字節,返回一個 32 位浮點數。
  • getFloat64:讀取 8 個字節,返回一個 64 位浮點數。

這一系列get方法的參數都是一個字節序號(不能是負數,否則會報錯),表示從哪個字節開始讀取。

const buffer = new ArrayBuffer(24); const dv = new DataView(buffer); // 從第1個字節讀取一個8位無符號整數 const v1 = dv.getUint8(0); // 從第2個字節讀取一個16位無符號整數 const v2 = dv.getUint16(1); // 從第4個字節讀取一個16位無符號整數 const v3 = dv.getUint16(3); 

上面代碼讀取了ArrayBuffer對象的前 5 個字節,其中有一個 8 位整數和兩個十六位整數。

如果一次讀取兩個或兩個以上字節,就必須明確數據的存儲方式,到底是小端字節序還是大端字節序。默認情況下,DataViewget方法使用大端字節序解讀數據,如果需要使用小端字節序解讀,必須在get方法的第二個參數指定true

// 小端字節序 const v1 = dv.getUint16(1, true); // 大端字節序 const v2 = dv.getUint16(3, false); // 大端字節序 const v3 = dv.getUint16(3); 

DataView 視圖提供 8 個方法寫入內存。

  • setInt8:寫入 1 個字節的 8 位整數。
  • setUint8:寫入 1 個字節的 8 位無符號整數。
  • setInt16:寫入 2 個字節的 16 位整數。
  • setUint16:寫入 2 個字節的 16 位無符號整數。
  • setInt32:寫入 4 個字節的 32 位整數。
  • setUint32:寫入 4 個字節的 32 位無符號整數。
  • setFloat32:寫入 4 個字節的 32 位浮點數。
  • setFloat64:寫入 8 個字節的 64 位浮點數。

這一系列set方法,接受兩個參數,第一個參數是字節序號,表示從哪個字節開始寫入,第二個參數為寫入的數據。對於那些寫入兩個或兩個以上字節的方法,需要指定第三個參數,false或者undefined表示使用大端字節序寫入,true表示使用小端字節序寫入。

// 在第1個字節,以大端字節序寫入值為25的32位整數 dv.setInt32(0, 25, false); // 在第5個字節,以大端字節序寫入值為25的32位整數 dv.setInt32(4, 25); // 在第9個字節,以小端字節序寫入值為2.5的32位浮點數 dv.setFloat32(8, 2.5, true); 

如果不確定正在使用的計算機的字節序,可以采用下面的判斷方式。

const littleEndian = (function() { const buffer = new ArrayBuffer(2); new DataView(buffer).setInt16(0, 256, true); return new Int16Array(buffer)[0] === 256; })(); 

如果返回true,就是小端字節序;如果返回false,就是大端字節序。

 

 

 

 

 

 

更多詳情參考:https://wangdoc.com/es6/arraybuffer.html

JS DataURL 整理(二) DataURL 和圖片

JS DataURL 整理(一)  

JavaScript 與 ECMAScript 的關系   


免責聲明!

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



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