• ES6 入门教程 29 ArrayBuffer 29.3 复合视图 & 29.4 DataView 视图 & 29.5 二进制数组的应用


    ES6 入门教程

    ECMAScript 6 入门

    作者:阮一峰

    本文仅用于学习记录,不存在任何商业用途,如侵删

    29 ArrayBuffer

    29.3 复合视图

    由于视图的构造函数可以指定起始位置和长度,所以在同一段内存之中,可以依次存放不同类型的数据,这叫做“复合视图”。

    const buffer = new ArrayBuffer(24);
    
    const idView = new Uint32Array(buffer, 0, 1);
    const usernameView = new Uint8Array(buffer, 4, 16);
    const amountDueView = new Float32Array(buffer, 20, 1);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面代码将一个 24 字节长度的ArrayBuffer对象,分成三个部分:

    • 字节 0 到字节 3:1 个 32 位无符号整数
    • 字节 4 到字节 19:16 个 8 位整数
    • 字节 20 到字节 23:1 个 32 位浮点数

    这种数据结构可以用如下的 C 语言描述:

    struct someStruct {
      unsigned long id;
      char username[16];
      float amountDue;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    29.4 DataView 视图

    如果一段数据包括多种类型(比如服务器传来的 HTTP 数据),这时除了建立ArrayBuffer对象的复合视图以外,还可以通过DataView视图进行操作。

    DataView视图提供更多操作选项,而且支持设定字节序。本来,在设计目的上,ArrayBuffer对象的各种TypedArray视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;而DataView视图的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。

    DataView视图本身也是构造函数,接受一个ArrayBuffer对象作为参数,生成视图。

    new DataView(ArrayBuffer buffer [, 字节起始位置 [, 长度]]);
    
    • 1

    下面是一个例子。

    const buffer = new ArrayBuffer(24);
    const dv = new DataView(buffer);
    
    • 1
    • 2

    DataView实例有以下属性,含义与TypedArray实例的同名方法相同。

    • DataView.prototype.buffer:返回对应的 ArrayBuffer 对象
    • DataView.prototype.byteLength:返回占据的内存字节长度
    • DataView.prototype.byteOffset:返回当前视图从对应的 ArrayBuffer 对象的哪个字节开始

    DataView实例提供 8 个方法读取内存。

    • getInt8:读取 1 个字节,返回一个 8 位整数。
    • getUint8:读取 1 个字节,返回一个无符号的 8 位整数。
    • getInt16:读取 2 个字节,返回一个 16 位整数。
    • getUint16:读取 2 个字节,返回一个无符号的 16 位整数。
    • getInt32:读取 4 个字节,返回一个 32 位整数。
    • getUint32:读取 4 个字节,返回一个无符号的 32 位整数。
    • getFloat32:读取 4 个字节,返回一个 32 位浮点数。
    • getFloat64:读取 8 个字节,返回一个 64 位浮点数。

    这一系列get方法的参数都是一个字节序号(不能是负数,否则会报错),表示从哪个字节开始读取。

    const buffer = new ArrayBuffer(24);
    const dv = new DataView(buffer);
    
    // 从第1个字节读取一个8位无符号整数
    const v1 = dv.getUint8(0);
    
    // 从第2个字节读取一个16位无符号整数
    const v2 = dv.getUint16(1);
    
    // 从第4个字节读取一个16位无符号整数
    const v3 = dv.getUint16(3);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上面代码读取了ArrayBuffer对象的前 5 个字节,其中有一个 8 位整数和两个十六位整数。

    如果一次读取两个或两个以上字节,就必须明确数据的存储方式,到底是小端字节序还是大端字节序。默认情况下,DataViewget方法使用大端字节序解读数据,如果需要使用小端字节序解读,必须在get方法的第二个参数指定true

    // 小端字节序
    const v1 = dv.getUint16(1, true);
    
    // 大端字节序
    const v2 = dv.getUint16(3, false);
    
    // 大端字节序
    const v3 = dv.getUint16(3);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    DataView 视图提供 8 个方法写入内存。

    • setInt8:写入 1 个字节的 8 位整数。
    • setUint8:写入 1 个字节的 8 位无符号整数。
    • setInt16:写入 2 个字节的 16 位整数。
    • setUint16:写入 2 个字节的 16 位无符号整数。
    • setInt32:写入 4 个字节的 32 位整数。
    • setUint32:写入 4 个字节的 32 位无符号整数。
    • setFloat32:写入 4 个字节的 32 位浮点数。
    • setFloat64:写入 8 个字节的 64 位浮点数。

    这一系列set方法,接受两个参数,第一个参数是字节序号,表示从哪个字节开始写入,第二个参数为写入的数据。对于那些写入两个或两个以上字节的方法,需要指定第三个参数,false或者undefined表示使用大端字节序写入,true表示使用小端字节序写入。

    // 在第1个字节,以大端字节序写入值为25的32位整数
    dv.setInt32(0, 25, false);
    
    // 在第5个字节,以大端字节序写入值为25的32位整数
    dv.setInt32(4, 25);
    
    // 在第9个字节,以小端字节序写入值为2.5的32位浮点数
    dv.setFloat32(8, 2.5, true);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果不确定正在使用的计算机的字节序,可以采用下面的判断方式。

    const littleEndian = (function() {
      const buffer = new ArrayBuffer(2);
      new DataView(buffer).setInt16(0, 256, true);
      return new Int16Array(buffer)[0] === 256;
    })();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果返回true,就是小端字节序;如果返回false,就是大端字节序。

    29.5 二进制数组的应用

    大量的 Web API 用到了ArrayBuffer对象和它的视图对象。

    29.5.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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果知道传回来的是 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";
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    29.5.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;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    需要注意的是,上面代码的uint8ClampedArray虽然是一个 TypedArray 数组,但是它的视图类型是一种针对Canvas元素的专有类型Uint8ClampedArray。这个视图类型的特点,就是专门针对颜色,把每个字节解读为无符号的 8 位整数,即只能取值 0 ~ 255,而且发生运算的时候自动过滤高位溢出。这为图像处理带来了巨大的方便。

    举例来说,如果把像素的颜色值设为Uint8Array类型,那么乘以一个 gamma 值的时候,就必须这样计算:

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

    因为Uint8Array类型对于大于 255 的运算结果(比如0xFF+1),会自动变为0x00,所以图像处理必须要像上面这样算。这样做很麻烦,而且影响性能。如果将颜色值设为Uint8ClampedArray类型,计算就简化许多。

    pixels[i] *= gamma;
    
    • 1

    Uint8ClampedArray类型确保将小于 0 的值设为 0,将大于 255 的值设为 255。注意,IE 10 不支持该类型。

    29.5.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;
      // ···
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    29.5.4 Fetch API

    Fetch API 取回的数据,就是ArrayBuffer对象。

    fetch(url)
    .then(function(response){
      return response.arrayBuffer()
    })
    .then(function(arrayBuffer){
      // ...
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    29.5.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;
      // ···
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    下面以处理 bmp 文件为例。假定file变量是一个指向 bmp 文件的文件对象,首先读取文件。

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

    然后,定义处理图像的回调函数:先在二进制数据之上建立一个DataView视图,再建立一个bitmap对象,用于存放处理后的数据,最后将图像展示在Canvas元素之中。

    function processimage(e) {
      const buffer = e.target.result;
      const datav = new DataView(buffer);
      const bitmap = {};
      // 具体的处理步骤
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    具体处理图像数据时,先处理 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    接着处理图像元信息部分。

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    最后处理图像本身的像素信息。

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

    至此,图像文件的数据全部处理完成。下一步,可以根据需要,进行图像变形,或者转换格式,或者展示在Canvas网页元素之中。

  • 相关阅读:
    流调溯源 腾讯接口实现批量地址转换
    RadioTransformer:用于视觉注意力引导疾病分类的级联全局焦点Transformer
    面试了1个月连续失败4次,自动化测试真没想象的那么简单
    Eclipse创建Servlet项目-7
    力扣刷题(2094. 找出 3 位偶数)有点东西
    股票换手率排行查询易语言代码
    极简试用期转正述职报告PPT模板
    JVM内存模型篇【JVM内存模型】
    Zabbix Timeout 设置不当导致的问题
    北京地标-自动驾驶高精度地图特征定位数据技术规范
  • 原文地址:https://blog.csdn.net/weixin_44226181/article/details/128031342