当前位置:   article > 正文

10分钟速览 JavaScript 处理二进制数据与文件_js读取二进制文件

js读取二进制文件

在 Web 开发中,当我们处理文件时(创建,上传,下载),经常会遇到二进制数据。另一个典型的应用场景是图像处理
这些都可以通过 JavaScript 进行处理,而且二进制操作性能更高。
不过,在 JavaScript 中有很多种二进制数据格式,会有点容易混淆。仅举几个例子:

  • ArrayBuffer,Uint8Array,DataView,Blob,File 及其他。

ArrayBuffer 二进制数组

基本的二进制对象是 ArrayBuffer —— 对固定长度的连续内存空间的引用。

let buffer = new ArrayBuffer(16); // 创建一个长度为 16 的 buffer
alert(buffer.byteLength); // 16
  • 1
  • 2

它会分配一个 16 字节的连续内存空间,并用 0 进行预填充。
注意:ArrayBuffer 并不是一个数组,它只是一个连续的内存区域,仅仅用来暂存一下数据。

操作 ArrayBuffer 中的数据

ArrayBuffer 是核心对象,是所有的基础,是原始的二进制数据。
如果要操作 ArrayBuffer,我们需要使用“视图”对象。

视图对象本身并不存储任何东西。它是一副“眼镜”,透过它来解释存储在 ArrayBuffer 中的字节。同样的东西用不同的方式来解析是不同的。
比如同样一个 16 字节 ArrayBuffer 中的二进制数据可以解释为 16 个“小数字”,或 8 个更大的数字(每个数字 2 个字节),或 4 个更大的数字(每个数字 4 个字节),或 2 个高精度的浮点数(每个数字 8 个字节)。

类型化视图

这里默认提供了几副固定了视角的眼镜:

  • Uint8Array —— 将 ArrayBuffer 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位,因此只能容纳那么多)。这称为 “8 位无符号整数”。
  • Uint16Array —— 将每 2 个字节视为一个 0 到 65535 之间的整数。这称为 “16 位无符号整数”。
  • Uint32Array —— 将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”。
  • Float64Array —— 将每 8 个字节视为一个 5.0x10-324 到 1.8x10308 之间的浮点数。

DataView

DataView 是在 ArrayBuffer 上的一种特殊的超灵活“未类型化”视图。上面类型化的眼镜,一戴上视角就固定了,所有的数据都已固定格式解析。而 DataView 这幅眼镜,视角是可调的,戴上后,想要什么视角就能调什么视角。

  • 对于类型化的数组,构造器决定了其格式。整个数组应该是统一的。第 i 个数字是 arr[i]。
  • 通过 DataView,我们可以使用 .getUint8(i) 或 .getUint16(i) 之类的方法访问数据。我们在调用方法时选择格式,而不是在构造的时候。

TextDecoder 和 TextEncoder

内建的 TextDecoder 对象在给定缓冲区(buffer)和编码格式(encoding)的情况下,允许将值读取为实际的 JavaScript 字符串。

  • 例如,我们收到了一个包含文本数据的文件,TextDecoder 可以解析为文本。

TextEncoder 做相反的事情 —— 将字符串转换为字节。

Blob

arrayBuffer,Uint8Array 及其他 BufferSource 是“二进制数据”,而 Blob 则表示“具有类型的二进制数据”,比 arrBuffer 层级更高。
Blob 带类型,所以很容易解析成对应文件,可以将 Blob 看成某种文件的二进制形式。比如图片的二进制形式。
又因为 Blob 的类型通常是 MIME 类型,所以 Blob 对象很适用于在浏览器中上传/下载

XMLHttpRequest,fetch 等进行 Web 请求的方法可以自然地使用 Blob,当然也可以使用其他类型的二进制数据。

我们可以轻松地在 Blob 和低级别的二进制数据类型之间进行转换:

  • 我们可以使用 new Blob(...) 构造函数从一个类型化数组(typed array)中创建 Blob。
  • 反过来,我们也可以使用 blob.arrayBuffer() 从 Blob 中取回 arrayBuffer,然后在其上创建一个视图(view),用于低级别的二进制处理。

当我们需要处理大型 blob 时,将其转换为 stream 非常有用。你可以轻松地从 blob 创建 ReadableStream。Blob 接口的 stream() 方法返回一个 ReadableStream,其在被读取时返回 blob 中包含的数据。

Blob 组成

Blob 由一个可选的字符串 type()和 blobParts 组成。blobParts 部分通常是一系列其他 Blob 对象,字符串 或 BufferSource。
image.png
构造函数的语法为:

new Blob(blobParts, options);
  • 1
  • blobParts 是 Blob/BufferSource/String 类型的值的数组。
  • options 可选对象:
    • type —— Blob 类型,通常是 MIME 类型,例如 image/png,
    • endings —— 是否转换换行符,使 Blob 对应于当前操作系统的换行符(\r\n 或 \n)。默认为 “transparent”(啥也不做),不过也可以是 “native”(转换)。
// 从字符串创建 Blob
let blob = new Blob(["<html>…</html>"], {type: 'text/html'});
// 请注意:第一个参数必须是一个数组 [...]

// 从类型化数组(typed array)和字符串创建 Blob
let hello = new Uint8Array([72, 101, 108, 108, 111]); // 二进制格式的 "hello"
let blob = new Blob([hello, ' ', 'world'], {type: 'text/plain'});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Blob 操作

Blob 对象是不可改变的。
我们无法直接在 Blob 中更改数据,但我们可以通过 slice 获得 Blob 的多个部分,从这些部分创建新的 Blob 对象,将它们组成新的 Blob,等。就像字符串一样,无法只能更改内容,但可以拼接、删除得到一个新字符串。

用 slice 方法来提取 Blob 片段:

blob.slice([byteStart], [byteEnd], [contentType]);
  • 1
  • byteStart —— 起始字节,默认为 0。
  • byteEnd —— 最后一个字节(不包括,默认为最后)。
  • contentType —— 新 blob 的 type,默认与源 blob 相同。

参数值类似于 array.slice,也允许是负数。

Blob 用作 URL 请求的内容

URL.createObjectURL(blob)可以为在内存中的 Blob 创建一个唯一的 URL。URL 形式为 blob:<origin>/<uuid>
浏览器内部将 URL 和 Blob 形成映射关系。通过这个短短的 URL 就可以访问到 Blob 了。这个 URL 用到, 等标签,Blob 数据就会作为请求的内容被下载。

<!-- download 特性(attribute)强制浏览器下载而不是导航 -->
<a download="hello.txt" href='#' id="link">Download</a>

<script>
  let blob = new Blob(["Hello, world!"], {type: 'text/plain'});

  link.href = URL.createObjectURL(blob);
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

点击 a 标签链接就会下载一个名为 hello.txt 的文件,文件内容为 hello, world!

有一个问题,Blob 始终存在内存中,当确认使用完后,我们可以手动将指向它的引用,也就是创建的 URL 断开。URL.revokeObjectURL(url)从内部映射中移除引用。

Blob 转成 base64

除了使用URL.createObjectURL转成 URL 进行映射内存下载外,还可以将 Blob 数据转换为 base64-编码的字符串数据。

  • 这种编码将二进制数据表示为一个由 0 到 64 的 ASCII 码组成的字符串,非常安全且“可读“。
    • 这个字符串本身就是数据,数据在字里行间。
  • 并且编码适用面更广,可以在任何地方使用这种 url,尤其是可以在data-url上使用。
    • Data URL,即前缀为 data: 协议的 URL,其允许内容创建者向文档中嵌入小文件。

我们使用内建的 FileReader 对象来将 Blob 转换为 base64。它可以将 Blob 中的数据读取为多种格式。

let link = document.createElement('a');
link.download = 'hello.txt';

let blob = new Blob(['Hello, world!'], {type: 'text/plain'});

let reader = new FileReader();
reader.readAsDataURL(blob); // 将 Blob 转换为 base64 并调用 onload

reader.onload = function() {
  link.href = reader.result; // data url
  link.click();
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

两种下载文件的方式对比:
URL.createObjectURL(blob):

  • 如果介意内存,我们需要撤销(revoke)它们。
  • 直接访问 Blob,无需“编码/解码”

Blob 转换为 data url:

  • 无需撤销(revoke)任何操作。
  • 对大的 Blob 进行编码时,性能和内存会有损耗。

Image 转换为 blob 上传

我们可以创建一个图像(image)的、图像的一部分、或者甚至创建一个页面截图的 Blob。这样方便将其上传至其他地方。

Blob 转换为 ArrayBuffer

Blob 构造器允许从几乎任何东西创建 blob,包括任何 BufferSource。
但是,如果我们需要执行低级别的处理时,我们可以从 blob.arrayBuffer() 中获取最低级别的 ArrayBuffer:

// 从 bolb 获取 arrayBuffer
const bufferPromise = await blob.arrayBuffer();

// 或
blob.arrayBuffer().then(buffer => /* 处理 ArrayBuffer */);
  • 1
  • 2
  • 3
  • 4
  • 5

Blob 转换为 Stream

什么是 stream 流?
流会将你想要从网络接受的资源分成一个个小的分块,然后按位处理它。现在浏览器就是这么干的,看视频边缓冲边继续看。
以前,如果我们想要处理某种资源(如视频、文本文件等),我们必须下载完整的文件,等待它反序列化成适当的格式,比如视频数据转成 mp4 格式,然后在完整地接收到所有的内容后再进行处理播放。

当 blob 很大时,我们就可以转成 stream,更好的上传和下载。

Blob 接口里的 stream() 方法返回一个 ReadableStream,在被读取时可以返回 Blob 中包含的数据。

// 从 blob 获取可读流(readableStream)
const readableStream = blob.stream();
const stream = readableStream.getReader();

while (true) {
  // 对于每次迭代:value 是下一个 blob 数据片段
  let { done, value } = await stream.read();
  if (done) {
    // 读取完毕,stream 里已经没有数据了
    console.log('all blob processed.');
    break;
  }

  // 对刚从 blob 中读取的数据片段做一些处理
  console.log(value);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

File 和 FileReader

File 对象继承自 Blob,并扩展了与文件系统相关的功能。虽然 Blob 相对 ArrayBuffer 已经是高级对象了,但用起来还是不够方便,所以就有了更高级的对象。

获取 file 对象

有两种方式获取文件对象:

  1. 手动构造函数创建:new File(fileParts, fileName, [options])
    • fileParts —— Blob/BufferSource/String 类型值的数组。
    • fileName —— 文件名字符串。
    • options —— 可选对象:
      • lastModified —— 最后一次修改的时间戳(整数日期)。
  2. 操作系统提供,比如从 或拖放或其他浏览器接口来获取文件的时候,操作系统就会提供文件的信息。

由于 File 是继承自 Blob 的,所以 File 对象具有相同的属性,附加:

  • name —— 文件名,
  • lastModified —— 最后一次修改的时间戳。

举个例子:我们从 中获取 File 对象的方式:

<input type="file" onchange="showFile(this)">

<script>
function showFile(input) {
  let file = input.files[0];

  alert(`File name: ${file.name}`); // 例如 my.png
  alert(`Last modified: ${file.lastModified}`); // 例如 1552830408824
}
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

FileReader

FileReader 是一个对象,其唯一目的是从 Blob(因此也从 File)对象中读取数据。
可以读取为以下三种格式:

  • 字符串(readAsText)。
  • ArrayBuffer(readAsArrayBuffer)。
  • data url,base-64 编码(readAsDataURL)。

但是,在很多情况下,我们不必读取文件内容。就像我们处理 blob 一样,我们可以使用 URL.createObjectURL(file) 创建一个短的 url,并将其赋给 或 。这样,文件便可以下载文件或者将其呈现为图像,作为 canvas 等的一部分。

而且,如果我们要通过网络发送一个 File,那也很容易:像 XMLHttpRequest 或 fetch 等网络 API 本身就接受 File 对象。

https://zh.javascript.info/

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/145083
推荐阅读
相关标签
  

闽ICP备14008679号