前端二进制相关知识点总结之Blob/File/ArrayBuffer篇(一)


前言

按照以下的流程来总结前端如何进行图片处理,然后穿插介绍二进制、Data URL、Base64、Blob、Blob URL、ArrayBuffer、TypedArray、DataView 和图片压缩相关的知识点。


一、选择本地图片: 图片预览

1. FileReader API

在支持 FileReader API 的浏览器中,可以利用该 API 实现图片本地预览功能,把本地图片对应的 File 对象转换为 Data URL。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>图片本地预览示例</title>
  </head>
  <body>
    <input type="file" accept="image/*" onchange="loadFile(event)" />
    <img id="previewContainer" />

    <script>
      const loadFile = function (event) {
        // 创建一个 FileReader 对象
        const reader = new FileReader();
        
        reader.onload = function () {
          const output = document.querySelector("#previewContainer");
          output.src = reader.result;
        };
        // 调用 FileReader 对象的 readAsDataURL() 方法
        // 把本地图片对应的 File 对象转换为 Data URL
        reader.readAsDataURL(event.target.files[0]);
      };
    </script>
  </body>
</html>

页面效果:
在这里插入图片描述
上传图片后:
在这里插入图片描述

其实对于 FileReader 对象来说,除了支持把 File/Blob 对象转换为 Data URL 之外,它还提供了 readAsArrayBuffer() 和 readAsText() 方法,用于把 File/Blob 对象转换为其它的数据格式。

通过使用 Chrome 开发者工具,我们可以在 Elements 面板中看到 Data URL:一串非常 “奇怪” 的字符串:data:image/png;base64,iVBORw0KGgoAAAANSUh...

这串 “奇怪” 的字符串被称为 Data URL,它由四个部分组成:前缀(data:)、指示数据类型的 MIME 类型、如果非文本则为可选的 base64 标记、数据本身:data:[<mediatype>][;base64],<data>

其中,mediatype 是个 MIME 类型 的字符串,比如 “image/png” 表示 PNG 图像文件。如果被省略,则默认值为 text/plain;charset=US-ASCII。

MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型,是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
常见的 MIME 类型有:超文本标记语言文本 .html text/html、PNG 图像 .png image/png、普通文本 .txt text/plain 等。

在 Data URL 中,数据是很重要的一部分,它使用 base64 编码的字符串来表示。因此要掌握 Data URL,还得了解 Base64。

2. Base64

2.1 Base64是什么?

Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法。由于 「2⁶ = 64」 ,所以每 6 个比特为一个单元,对应某个可打印字符。3 个字节有 24 个比特,对应于 4 个 base64 单元,即 3 个字节可由 4 个可打印字符来表示。相应的转换过程如下图所示:

在这里插入图片描述

Base64 常用于在处理文本数据的场合,表示、传输、存储一些二进制数据,包括 MIME 的电子邮件及 XML 的一些复杂数据。 在 MIME 格式的电子邮件中,base64 可以用来将二进制的字节序列数据编码成 ASCII 字符序列构成的文本。使用时,在传输编码方式中指定 base64。使用的字符包括大小写拉丁字母各 26 个、数字 10 个、加号 + 和斜杠 /,共 64 个字符,等号 = 用来作为后缀用途。

Base64 相应的索引表如下:
在这里插入图片描述

了解完上述的知识,以编码 Man 为例,来直观的感受一下编码过程。Man 由 M、a 和 n 这 3 个字符组成,它们对应的 ASCII 码为 77、97 和 110。

在这里插入图片描述
接着以每 6 个比特为一个单元,进行 base64 编码操作,具体如下图所示:
在这里插入图片描述
由图可知,Man (3 字节)编码的结果为 TWFu(4 字节),很明显经过 base64 编码后体积会增加 1/3。Man 这个字符串的长度刚好是 3,我们可以用 4 个 base64 单元来表示。但如果待编码的字符串长度不是 3 的整数倍时,最后会多出 1 个或 2 个字节,那么可以先使用 0 字节值在 末尾补足 ,使其能够被 3 整除,然后再进行 base64 的编码。

在这里插入图片描述

2.2 btoa 与 atob

在 JavaScript 中,有两个函数被分别用来处理解码和编码 base64 字符串:

  • btoa():该函数能够基于二进制数据 “字符串” 创建一个 base64 编码的 ASCII 字符串;
  • atob(): 该函数能够解码通过 base64 编码的字符串数据;

其中的 a 代表 ASCII,而 b 代表 Blob,即二进制
因此 atob 表示 ASCII 到 二进制,对应的是解码操作,而 btoa 表示 二进制 到 ASCII,对应的是编码操作。

btoa 使用示例:

const name = 'Semlinker';
const encodedName = btoa(name);
console.log(encodedName); // U2VtbGlua2Vy

atob 使用示例:

const encodedName = 'U2VtbGlua2Vy';
const name = atob(encodedName);
console.log(name); // Semlinker

二、网络下载图片:图片预览

使用 fetch API 获取百度上的头像,具体代码如下所示:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>获取远程图片预览示例</title>
  </head>
  <body>
    <h3>获取远程图片预览示例</h3>
    <img id="previewContainer" style="width: 300px;"/>

    <script>
      const image = document.querySelector("#previewContainer");
      fetch("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F11%2F20210711101603_fd40d.thumb.1000_0.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1680083165&t=221952b37009a7951c7860640bf9bcb2")
        .then((response) => response.blob())
        .then((blob) => {
          const objectURL = URL.createObjectURL(blob);
          image.src = objectURL;
        });
    </script>
  </body>
</html>

以上示例中,通过 fetch API 下载图片;当请求成功后,把响应对象(Response)转换为 Blob 对象,然后使用 URL.createObjectURL 方法,创建 Object URL 并把它赋给 img 元素的 src 属性,从而实现图片的显示。
通过使用 Chrome 开发者工具,我们可以在 Elements 面板中看到 Object URL 的 取值:
在这里插入图片描述
在图中可以清楚的看到 img 元素 src 属性值是一串非常 「特殊」 的字符串:

blob:http://localhost:63342/7d2ae666-d4d1-4822-9daa-be85d198890e

以上的特殊字符串,我们称之为 「Object URL」,相比前面介绍的 Data URL,它简洁很多。

1. Object URL

Object URL 是一种伪协议,也被称为 Blob URL。它允许 Blob 或 File 对象用作图像,下载二进制数据链接等的 URL 源。在浏览器中,我们使用 URL.createObjectURL 方法来创建 Blob URL,该方法接收一个 Blob 对象,并为其创建一个唯一的 URL,其形式为 blob: origin / uuid。

浏览器内部为每个通过 URL.createObjectURL 生成的 URL 存储了一个 「URL → Blob」 映射。因此,此类 URL 较短,但可以访问 Blob。生成的 URL 仅在当前文档打开的状态下才有效,但如果访问的 Blob URL 不再存在,则会从浏览器中收到 404 错误。

上述的 Blob URL 看似很不错,但实际上它也有副作用。虽然存储了 URL → Blob 的映射,但 Blob 本身仍驻留在内存中,浏览器无法释放它。映射在文档卸载时自动清除,因此 Blob 对象随后被释放。但是,如果应用程序寿命很长,那不会很快发生。因此,如果我们创建一个 Blob URL,即使不再需要该 Blob,它也会存在内存中。

针对这个问题,可以调用 URL.revokeObjectURL(url) 方法,从内部映射中删除引用,从而允许删除 Blob,并释放内存。

2. Blob API

Blob(Binary Large Object)表示二进制类型的大对象。在数据库管理系统中,将二进制数据存储为一个单一个体的集合。Blob 通常是影像、声音或多媒体文件。在 JavaScript 中 Blob 类型的对象表示不可变的类似文件对象的原始数据。

为了更直观的感受 Blob 对象,先来使用 Blob 构造函数,创建一个 myBlob 对象:

const myBlob = new Blob(
	['Blob Web Api'],
	{type: "text/plain"}
)
console.log(myBlob);

在这里插入图片描述
myBlob 对象含有两个属性:size 和 type:

  • size :用于表示数据的大小(以字节为单位);
  • type : MIME 类型的字符串。

Blob 由一个可选的字符串 type(通常是 MIME 类型)和 blobParts 组成:

在这里插入图片描述

2.1 Blob 构造函数

Blob 构造函数的语法为:

let myBlob = new Blob(blobParts, options);

相关的参数说明如下:

  • blobParts:它是一个由 ArrayBuffer,ArrayBufferView,Blob,DOMString 等对象构成的数组(DOMStrings 会被编码为 UTF-8);

  • options:一个可选的对象,包含以下两个属性:typeendings
    type : 默认值为 “”,它代表了将会被放入到 blob 中的数组内容的 MIME 类型;
    endings : 默认值为 “transparent”,用于指定包含行结束符 \n 的字符串如何被写入;

    它是以下两个值中的一个:
    native :代表行结束符会被更改为适合宿主操作系统文件系统的换行符,
    transparent :代表会保持 blob 中保存的结束符不变。

let myBlob = new Blob(['<html><h2>Hello Semlinker</h2></html>'], {type : 'text/html', endings: "transparent"});
console.log(myBlob);

// 属性
console.log(myBlob.size);
console.log(myBlob.type);

在这里插入图片描述

2.2 Blob 属性

  • size(只读):表示 Blob 对象中所包含数据的大小(以字节为单位);
  • type(只读):一个字符串,表明该 Blob对象所包含数据的 MIME 类型;如果类型未知,则该值为空字符串。

2.3 Blob 方法

  • slice([start[, end[, contentType]]]):返回一个新的 Blob 对象,包含了源 Blob对象中指定范围内的数据;
  • stream():返回一个能读取 blob 内容的 ReadableStream。
  • text():返回一个 Promise 对象且包含 blob 所有内容的 UTF-8 格式的 USVString。
  • arrayBuffer():返回一个 Promise 对象且包含 blob 所有内容的二进制格式的 ArrayBuffer。

注意:Blob 对象是不可改变的。所以不能直接在一个 Blob 中更改数据,但是可以对一个 Blob 进行分割,从其中创建新的 Blob 对象,将它们混合到一个新的 Blob 中。这种行为类似于 JavaScript 字符串。

对于前面的示例,也可以把响应对象转换为 ArrayBuffer 对象,同样可以正常显示从网络下载的图像:

<h3>获取远程图片预览示例</h3>
<img id="previewContainer" style="width: 50%;"/>

<script>
   const image = document.querySelector("#previewContainer");
   fetch("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F11%2F20210711101603_fd40d.thumb.1000_0.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1680083165&t=221952b37009a7951c7860640bf9bcb2")
   	 // 把响应对象转换为 ArrayBuffer 对象
     .then((response) => response.arrayBuffer())
     .then((buffer) => {
        const blob = new Blob([buffer]);
        const objectURL = URL.createObjectURL(blob);
        image.src = objectURL;
   });
</script>

3. ArrayBuffer 与 TypedArray

3.1 ArrayBuffer

ArrayBuffer 对象用来表示 通用的、固定长度的 原始二进制数据缓冲区。ArrayBuffer 不能直接操作,而是要通过类型数组对象 或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。

ArrayBuffer 简单说是一片内存,但是你不能直接用它。这就好比你在 C 里面,malloc 一片内存出来,你也会把它转换成 unsigned_int32 或者 int16 这些你需要的实际类型的数组/指针来用。

语法:

let buffer = new ArrayBuffer(length)
  • 参数:length 表示要创建的 ArrayBuffer 的大小,单位为字节;
  • 返回值:一个指定大小的 ArrayBuffer 对象,其内容被初始化为 0;
  • 异常:如果 length 大于 Number.MAX_SAFE_INTEGER(>= 2 ** 53)或为负数,则抛出一个
    RangeError 异常。

下面创建一个 8 字节的缓冲区,并使用一个 Int32Array 来引用它:

let buffer = new ArrayBuffer(8);
let view   = new Int32Array(buffer);

对于一些常用的 Web API,如 FileReader API 和 Fetch API 底层也是支持 ArrayBuffer。
下面以 FileReader API 为例,看一下如何把 File 对象读取为 ArrayBuffer 对象:

const reader = new FileReader();

reader.onload = function(e) {
  let arrayBuffer = reader.result;
}

reader.readAsArrayBuffer(file);

3.2 Unit8Array

Uint8Array 数组类型表示一个 8 位无符号整型数组,创建时内容被初始化为 0;创建完后,可以 以对象的方式或使用数组下标索引的方式 引用数组中的元素。

语法:

new Uint8Array(); // ES2017 最新语法
new Uint8Array(length); // 创建初始化为0的,包含length个元素的无符号整型数组
new Uint8Array(typedArray);
new Uint8Array(object);
new Uint8Array(buffer, byteOffset, length);

示例:
1. new Uint8Array();

let uint8 = new Uint8Array();
console.log(uint8);

在这里插入图片描述
2. new Uint8Array(length);

let uint8 = new Uint8Array(2);
uint8[0] = 42;
console.log(uint8[0]); // 42
console.log(uint8.length); // 2
console.log(uint8.BYTES_PER_ELEMENT); // 1
console.log(uint8); 

在这里插入图片描述
3. new TypedArray(object);

let arr = new Uint8Array([21,31]);
console.log(arr[1]); // 31
console.log(arr); 

在这里插入图片描述
4. new TypedArray(typedArray);

let x = new Uint8Array([21, 31]);
let y = new Uint8Array(x);
console.log(y[0]); // 21
console.log(y); 

在这里插入图片描述

5.new Uint8Array(buffer, byteOffset, length]]);

let buffer = new ArrayBuffer(8);
let z = new Uint8Array(buffer, 1, 4);
console.log(z); 

在这里插入图片描述

3.3 ArrayBuffer 与 TypedArray 之间的关系

ArrayBuffer 本身只是一行 0 和 1 串。 ArrayBuffer 不知道该数组中第一个元素和第二个元素之间的分隔位置。
在这里插入图片描述

为了提供上下文,实际上要将其分解为多个盒子,我们需要将其包装在所谓的视图中。可以使用类型数组添加这些数据视图,并且可以使用许多不同类型的类型数组。

例如,你可以有一个 Int8 类型的数组,它将把这个数组分成 8-bit 的字节数组:
在这里插入图片描述
或者也可以有一个无符号 Int16 数组,它会把数组分成 16-bit 的字节数组,并且把它当作无符号整数来处理:
在这里插入图片描述
甚至可以在同一基本缓冲区上拥有多个视图。对于相同的操作,不同的视图会给出不同的结果。

在这里插入图片描述
这样,ArrayBuffer 基本上就像原始内存一样。它模拟了使用 C 之类的语言进行的直接内存访问。你可能想知道为什么我们不让程序直接访问内存,而是添加了这种抽象层,因为直接访问内存将导致一些安全漏洞。

4. Blob 与 ArrayBuffer

  • ArrayBuffer 对象用于表示通用的,固定长度的原始二进制数据缓冲区。你不能直接操纵 ArrayBuffer 的内容,而是需要创建一个类型化数组对象或 DataView 对象,该对象以特定格式表示缓冲区,并使用该对象读取和写入缓冲区的内容;
  • Blob 类型的对象表示不可变的类似文件对象的原始数据。Blob 表示的不一定是 JavaScript 原生格式的数据。File 接口基于 Blob,继承了Blob 功能并将其扩展为支持用户系统上的文件。

4.1 Blob 与 ArrayBuffer 对象之间的转化

  • 使用 FileReaderreadAsArrayBuffer() 方法,可以把 Blob 对象转换为 ArrayBuffer 对象;

  • 使用 Blob 构造函数 ,如 new Blob([new Uint8Array(data]);,可以把 ArrayBuffer 对象转换为 Blob 对象。

1. Blob 转换为 ArrayBuffer:

let blob = new Blob(["\x01\x02\x03\x04"]);
let fileReader = new FileReader();

fileReader.readAsArrayBuffer(blob);
console.log(fileReader);

在这里插入图片描述

2. ArrayBuffer 转换为 Blob:

let buffer = new ArrayBuffer(8)
console.log(buffer);

let z = new Uint8Array(buffer, 1, 4);
let blob = new Blob(z);
console.log(blob);

在这里插入图片描述

5. DataView 与 ArrayBuffer

DataView 视图是一个可以从二进制 ArrayBuffer 对象中读写多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序问题。

字节顺序,又称端序或尾序(英语:Endianness),在计算机科学领域中,指存储器中或在数字通信链路中,组成多字节的字的字节的排列顺序。
字节的排列方式有两个通用规则。例如,一个多位的整数,按照存储地址从低到高排序的字节中,如果该整数的最低有效字节(类似于最低有效位)在最高有效字节的前面,则称小端序;反之则称大端序。在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化。
例如:假设上述变量 x 类型为int,位于地址 0x100 处,它的值为 0x01234567,地址范围为 0x100~0x103字节,其内部排列顺序依赖于机器的类型。大端法从首位开始将是:0x100: 01, 0x101: 23,…,而小端法将是:0x100: 67, 0x101: 45,…。

5.1 DataView 构造函数

语法:

new DataView(buffer, byteOffset, byteLength)

相关的参数说明如下:

  • buffer:DataView 对象的数据源,一个已经存在的 ArrayBuffer 或 SharedArrayBuffer 对象,;
  • byteOffset:此 DataView 对象的第一个字节在 buffer 中的 字节偏移 ;如果未指定,则默认从第一个字节开始;
  • byteLength:此 DataView 对象的字节长度;如果未指定,这个视图的长度将匹配 buffer 的长度。

DataView 返回值:

使用 new 调用 DataView 构造函数后,会返回一个表示指定数据缓存区的新 DataView 对象。你可以把返回的对象想象成一个二进制字节缓存区 ArrayBuffer 的 “解释器” —— 它知道如何在读取或写入时正确地转换字节码。这意味着它能在二进制层面处理整数与浮点转化、字节顺序等其他有关的细节问题。

DataView 使用示例:

const buffer = new ArrayBuffer(16);

const view1 = new DataView(buffer);
const view2 = new DataView(buffer, 12, 4);

console.log(view1);
console.log(view2);

在这里插入图片描述

5.2 DataView 属性

所有 DataView 实例都继承自 DataView.prototype,并且允许向 DataView 对象中添加额外属性。

  • DataView.prototype.buffer:只读,指向创建 DataView 时设定的 ArrayBuffer 对象;
  • DataView.prototype.byteOffset:只读,表示从 ArrayBuffer 读取时的偏移字节长度;
  • DataView.prototype.byteLength:只读,表示 ArrayBuffer 或 SharedArrayBuffer 对象的字节长度。

5.3 DataView 方法

DataView 对象提供了 getInt8()、getUint8()、setInt8() 和 setUint8() 等方法来操作数据。
下面我们以getInt8()、setInt8()为例:

  • setInt8(byteOffset, value): 从DataView起始位置以 byte 为计数的 指定偏移量(byteOffset)
    处储存一个8-bit数(一个字节);
  • getInt8(byteOffset): 从DataView 起始位置以 byte 为计数的 指定偏移量(byteOffset) 处获取一个有符号的8-bit整数(一个字节)。
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
view.setInt8(1, 42);
console.log(view.getInt8(1)); // 42

类似的方法请参考:数据 | DataView


总结

在这里插入图片描述

博客转载自:玩转前端二进制

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
将PCM的dataView格式二进制转换为audio/wav的blob格式,您可以按照以下步骤进行操作: 1. 创建一个空的Uint8Array数组,并将其长度设置为44加上PCM数据的长度。 ```javascript var buffer = new ArrayBuffer(44 + dataView.byteLength); var view = new DataView(buffer); var blob = new Blob([buffer], { type: 'audio/wav' }); ``` 2. 将RIFF标识写入DataView中,从字节0开始写入。 ```javascript view.setUint32(0, 0x52494646, false); // RIFF ``` 3. 写入文件的总大小,从字节4开始写入。 ```javascript view.setUint32(4, 36 + dataView.byteLength, true); // file length ``` 4. 将WAVE标识写入DataView中,从字节8开始写入。 ```javascript view.setUint32(8, 0x57415645, false); // WAVE ``` 5. 将fmt标识写入DataView中,从字节12开始写入。 ```javascript view.setUint32(12, 0x666d7420, false); // fmt ``` 6. 写入fmt块的长度,从字节16开始写入。 ```javascript view.setUint32(16, 16, true); // fmt chunk length ``` 7. 写入音频格式,从字节20开始写入。 ```javascript view.setUint16(20, 1, true); // audio format (PCM) ``` 8. 写入声道数,从字节22开始写入。 ```javascript view.setUint16(22, 1, true); // number of channels ``` 9. 写入采样率,从字节24开始写入。 ```javascript view.setUint32(24, 44100, true); // sample rate ``` 10. 写入字节率,从字节28开始写入。 ```javascript view.setUint32(28, 44100 * 2, true); // byte rate ``` 11. 写入块对齐,从字节32开始写入。 ```javascript view.setUint16(32, 2, true); // block align ``` 12. 写入位深度,从字节34开始写入。 ```javascript view.setUint16(34, 16, true); // bits per sample ``` 13. 将data标识写入DataView中,从字节36开始写入。 ```javascript view.setUint32(36, 0x64617461, false); // data ``` 14. 写入PCM数据的长度,从字节40开始写入。 ```javascript view.setUint32(40, dataView.byteLength, true); // data chunk length ``` 15. 写入PCM数据,从字节44开始写入。 ```javascript var offset = 44; for (var i = 0; i < dataView.byteLength; i++, offset++) { view.setUint8(offset, dataView.getUint8(i)); } ``` 最终,您将得到一个包含PCM数据的audio/wav格式的Blob对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值