您好,登錄后才能下訂單哦!
在寫作這篇博客的時候,參照了下面三篇博客:
https://www.cnblogs.com/jixiaohua/p/10714662.html (寫的很詳細,參照比較多)
https://www.cnblogs.com/copperhaze/p/6149041.html
https://zh.javascript.info/arraybuffer-binary-arrays
文章中有一些內容是直接從上面博客復制過來的,并不是想要抄襲,只是覺得寫博客可以增加理解度,別切可以避免遺忘。在此感謝上面三位博主的文章。
DataView部分完全復制上面第一個鏈接的博客。
ArrauBuffer對象、TypedArray視圖和DataView視圖是JavaScript中**專門操作二進制數據的接口**。他們都是以數組的方式操作二進制數組,所以被稱為二進制數組。最初為了滿足JavaScript與顯卡之間大量的、實時的數據交換,它們之間的數據通信必須是二進制的,而不能是傳統的文本格式的背景下誕生的。
一.ArrayBuffer相關介紹
ArrayBuffer指的是一段連續的內存區域。
let buffer = new ArrayBuffer(40); // 在內存中開辟40個字節長度的內存區域
alert(buffer.byteLength); // 40
1.通過ArrayBuffer的構造函數可以開辟指定長度的內存區域,單位是字節。
2.在沒有賦值的情況下,開辟的內存區域中都是以0填充的。
3.創建內存區域后,無論賦不賦值,內存區域的大小不會改變。
4.通過ArrayBuffer創建的內存區域,不能直接讀寫,需要通過視圖來進行操作(在視圖部分會進行講解)。
5.開辟出來的內存區域是用來存放原始二進制數據的。
當開辟的內存區域比較大的時候,可能會由于內存區域不足而報錯,所以可以在創建完后看一看是否創建成功。
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視圖的數組成員可以是不同的類型。
二.TypedArray視圖
TypedArray視圖并沒有什么特別之處,它只是用來操作存放二進制數據內存的一種方式。
上面也已經提到過,作用就是講內存區域以特定的格式表示出來,具體都有哪些特定的格式,JavaScript中有以下幾種方式。
數據類型 | 字節長度 | 含義 |
---|---|---|
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也被成為類型數組。
普通數組與 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內存示意圖如下
從上面的代碼以及示意圖中可以看出,對于一段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);
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。