• 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 整理(一)

  • 相关阅读:
    1265 四点共面
    1298 圆与三角形
    1264 线段相交
    1185 威佐夫游戏 V2
    1183 编辑距离
    1089 最长回文子串
    HTML5 boilerplate 笔记(转)
    Grunt上手指南(转)
    RequireJS 2.0初探
    RequireJS学习笔记
  • 原文地址:https://www.cnblogs.com/tianma3798/p/13582296.html
Copyright © 2020-2023  润新知