前端中的二进制数据,你还在傻傻分不清?
本帖最后由 Winny 于 2022-7-28 16:22 编辑在Web开发中,当我们处理文件时,经常会遇到二进制数据,这是因为JavaScript对于二进制操作性能更高一些。不过,在JavaScript中会有多种二进制数据格式,例如ArrayBuffer,Uint8Array,DataView,Blob,File等等。基本的二进制对象是ArrayBuffer,是所有其它类型的基础。可以使用如下代码来创建它:
// 创建一个长度为16的buffer
let buffer = new ArrayBuffer(16)
buffer.byteLength
它会分配一个16字节的连续内存控件,并用0进行预填充。ArrayBuffer与Array没有任何共同之处,ArrayBuffer长度是固定的,创建之后无法增加或减少它的长度,是一个原始的字节序列,里边存储的具体是什么无从知晓,需要使用其它工具来解析,这个解析工具就是衍生出的其它二进制数据类型。
1.TypedArray系列
Uint8Array —— 将 ArrayBuffer 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位,因此只能容纳那么多)。这称为 “8 位无符号整数”。
Uint16Array —— 将每 2 个字节视为一个 0 到 65535 之间的整数。这称为 “16 位无符号整数”。
Uint32Array —— 将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”。
Float64Array —— 将每 8 个字节视为一个 5.0x10-324 到 1.8x10308 之间的浮点数。
因此,一个 16 字节 ArrayBuffer 中的二进制数据可以解释为 16 个“小数字”,或 8 个更大的数字(每个数字 2 个字节),或 4 个更大的数字(每个数字 4 个字节),或 2 个高精度的浮点数(每个数字 8 个字节)
我们已Uint32Array为例,基于长度为16的ArrayBuffer创建的Uint32Array长度会为4:
2. DataView
DataView语法如下:
new DataView(buffer, , )各参数含义如下:
buffer —— 底层的 ArrayBuffer。与类型化数组不同,DataView 不会自行创建缓冲区(buffer)。我们需要事先准备好。
byteOffset —— 视图的起始字节位置(默认为 0)。
byteLength —— 视图的字节长度(默认至 buffer 的末尾)。
通过DataView,可以使用.getUint8(i)或.getUint16(i)之类的方法访问数据,在实际调用时选择格式,而不是在构造时。
3. TextDecoder&TextEmcoder
之前两种形式都属于数字,如果二进制数据实际上是一个字符串,我们就需要使用TextDecoder对象在给定缓冲区和编码格式,读取实际的字符串,核心API如下:
let decoder = new TextDecoder(, );label —— 编码格式,默认为 utf-8,但同时也支持 big5,windows-1251 等许多其他编码格式。
options —— 可选对象:
fatal —— 布尔值,如果为 true 则为无效(不可解码)字符抛出异常,否则(默认)用字符 \uFFFD 替换无效字符。
ignoreBOM —— 布尔值,如果为 true 则 BOM(可选的字节顺序 Unicode 标记),很少需要使用。
let str = decoder.decode(, );input —— 要被解码的 BufferSource。
options —— 可选对象:
stream —— 对于解码流,为 true,则将传入的数据块(chunk)作为参数重复调用 decoder。在这种情况下,多字节的字符可能偶尔会在块与块之间被分割。这个选项告诉 TextDecoder 记住“未完成”的字符,并在下一个数据块来的时候进行解码。
TextEncoder做的是一件相反的事情,用来将字符串转为字节。核心语法为:
let encoder = new TextEncoder();TextEncoder只支持 utf-8 编码,它有两个方法:
encode(str) —— 从字符串返回 Uint8Array。
encodeInto(str, destination) —— 将 str 编码到 destination 中,该目标必须为 Uint8Array。
4. Blob
ArrayBuffer属于ECMA标准的一部分,是JavaScript的一部分。在浏览器中,还有其它更高级的对象,特别是Blob。Blob是由一个可选的字符串type(通常是MIME类型)和blobParts组成。
构造函数的语法为:
new Blob(blobParts, options);blobParts 是 Blob/BufferSource/String 类型的值的数组。
options 可选对象:
type —— Blob 类型,通常是 MIME 类型,例如 image/png,
endings —— 是否转换换行符,使 Blob 对应于当前操作系统的换行符(\r\n 或 \n)。默认为 "transparent"(啥也不做),不过也可以是 "native"(转换)。
Blob转URL
Blob也可以转化成<a>,<img>或其它标签的URL,来显示Blob中的内容。而Blob中的type,可以让我们下载上传Blob对象。在网络请求中,type自然的变成了Content-Type。
浏览器内部为每个通过URL.createObjectURL生成的URL存储了一个URL到Blob的映射,因此,此类URL很短,但是可以访问Blob。生成的URL仅在当前文档打开的状态下才有效。但是这种也是有一定的副作用的,虽然这里有Blob的映射,但Blob本身只保存在内存中,浏览器无法释放它。只有在文档unload时,该映射才会被自动清除,Blob随之会被释放。但是如果程序声明较长,那么这个释放就不会很快发生。也就是说,如果我们创建了一个URL,即使我们不再需要Blob了,它也会挂载内存中。调用delete(blob)也会返回false。而URL.revokeObjectURL(url)就可以删除对应的url与blob的映射,从而尽早的进行资源回收。
Blob转base64
base64是一种常见的编码形式,它将二进制数据表示为一个由0到64的ASCII码组成的字符串,比较安全。更重要的是它可以直接在“data-url”中使用此编码。“data-url” 的形式为 data:[<mediatype>][;base64],<data>。我们可以在任何地方使用这种 url,和使用“常规” url 一样。
这两种从Blob创建URL的方法都可以用,但通常URL.createObjectURL(blob)更加快捷简单。使用URL.createObjectURL(blob)时,如果介意内存,在URL使用完成之后需要移除URL,对应的Blob可以直接访问。而Blob转为DataUrl时,不需要任何撤销行为,但对大的Blob进行编码时,性能与内存会有损耗。
Blob转ArrayBuffer
Blob构造器允许从几乎任何东西创建blob,包括BufferSource。在需要窒息感低级别处理时,可以使用blob.arrayBuffer()获取低级别的ArrayBuffer。
// 从 bolb 获取 arrayBuffer
const bufferPromise = await blob.arrayBuffer();
Blob转Stream
当读取和写入超过2G的blob时,将其转换为ArrayBuffer会更占用内存,这是可以将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);
}
File
File对象继承Blob,并扩展了与文件系统相关的功能。有两种获取File对象的方法,第一种是使用构造器:
new File(fileParts, fileName, )
fileParts —— Blob/BufferSource/String 类型值的数组。
fileName —— 文件名字符串。
options —— 可选对象:
lastModified —— 最后一次修改的时间戳(整数日期)
第二种是常见的从<input type="file"> 或拖放或其他浏览器接口来获取文件,在这种情况下,file将从操作系统OS来获得this信息。由于File继承Blob,所以File对象具备与Blob相同的属性,附加了name、lastModified额外属性。
<input type="file" onchange="showFile(this)">
<script>
function showFile(input) {
let file = input.files;
alert(`File name: ${file.name}`); // 例如 my.png
alert(`Last modified: ${file.lastModified}`); // 例如 1552830408824
}
</script>FileReader 是一个对象,其唯一目的是从 Blob(因此也从 File)对象中读取数据。它使用事件来传递数据,因为从磁盘读取数据可能比较费时间。
构造函数:
let reader = new FileReader()主要方法:
readAsArrayBuffer(blob) —— 将数据读取为二进制格式的 ArrayBuffer。
readAsText(blob, ) —— 将数据读取为给定编码(默认为 utf-8 编码)的文本字符串。
readAsDataURL(blob) —— 读取二进制数据,并将其编码为 base64 的 data url。
abort() —— 取消操作。
readAsArrayBuffer —— 用于二进制文件,执行低级别的二进制操作。对于诸如切片(slicing)之类的高级别的操作,File 是继承自 Blob 的,所以我们可以直接调用它们,而无需读取。
readAsText —— 用于文本文件,当我们想要获取字符串时。
readAsDataURL —— 当我们想在 src 中使用此数据,并将其用于 img 或其他标签时。正如我们在 Blob 一章中所讲的,还有一种用于此的读取文件的替代方案:URL.createObjectURL(file)。
读取过程中,有以下事件:
loadstart —— 开始加载。
progress —— 在读取过程中出现。
load —— 读取完成,没有 error。
abort —— 调用了 abort()。
error —— 出现 error。
loadend —— 读取完成,无论成功还是失败。
读取完成后,我们可以通过以下方式访问读取结果:
reader.result 是结果(如果成功)
reader.error 是 error(如果失败)。
使用最广泛的事件无疑是 load 和 error。
这是一个读取文件的示例:
<input type="file" onchange="readFile(this)">
<script>
function readFile(input) {
let file = input.files;
let reader = new FileReader();
reader.readAsText(file);
reader.onload = function() {
console.log(reader.result);
};
reader.onerror = function() {
console.log(reader.error);
};
}
</script>
页:
[1]