Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript之typed arrays那些事儿 #164

Open
FrankKai opened this issue Oct 23, 2019 · 4 comments
Open

JavaScript之typed arrays那些事儿 #164

FrankKai opened this issue Oct 23, 2019 · 4 comments

Comments

@FrankKai
Copy link
Owner

主要参考这篇文章:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays

  • typed arrays基本介绍
  • Buffers和views:typed array 架构
  • typed arrays的Web API
  • typed array使用例子
@FrankKai
Copy link
Owner Author

typed arrays基本介绍

  • js中的typed arrays是类数组对象,主要用来处理未加工过的二进制数据
  • 元素为对象的数组,是非常灵活的,可以动态收缩并且可以存放任意js类型的数据。因为js引擎会做优化,所以这些数组才会足够快
  • 随着web应用的成长越来越快,而且复杂度越来越高,增加了一些新特性:audio或video的操作;使用WebSocket收发未经过加工的二进制数据等等,因此通过js的typed array容易且快速的操作未经加工的二进制数据就变得很重要,可以在更深层次或者说更细粒度对数据做控制
  • typed array不能与普通的array混淆,通过Array.isArray()检测typed array的话,会返回false
  • typed array不会继承所有array的方法,例如push,pop就在typed array中不能使用

@FrankKai
Copy link
Owner Author

FrankKai commented Oct 23, 2019

Buffers和views:typed array 架构

  • 为了最大程度提高灵活性和有效性,js中的typed array 分为buffers和views两种。
  • buffer(通过ArrayBuffer类实现)指的是一个数据块对象;buffer没有固定的格式;buffer中的内容是不能访问到的。
  • 为了获得buffer中内存的访问权限,需要用到view;view提供了一个上下文(包括数据类型,初始位置,元素数量),这个上下文将数据转换为typed array。
  • 通过下面这张图可以发现:同一个buffer可以通过不同的view来反应数据存储情况
    image

ArrayBuffer

  • ArrayBuffer是一种特殊的数据类型,代表着一个通用的、固定长度的二进制数据缓冲区。
  • 不能直接修改ArrayBuffer中的内容,需要通过创建一个typed array的view或者DataView去修改。因为view或者DataView是一种特殊格式的buffer,从而读写buffer中的内容。

Typed array views

  • Typed array views有自描述名字,为Int8,Uint32,Float64等多种常见数字类型提供了view。
  • 有一种特殊的typed array view Uint8ClampedArray,它将数值严格限制在0到255之间,这个类型在canvas data processing 方面很有用,因为canvas的getImageData()返回的ImageData,而ImageData实例是由Uint8ClampedArray和image的大小组成,在node 标准库常用语法中有相关资料
  • 目前主要有11种typed array view。不同类型的typed array views,主要在4个方面有差异:Value Range,Size in bytes,Web IDL types以及等价的C语言中的数据类型。例如:Int8Array typed array view,value range是-128到127,字节大小是1 byte,Web IDL type是byte,等价C语言中的int8_t类型。

扩展:
1.什么是Web IDL type
interface definition language,用来描述打算在web浏览器中实现的interface。

DataView

  • DataView是一个更底层的接口,它提供了读写缓冲区中二进制数据的getter/setter。
  • 用来处理不同类型数据很有用,例如typed array views采用平台的原生字节顺序(可以查看Endianness。)但是通过DataView,我们可以调整字节顺序,默认情况字节顺序是大端模式,可以通过DataView的api调整为小端模式
  • ArrayBuffer构造器new DataView(buffer [, byteOffset [, byteLength]]),buffer是ArrayBuffer实例,byteOffset是新DataView的偏移量,byteLength是新DataView的字节长度。
  • DataView上,setInt16的第二个参数怎么理解?view.setInt16(1, 32767); // (max signed 16-bit integer),值。
// create an ArrayBuffer with a size in bytes
var buffer = new ArrayBuffer(16);
// Create a couple of views
var view1 = new DataView(buffer);
var view2 = new DataView(buffer,12,4); //from byte 12 for the next 4 bytes
view1.setInt8(12, 42); // put 42 in slot 12
console.log(view1.buffer);
console.log(view2.buffer);
console.log(view1.getInt8(12));
console.log(view2.getInt8(0));

image

new DataView(buffer).setInt16(0, 256, true)怎么理解?
true是littleEndian,也就是小端模式,低位数据存储在低地址,Int16Array uses the platform's endianness。

扩展:
1.什么是Endianness
内存存储的大小端模式。
所谓大端模式,指的是低位数据高地址,0x12345678,12存buf[0],78(低位数据)存buf[3](高地址)。也就是常规的正序存储。
小端模式与大端模式相反。0x12345678,78存在buf[0],存在低地址。

@FrankKai
Copy link
Owner Author

typed arrays的Web API

使用了typed array的web api有以下这些。

  • FileReader.prototype.readAsArrayBuffer()
    之前接触过FileReader的readAsDataURL(),读取Blob或者File对象,在reader.result上输出base64格式的字符串。
    readAsArrayBuffer()方法也是读取File或者Blob对象的内容。
    和readAsDataUR()类似,读取Blob或者File对象完毕后,readyState值变为DONE,loadend事件触发,在reader.result属性上包含代表file data的ArrayBuffer。也可以直接用blob.arrayBuffer()得到blob的二进制数据并且以promise的方式处理。blob.arrayBuffer().then(buffer => /* 处理ArrayBuffer */)
  • XMLHttpRequest.prototype.send()支持发送typed arrays 和ArrayBuffer对象作为入参
    XMLHttpRequest.send(body) body支持多种数据body。Document(序列化的);BodyInit:包括Blob,BufferSource,FormData,URLSearchParams,ReadableStream或者USVString object。规范中写明了其算法:https://fetch.spec.whatwg.org/#bodyinit。其中有一步是设置对象类型,切换Content-Type为对象的MIME类型。
    IDL的定义如下:
interface mixin Body {
  readonly attribute ReadableStream? body;
  readonly attribute boolean bodyUsed;
  [NewObject] Promise<ArrayBuffer> arrayBuffer();
  [NewObject] Promise<Blob> blob();
  [NewObject] Promise<FormData> formData();
  [NewObject] Promise<any> json();
  [NewObject] Promise<USVString> text();
};

通过send方法发送二进制数据的最好方式是:ArrayBufferView或Blob。

拓展:interface mixin Body{}怎么理解?
引出一个很有趣的东西。interCaps。

Rule: Use interCaps for naming.
All methods and attributes should begin with a lowercase letter. Every subsequent word in the method or attribute name should be capitalized. Avoid capitalizing first letters and using underscores to separate words.
Why: IDL usually follows Java and JavaScript convention, which is intercaps.
In addition, the first letter of all names are promoted to upper case in C++. The C++ signature is the same whether or not the first letter is capitalized.

IDL通常用js或者java实现。此处的interface mixin是java实现。

  • ImageData.data
    它是一个一维Uint8ClampedArray类型的数组,数据内容是按照RGBA顺序排列的数据,它的值在0到255之间。

如何将 typedArray 转换为普通数组

处理完typed array以后,很多情况下会将其转换为普通数组。
可以使用Array.from做转换。或者使用以下代码转换。

let typedArray = new Uint8Array([1, 2, 3, 4]);
let normalArray = Array.from(typedArray);
let typedArray = new Uint8Array([1, 2, 3, 4]),
    normalArray = Array.prototype.slice.call(typedArray);
normalArray.length === 4;
normalArray.constructor === Array;

@FrankKai
Copy link
Owner Author

typed array使用例子

使用带buffer(缓冲区)的view(视图)

// 创建一个16字节定长的buffer
let buffer = new ArrayBuffer(16);

image
此处会用到通信原理中学到的:1byte = 8bit。(1个字节等于8个比特)
上图中的Int8,Int16,Int32,Uint8中的数字都是以bit为单位的。
其总空间为16*8 = 128bit。
因此Int8Array和Uint8Array类型的ArrayBuffer长度为16。
因此Int16Array类型的ArrayBuffer长度为8。
因此Int32Array类型的ArrayBuffer长度为4。
位数越大,说明ArrayBuffer中的一个0占用的内存空间越大。

// 此时对于一块初始字节数是0的内存,在这块内存上可以为所欲为
// 通过byteLength属性,可以获得一个buffer占用的字节内存空间
console.log(buffer.byteLength); // 16(bytes)
// 此时buffer是无法作用的,我们需要创建一个view
// 以下代码会创建一个将buffer中的数据转换为32-bit的有符号整型数组
let int32View = new Int32Array(buffer);

这里除了可以创建Int32Array的ArrayBufferView,还可以创建Int8Array,Uint8Array,Int16Array等等类型的ArrayBufferView。

// 可以像普通数组一样去遍历ArrayBufferView数组
int32View.forEach((e,i,arr)=>{ arr[i] = i; })// 0,1,2,3

这个数组有4个entry,每个entry占用4个byte(字节),所以总计16个字节。

处理复杂的数据结构

  • 用多个不同类型的view联接1个单独的buffer,需要从一个buffer的不同offset开始。
  • 可以与一个包含了多数据类型的data object交互。例如WebGL交互复杂数据结构;data files;或者C数据结构。
struct someStruct {
  unsigned long id; // long 32bit
  char username[16];// char 8bit
  float amountDue;// float 32bit
};

上面的C结构在js中可以这样定义:

let buffer = new ArrayBuffer(24);
// ... read the data into the buffer ...
let idView = new Uint32Array(buffer, 0, 1);
let usernameView = new Uint8Array(buffer, 4, 16);
let amountDueView = new Float32Array(buffer, 20, 1);

偏移量为什么是1,4,20?
因为32/8 = 4。0到3属于idView。
8/8 =1。4到19属于usernameView。
32/8 = 4。20到23属于amountView。

需要注意C语言的数据结构是与平台对齐的。因此C语言中的long,char,float具体占用多少bit,与平台有关。

扩展

1.Uint32Array(buffer, 0, 1)其中的0,1代表什么?
buffer:ArrayBuffer constructor。构造时固定。只读。
byteOffset:0代表从ArrayBuffer开始start位置的offset。构造时固定。只读。
length:ArrayBuffer中元素的个数。构造时固定。只读。
byteLength: ArrayBuffer占用空间的大小。构造时固定。只读。
2.length与byteLength区别是什么?

var buffer = new ArrayBuffer(16);
var int32View = new Int32Array(buffer);
int32View.length; // 4
int32View.byteLength; // 16

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant