JavaScript之ArrayBuffer

在寫做這篇博客的時候,參照了下面三篇博客:
http://www.javashuo.com/article/p-rbafwgjx-k.html (寫的很詳細,參照比較多)
http://www.javashuo.com/article/p-mheovhjb-bx.html
https://zh.javascript.info/arraybuffer-binary-arrays
文章中有一些內容是直接從上面博客複製過來的,並非想要抄襲,只是以爲寫博客能夠增長理解度,別切能夠避免遺忘。在此感謝上面三位博主的文章。
DataView部分徹底複製上面第一個連接的博客。javascript

ArrauBuffer對象、TypedArray視圖和DataView視圖是JavaScript中**專門操做二進制數據的接口**。他們都是以數組的方式操做二進制數組,因此被稱爲二進制數組。最初爲了知足JavaScript與顯卡之間大量的、實時的數據交換,它們之間的數據通訊必須是二進制的,而不能是傳統的文本格式的背景下誕生的。

一.ArrayBuffer相關介紹

ArrayBuffer指的是一段連續的內存區域。html

let buffer = new ArrayBuffer(40);  //  在內存中開闢40個字節長度的內存區域
alert(buffer.byteLength);               //  40

1.經過ArrayBuffer的構造函數能夠開闢指定長度的內存區域,單位是字節。
2.在沒有賦值的狀況下,開闢的內存區域中都是以0填充的。
3.建立內存區域後,不管賦不賦值,內存區域的大小不會改變。
4.經過ArrayBuffer建立的內存區域,不能直接讀寫,須要經過視圖來進行操做(在視圖部分會進行講解)。
5.開闢出來的內存區域是用來存放原始二進制數據的。java

當開闢的內存區域比較大的時候,可能會因爲內存區域不足而報錯,因此能夠在建立完後看一看是否建立成功。數組

let buffer = new ArrayBuffer(40);

if(buffer.byteLength === 40){
    alert("建立成功");
}else{
    alert("建立失敗");
}

ArrayBuffer有一個長度屬性,是byteLength,返回的是這個ArrayBuffer對象佔多少個字節的內存大小。
還有一個比較經常使用的方法,是slice(begin,end)服務器

let buffer = new ArrayBuffer(40);

let newBuffer = buffer.slice(10,30);
alert(newBuffer.byteLength);   // 20

slice經過拷貝原有對象的內存區域,生成一個新的內存區域。
由於不能直接對ArrayBuffer直接進行讀寫,因此可用的屬性和方法也並很少。網絡

上面也已經提到過,經過ArrayBuffer建立的內存區域並不能直接進行讀寫,而要經過TypedArray視圖或者DataView視圖進行讀寫操做,
他們的做用就是將內存區域中的數據以特定的格式表示出來,並經過這種特定的格式來操做這段內存區域。TypedArray視圖和DataView視圖的區別是,
TypedArray視圖中的數組成員都是同一種類型,而DataView視圖的數組成員能夠是不一樣的類型。app

二.TypedArray視圖
TypedArray視圖並無什麼特別之處,它只是用來操做存放二進制數據內存的一種方式。
上面也已經提到過,做用就是講內存區域以特定的格式表示出來,具體都有哪些特定的格式,JavaScript中有如下幾種方式。ide

數據類型 字節長度 含義
Int8Array 1個字節 有符號整數,以8位(1個字節)的內存長度讀寫內存區域
Uint8Array 1個字節 無符號整數,以8位(1個字節)的內存長度讀寫內存區域
Int16Array 2個字節 有符號整數,以16位(2個字節)的內存長度讀寫內存區域
Unit16Array 2個字節 無符號整數,以16位(2個字節)的內存長度讀寫內存區域
Int32Array 4個字節 有符號整數,以32位(4個字節)的內存長度讀寫內存區域
Unit32Array 4個字節 無符號整數,以32位(4個字節)的內存長度讀寫內存區域
Float32Array 4個字節 浮點數,以32位(4個字節)的內存長度讀寫內存區域
Float64Array 8個字節 浮點數,以64位(8個字節)的內存長度讀寫內存區域

以上8個數據類型,也是8個構造函數,都被稱爲TypedArray視圖。函數

它們很像普通數組,都有length屬性,都能用方括號運算符([])獲取單個元素,全部數組的方法,在它們上面都能使用,因此TypedArray也被成爲類型數組。ui

普通數組與 TypedArray 數組的差別主要在如下方面。

TypedArray 數組的全部成員,都是同一種類型。
TypedArray 數組的成員是連續的,不會有空位。
TypedArray 數組成員的默認值爲 0。好比,new Array(10)返回一個普通數組,裏面沒有任何成員,只是 10 個空位;new Uint8Array(10)返回一個 TypedArray 數組,裏面 10 個成員都是 0。
TypedArray 數組只是一層視圖,自己不儲存數據,它的數據都儲存在底層的ArrayBuffer對象之中,要獲取底層對象必須使用buffer屬性。

咱們以Int16Array做爲例子,來說解TypedArray視圖的用法。
Int16Array中的16表示的單個元素所佔內存的位數,也就是說若是用Int16Array構造函數建立了一個對象來讀寫二進制數據內存,這個數組對象每次的每一個元素都是16位(2個字節)大小。
能夠經過BYTES_PER_ELEMENT屬性,查看每一種類型中,元素所佔內存大小。

alert(Int16Array.BYTES_PER_ELEMENT);   // 2  每一個元素佔2個字節內存大小

使用方法:
能夠經過構造函數來建立TypedArray數組對象。
1.TypedArray(buffer, byteOffset=0, length)
第一個參數(必須):指定Arraybuffer對象,也就是說是經過指定的ArrayBuffer對象來建立TypedArray數組對象,前面也已經說過了,TypedArray視圖自己並不存儲數據,實際的數據仍是存在ArrayBuffer開闢的二進制內存區域上。
第二個參數:視圖對象從ArrayBuffer內存區域的第幾個字節開始,默認從0開始。
第三個參數:視圖中包含的數據個數,默認是到指定ArrayBuffer對象內存區域的末尾。

// 開闢一段8個字節的內存區域
const buffer = new ArrayBuffer(8);

// 在buffer內存區域上建立一個Unit32位視圖,從字節0開始,一直到結尾
// 由於一個元素佔32位(4個字節),總共有8個字節,因此在buffer這段內存區域上,能存儲2個數據類型爲Unit32的數據
let uint32 = new Uint32Array(buffer);

// 在buffer內存區域上從第二個字節開始,建立4個數據類型爲Unit8Array的數據
let uint8 = new Uint8Array(buffer,2,4);

//  在buffer內存區域上從第四個字節開始,建立2個數據類型爲Int16Array的數據
let int16 = new Int16Array(buffer,4,2);

上述代碼執行結束後buffer內存示意圖以下
JavaScript之ArrayBuffer
從上面的代碼以及示意圖中能夠看出,對於一段ArrayBuffer內存區域,能夠同時建立多個不一樣類型的TypedArray視圖,可是這樣會形成視圖重疊,若是經過視圖給內存賦值了,會形成數據覆蓋。

2.TypedArray(length)
不經過ArrayBuffer,直接分配內存生成。其實這種方式的本質仍是會先建立一個Arraybuffer對象。

let uint32 = new Uint32Array(4);    // 建立一個元素個數爲4的Uint32類型視圖對象
alert(uint32.buffer.byteLength);     // 16   經過buffer屬性,能夠獲取該視圖鎖對應的ArrayBuffer對象,能夠看到該視圖對象對應的ArrayBuffer內存區域大小爲16個字節

3.TypedArray(typedArray)
經過另外一個TypedArray實例對象來建立一個TypedArray。

const typedArray = new Int8Array(new Uint8Array(4));    //0,0,0,0

上面代碼中,Int8Array構造函數接受一個Uint8Array實例做爲參數。
注意,此時生成的新數組,只是複製了參數數組的值,對應的底層內存是不同的。新數組會開闢一段新的內存儲存數據,不會在原數組的內存之上創建視圖。

const x = new Int8Array([1, 1]);
const y = new Int8Array(x);
x[0] // 1
y[0] // 1

x[0] = 2;
y[0] // 1

上面代碼中,數組y是以數組x爲模板而生成的,當x變更的時候,y並無變更。
若是想基於同一段內存,構造不一樣的視圖,能夠採用下面的寫法。

const x = new Int8Array([1, 1]);
const y = new Int8Array(x.buffer);
x[0] // 1
y[0] // 1

x[0] = 2;
y[0] // 2

4.TypedArray(arrayLikeObject)
構造函數的參數也能夠是一個普通數組,而後直接生成TypedArray實例。
const typedArray = new Uint8Array([1, 2, 3, 4]);
注意,這時TypedArray視圖會從新開闢內存,不會在原數組的內存上創建視圖。
上面代碼從一個普通的數組,生成一個 8 位無符號整數的TypedArray實例。該數組有4個成員,每個都是8位無符號整數。
TypedArray 數組也能夠轉換回普通數組。
const normalArray = [...typedArray];<br/>// or<br/>const normalArray = Array.from(typedArray);<br/>// or<br/>const normalArray = Array.prototype.slice.call(typedArray);

與普通數組相比,TypedArray 數組的最大優勢就是能夠直接操做內存,不須要數據類型轉換,因此速度快得多。

ArrayBuffer 與字符串的互相轉換
ArrayBuffer轉爲字符串,或者字符串轉爲ArrayBuffer,有一個前提,即字符串的編碼方法是肯定的。假定字符串採用 UTF-16 編碼(JavaScript 的內部編碼方式),能夠本身編寫轉換函數。

// ArrayBuffer 轉爲字符串,參數爲 ArrayBuffer 對象
function ab2str(buf) {
  // 注意,若是是大型二進制數組,爲了不溢出,
  // 必須一個一個字符地轉
  if (buf && buf.byteLength < 1024) {
    return String.fromCharCode.apply(null, new Uint16Array(buf));
  }

  const bufView = new Uint16Array(buf);
  const len =  bufView.length;
  const bstr = new Array(len);
  for (let i = 0; i < len; i++) {
    bstr[i] = String.fromCharCode.call(null, bufView[i]);
  }
  return bstr.join('');
}

// 字符串轉爲 ArrayBuffer 對象,參數爲字符串
function str2ab(str) {
  const buf = new ArrayBuffer(str.length * 2); // 每一個字符佔用2個字節
  const bufView = new Uint16Array(buf);
  for (let i = 0, strLen = str.length; i < strLen; i++) {
    bufView[i] = str.charCodeAt(i);
  }
  return buf;
}

三.DataView 視圖
若是一段數據包括多種類型(好比服務器傳來的 HTTP 數據),這時除了創建ArrayBuffer對象的複合視圖之外,還能夠經過DataView視圖進行操做。
DataView視圖提供更多操做選項,並且支持設定字節序。
原本,在設計目的上,ArrayBuffer對象的各類TypedArray視圖,是用來向網卡、聲卡之類的本機設備傳送數據,因此使用本機的字節序就能夠了;
而DataView視圖的設計目的,是用來處理網絡設備傳來的數據,因此大端字節序或小端字節序是能夠自行設定的。
DataView視圖自己也是構造函數,接受一個ArrayBuffer對象做爲參數,生成視圖。
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 的讀取
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方法的參數都是一個字節序號(不能是負數,不然會報錯),表示從哪一個字節開始讀取。

// 從第一個字節開始讀取8位無符號整數
const v1 = dv.getUint8(0);

// 從第2個字節開始讀取16位有符號整數,佔2個字節
const v2 = dv.getInt16(1);

// 從第4個字節開始讀取16位有符號整數,2個字節
const v3 = dv.getInt16(3);

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

若是一次讀取兩個或兩個以上字節,就必須明確數據的存儲方式,究竟是小端字節序仍是大端字節序。

默認狀況下,DataView的get方法使用大端字節序解讀數據,若是須要使用小端字節序解讀,必須在get方法的第二個參數指定true。

// 小端字節序
const v1 = dv.getUint16(1, true);

// 大端字節序
const v2 = dv.getUint16(3, false);

// 大端字節序
const v3 = dv.getUint16(3);

DataView 的寫入
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);
相關文章
相關標籤/搜索