JavaScript 讀寫二進制數據

類型化數組的出現

類型化數組是 HTML5 中引入的API,它可以讓開發者使用 JavaScript 直接操做二進制數據。在類型化數組出現以前,咱們是沒法直接經過 JavaScript 操做二進制數據,一般都是操做 JavaScript 中的數據類型,由運行時轉化成二進制。這就多了一個轉化的過程,儘管 JavaScript 對數據類型作了不少優化以提升效率,但相比直接操做二進制來講,仍然有效率上的差別。因而類型化數組就順勢推出了。canvas

用途

那麼,類型化數組的應用場景都有哪些呢?數組

  • canvas 圖像處理。
  • WebGL 與顯卡通訊。
  • 文件操做
  • Ajax響應

如何使用

那麼,既然類型化數組這麼重要,那還等什麼,趕忙來掌握它們吧。瀏覽器

既然咱們要直接操做二進制數據,二進制數據又是存放在一段連續的內存區域中,因此咱們首先要有這麼一段內存區域。bash

咱們能夠建立一個內存區域:數據結構

let buffer = new ArrayBuffer()
複製代碼

ArrayBuffer 是一個構造函數,容許咱們實例化數組緩衝區,數組緩衝區能夠理解爲是一段連續的內存區域。 因爲咱們構造函數傳入的參數是空,因此生成的 buffer 指向的內存長度是 0 字節,沒有意義。函數

嗯,那咱們就建立一個有意義的內存區域。優化

buffer = new ArrayBuffer(8)
複製代碼

咱們給ArrayBuffer 傳入參數 8,意思是讓瀏覽器幫咱們建立一段 8 個字節長度的內存區域。ui

咱們看下這段內存區域的長度是不是 8 個字節spa

console.log(buffer.byteLength);
複製代碼

輸出是 8, 看來瀏覽器沒有欺騙咱們。3d

咱們猜測這 8 個字節裏面的值應該都是 0 ,由於咱們並無給 buffer 賦值。讓咱們確認一下吧,先看第一個字節:

console.log(buffer[0])
複製代碼

輸出: undefined。

咦?怎麼是 undefined 呢?

哦,原來 buffer[0] 的意思是查看 buffer 這個對象 的屬性爲 0 的值,由於 buffer 沒有 0 這個屬性,因此是 undefined。

好吧,看來咱們查看 buffer 內容的姿式不對。

那該如何查看 buffer 內容呢?

數組視圖

璫璫璫璫,八大金剛閃亮登場~

  • Int8Array:8 位有符號整數,長度 1 個字節。
  • Uint8Array: 8位無符號整數, 1 個字節長度。
  • Int16Array:16位有符號整數, 2 個字節長度。
  • Uint16Array:16位無符號整數,2 個字節長度。
  • Int32Array:32位有符號整數, 4 個字節長度。
  • Uint32Array:32位無符號整數, 4 個字節長度。
  • Float32Array:32位浮點數, 4 個字節長度。
  • Float64Array:64位浮點數,8 個字節長度。

這八大金剛有什麼神通呢?咱們沒法直接讀寫 buffer 數據,而這八種數據類型充當了讀寫 buffer 內容的橋樑。

仍是上面的 buffer,咱們想查看一下 buffer 內容。首先建立一個讀寫該 buffer 的橋樑:

let int8Array = new Int8Array(buffer);
複製代碼

咱們建立了一個讀寫 buffer 的橋樑,用 8 位有符號整數來讀寫 buffer。那如今咱們看看 buffer 第一位的內容是什麼吧?

console.log(int8Array[0]);
複製代碼

輸出:0,看來初始化的時候,buffer 的各個字節存儲的值默認都是 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 是一個字節的長度,和 buffer 的單位一致,因此咱們能夠經過索引的形式對 buffer 指定位置進行賦值操做。

很簡單吧?就是這麼簡單。

另外七大金剛和 Int8Array 的用法同樣,可是有所不一樣,咱們看下他們之間的區別。

此次咱們使用 Int16Array,仍然是剛纔的 buffer,咱們建立一個新的橋樑,這座橋樑仍然通往 buffer 。

let int16Array = new Int16Array(buffer);
複製代碼

你們試想一下 int16Array[0] 是什麼內容? 咱們輸出一下:

console.log(int16Array[0])
複製代碼

咦,結果怎麼是 10526?

不太理解了。好吧,咱們分析下 10526 怎麼得來的。

咱們看下 buffer 中的二進制數據。

因爲 Int16Array 佔兩個字節,因此咱們在用它讀寫數據的時候,一個索引所表明的數據等於 buffer 中兩個字節。

咱們能夠看到 int16Array[0] 裏面的二進制數據是由30的二進制和41的二進制數據拼接而成:00011110(30) 00101001(41)。

咱們按照 30、41的順序計算一下二進制對應的十進制數。

parseInt(1111000101001, 2) //輸出 7721
複製代碼

算出來的值是 7721,這和咱們輸出的不一致呀?

這就涉及到字節順序的概念。在咱們的我的筆記本上通常都是小端字節序。小端字節序體如今咱們這個示例中便是 4一、30的二進制順序,咱們剛纔的計算順序有問題,那按照 4一、30 的二進制順序計算一下

parseInt(10100100011110, 2) //輸出 10526
複製代碼

能夠看到輸出結果是 10526,和咱們直接使用 int16Array[0] 得出的結果一致。

上面這個例子,告訴咱們在換數據結構解析 buffer 的時候,數據會變得不容易理解,咱們必定要謹慎處理。

屬性和方法

類型化數組實例化的對象包含一些頗有用的屬性和方法:

length

length屬性返回類型化數組的數據成員個數。

byteLength

返回類型化數組的字節長度。注意與length的區別。通長 byteLength = length * 每一個數據佔用字節數

byteOffset

返回該類型化數組的數據從所處 buffer 中的哪一個字節開始。

buffer

類型化數組對應的 buffer。

set

複製數組,將某段內存中的數據完整地複製到另外一段內存。

let a = new Uint8Array(12);
a[0] = 31;
a[1] = 32;
let b = new Uint8Array(12);
b.set(a);
複製代碼

上面這段代碼的意思是將 a 這段buffer中的內容,完整地拷貝到 b 這段 buffer 中,這種方式比按索引賦值要快速地多。

固然,set 支持從某個索引開始複製數據

let a = new Uint8Array(12);
a[0] = 31;
a[1] = 32;
let b = new Uint8Array(10);
b.set(a, 2);
複製代碼

上面這段代碼意思是從b的第三個索引位置開始複製 a 中的數據。

subarray

subarray的意思是對一個類型化數組,取其子數組的內容,返回一個新的類型化數組。

let a = new Uint8Array(8);
a[2] = 1;
let b = a.subarray(2,3);
console.log(b.length);
console.log(b.byteLength);
複製代碼

subarray 的第一個參數,表明從源數組的第幾個索引開始截取,第二個參數表明截取到第幾個索引。

混合視圖

有一點須要注意,咱們的類型數組初始化的時候,能夠指定 buffer的某一段,這就意味着,咱們能夠對一段 buffer 內存區域指定多個類型數,咱們稱之爲 混合視圖

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);
複製代碼

咱們用一段內存區域表示一我的的 id、name、age。這種結構相似於 C 語言中的 struct 。

咱們將一段 8 個字節的內存分紅三個部分:

  • 字節 0 ~ 字節 1 表明 id。
  • 字節 2 ~ 字節 5 表明 username。
  • 字節 6 ~ 字節 7 表明 age。

DataView

JavaScript 還引入了另外種視圖DataView,也能達到操做 buffer 的目的,但相比之下,DataView 操做粒度更細一些,並且還可以設置字節序爲大端仍是小端。

DataView 的構造函數:

DataView(ArrayBuffer對象 buffer, 從 buffer 的第幾個字節開始讀取, 讀取的長度);
複製代碼

舉個例子來講:

let buffer = new ArrayBuffer(10);
let view = new DataView(buffer);

複製代碼

如何讀取?

咱們建立好了視圖 view, 那該如何讀取呢?

  • getInt8(index, order):從第 index 個字節讀取一個 8 位整數。
  • getUint8(index, order):從第 index 個字節開始讀取一個無符號的 8 位整數。
  • getInt16(index, order):從第 index 個字節開始讀取 2 個字節,返回一個 16 位整數。
  • getUint16(index, order):從第 index 個字節開始讀取 2 個字節,返回一個無符號的 16 位整數。
  • getInt32(index, order):從第 index 個字節開始讀取 4 個字節,返回一個32位的整數。
  • getUint32(index, order):從第 index 個字節開始讀取 4 個字節,返回一個無符號的 32 位整數。
  • getFloat32(index, order):從第 index 個字節開始讀取 4 個字節,返回一個 32 位 浮點數。
  • getFloat64(index, order):從第 index 個字節開始讀取 8 個字節,返回一個 64 位的浮點數。

JavaScript 提供了 8 種讀取方式,功能很簡單,也很容易理解,這裏就不一一作示例了,你們能夠本身試一下。

剛剛咱們也說了,DataView 也支持設置字節序,在上面 8 中讀取方式中,第一個字節是索引,第二個字節容許咱們設置字節序,true 表明 小端字節序讀取,false 表明大端字節序讀取,默認爲 false。

如何寫入?

DataView 不只能支持細粒度的讀取操做,也支持細粒度的寫入操做:

  • setInt8(index, value, order):從第 index 個字節開始,寫入 1 個字節的值爲 value 的 8 位整數。
  • setUint8(index, value, order):從第 index 個字節開始,寫入 1 個字節的值爲 value 的無符號 8 位整數。
  • setInt16(index, value, order):從第 index 個字節開始,寫入 2 個字節的值爲 value 的 16 位整數。
  • setUint16(index, value, order):從第 index 個字節開始,寫入 2 個字節的值爲 value 的無符號的 16 位整數。
  • setInt32(index, value, order):從第 index 個字節開始,寫入 4 個字節的值爲 value 的 32 位整數。
  • setUint32(index, value, order):從第 index 個字節開始,寫入 4 個字節的值爲 value 的無符號 32 位整數。
  • setFloat32(index, value, order):從第 index 個字節開始,寫入 4 個字節的值爲 value 的 32 位浮點數。
  • setFloat64(index, value, order):從第 index 個字節開始,寫入 8 個字節的值爲 value 的 64 位浮點數。

order 的意思仍然是設置寫入時的字節序, true 爲小端字節序,false 爲大端字節序,默認爲 false。

用法也很簡單,你們能夠練習一下。

至此,關於 JavaScript 操做二進制的方式就介紹完了,你們之後碰到須要直接操做內存的場景時,不妨用用這兩種方式。

將來的你會感謝今天努力的你。

相關文章
相關標籤/搜索