1. 程式人生 > 實用技巧 >JavaScript ArrayBuffer 二進位制陣列(二) 應用場景

JavaScript ArrayBuffer 二進位制陣列(二) 應用場景

ArrayBuffer 的應用場景

1.AJAX

傳統上,伺服器通過 AJAX 操作只能返回文字資料,即responseType屬性預設為textXMLHttpRequest第二版XHR2允許伺服器返回二進位制資料,這時分成兩種情況。如果明確知道返回的二進位制資料型別,可以把返回型別(responseType)設為arraybuffer;如果不知道,就設為blob

let xhr = new XMLHttpRequest();
xhr.open('GET', someUrl);
xhr.responseType = 'arraybuffer';

xhr.onload = function
() { let arrayBuffer = xhr.response; // ··· }; xhr.send();

如果知道傳回來的是 32 位整數,可以像下面這樣處理。

xhr.onreadystatechange = function () {
  if (req.readyState === 4 ) {
    const arrayResponse = xhr.response;
    const dataView = new DataView(arrayResponse);
    const ints = new Uint32Array(dataView.byteLength / 4);

    xhrDiv.style.backgroundColor 
= "#00FF00"; xhrDiv.innerText = "Array is " + ints.length + "uints long"; } }

2.Canvas

網頁Canvas元素輸出的二進位制畫素資料,就是 TypedArray 陣列。

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const uint8ClampedArray 
= imageData.data;

需要注意的是,上面程式碼的uint8ClampedArray雖然是一個 TypedArray 陣列,但是它的檢視型別是一種針對Canvas元素的專有型別Uint8ClampedArray。這個檢視型別的特點,就是專門針對顏色,把每個位元組解讀為無符號的 8 位整數,即只能取值 0 ~ 255,而且發生運算的時候自動過濾高位溢位。這為影象處理帶來了巨大的方便。

舉例來說,如果把畫素的顏色值設為Uint8Array型別,那麼乘以一個 gamma 值的時候,就必須這樣計算:

u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));

因為Uint8Array型別對於大於 255 的運算結果(比如0xFF+1),會自動變為0x00,所以影象處理必須要像上面這樣算。這樣做很麻煩,而且影響效能。如果將顏色值設為Uint8ClampedArray型別,計算就簡化許多。

pixels[i] *= gamma;

Uint8ClampedArray型別確保將小於 0 的值設為 0,將大於 255 的值設為 255。注意,IE 10 不支援該型別。

3.WebSocket

WebSocket可以通過ArrayBuffer,傳送或接收二進位制資料。

let socket = new WebSocket('ws://127.0.0.1:8081');
socket.binaryType = 'arraybuffer';

// Wait until socket is open
socket.addEventListener('open', function (event) {
  // Send binary data
  const typedArray = new Uint8Array(4);
  socket.send(typedArray.buffer);
});

// Receive binary data
socket.addEventListener('message', function (event) {
  const arrayBuffer = event.data;
  // ···
});

4.Fetch API

Fetch API 取回的資料,就是ArrayBuffer物件。

fetch(url)
.then(function(response){
  return response.arrayBuffer()
})
.then(function(arrayBuffer){
  // ...
});

5.File API

如果知道一個檔案的二進位制資料型別,也可以將這個檔案讀取為ArrayBuffer物件。

const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function () {
  const arrayBuffer = reader.result;
  // ···
};

下面以處理 bmp 檔案為例。假定file變數是一個指向 bmp 檔案的檔案物件,首先讀取檔案。

const reader = new FileReader();
reader.addEventListener("load", processimage, false);
reader.readAsArrayBuffer(file);

然後,定義處理影象的回撥函式:先在二進位制資料之上建立一個DataView檢視,再建立一個bitmap物件,用於存放處理後的資料,最後將影象展示在Canvas元素之中。

function processimage(e) {
  const buffer = e.target.result;
  const datav = new DataView(buffer);
  const bitmap = {};
  // 具體的處理步驟
}

具體處理影象資料時,先處理 bmp 的檔案頭。具體每個檔案頭的格式和定義,請參閱有關資料。

bitmap.fileheader = {};
bitmap.fileheader.bfType = datav.getUint16(0, true);
bitmap.fileheader.bfSize = datav.getUint32(2, true);
bitmap.fileheader.bfReserved1 = datav.getUint16(6, true);
bitmap.fileheader.bfReserved2 = datav.getUint16(8, true);
bitmap.fileheader.bfOffBits = datav.getUint32(10, true);

接著處理影象元資訊部分。

bitmap.infoheader = {};
bitmap.infoheader.biSize = datav.getUint32(14, true);
bitmap.infoheader.biWidth = datav.getUint32(18, true);
bitmap.infoheader.biHeight = datav.getUint32(22, true);
bitmap.infoheader.biPlanes = datav.getUint16(26, true);
bitmap.infoheader.biBitCount = datav.getUint16(28, true);
bitmap.infoheader.biCompression = datav.getUint32(30, true);
bitmap.infoheader.biSizeImage = datav.getUint32(34, true);
bitmap.infoheader.biXPelsPerMeter = datav.getUint32(38, true);
bitmap.infoheader.biYPelsPerMeter = datav.getUint32(42, true);
bitmap.infoheader.biClrUsed = datav.getUint32(46, true);
bitmap.infoheader.biClrImportant = datav.getUint32(50, true);

最後處理影象本身的畫素資訊。

const start = bitmap.fileheader.bfOffBits;
bitmap.pixels = new Uint8Array(buffer, start);

至此,影象檔案的資料全部處理完成。下一步,可以根據需要,進行影象變形,或者轉換格式,或者展示在Canvas網頁元素之中。

6.SharedArrayBuffer

JavaScript 是單執行緒的,Web worker 引入了多執行緒:主執行緒用來與使用者互動,Worker 執行緒用來承擔計算任務。每個執行緒的資料都是隔離的,通過postMessage()通訊。下面是一個例子。

// 主執行緒
const w = new Worker('myworker.js');

上面程式碼中,主執行緒新建了一個 Worker 執行緒。該執行緒與主執行緒之間會有一個通訊渠道,主執行緒通過w.postMessage向 Worker 執行緒發訊息,同時通過message事件監聽 Worker 執行緒的迴應。

// 主執行緒
w.postMessage('hi');
w.onmessage = function (ev) {
  console.log(ev.data);
}

上面程式碼中,主執行緒先發一個訊息hi,然後在監聽到 Worker 執行緒的迴應後,就將其打印出來。

Worker 執行緒也是通過監聽message事件,來獲取主執行緒發來的訊息,並作出反應。

// Worker 執行緒
onmessage = function (ev) {
  console.log(ev.data);
  postMessage('ho');
}

執行緒之間的資料交換可以是各種格式,不僅僅是字串,也可以是二進位制資料。這種交換採用的是複製機制,即一個程序將需要分享的資料複製一份,通過postMessage方法交給另一個程序。如果資料量比較大,這種通訊的效率顯然比較低。很容易想到,這時可以留出一塊記憶體區域,由主執行緒與 Worker 執行緒共享,兩方都可以讀寫,那麼就會大大提高效率,協作起來也會比較簡單(不像postMessage那麼麻煩)。

ES2017 引入SharedArrayBuffer,允許 Worker 執行緒與主執行緒共享同一塊記憶體。SharedArrayBuffer的 API 與ArrayBuffer一模一樣,唯一的區別是後者無法共享資料。

// 主執行緒

// 新建 1KB 共享記憶體
const sharedBuffer = new SharedArrayBuffer(1024);

// 主執行緒將共享記憶體的地址傳送出去
w.postMessage(sharedBuffer);

// 在共享記憶體上建立檢視,供寫入資料
const sharedArray = new Int32Array(sharedBuffer);

上面程式碼中,postMessage方法的引數是SharedArrayBuffer物件。

Worker 執行緒從事件的data屬性上面取到資料。

// Worker 執行緒
onmessage = function (ev) {
  // 主執行緒共享的資料,就是 1KB 的共享記憶體
  const sharedBuffer = ev.data;

  // 在共享記憶體上建立檢視,方便讀寫
  const sharedArray = new Int32Array(sharedBuffer);

  // ...
};

共享記憶體也可以在 Worker 執行緒建立,發給主執行緒。

SharedArrayBufferArrayBuffer一樣,本身是無法讀寫的,必須在上面建立檢視,然後通過檢視讀寫。

// 分配 10 萬個 32 位整數佔據的記憶體空間
const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 100000);

// 建立 32 位整數檢視
const ia = new Int32Array(sab);  // ia.length == 100000

// 新建一個質數生成器
const primes = new PrimeGenerator();

// 將 10 萬個質數,寫入這段記憶體空間
for ( let i=0 ; i < ia.length ; i++ )
  ia[i] = primes.next();

// 向 Worker 執行緒傳送這段共享記憶體
w.postMessage(ia);

Worker 執行緒收到資料後的處理如下。

// Worker 執行緒
let ia;
onmessage = function (ev) {
  ia = ev.data;
  console.log(ia.length); // 100000
  console.log(ia[37]); // 輸出 163,因為這是第38個質數
};

7.Atomics 物件

多執行緒共享記憶體,最大的問題就是如何防止兩個執行緒同時修改某個地址,或者說,當一個執行緒修改共享記憶體以後,必須有一個機制讓其他執行緒同步。SharedArrayBuffer API 提供Atomics物件,保證所有共享記憶體的操作都是“原子性”的,並且可以在所有執行緒內同步。

此處不錯更多探討.........

更多:

JavaScript ArrayBuffer 二進位制陣列(一)

JS DataURL 整理(二) DataURL 和圖片

JS DataURL 整理(一)