JavaScript ArrayBuffer和Blob

图片来源:https://zhuanlan.zhihu.com/p/97768916

这篇主要介绍Blob和ArrayBuffer相关的一些API之间的关系和用途,并不会详细介绍每个属性和方法,更多的是想讲述清楚一些概念。

Blob

我们在做预览本地图片需求时往往需要再<Input>中拿到File对象,再根据File生成一个Blob URL从而放进img src中显示。

其实<input>中的File实例对象和DataTransfer对象(拖拽)是一个特殊的 Blob 实例,继承了Blob的属性和方法,只是增加了name和lastModifiedDate等专有属性。

Blob对象表示一个不可变、原始数据的类文件对象,它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream(读写流) 来用于数据操作。

我们无法直接在 Blob 中更改数据,但我们可以通过 slice 将 Blob 分割成多个部分,从这些部分创建新的 Blob 对象,将它们组成新的 Blob。

    // 从字符串创建 Blob
    const blob = new Blob(['hello', ' ', 'world'], {type: 'text/plain'});
    
    // 截取blob中不同下标之间的字节
    const newBlob1 = blob.slice(0, 2)
    const newBlob2 = blob.slice(6, 8)
    
    // 组合成新的blob
    const newBlob3 = new Blob([newBlob1, ' ', newBlob2], {type: 'text/plain'})
    newBlob3.text().then(console.log) // he wo

Blob URL

通过URL.createObjectURL可以为Blob生成Blob URL,最常用到的场景就是展示本地图片,将File生成的Blob URL放进img src中。

和较长的Base64格式的Data URL相比,Blob URL的长度显然不能够存储足够的信息,这也就意味着它只是类似于一个浏览器内部的“引用”。从这个角度看,Blob URL是一个浏览器自行制定的一个伪协议。也正是因为Blob数据是存储在内存中,它的生命周期和创建它的窗口中的document 绑定,所以当用完了这个URL最好手动将其占用的内存释放。如果想要将信息留存下来作为url,将Blob对象转换为base64也是个方案。

FileReader

通过FileReader将Blob对象转为字符串、ArrayBuffer和base64

    // 将字符串转换成 Blob对象
    const blob = new Blob(['中文字符串'], {type: 'text/plain'});
    const reader = new FileReader();
    
    // 将Blob 对象转换成字符串
    reader.readAsText(blob, 'utf-8');
    reader.onload = function (e) {
        console.info(reader.result);
    }
    
    // 将Blob 对象转换成 ArrayBuffer
    reader.readAsArrayBuffer(blob);
    reader.onload = function (e) {
        console.info(reader.result); 
    }
    
    // 将Blob 对象转换成 base64
    // FileReader.readAsDataURL()

Blob应用场景:

  • 将blob转为blob URL或data URL作媒体资源,即本地媒体文件显示;
  • 将blob通过slice进行分割从而实现分段上传;
  • canvas输出二进制图像数据;(HTMLCanvasElement.toBlob
  • ...

ArrayBuffer、TypedArray和DataView

历史:为了充分利用3D图形API和GPU加速在canvas上渲染复杂图形,出现了WebGL(Web Graphics Library)。但因为JavaScript运行时中的数组并不存在类型,所以当WebGL底层与JavaScript之间传递数据时,需要为目标环境分配新数组,并以当前格式迭代,这将花费很多时间。

为了解决这个问题,则出现了定型数组(TypeArray)。通过定型数组JavaScript可以分配、读取、写入数组,并直接传给底层图形驱动程序,也可直接从底层获取。

既然定型数组赋予JavaScript跟底层进行数据交换的能力,那么就同样会出现与其他设备/网络进行二进制数据的交流,应对更复杂的场景,DataView也应运而生。

他们以数组的语法处理二进制数据,所以统称为二进制数组,TypedArray和DataView可以像C语言一样通过修改下标的方式直接操作内存。

ArrayBuffer对象存储原始的二进制数据,只是容器,需要TypedArray和DataView来读写。TypedArray视图用来读写单一类型的二进制数据,DataView视图用来读写复杂类型的二进制数据

ArrayBuffer对象作为内存区域,可以存放多种类型的数据。同一段内存,不同数据有不同的解读方式,这就叫做“视图”(view);

    const buffer = new ArrayBuffer(12);

    const x1 = new Int32Array(buffer);
    x1[0] = 1;
    const x2 = new Uint8Array(buffer);
    x2[0]  = 2;
    
    x1[0] // 2
    
    // 由于两个视图对应的是同一段内存,一个视图修改底层内存,会影响到另一个视图。

本来,在设计目的上,ArrayBuffer对象的各种TypedArray视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;但由于不同设备的操作系统中字节序的不同,所以需要DataView视图来做支持,它是用来处理网络设备传来的数据,可以自行设定大端字节序或小端字节序;

字节序

0x1234567的大端字节序和小端字节序的写法如上图,图片来源:https://www.ruanyifeng.com/blog/2016/11/byte-order.html

  • 大端字节序:高位字节在前,低位字节在后,这是人类读写数值的习惯顺序;
  • 小端字节序:低位字节在前,高位字节在后;

计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。但是,人类还是习惯读写大端字节序。所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。(计算机内部都是使用小端字节这点不严谨,有人说是因为不同公司的习惯而已,因为X86和ARM架构是使用小端,但IBM的PowerPC是用大端,但这里不深究)

一般向外部写入数据是不需要管什么字节序的,直接用本机字节序即可,因为被写入的设备会有对应的驱动去判断字节序并正确读取数据。

ArrayBuffer

ArrayBuffer对象用来表示通用的、固定长度的原始数据缓冲区,是一个普通的JavaScript构造函数,可用于内存中分配特定数量的字节空间。ArrayBuffer本身是可读不可写的,只是一个数据容器

    const buf = new ArrayBuffer(16) // 在内存中分配16字节
    console.log(buf.byteLength) // 16

ArrayBuffer和JavaScript数组在使用上是完全不同的,有三个区别:

  • ArrayBuffer初始化后是固定大小的,并且可读不可写
  • 数组里面可以放数字、字符串、布尔值以及对象和数组等,ArrayBuffer放0和1组成的二进制数据;
  • ArrayBuffer放在中,而Array放在中;

TypeArray

TypeArray是一个统称,实际使用的是特定元素类型的类型化数组构造函数;

    const typedArray1 = new Int8Array(8);
    typedArray1[0] = 32;
    
    console.log(typedArray1);
    // Int8Array [32, 0, 0, 0, 0, 0, 0, 0] 
    
    // 总共有
    Int8Array(); 
    Uint8Array(); 
    Uint8ClampedArray();
    Int16Array(); 
    Uint16Array();
    Int32Array(); 
    Uint32Array(); 
    Float32Array(); 
    Float64Array();

TypeArray操作的数组成员都必须是同一个数据类型。每一种视图的构造函数,都有一个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

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

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

二进制数组与字符串可以通过TextDecoder和TextEncoder来互相转换:

    let uint8Array = new Uint8Array([72, 101, 108, 108, 111]);
    alert( new TextDecoder().decode(uint8Array) ); // Hello
    
    let uint8Array = new TextEncoder();.encode("Hello");
    alert( uint8Array ); // 72,101,108,108,111

DataView

专为文件I/O和网络I/O设计,对缓冲数据有高度的控制,但比其他视图性能差一点。跟TypeArray不同,DataView视图中允许存在多种类型,并且可以声明数据的字节序。

const buffer = new ArrayBuffer(4);

    const view1 = new DataView(buffer);

    // 在不同位置设置不同类型数字
    view1.setInt8(0, 42); 
    view1.setInt16(1, 22)

    console.log(view1.getInt8(0)) // 42
    console.log(view1.getInt16(1)) // 22

如果一次读取两个或两个以上字节,就必须明确数据的存储方式,到底是小端字节序还是大端字节序。默认情况下,DataView的get方法使用大端字节序解读数据;

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

Blob和ArratBuffer

  • Blob实际上就是针对文件设计出来的对象,而ArratBuffer针对需要传输的数据本身;
  • Blob主要解决媒体类型(MIME)的问题,ArratBuffer解决的是数据类型问题;
  • Blob是浏览器的api,ArratBuffer数据JavaScript中的标准,ArratBuffer是更底层的API,可以直接操作内存;

二进制数组操作场景

  • 与底层显卡/外部设备进行二进制数据交互;
  • 利用SharedArrayBuffer在不同worker间共享内存(SharedArrayBuffer是ArrayBuffer的变体)
  • ...

 

 

 

 

 

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值