当前位置 博文首页 > 文章内容

    JavaScript ArrayBuffer 二进制数组(一)

    作者: 栏目:未分类 时间:2020-08-29 15:01:13

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    一、 ArrayBuffer 

    ArrayBuffer对象、TypedArray视图和DataView视图是 JavaScript 操作二进制数据的一个接口。这些对象早就存在,属于独立的规格(2011 年 2 月发布),ES6 将它们纳入了 ECMAScript 规格,并且增加了新的方法。它们都是以数组的语法处理二进制数据,所以统称为二进制数组。

    二进制数组由三类对象组成。

    (1)ArrayBuffer对象:代表内存之中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。

    (2)TypedArray视图:共包括 9 种类型的视图,比如Uint8Array(无符号 8 位整数)数组视图, Int16Array(16 位整数)数组视图, Float32Array(32 位浮点数)数组视图等等。

    (3)DataView视图:可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号 8 位整数)、第二、三个字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序。

    简单说,ArrayBuffer对象代表原始的二进制数据,TypedArray视图用来读写简单类型的二进制数据,DataView视图用来读写复杂类型的二进制数据。

    TypedArray视图支持的数据类型一共有 9 种(DataView视图支持除Uint8C以外的其他 8 种)。

    数据类型字节长度含义对应的 C 语言类型
    Int8 1 8 位带符号整数 signed char
    Uint8 1 8 位不带符号整数 unsigned char
    Uint8C 1 8 位不带符号整数(自动过滤溢出) unsigned char
    Int16 2 16 位带符号整数 short
    Uint16 2 16 位不带符号整数 unsigned short
    Int32 4 32 位带符号整数 int
    Uint32 4 32 位不带符号的整数 unsigned int
    Float32 4 32 位浮点数 float
    Float64 8 64 位浮点数 double

    注意,二进制数组并不是真正的数组,而是类似数组的对象。

    很多浏览器操作的 API,用到了二进制数组操作二进制数据,下面是其中的几个。

    ArrayBuffer 对象概述

    ArrayBuffer对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(TypedArray视图和DataView视图)来读写,视图的作用是以指定格式解读二进制数据。

    ArrayBuffer也是一个构造函数,可以分配一段可以存放数据的连续内存区域。

    const buf = new ArrayBuffer(32);
    

    上面代码生成了一段 32 字节的内存区域,每个字节的值默认都是 0。可以看到,ArrayBuffer构造函数的参数是所需要的内存大小(单位字节)。

    为了读写这段内容,需要为它指定视图。DataView视图的创建,需要提供ArrayBuffer对象实例作为参数。

    const buf = new ArrayBuffer(32);
    const dataView = new DataView(buf);
    dataView.getUint8(0) // 0
    

    上面代码对一段 32 字节的内存,建立DataView视图,然后以不带符号的 8 位整数格式,从头读取 8 位二进制数据,结果得到 0,因为原始内存的ArrayBuffer对象,默认所有位都是 0。

    另一种TypedArray视图,与DataView视图的一个区别是,它不是一个构造函数,而是一组构造函数,代表不同的数据格式。

    const buffer = new ArrayBuffer(12);
    
    const x1 = new Int32Array(buffer);
    x1[0] = 1;
    const x2 = new Uint8Array(buffer);
    x2[0]  = 2;
    
    x1[0] // 2
    

    上面代码对同一段内存,分别建立两种视图:32 位带符号整数(Int32Array构造函数)和 8 位不带符号整数(Uint8Array构造函数)。由于两个视图对应的是同一段内存,一个视图修改底层内存,会影响到另一个视图。

    TypedArray视图的构造函数,除了接受ArrayBuffer实例作为参数,还可以接受普通数组作为参数,直接分配内存生成底层的ArrayBuffer实例,并同时完成对这段内存的赋值。

    const typedArray = new Uint8Array([0,1,2]);
    typedArray.length // 3
    
    typedArray[0] = 5;
    typedArray // [5, 1, 2]
    

    上面代码使用TypedArray视图的Uint8Array构造函数,新建一个不带符号的 8 位整数视图。可以看到,Uint8Array直接使用普通数组作为参数,对底层内存的赋值同时完成。

    ArrayBuffer.prototype.byteLength :返回所分配的内存区域的字节长度

    ArrayBuffer.prototype.slice() :允许将内存区域的一部分,拷贝生成一个新的ArrayBuffer对象。

    ArrayBuffer.isView()

    ArrayBuffer有一个静态方法isView,返回一个布尔值,表示参数是否为ArrayBuffer的视图实例。这个方法大致相当于判断参数,是否为TypedArray实例或DataView实例。

    const buffer = new ArrayBuffer(8);
    ArrayBuffer.isView(buffer) // false
    
    const v = new Int32Array(buffer);
    ArrayBuffer.isView(v) // true

     

    二、TypedArray 视图

    ArrayBuffer对象作为内存区域,可以存放多种类型的数据。同一段内存,不同数据有不同的解读方式,这就叫做“视图”(view)。ArrayBuffer有两种视图,一种是TypedArray视图,另一种是DataView视图。前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。

    目前,TypedArray视图一共包括 9 种类型,每一种视图都是一种构造函数。

    • Int8Array:8 位有符号整数,长度 1 个字节。
    • Uint8Array:8 位无符号整数,长度 1 个字节。
    • Uint8ClampedArray:8 位无符号整数,长度 1 个字节,溢出处理不同。
    • Int16Array:16 位有符号整数,长度 2 个字节。
    • Uint16Array:16 位无符号整数,长度 2 个字节。
    • Int32Array:32 位有符号整数,长度 4 个字节。
    • Uint32Array:32 位无符号整数,长度 4 个字节。
    • Float32Array:32 位浮点数,长度 4 个字节。
    • Float64Array:64 位浮点数,长度 8 个字节。

    这 9 个构造函数生成的数组,统称为TypedArray视图。它们很像普通数组,都有length属性,都能用方括号运算符([])获取单个元素,所有数组的方法,在它们上面都能使用。普通数组与 TypedArray 数组的差异主要在以下方面。

    • TypedArray 数组的所有成员,都是同一种类型。
    • TypedArray 数组的成员是连续的,不会有空位。
    • TypedArray 数组成员的默认值为 0。比如,new Array(10)返回一个普通数组,里面没有任何成员,只是 10 个空位;new Uint8Array(10)返回一个 TypedArray 数组,里面 10 个成员都是 0。
    • TypedArray 数组只是一层视图,本身不储存数据,它的数据都储存在底层的ArrayBuffer对象之中,要获取底层对象必须使用buffer属性。
    // 创建一个8字节的ArrayBuffer
    const b = new ArrayBuffer(8);
    
    // 创建一个指向b的Int32视图,开始于字节0,直到缓冲区的末尾
    const v1 = new Int32Array(b);
    
    // 创建一个指向b的Uint8视图,开始于字节2,直到缓冲区的末尾
    const v2 = new Uint8Array(b, 2);
    
    // 创建一个指向b的Int16视图,开始于字节2,长度为2
    const v3 = new Int16Array(b, 2, 2);
    • TypedArray.prototype.copyWithin(target, start[, end = this.length])
    • TypedArray.prototype.entries()
    • TypedArray.prototype.every(callbackfn, thisArg?)
    • TypedArray.prototype.fill(value, start=0, end=this.length)
    • TypedArray.prototype.filter(callbackfn, thisArg?)
    • TypedArray.prototype.find(predicate, thisArg?)
    • TypedArray.prototype.findIndex(predicate, thisArg?)
    • TypedArray.prototype.forEach(callbackfn, thisArg?)
    • TypedArray.prototype.indexOf(searchElement, fromIndex=0)
    • TypedArray.prototype.join(separator)
    • TypedArray.prototype.keys()
    • TypedArray.prototype.lastIndexOf(searchElement, fromIndex?)
    • TypedArray.prototype.map(callbackfn, thisArg?)
    • TypedArray.prototype.reduce(callbackfn, initialValue?)
    • TypedArray.prototype.reduceRight(callbackfn, initialValue?)
    • TypedArray.prototype.reverse()
    • TypedArray.prototype.slice(start=0, end=this.length)
    • TypedArray.prototype.some(callbackfn, thisArg?)
    • TypedArray.prototype.sort(comparefn)
    • TypedArray.prototype.toLocaleString(reserved1?, reserved2?)
    • TypedArray.prototype.toString()
    • TypedArray.prototype.values()

    BYTES_PER_ELEMENT 属性

    每一种视图的构造函数,都有一个BYTES_PER_ELEMENT属性,表示这种数据类型占据的字节数。

    Int8Array.BYTES_PER_ELEMENT // 1
    Uint8Array.BYTES_PER_ELEMENT // 1
    Uint8ClampedArray.BYTES_PER_ELEMENT // 1
    Int16Array.BYTES_PER_ELEMENT // 2
    Uint16Array.BYTES_PER_ELEMENT // 2
    Int32Array.BYTES_PER_ELEMENT // 4
    Uint32Array.BYTES_PER_ELEMENT // 4
    Float32Array.BYTES_PER_ELEMENT // 4
    Float64Array.BYTES_PER_ELEMENT // 8
    

     

    三、DataView 视图

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

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

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

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

    下面是一个例子。

    const buffer = new ArrayBuffer(24);
    const dv = new DataView(buffer);
    

    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);
    

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

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

    // 小端字节序
    const v1 = dv.getUint16(1, true);
    
    // 大端字节序
    const v2 = dv.getUint16(3, false);
    
    // 大端字节序
    const v3 = dv.getUint16(3);
    

    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);
    

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

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

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

     

     

     

     

     

     

    更多详情参考:https://wangdoc.com/es6/arraybuffer.html

    JS DataURL 整理(二) DataURL 和图片

    JS DataURL 整理(一)  

    JavaScript 与 ECMAScript 的关系