JavaScript ArrayBuffer 二進位制陣列(二) 應用場景
ArrayBuffer 的應用場景
1.AJAX
傳統上,伺服器通過 AJAX 操作只能返回文字資料,即responseType
屬性預設為text
。XMLHttpRequest
第二版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 執行緒建立,發給主執行緒。
SharedArrayBuffer
與ArrayBuffer
一樣,本身是無法讀寫的,必須在上面建立檢視,然後通過檢視讀寫。
// 分配 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
物件,保證所有共享記憶體的操作都是“原子性”的,並且可以在所有執行緒內同步。
此處不錯更多探討.........
更多: