W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
在 Web 開(kāi)發(fā)中,當(dāng)我們處理文件時(shí)(創(chuàng)建,上傳,下載),經(jīng)常會(huì)遇到二進(jìn)制數(shù)據(jù)。另一個(gè)典型的應(yīng)用場(chǎng)景是圖像處理。
這些都可以通過(guò) JavaScript 進(jìn)行處理,而且二進(jìn)制操作性能更高。
不過(guò),在 JavaScript 中有很多種二進(jìn)制數(shù)據(jù)格式,會(huì)有點(diǎn)容易混淆。僅舉幾個(gè)例子:
ArrayBuffer
?,?Uint8Array
?,?DataView
?,?Blob
?,?File
? 及其他。與其他語(yǔ)言相比,JavaScript 中的二進(jìn)制數(shù)據(jù)是以非標(biāo)準(zhǔn)方式實(shí)現(xiàn)的。但是,當(dāng)我們理清楚以后,一切就會(huì)變得相當(dāng)簡(jiǎn)單了。
基本的二進(jìn)制對(duì)象是 ArrayBuffer
—— 對(duì)固定長(zhǎng)度的連續(xù)內(nèi)存空間的引用。
我們這樣創(chuàng)建它:
let buffer = new ArrayBuffer(16); // 創(chuàng)建一個(gè)長(zhǎng)度為 16 的 buffer
alert(buffer.byteLength); // 16
它會(huì)分配一個(gè) 16 字節(jié)的連續(xù)內(nèi)存空間,并用 0 進(jìn)行預(yù)填充。
?
ArrayBuffer
? 不是某種東西的數(shù)組讓我們先澄清一個(gè)可能的誤區(qū)。
ArrayBuffer
與Array
沒(méi)有任何共同之處:
- 它的長(zhǎng)度是固定的,我們無(wú)法增加或減少它的長(zhǎng)度。
- 它正好占用了內(nèi)存中的那么多空間。
- 要訪問(wèn)單個(gè)字節(jié),需要另一個(gè)“視圖”對(duì)象,而不是 ?
buffer[index]
?。
ArrayBuffer
是一個(gè)內(nèi)存區(qū)域。它里面存儲(chǔ)了什么?無(wú)從判斷。只是一個(gè)原始的字節(jié)序列。
如要操作 ArrayBuffer
,我們需要使用“視圖”對(duì)象。
視圖對(duì)象本身并不存儲(chǔ)任何東西。它是一副“眼鏡”,透過(guò)它來(lái)解釋存儲(chǔ)在 ArrayBuffer
中的字節(jié)。
例如:
Uint8Array
? —— 將 ?ArrayBuffer
? 中的每個(gè)字節(jié)視為 0 到 255 之間的單個(gè)數(shù)字(每個(gè)字節(jié)是 8 位,因此只能容納那么多)。這稱為 “8 位無(wú)符號(hào)整數(shù)”。Uint16Array
? —— 將每 2 個(gè)字節(jié)視為一個(gè) 0 到 65535 之間的整數(shù)。這稱為 “16 位無(wú)符號(hào)整數(shù)”。Uint32Array
? —— 將每 4 個(gè)字節(jié)視為一個(gè) 0 到 4294967295 之間的整數(shù)。這稱為 “32 位無(wú)符號(hào)整數(shù)”。Float64Array
? —— 將每 8 個(gè)字節(jié)視為一個(gè) ?5.0x10-324
? 到 ?1.8x10308
? 之間的浮點(diǎn)數(shù)。因此,一個(gè) 16 字節(jié) ArrayBuffer
中的二進(jìn)制數(shù)據(jù)可以解釋為 16 個(gè)“小數(shù)字”,或 8 個(gè)更大的數(shù)字(每個(gè)數(shù)字 2 個(gè)字節(jié)),或 4 個(gè)更大的數(shù)字(每個(gè)數(shù)字 4 個(gè)字節(jié)),或 2 個(gè)高精度的浮點(diǎn)數(shù)(每個(gè)數(shù)字 8 個(gè)字節(jié))。
ArrayBuffer
是核心對(duì)象,是所有的基礎(chǔ),是原始的二進(jìn)制數(shù)據(jù)。
但是,如果我們要寫(xiě)入值或遍歷它,基本上幾乎所有操作 —— 我們必須使用視圖(view),例如:
let buffer = new ArrayBuffer(16); // 創(chuàng)建一個(gè)長(zhǎng)度為 16 的 buffer
let view = new Uint32Array(buffer); // 將 buffer 視為一個(gè) 32 位整數(shù)的序列
alert(Uint32Array.BYTES_PER_ELEMENT); // 每個(gè)整數(shù) 4 個(gè)字節(jié)
alert(view.length); // 4,它存儲(chǔ)了 4 個(gè)整數(shù)
alert(view.byteLength); // 16,字節(jié)中的大小
// 讓我們寫(xiě)入一個(gè)值
view[0] = 123456;
// 遍歷值
for(let num of view) {
alert(num); // 123456,然后 0,0,0(一共 4 個(gè)值)
}
所有這些視圖(Uint8Array
,Uint32Array
等)的通用術(shù)語(yǔ)是 TypedArray。它們共享同一方法和屬性集。
請(qǐng)注意,沒(méi)有名為 TypedArray
的構(gòu)造器,它只是表示 ArrayBuffer
上的視圖之一的通用總稱術(shù)語(yǔ):Int8Array
,Uint8Array
及其他,很快就會(huì)有完整列表。
當(dāng)你看到 new TypedArray
之類的內(nèi)容時(shí),它表示 new Int8Array
、new Uint8Array
及其他中之一。
類型化數(shù)組的行為類似于常規(guī)數(shù)組:具有索引,并且是可迭代的。
一個(gè)類型化數(shù)組的構(gòu)造器(無(wú)論是 Int8Array
或 Float64Array
,都無(wú)關(guān)緊要),其行為各不相同,并且取決于參數(shù)類型。
參數(shù)有 5 種變體:
new TypedArray(buffer, [byteOffset], [length]);
new TypedArray(object);
new TypedArray(typedArray);
new TypedArray(length);
new TypedArray();
ArrayBuffer
參數(shù),則會(huì)在其上創(chuàng)建視圖。我們已經(jīng)用過(guò)該語(yǔ)法了。可選,我們可以給定起始位置 byteOffset
(默認(rèn)為 0)以及 length
(默認(rèn)至 buffer 的末尾),這樣視圖將僅涵蓋 buffer
的一部分。
Array
,或任何類數(shù)組對(duì)象,則會(huì)創(chuàng)建一個(gè)相同長(zhǎng)度的類型化數(shù)組,并復(fù)制其內(nèi)容。我們可以使用它來(lái)預(yù)填充數(shù)組的數(shù)據(jù):
let arr = new Uint8Array([0, 1, 2, 3]);
alert( arr.length ); // 4,創(chuàng)建了相同長(zhǎng)度的二進(jìn)制數(shù)組
alert( arr[1] ); // 1,用給定值填充了 4 個(gè)字節(jié)(無(wú)符號(hào) 8 位整數(shù))
TypedArray
,也是如此:創(chuàng)建一個(gè)相同長(zhǎng)度的類型化數(shù)組,并復(fù)制其內(nèi)容。如果需要的話,數(shù)據(jù)在此過(guò)程中會(huì)被轉(zhuǎn)換為新的類型。let arr16 = new Uint16Array([1, 1000]);
let arr8 = new Uint8Array(arr16);
alert( arr8[0] ); // 1
alert( arr8[1] ); // 232,試圖復(fù)制 1000,但無(wú)法將 1000 放進(jìn) 8 位字節(jié)中(詳述見(jiàn)下文)。
length
—— 創(chuàng)建類型化數(shù)組以包含這么多元素。它的字節(jié)長(zhǎng)度將是 length
乘以單個(gè) TypedArray.BYTES_PER_ELEMENT
中的字節(jié)數(shù):let arr = new Uint16Array(4); // 為 4 個(gè)整數(shù)創(chuàng)建類型化數(shù)組
alert( Uint16Array.BYTES_PER_ELEMENT ); // 每個(gè)整數(shù) 2 個(gè)字節(jié)
alert( arr.byteLength ); // 8(字節(jié)中的大?。?/code>
我們可以直接創(chuàng)建一個(gè) TypedArray
,而無(wú)需提及 ArrayBuffer
。但是,視圖離不開(kāi)底層的 ArrayBuffer
,因此,除第一種情況(已提供 ArrayBuffer
)外,其他所有情況都會(huì)自動(dòng)創(chuàng)建 ArrayBuffer
。
如要訪問(wèn)底層的 ArrayBuffer
,那么在 TypedArray
中有如下的屬性:
arr.buffer
? —— 引用 ?ArrayBuffer
?。arr.byteLength
? —— ?ArrayBuffer
? 的長(zhǎng)度。因此,我們總是可以從一個(gè)視圖轉(zhuǎn)到另一個(gè)視圖:
let arr8 = new Uint8Array([0, 1, 2, 3]);
// 同一數(shù)據(jù)的另一個(gè)視圖
let arr16 = new Uint16Array(arr8.buffer);
下面是類型化數(shù)組的列表:
Uint8Array
?,?Uint16Array
?,?Uint32Array
? —— 用于 8、16 和 32 位的整數(shù)。Uint8ClampedArray
? —— 用于 8 位整數(shù),在賦值時(shí)便“固定“其值(見(jiàn)下文)。Int8Array
?,?Int16Array
?,?Int32Array
? —— 用于有符號(hào)整數(shù)(可以為負(fù)數(shù))。Float32Array
?,?Float64Array
? —— 用于 32 位和 64 位的有符號(hào)浮點(diǎn)數(shù)。沒(méi)有 ?
int8
? 或類似的單值類型請(qǐng)注意,盡管有類似
Int8Array
這樣的名稱,但 JavaScript 中并沒(méi)有像int
,或int8
這樣的單值類型。
這是合乎邏輯的,因?yàn)?nbsp;
Int8Array
不是這些單值的數(shù)組,而是ArrayBuffer
上的視圖。
如果我們嘗試將越界值寫(xiě)入類型化數(shù)組會(huì)出現(xiàn)什么情況?不會(huì)報(bào)錯(cuò)。但是多余的位被切除。
例如,我們嘗試將 256 放入 Uint8Array
。256 的二進(jìn)制格式是 100000000
(9 位),但 Uint8Array
每個(gè)值只有 8 位,因此可用范圍為 0 到 255。
對(duì)于更大的數(shù)字,僅存儲(chǔ)最右邊的(低位有效)8 位,其余部分被切除:
因此結(jié)果是 0。
257 的二進(jìn)制格式是 100000001
(9 位),最右邊的 8 位會(huì)被存儲(chǔ),因此數(shù)組中會(huì)有 1
:
換句話說(shuō),該數(shù)字對(duì) 28 取模的結(jié)果被保存了下來(lái)。
示例如下:
let uint8array = new Uint8Array(16);
let num = 256;
alert(num.toString(2)); // 100000000(二進(jìn)制表示)
uint8array[0] = 256;
uint8array[1] = 257;
alert(uint8array[0]); // 0
alert(uint8array[1]); // 1
Uint8ClampedArray
在這方面比較特殊,它的表現(xiàn)不太一樣。對(duì)于大于 255 的任何數(shù)字,它將保存為 255,對(duì)于任何負(fù)數(shù),它將保存為 0。此行為對(duì)于圖像處理很有用。
TypedArray
具有常規(guī)的 Array
方法,但有個(gè)明顯的例外。
我們可以遍歷(iterate),map
,slice
,find
和 reduce
等。
但有幾件事我們做不了:
splice
? —— 我們無(wú)法“刪除”一個(gè)值,因?yàn)轭愋突瘮?shù)組是緩沖區(qū)(buffer)上的視圖,并且緩沖區(qū)(buffer)是固定的、連續(xù)的內(nèi)存區(qū)域。我們所能做的就是分配一個(gè)零值。concat
? 方法。還有兩種其他方法:
arr.set(fromArr, [offset])
? 從 ?offset
?(默認(rèn)為 0)開(kāi)始,將 ?fromArr
? 中的所有元素復(fù)制到 ?arr
?。arr.subarray([begin, end])
? 創(chuàng)建一個(gè)從 ?begin
? 到 ?end
?(不包括)相同類型的新視圖。這類似于 ?slice
? 方法(同樣也支持),但不復(fù)制任何內(nèi)容 —— 只是創(chuàng)建一個(gè)新視圖,以對(duì)給定片段的數(shù)據(jù)進(jìn)行操作。有了這些方法,我們可以復(fù)制、混合類型化數(shù)組,從現(xiàn)有數(shù)組創(chuàng)建新數(shù)組等。
DataView 是在 ArrayBuffer
上的一種特殊的超靈活“未類型化”視圖。它允許以任何格式訪問(wèn)任何偏移量(offset)的數(shù)據(jù)。
arr[i]
?。DataView
?,我們可以使用 ?.getUint8(i)
? 或 ?.getUint16(i)
? 之類的方法訪問(wèn)數(shù)據(jù)。我們?cè)谡{(diào)用方法時(shí)選擇格式,而不是在構(gòu)造的時(shí)候。語(yǔ)法:
new DataView(buffer, [byteOffset], [byteLength])
buffer
? —— 底層的 ?ArrayBuffer
?。與類型化數(shù)組不同,?DataView
? 不會(huì)自行創(chuàng)建緩沖區(qū)(buffer)。我們需要事先準(zhǔn)備好。byteOffset
? —— 視圖的起始字節(jié)位置(默認(rèn)為 0)。byteLength
? —— 視圖的字節(jié)長(zhǎng)度(默認(rèn)至 ?buffer
? 的末尾)。例如,這里我們從同一個(gè) buffer 中提取不同格式的數(shù)字:
// 4 個(gè)字節(jié)的二進(jìn)制數(shù)組,每個(gè)都是最大值 255
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
let dataView = new DataView(buffer);
// 在偏移量為 0 處獲取 8 位數(shù)字
alert( dataView.getUint8(0) ); // 255
// 現(xiàn)在在偏移量為 0 處獲取 16 位數(shù)字,它由 2 個(gè)字節(jié)組成,一起解析為 65535
alert( dataView.getUint16(0) ); // 65535(最大的 16 位無(wú)符號(hào)整數(shù))
// 在偏移量為 0 處獲取 32 位數(shù)字
alert( dataView.getUint32(0) ); // 4294967295(最大的 32 位無(wú)符號(hào)整數(shù))
dataView.setUint32(0, 0); // 將 4 個(gè)字節(jié)的數(shù)字設(shè)為 0,即將所有字節(jié)都設(shè)為 0
當(dāng)我們將混合格式的數(shù)據(jù)存儲(chǔ)在同一緩沖區(qū)(buffer)中時(shí),DataView
非常有用。例如,當(dāng)我們存儲(chǔ)一個(gè)成對(duì)序列(16 位整數(shù),32 位浮點(diǎn)數(shù))時(shí),用 DataView
可以輕松訪問(wèn)它們。
ArrayBuffer
是核心對(duì)象,是對(duì)固定長(zhǎng)度的連續(xù)內(nèi)存區(qū)域的引用。
幾乎任何對(duì) ArrayBuffer
的操作,都需要一個(gè)視圖。
TypedArray
?:Uint8Array
?,?Uint16Array
?,?Uint32Array
? —— 用于 8 位、16 位和 32 位無(wú)符號(hào)整數(shù)。Uint8ClampedArray
? —— 用于 8 位整數(shù),在賦值時(shí)便“固定”其值。Int8Array
?,?Int16Array
?,?Int32Array
? —— 用于有符號(hào)整數(shù)(可以為負(fù)數(shù))。Float32Array
?,?Float64Array
? —— 用于 32 位和 64 位的有符號(hào)浮點(diǎn)數(shù)。DataView
? —— 使用方法來(lái)指定格式的視圖,例如,?getUint8(offset)
?。在大多數(shù)情況下,我們直接對(duì)類型化數(shù)組進(jìn)行創(chuàng)建和操作,而將 ArrayBuffer
作為“共同之處(common denominator)”隱藏起來(lái)。我們可以通過(guò) .buffer
來(lái)訪問(wèn)它,并在需要時(shí)創(chuàng)建另一個(gè)視圖。還有另外兩個(gè)術(shù)語(yǔ),用于對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行操作的方法的描述:
ArrayBufferView
? 是所有這些視圖的總稱。BufferSource
? 是 ?ArrayBuffer
? 或 ?ArrayBufferView
? 的總稱。我們將在下一章中學(xué)習(xí)這些術(shù)語(yǔ)。BufferSource
是最常用的術(shù)語(yǔ)之一,因?yàn)樗囊馑际恰叭魏晤愋偷亩M(jìn)制數(shù)據(jù)” —— ArrayBuffer
或其上的視圖。
這是一份備忘單:
給定一個(gè) Uint8Array
數(shù)組,請(qǐng)寫(xiě)一個(gè)函數(shù) concat(arrays)
,將數(shù)組拼接成一個(gè)單一數(shù)組并返回。
function concat(arrays) {
// sum of individual array lengths
let totalLength = arrays.reduce((acc, value) => acc + value.length, 0);
let result = new Uint8Array(totalLength);
if (!arrays.length) return result;
// for each array - copy it over result
// next array is copied right after the previous one
let length = 0;
for(let array of arrays) {
result.set(array, length);
length += array.length;
}
return result;
}
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: