在开发前端「后台管理系统」时,免不了有操做文件的需求,好比说要运营人员要上传一份 txt 形式的号码包文件,一行就是一个号码,而后展示在前端界面上给运营人员 review,没问题以后再将号码包文件上传。javascript
在咱们理清 Blob 和 FileReader 这些你有可能接触过的概念以前,咱们先来打牢一下基础,看看咱们 js 是如何操做内存中的二进制数据?了解完这块部分,可以帮助咱们更好地去操做文件。html
这篇文章,我带你们理清以下概念:前端
ArrayBuffer
TypedArray
DataView
Uint8Array
/ Uint16Array
等等ArrayBuffer 对象、TypedArray 对象、DataView 对象是 JavaScript 操做二进制数据的一个接口。这些对象早就存在,属于独立的规格,ES6将它们归入了 ECMAScript 规格,而且增长了新的方法。java
这些对象原始的设计目的,与 WebGL 项目有关。所谓 WebGL,就是指浏览器与显卡之间的通讯接口,为了知足 JavaScript 与显卡之间大量的、实时的数据交换,它们之间的数据通讯必须是二进制的,而不能是传统的文本格式。文本格式传递一个 32 位整数,两端的 JavaScript 脚本与显卡都要进行格式转化,将很是耗时。这时要是存在一种机制,能够像 C 语言那样,直接操做字节,将 4 个字节的 32 位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提高。数组
二进制数组就是在这种背景下诞生的。它很像 C 语言的数组,容许开发者以数组下标的形式,直接操做内存,大大加强了 JavaScript 处理二进制数据的能力,使得开发者有可能经过 JavaScript 与操做系统的原生接口进行二进制通讯。浏览器
二进制数组的读写通常是经过如下三类对象来操做:app
ArrayBuffer
对象,表明内存之中的一段二进制数据,能够经过「视图」进行操做。「视图」部署了数组接口,这意味着,能够用数组的方法操做内存。函数
建立一个指定长度的 ArrayBuffer
对象,并读取该对象的字节大小:post
// 8 表明所须要分配的内存大小(单位:字节)
const buffer = new ArrayBuffer(8);
console.log(buffer.byteLength);
// 8
复制代码
可是 ArrayBuffer
对象只读,是不能被直接操做的,只能经过「视图实例对象」来操做,它们会将缓冲区中的数据表示为特定的格式,并经过这些格式来读写缓冲区的内容。性能
而且,ArrayBuffer
类提供了一个方法判断某变量是否为「视图实例对象」:
console.log( ArrayBuffer.isView(arg) );
复制代码
而所谓的「视图实例对象」,也就是「类型数组对象」(TypedArray
对象) 或 DataView
对象。
二者的区别在于,后者能够自定义格式和字节序。例如,前者只能实例化一样格式(好比都是 Unit8Array
类型)的对象,然后者能够第一个字节为 Unit8
类型,第二个字节为 Int16
类型,等等。
DataView
类型对象笔者平常工做状况下比较少用,可是应用场景很是丰富,笔者打算另起一篇文章来单独阐述其使用方法和场景。
TypedArray
视图类型很特殊,它不是一个构造函数,而是一组构造函数的统称,也就是说不能在程序中直接使用 TypedArray
来实例化对象,由于它只属于一个「概念」。
而真正能实例化对象、且隶属于 TypedArray
视图的构造函数有以下:
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个字节咱们在此仅拿 Uint8Array
类型举例说明,其余同理的。
首先看看 Uint8Array
每一个元素的所占字节数:
console.log( Uint8Array.BYTES_PER_ELEMENT );
// 1
// 表示 1 个字节
复制代码
实例化对象能够有以下写法:
// 写法 1:实例化一个空的视图对象
new Uint8Array();
// 写法 2:建立初始化为 0 的,包含 2 个元素的无符号整型数组
new Uint8Array(2);
// 写法 3:从 ArrayBuffer 中建立
// uint8Buff 和 uint16Buff 视图对象底层对应的是同一段内存对象
// 一方修改,会影响到另外一方
const buff = new ArrayBuffer(8);
const uint8Buff = new Uint8Array(buff);
const uint16Buff = new Uint16Array(buff);
console.log(uint8Buff.length);
// 8
复制代码
另外,很厉害的一点是:普通数组的操做方法和属性,对 TypedArray
类型数组彻底适用。
在肯定好编码方式的前提下,ArrayBuffer 与字符串是能够互相转换的。咱们就经过 String.prototype.charCodeAt
与 String.fromCharCode
来实现一下。咱们知道这两个函数都是基于 UTF-16 编码格式将字符串和整数值进行转换的,因此能够肯定的编码方式就是 「UTF-16」。
来看看代码的实现:
function ab2str (arraybuffer) {
return String.fromCharCode.apply(
null,
new Uint16Array(arraybuffer)
);
}
function str2ab (str) {
// UTF-16 编码中,一个字符在内存中须要占用两个字节
var arraybuffer = new ArrayBuffer(str.length * 2);
var u16Arr = new Uint16Array(arraybuffer);
var len = u16Arr.length;
for (var i=0; i<len; i++) {
u16Arr[i] = str.charCodeAt(i);
}
return u16Arr;
}
console.log(
ab2str( str2ab('Hello World') )
);
复制代码
回到一开始的命题,「二进制数组」的知识可以帮助咱们更好地去操做文件。
那咱们来看看,在读取文件的时候,验证下使用 ArrayBuffer 方式读取出来的文件内容是否正确。
建立一份 test.txt 文件,准备读取
Hello World
复制代码
前端 html 代码
<input id="fileInputer" type="file" />
复制代码
前端 js 代码
const fileInputer = document.getElementById('fileInputer');
function ab2str (arraybuffer) {
return String.fromCharCode.apply(
null,
// 注意换成了 Uint8Array
// 由于 txt 文档在电脑上通常都是 UTF-8 编码
new Uint8Array(arraybuffer)
);
}
fileInputer.addEventListener('change', function (event) {
const target = event.target;
if (!target.files[0]) {
return ;
}
const file = evt.target.files[0];
const reader1 = new FileReader();
const reader2 = new FileReader();
// 以 ArrayBuffer 方式读取,获取结果后转成字符串
reader1.onloadend = function (evt) {
console.log(
reader1.result
);
console.log(
ab2str(new Uint8Array(reader1.result))
);
};
reader1.readAsArrayBuffer(file);
// 以 text 方式读取,获取结果后直接展现
reader2.onloadend = function (evt) {
console.log(
reader2.result
);
};
reader2.readAsText(file);
}, false);
复制代码
看到这里,你们可能会有个疑问,为何 FileReader 的实例已经提供了 readAsText
方法了,你还要使用 readAsArrayBuffer
方法费尽地去读取,再转换成字符串呢?
是这个道理没错,可是实际会有这两种状况:
我来给你们解答下:
readAsText
方法将文件读到内存,是很是浪费以及损耗性能的,好比,1 G的日志文件,700 MB 的号码包文件,分分钟会把浏览器卡死,严重影响用户体验。那么,使用 readAsArrayBuffer
就能够解决问题了吗?是的,能够的,这时候,就须要使用到 Blob 以及 Blob 与文件之间关系的知识了,咱们下一篇文章继续阐述。