JavaScript讀寫二進制數(shù)據(jù)的方法詳解
前言
二進制是計算技術(shù)中廣泛采用的一種數(shù)制。二進制數(shù)據(jù)是用0和1兩個數(shù)碼來表示的數(shù),如果想要在前端中處理音頻和視頻。那你必須要對二進制數(shù)據(jù)有很好地掌握和操作能力。下面話不多說了,來一起看看詳細介紹的吧
類型化數(shù)組的出現(xiàn)
類型化數(shù)組是 HTML5 中引入的API,它能夠讓開發(fā)者使用 JavaScript 直接操作二進制數(shù)據(jù)。在類型化數(shù)組出現(xiàn)之前,我們是無法直接通過 JavaScript 操作二進制數(shù)據(jù),通常都是操作 JavaScript 中的數(shù)據(jù)類型,由運行時轉(zhuǎn)化成二進制。這就多了一個轉(zhuǎn)化的過程,盡管 JavaScript 對數(shù)據(jù)類型做了很多優(yōu)化以提高效率,但相比直接操作二進制來說,仍然有效率上的差異。于是類型化數(shù)組就順勢推出了。
用途
那么,類型化數(shù)組的應(yīng)用場景都有哪些呢?
- canvas 圖像處理。
- WebGL 與顯卡通信。
- 文件操作
- Ajax響應(yīng)
如何使用
那么,既然類型化數(shù)組這么重要,那還等什么,趕緊來掌握它們吧。
既然我們要直接操作二進制數(shù)據(jù),二進制數(shù)據(jù)又是存放在一段連續(xù)的內(nèi)存區(qū)域中,所以我們首先要有這么一段內(nèi)存區(qū)域。
我們可以創(chuàng)建一個內(nèi)存區(qū)域:
let buffer = new ArrayBuffer()
ArrayBuffer 是一個構(gòu)造函數(shù),允許我們實例化數(shù)組緩沖區(qū),數(shù)組緩沖區(qū)可以理解為是一段連續(xù)的內(nèi)存區(qū)域。
由于我們構(gòu)造函數(shù)傳入的參數(shù)是空,所以生成的 buffer 指向的內(nèi)存長度是 0 字節(jié),沒有意義。
嗯,那我們就創(chuàng)建一個有意義的內(nèi)存區(qū)域。
buffer = new ArrayBuffer(8)
我們給ArrayBuffer 傳入?yún)?shù) 8,意思是讓瀏覽器幫我們創(chuàng)建一段 8 個字節(jié)長度的內(nèi)存區(qū)域。
我們看下這段內(nèi)存區(qū)域的長度是否是 8 個字節(jié)
console.log(buffer.byteLength);
輸出是 8, 看來瀏覽器沒有欺騙我們。
我們猜想這 8 個字節(jié)里面的值應(yīng)該都是 0 ,因為我們并沒有給 buffer 賦值。讓我們確認一下吧,先看第一個字節(jié):
console.log(buffer[0])
輸出: undefined。
咦?怎么是 undefined 呢?
哦,原來 buffer[0] 的意思是查看 buffer 這個對象 的屬性為 0 的值,因為 buffer 沒有 0 這個屬性,所以是 undefined。
好吧,看來我們查看 buffer 內(nèi)容的姿勢不對。
那該如何查看 buffer 內(nèi)容呢?
數(shù)組視圖
珰珰珰珰,八大金剛閃亮登場~
Int8Array:8 位有符號整數(shù),長度 1 個字節(jié)。
Uint8Array: 8位無符號整數(shù), 1 個字節(jié)長度。
Int16Array:16位有符號整數(shù), 2 個字節(jié)長度。
Uint16Array:16位無符號整數(shù),2 個字節(jié)長度。
Int32Array:32位有符號整數(shù), 4 個字節(jié)長度。
Uint32Array:32位無符號整數(shù), 4 個字節(jié)長度。
Float32Array:32位浮點數(shù), 4 個字節(jié)長度。
Float64Array:64位浮點數(shù),8 個字節(jié)長度。
這八大金剛有什么神通呢?我們無法直接讀寫 buffer 數(shù)據(jù),而這八種數(shù)據(jù)類型充當了讀寫 buffer 內(nèi)容的橋梁。
還是上面的 buffer,我們想查看一下 buffer 內(nèi)容。首先創(chuàng)建一個讀寫該 buffer 的橋梁:
let int8Array = new Int8Array(buffer);
我們創(chuàng)建了一個讀寫 buffer 的橋梁,用 8 位有符號整數(shù)來讀寫 buffer。那現(xiàn)在我們看看 buffer 第一位的內(nèi)容是什么吧?
console.log(int8Array[0]);
輸出:0,看來初始化的時候,buffer 的各個字節(jié)存儲的值默認都是 0 了。
我們看看如何使用 Int8Array 給 buffer 賦值:
int8Array[0] = 30; int8Array[1] = 41; int8Array[2] = 52; int8Array[3] = 63; int8Array[4] = 74; int8Array[5] = 85; int8Array[6] = 86; int8Array[7] = 97;
很簡單,因為 Int8Array 是一個字節(jié)的長度,和 buffer 的單位一致,所以我們可以通過索引的形式對 buffer 指定位置進行賦值操作。
很簡單吧?就是這么簡單。
另外七大金剛和 Int8Array 的用法一樣,但是有所不同,我們看下他們之間的區(qū)別。
這次我們使用 Int16Array,仍然是剛才的 buffer,我們創(chuàng)建一個新的橋梁,這座橋梁仍然通往 buffer 。
let int16Array = new Int16Array(buffer);
大家試想一下 int16Array[0] 是什么內(nèi)容?
我們輸出一下:
console.log(int16Array[0])
咦,結(jié)果怎么是 10526?
不太理解了。好吧,我們分析下 10526 怎么得來的。
我們看下 buffer 中的二進制數(shù)據(jù)。
由于 Int16Array 占兩個字節(jié),所以我們在用它讀寫數(shù)據(jù)的時候,一個索引所代表的數(shù)據(jù)等于 buffer 中兩個字節(jié)。
我們可以看到 int16Array[0] 里面的二進制數(shù)據(jù)是由30的二進制和41的二進制數(shù)據(jù)拼接而成:00011110(30) 00101001(41)。
我們按照 30、41的順序計算一下二進制對應(yīng)的十進制數(shù)。
parseInt(1111000101001, 2) //輸出 7721
算出來的值是 7721,這和我們輸出的不一致呀?
這就涉及到字節(jié)順序的概念。在我們的個人筆記本上一般都是小端字節(jié)序。小端字節(jié)序體現(xiàn)在我們這個示例中即是 41、30的二進制順序,我們剛才的計算順序有問題,那按照 41、30 的二進制順序計算一下
parseInt(10100100011110, 2) //輸出 10526
可以看到輸出結(jié)果是 10526,和我們直接使用 int16Array[0]
得出的結(jié)果一致。
上面這個例子,告訴我們在換數(shù)據(jù)結(jié)構(gòu)解析 buffer 的時候,數(shù)據(jù)會變得不容易理解,我們一定要謹慎處理。
屬性和方法
類型化數(shù)組實例化的對象包含一些很有用的屬性和方法:
length
length屬性返回類型化數(shù)組的數(shù)據(jù)成員個數(shù)。
byteLength
返回類型化數(shù)組的字節(jié)長度。注意與length的區(qū)別。通長 byteLength = length * 每個數(shù)據(jù)占用字節(jié)數(shù)
byteOffset
返回該類型化數(shù)組的數(shù)據(jù)從所處 buffer 中的哪個字節(jié)開始。
buffer
類型化數(shù)組對應(yīng)的 buffer。
set
復(fù)制數(shù)組,將某段內(nèi)存中的數(shù)據(jù)完整地復(fù)制到另一段內(nèi)存。
let a = new Uint8Array(12); a[0] = 31; a[1] = 32; let b = new Uint8Array(12); b.set(a);
上面這段代碼的意思是將 a 這段buffer中的內(nèi)容,完整地拷貝到 b 這段 buffer 中,這種方式比按索引賦值要快速地多。
當然,set 支持從某個索引開始復(fù)制數(shù)據(jù)
let a = new Uint8Array(12); a[0] = 31; a[1] = 32; let b = new Uint8Array(10); b.set(a, 2);
上面這段代碼意思是從b的第三個索引位置開始復(fù)制 a 中的數(shù)據(jù)。
subarray
subarray的意思是對一個類型化數(shù)組,取其子數(shù)組的內(nèi)容,返回一個新的類型化數(shù)組。
let a = new Uint8Array(8); a[2] = 1; let b = a.subarray(2,3); console.log(b.length); console.log(b.byteLength);
subarray 的第一個參數(shù),代表從源數(shù)組的第幾個索引開始截取,第二個參數(shù)代表截取到第幾個索引。
混合視圖
有一點需要注意,我們的類型數(shù)組初始化的時候,可以指定 buffer的某一段,這就意味著,我們可以對一段 buffer 內(nèi)存區(qū)域指定多個類型數(shù),我們稱之為 混合視圖。
let buffer = new Buffer(8); let idArray = new Int8Array(buffer, 0,2); let nameArray = new Int8Array(buffer, 2, 4); let ageArray = new Int8Array(buffer, 6, 2);
我們用一段內(nèi)存區(qū)域表示一個人的 id、name、age。這種結(jié)構(gòu)類似于 C 語言中的 struct 。
我們將一段 8 個字節(jié)的內(nèi)存分成三個部分:
- 字節(jié) 0 ~ 字節(jié) 1 代表 id。
- 字節(jié) 2 ~ 字節(jié) 5 代表 username。
- 字節(jié) 6 ~ 字節(jié) 7 代表 age。
DataView
JavaScript 還引入了另外種視圖DataView,也能達到操作 buffer 的目的,但相比之下,DataView 操作粒度更細一些,而且還能夠設(shè)置字節(jié)序為大端還是小端。
DataView 的構(gòu)造函數(shù):
DataView(ArrayBuffer對象 buffer, 從 buffer 的第幾個字節(jié)開始讀取, 讀取的長度);
舉個例子來說:
let buffer = new ArrayBuffer(10); let view = new DataView(buffer);
如何讀???
我們創(chuàng)建好了視圖 view, 那該如何讀取呢?
- getInt8(index, order):從第 index 個字節(jié)讀取一個 8 位整數(shù)。
- getUint8(index, order):從第 index 個字節(jié)開始讀取一個無符號的 8 位整數(shù)。
- getInt16(index, order):從第 index 個字節(jié)開始讀取 2 個字節(jié),返回一個 16 位整數(shù)。
- getUint16(index, order):從第 index 個字節(jié)開始讀取 2 個字節(jié),返回一個無符號的 16 位整數(shù)。
- getInt32(index, order):從第 index 個字節(jié)開始讀取 4 個字節(jié),返回一個32位的整數(shù)。
- getUint32(index, order):從第 index 個字節(jié)開始讀取 4 個字節(jié),返回一個無符號的 32 位整數(shù)。
- getFloat32(index, order):從第 index 個字節(jié)開始讀取 4 個字節(jié),返回一個 32 位 浮點數(shù)。
- getFloat64(index, order):從第 index 個字節(jié)開始讀取 8 個字節(jié),返回一個 64 位的浮點數(shù)。
JavaScript 提供了 8 種讀取方式,功能很簡單,也很容易理解,這里就不一一做示例了,大家可以自己試一下。
剛剛我們也說了,DataView 也支持設(shè)置字節(jié)序,在上面 8 中讀取方式中,第一個字節(jié)是索引,第二個字節(jié)允許我們設(shè)置字節(jié)序,true 代表 小端字節(jié)序讀取,false 代表大端字節(jié)序讀取,默認為 false。
如何寫入?
DataView 不僅能支持細粒度的讀取操作,也支持細粒度的寫入操作:
- setInt8(index, value, order):從第 index 個字節(jié)開始,寫入 1 個字節(jié)的值為 value 的 8 位整數(shù)。
- setUint8(index, value, order):從第 index 個字節(jié)開始,寫入 1 個字節(jié)的值為 value 的無符號 8 位整數(shù)。
- setInt16(index, value, order):從第 index 個字節(jié)開始,寫入 2 個字節(jié)的值為 value 的 16 位整數(shù)。
- setUint16(index, value, order):從第 index 個字節(jié)開始,寫入 2 個字節(jié)的值為 value 的無符號的 16 位整數(shù)。
- setInt32(index, value, order):從第 index 個字節(jié)開始,寫入 4 個字節(jié)的值為 value 的 32 位整數(shù)。
- setUint32(index, value, order):從第 index 個字節(jié)開始,寫入 4 個字節(jié)的值為 value 的無符號 32 位整數(shù)。
- setFloat32(index, value, order):從第 index 個字節(jié)開始,寫入 4 個字節(jié)的值為 value 的 32 位浮點數(shù)。
- setFloat64(index, value, order):從第 index 個字節(jié)開始,寫入 8 個字節(jié)的值為 value 的 64 位浮點數(shù)。
order 的意思仍然是設(shè)置寫入時的字節(jié)序, true 為小端字節(jié)序,false 為大端字節(jié)序,默認為 false。
用法也很簡單,大家可以練習(xí)一下。
至此,關(guān)于 JavaScript 操作二進制的方式就介紹完了,大家以后碰到需要直接操作內(nèi)存的場景時,不妨用用這兩種方式。
未來的你會感謝今天努力的你。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
前端用echarts繪制含有多個分層的波形圖關(guān)鍵代碼
每次實現(xiàn)各種圖表時,總會用到echarts,不得不說確實是一個非常好用的開源庫,這篇文章主要給大家介紹了關(guān)于前端用echarts繪制含有多個分層的波形圖的相關(guān)資料,需要的朋友可以參考下2024-03-03結(jié)合?ES6?類編寫JavaScript?創(chuàng)建型模式
這篇文章主要介紹了結(jié)合ES6類編寫JavaScript創(chuàng)建型模式,本文開始系統(tǒng)性的對20多種JavaScript?設(shè)計模式進行簡單概述,然后結(jié)合ES6類的方式來編寫實例代碼展示其使用方式,需要的朋友可以參考一下2022-07-07JavaScript股票的動態(tài)買賣規(guī)劃實例分析上篇
這篇文章主要介紹了JavaScript對于動態(tài)規(guī)劃解決股票問題的真題例舉講解。文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08js實現(xiàn)操作cookie的常見方法總結(jié)【創(chuàng)建、讀取、刪除】
這篇文章主要介紹了js實現(xiàn)操作cookie的常見方法,結(jié)合實例形式分析了js操作cookie創(chuàng)建、讀取、刪除相關(guān)實現(xiàn)技巧與注意事項,需要的朋友可以參考下2020-03-03Bootstrap 設(shè)置datetimepicker在屏幕上面彈出設(shè)置方法
datetimepicker默認是在輸入框下面彈出的,但是遇到輸入框在屏幕下面時,日期選擇框會有一部分在屏幕下面,顯示不了,因此需要能夠從上面彈出,下面小編給大家介紹下Bootstrap 設(shè)置datetimepicker在屏幕上面彈出的設(shè)置方法2017-03-03