2个字节能存多少个16进制_5 分钟知识系列 | 前端文件基础:二进制数组

9f80c0b929c085a70fb3a0ea83f30b3d.png

在开发前端「后台管理系统」时,免不了有操作文件的需求,比如说要运营人员要上传一份 txt 形式的号码包文件,一行就是一个号码,然后展现在前端界面上给运营人员 review,没问题之后再将号码包文件上传。

在我们理清 Blob 和 FileReader 这些你有可能接触过的概念之前,我们先来打牢一下基础,看看我们 js 是如何操作内存中的二进制数据?了解完这块部分,能够帮助我们更好地去操作文件。

这篇文章,我带大家理清如下概念:

  • ArrayBuffer
  • TypedArray
  • DataView
  • Uint8Array / Uint16Array 等等

前言

ArrayBuffer 对象、TypedArray 对象、DataView 对象是 JavaScript 操作二进制数据的一个接口。这些对象早就存在,属于独立的规格,ES6将它们纳入了 ECMAScript 规格,并且增加了新的方法。
这些对象原始的设计目的,与 WebGL 项目有关。所谓 WebGL,就是指浏览器与显卡之间的通信接口,为了满足 JavaScript 与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。文本格式传递一个 32 位整数,两端的 JavaScript 脚本与显卡都要进行格式转化,将非常耗时。这时要是存在一种机制,可以像 C 语言那样,直接操作字节,将 4 个字节的 32 位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提升。
二进制数组就是在这种背景下诞生的。它很像 C 语言的数组,允许开发者以数组下标的形式,直接操作内存,大大增强了 JavaScript 处理二进制数据的能力,使得开发者有可能通过 JavaScript 与操作系统的原生接口进行二进制通信。

二进制数组的读写一般是通过以下三类对象来操作:

  1. ArrayBuffer
  2. TypedArray
  3. DataView

ArrayBuffer

ArrayBuffer 对象,代表内存之中的一段二进制数据,可以通过「视图」进行操作。「视图」部署了数组接口,这意味着,可以用数组的方法操作内存。

创建一个指定长度的 ArrayBuffer 对象,并读取该对象的字节大小:

// 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 来实例化对象,因为它只属于一个「概念」。

而真正能实例化对象、且隶属于 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 与字符串的互相转换

在确定好编码方式的前提下,ArrayBuffer 与字符串是可以互相转换的。我们就通过 String.prototype.charCodeAtString.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 方法费尽地去读取,再转换成字符串呢?

是这个道理没错,但是实际会有这两种情况:

  1. 读取的 txt 文件内容太多,前端展示需要做分页;
  2. 读取的 txt 文件内容太大,整个文件读到内存中,会非常浪费资源。

我来给大家解答下:

  1. txt 文件内容条目太多,前端展示需要做分页是正确的,但是为了节省浏览器占用的内存资源,我们追求下性能,那当然是需要多少读多少是最好的;
  2. 在分页展示的情况下,一次性使用 readAsText 方法将文件读到内存,是非常浪费以及损耗性能的,比如,1 G的日志文件,700 MB 的号码包文件,分分钟会把浏览器卡死,严重影响用户体验。

那么,使用 readAsArrayBuffer 就可以解决问题了吗?是的,可以的,这时候,就需要使用到 Blob 以及 Blob 与文件之间关系的知识了,我们下一篇文章继续阐述。

参考链接

  • 二进制数组
  • 为什么视频网站的视频链接地址是 blob?
  • ArrayBuffer
  • TypedArray
  • Uint8Array
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值