帅起来 發表於 2020-8-29 14:57:00

JavaScript ArrayBuffer 二进制数组(一)

<p><span style="color: rgba(255, 0, 255, 1)"><strong>一、 ArrayBuffer&nbsp;</strong></span></p>
<p><code>ArrayBuffer</code>对象、<code>TypedArray</code>视图和<code>DataView</code>视图是 JavaScript 操作二进制数据的一个接口。这些对象早就存在,属于独立的规格(2011 年 2 月发布),ES6 将它们纳入了 ECMAScript 规格,并且增加了新的方法。它们都是以数组的语法处理二进制数据,所以统称为二进制数组。</p>
<p>二进制数组由三类对象组成。</p>
<p>(1)<code>ArrayBuffer</code>对象:代表内存之中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。</p>
<p>(2)<code>TypedArray</code>视图:共包括 9 种类型的视图,比如<code>Uint8Array</code>(无符号 8 位整数)数组视图,&nbsp;<code>Int16Array</code>(16 位整数)数组视图,&nbsp;<code>Float32Array</code>(32 位浮点数)数组视图等等。</p>
<p>(3)<code>DataView</code>视图:可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号 8 位整数)、第二、三个字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序。</p>
<p>简单说,<code>ArrayBuffer</code>对象代表原始的二进制数据,<code>TypedArray</code>视图用来读写简单类型的二进制数据,<code>DataView</code>视图用来读写复杂类型的二进制数据。</p>
<p><code>TypedArray</code>视图支持的数据类型一共有 9 种(<code>DataView</code>视图支持除<code>Uint8C</code>以外的其他 8 种)。</p>
<table>
<thead>
<tr><th>数据类型</th><th>字节长度</th><th>含义</th><th>对应的 C 语言类型</th></tr>
</thead>
<tbody>
<tr>
<td>Int8</td>
<td>1</td>
<td>8 位带符号整数</td>
<td>signed char</td>
</tr>
<tr>
<td>Uint8</td>
<td>1</td>
<td>8 位不带符号整数</td>
<td>unsigned char</td>
</tr>
<tr>
<td>Uint8C</td>
<td>1</td>
<td>8 位不带符号整数(自动过滤溢出)</td>
<td>unsigned char</td>
</tr>
<tr>
<td>Int16</td>
<td>2</td>
<td>16 位带符号整数</td>
<td>short</td>
</tr>
<tr>
<td>Uint16</td>
<td>2</td>
<td>16 位不带符号整数</td>
<td>unsigned short</td>
</tr>
<tr>
<td>Int32</td>
<td>4</td>
<td>32 位带符号整数</td>
<td>int</td>
</tr>
<tr>
<td>Uint32</td>
<td>4</td>
<td>32 位不带符号的整数</td>
<td>unsigned int</td>
</tr>
<tr>
<td>Float32</td>
<td>4</td>
<td>32 位浮点数</td>
<td>float</td>
</tr>
<tr>
<td>Float64</td>
<td>8</td>
<td>64 位浮点数</td>
<td>double</td>
</tr>
</tbody>
</table>
<p>注意,二进制数组并不是真正的数组,而是类似数组的对象。</p>
<p>很多浏览器操作的 API,用到了二进制数组操作二进制数据,下面是其中的几个。</p>
<ul>
<li>Canvas</li>
<li>Fetch API</li>
<li>File API</li>
<li>WebSockets</li>
<li>XMLHttpRequest</li>
</ul>
<h2 id="arraybuffer-对象">ArrayBuffer 对象概述</h2>
<p><code>ArrayBuffer</code>对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(<code>TypedArray</code>视图和<code>DataView</code>视图)来读写,视图的作用是以指定格式解读二进制数据。</p>
<p><code>ArrayBuffer</code>也是一个构造函数,可以分配一段可以存放数据的连续内存区域。</p>
<pre class="hljs"><code><span class="hljs-keyword">const buf = <span class="hljs-keyword">new <span class="hljs-built_in">ArrayBuffer(<span class="hljs-number">32);
</span></span></span></span></code></pre>
<p>上面代码生成了一段 32 字节的内存区域,每个字节的值默认都是 0。可以看到,<code>ArrayBuffer</code>构造函数的参数是所需要的内存大小(单位字节)。</p>
<p>为了读写这段内容,需要为它指定视图。<code>DataView</code>视图的创建,需要提供<code>ArrayBuffer</code>对象实例作为参数。</p>
<pre class="hljs"><code><span class="hljs-keyword">const buf = <span class="hljs-keyword">new <span class="hljs-built_in">ArrayBuffer(<span class="hljs-number">32);
<span class="hljs-keyword">const dataView = <span class="hljs-keyword">new <span class="hljs-built_in">DataView(buf);
dataView.getUint8(<span class="hljs-number">0) <span class="hljs-comment">// 0
</span></span></span></span></span></span></span></span></span></code></pre>
<p>上面代码对一段 32 字节的内存,建立<code>DataView</code>视图,然后以不带符号的 8 位整数格式,从头读取 8 位二进制数据,结果得到 0,因为原始内存的<code>ArrayBuffer</code>对象,默认所有位都是 0。</p>
<p>另一种<code>TypedArray</code>视图,与<code>DataView</code>视图的一个区别是,它不是一个构造函数,而是一组构造函数,代表不同的数据格式。</p>
<pre class="hljs"><code><span class="hljs-keyword">const buffer = <span class="hljs-keyword">new <span class="hljs-built_in">ArrayBuffer(<span class="hljs-number">12);

<span class="hljs-keyword">const x1 = <span class="hljs-keyword">new <span class="hljs-built_in">Int32Array(buffer);
x1[<span class="hljs-number">0] = <span class="hljs-number">1;
<span class="hljs-keyword">const x2 = <span class="hljs-keyword">new <span class="hljs-built_in">Uint8Array(buffer);
x2[<span class="hljs-number">0]= <span class="hljs-number">2;

x1[<span class="hljs-number">0] <span class="hljs-comment">// 2
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>上面代码对同一段内存,分别建立两种视图:32 位带符号整数(<code>Int32Array</code>构造函数)和 8 位不带符号整数(<code>Uint8Array</code>构造函数)。由于两个视图对应的是同一段内存,一个视图修改底层内存,会影响到另一个视图。</p>
<p><code>TypedArray</code>视图的构造函数,除了接受<code>ArrayBuffer</code>实例作为参数,还可以接受普通数组作为参数,直接分配内存生成底层的<code>ArrayBuffer</code>实例,并同时完成对这段内存的赋值。</p>
<pre class="hljs"><code><span class="hljs-keyword">const typedArray = <span class="hljs-keyword">new <span class="hljs-built_in">Uint8Array([<span class="hljs-number">0,<span class="hljs-number">1,<span class="hljs-number">2]);
typedArray.length <span class="hljs-comment">// 3

typedArray[<span class="hljs-number">0] = <span class="hljs-number">5;
typedArray <span class="hljs-comment">//
</span></span></span></span></span></span></span></span></span></span></code></pre>
<p>上面代码使用<code>TypedArray</code>视图的<code>Uint8Array</code>构造函数,新建一个不带符号的 8 位整数视图。可以看到,<code>Uint8Array</code>直接使用普通数组作为参数,对底层内存的赋值同时完成。</p>
<h3 id="arraybufferprototypebytelength">ArrayBuffer.prototype.byteLength :返回所分配的内存区域的字节长度</h3>
<h3 id="arraybufferprototypeslice">ArrayBuffer.prototype.slice() :允许将内存区域的一部分,拷贝生成一个新的<code>ArrayBuffer</code>对象。</h3>
<h3 id="arraybufferisview">ArrayBuffer.isView()</h3>
<p><code>ArrayBuffer</code>有一个静态方法<code>isView</code>,返回一个布尔值,表示参数是否为<code>ArrayBuffer</code>的视图实例。这个方法大致相当于判断参数,是否为<code>TypedArray</code>实例或<code>DataView</code>实例。</p>
<div class="cnblogs_code">
<pre>const buffer = <span style="color: rgba(0, 0, 255, 1)">new</span> ArrayBuffer(8<span style="color: rgba(0, 0, 0, 1)">);
ArrayBuffer.isView(buffer) </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> false</span>
<span style="color: rgba(0, 0, 0, 1)">
const v </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Int32Array(buffer);
ArrayBuffer.isView(v) </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> true</span></pre>
</div>
<p>&nbsp;</p>
<p><strong><span style="color: rgba(255, 0, 255, 1)">二、TypedArray 视图</span></strong></p>
<p><code>ArrayBuffer</code>对象作为内存区域,可以存放多种类型的数据。同一段内存,不同数据有不同的解读方式,这就叫做“视图”(view)。<code>ArrayBuffer</code>有两种视图,一种是<code>TypedArray</code>视图,另一种是<code>DataView</code>视图。前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。</p>
<p>目前,<code>TypedArray</code>视图一共包括 9 种类型,每一种视图都是一种构造函数。</p>
<ul>
<li><code>Int8Array</code>:8 位有符号整数,长度 1 个字节。</li>
<li><code>Uint8Array</code>:8 位无符号整数,长度 1 个字节。</li>
<li><code>Uint8ClampedArray</code>:8 位无符号整数,长度 1 个字节,溢出处理不同。</li>
<li><code>Int16Array</code>:16 位有符号整数,长度 2 个字节。</li>
<li><code>Uint16Array</code>:16 位无符号整数,长度 2 个字节。</li>
<li><code>Int32Array</code>:32 位有符号整数,长度 4 个字节。</li>
<li><code>Uint32Array</code>:32 位无符号整数,长度 4 个字节。</li>
<li><code>Float32Array</code>:32 位浮点数,长度 4 个字节。</li>
<li><code>Float64Array</code>:64 位浮点数,长度 8 个字节。</li>
</ul>
<p>这 9 个构造函数生成的数组,统称为<code>TypedArray</code>视图。它们很像普通数组,都有<code>length</code>属性,都能用方括号运算符(<code>[]</code>)获取单个元素,所有数组的方法,在它们上面都能使用。普通数组与 TypedArray 数组的差异主要在以下方面。</p>
<ul>
<li>TypedArray 数组的所有成员,都是同一种类型。</li>
<li>TypedArray 数组的成员是连续的,不会有空位。</li>
<li>TypedArray 数组成员的默认值为 0。比如,<code>new Array(10)</code>返回一个普通数组,里面没有任何成员,只是 10 个空位;<code>new Uint8Array(10)</code>返回一个 TypedArray 数组,里面 10 个成员都是 0。</li>
<li>TypedArray 数组只是一层视图,本身不储存数据,它的数据都储存在底层的<code>ArrayBuffer</code>对象之中,要获取底层对象必须使用<code>buffer</code>属性。</li>
</ul>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建一个8字节的ArrayBuffer</span>
const b = <span style="color: rgba(0, 0, 255, 1)">new</span> ArrayBuffer(8<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建一个指向b的Int32视图,开始于字节0,直到缓冲区的末尾</span>
const v1 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Int32Array(b);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建一个指向b的Uint8视图,开始于字节2,直到缓冲区的末尾</span>
const v2 = <span style="color: rgba(0, 0, 255, 1)">new</span> Uint8Array(b, 2<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建一个指向b的Int16视图,开始于字节2,长度为2</span>
const v3 = <span style="color: rgba(0, 0, 255, 1)">new</span> Int16Array(b, 2, 2);</pre>
</div>
<ul>
<li><code>TypedArray.prototype.copyWithin(target, start[, end = this.length])</code></li>
<li><code>TypedArray.prototype.entries()</code></li>
<li><code>TypedArray.prototype.every(callbackfn, thisArg?)</code></li>
<li><code>TypedArray.prototype.fill(value, start=0, end=this.length)</code></li>
<li><code>TypedArray.prototype.filter(callbackfn, thisArg?)</code></li>
<li><code>TypedArray.prototype.find(predicate, thisArg?)</code></li>
<li><code>TypedArray.prototype.findIndex(predicate, thisArg?)</code></li>
<li><code>TypedArray.prototype.forEach(callbackfn, thisArg?)</code></li>
<li><code>TypedArray.prototype.indexOf(searchElement, fromIndex=0)</code></li>
<li><code>TypedArray.prototype.join(separator)</code></li>
<li><code>TypedArray.prototype.keys()</code></li>
<li><code>TypedArray.prototype.lastIndexOf(searchElement, fromIndex?)</code></li>
<li><code>TypedArray.prototype.map(callbackfn, thisArg?)</code></li>
<li><code>TypedArray.prototype.reduce(callbackfn, initialValue?)</code></li>
<li><code>TypedArray.prototype.reduceRight(callbackfn, initialValue?)</code></li>
<li><code>TypedArray.prototype.reverse()</code></li>
<li><code>TypedArray.prototype.slice(start=0, end=this.length)</code></li>
<li><code>TypedArray.prototype.some(callbackfn, thisArg?)</code></li>
<li><code>TypedArray.prototype.sort(comparefn)</code></li>
<li><code>TypedArray.prototype.toLocaleString(reserved1?, reserved2?)</code></li>
<li><code>TypedArray.prototype.toString()</code></li>
<li><code>TypedArray.prototype.values()</code></li>
</ul>
<h3 id="bytes_per_element-属性">BYTES_PER_ELEMENT 属性</h3>
<p>每一种视图的构造函数,都有一个<code>BYTES_PER_ELEMENT</code>属性,表示这种数据类型占据的字节数。</p>
<pre class="hljs"><code><span class="hljs-built_in">Int8Array.BYTES_PER_ELEMENT <span class="hljs-comment">// 1
<span class="hljs-built_in">Uint8Array.BYTES_PER_ELEMENT <span class="hljs-comment">// 1
<span class="hljs-built_in">Uint8ClampedArray.BYTES_PER_ELEMENT <span class="hljs-comment">// 1
<span class="hljs-built_in">Int16Array.BYTES_PER_ELEMENT <span class="hljs-comment">// 2
<span class="hljs-built_in">Uint16Array.BYTES_PER_ELEMENT <span class="hljs-comment">// 2
<span class="hljs-built_in">Int32Array.BYTES_PER_ELEMENT <span class="hljs-comment">// 4
<span class="hljs-built_in">Uint32Array.BYTES_PER_ELEMENT <span class="hljs-comment">// 4
<span class="hljs-built_in">Float32Array.BYTES_PER_ELEMENT <span class="hljs-comment">// 4
<span class="hljs-built_in">Float64Array.BYTES_PER_ELEMENT <span class="hljs-comment">// 8
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>&nbsp;</p>
<h2 id="dataview-视图"><span style="color: rgba(255, 0, 255, 1)">三、DataView 视图</span></h2>
<p>如果一段数据包括多种类型(比如服务器传来的 HTTP 数据),这时除了建立<code>ArrayBuffer</code>对象的复合视图以外,还可以通过<code>DataView</code>视图进行操作。</p>
<p><code>DataView</code>视图提供更多操作选项,而且支持设定字节序。本来,在设计目的上,<code>ArrayBuffer</code>对象的各种<code>TypedArray</code>视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;而<code>DataView</code>视图的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。</p>
<p><code>DataView</code>视图本身也是构造函数,接受一个<code>ArrayBuffer</code>对象作为参数,生成视图。</p>
<pre class="hljs"><code><span class="hljs-keyword">new <span class="hljs-built_in">DataView(<span class="hljs-built_in">ArrayBuffer buffer [, 字节起始位置 [, 长度]]);
</span></span></span></code></pre>
<p>下面是一个例子。</p>
<pre class="hljs"><code><span class="hljs-keyword">const buffer = <span class="hljs-keyword">new <span class="hljs-built_in">ArrayBuffer(<span class="hljs-number">24);
<span class="hljs-keyword">const dv = <span class="hljs-keyword">new <span class="hljs-built_in">DataView(buffer);
</span></span></span></span></span></span></span></code></pre>
<p><code>DataView</code>实例有以下属性,含义与<code>TypedArray</code>实例的同名方法相同。</p>
<ul>
<li><code>DataView.prototype.buffer</code>:返回对应的 ArrayBuffer 对象</li>
<li><code>DataView.prototype.byteLength</code>:返回占据的内存字节长度</li>
<li><code>DataView.prototype.byteOffset</code>:返回当前视图从对应的 ArrayBuffer 对象的哪个字节开始</li>
</ul>
<p><code>DataView</code>实例提供 8 个方法读取内存。</p>
<ul>
<li><code>getInt8</code>:读取 1 个字节,返回一个 8 位整数。</li>
<li><code>getUint8</code>:读取 1 个字节,返回一个无符号的 8 位整数。</li>
<li><code>getInt16</code>:读取 2 个字节,返回一个 16 位整数。</li>
<li><code>getUint16</code>:读取 2 个字节,返回一个无符号的 16 位整数。</li>
<li><code>getInt32</code>:读取 4 个字节,返回一个 32 位整数。</li>
<li><code>getUint32</code>:读取 4 个字节,返回一个无符号的 32 位整数。</li>
<li><code>getFloat32</code>:读取 4 个字节,返回一个 32 位浮点数。</li>
<li><code>getFloat64</code>:读取 8 个字节,返回一个 64 位浮点数。</li>
</ul>
<p>这一系列<code>get</code>方法的参数都是一个字节序号(不能是负数,否则会报错),表示从哪个字节开始读取。</p>
<pre class="hljs"><code><span class="hljs-keyword">const buffer = <span class="hljs-keyword">new <span class="hljs-built_in">ArrayBuffer(<span class="hljs-number">24);
<span class="hljs-keyword">const dv = <span class="hljs-keyword">new <span class="hljs-built_in">DataView(buffer);

<span class="hljs-comment">// 从第1个字节读取一个8位无符号整数
<span class="hljs-keyword">const v1 = dv.getUint8(<span class="hljs-number">0);

<span class="hljs-comment">// 从第2个字节读取一个16位无符号整数
<span class="hljs-keyword">const v2 = dv.getUint16(<span class="hljs-number">1);

<span class="hljs-comment">// 从第4个字节读取一个16位无符号整数
<span class="hljs-keyword">const v3 = dv.getUint16(<span class="hljs-number">3);
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>上面代码读取了<code>ArrayBuffer</code>对象的前 5 个字节,其中有一个 8 位整数和两个十六位整数。</p>
<p>如果一次读取两个或两个以上字节,就必须明确数据的存储方式,到底是小端字节序还是大端字节序。默认情况下,<code>DataView</code>的<code>get</code>方法使用大端字节序解读数据,如果需要使用小端字节序解读,必须在<code>get</code>方法的第二个参数指定<code>true</code>。</p>
<pre class="hljs"><code><span class="hljs-comment">// 小端字节序
<span class="hljs-keyword">const v1 = dv.getUint16(<span class="hljs-number">1, <span class="hljs-literal">true);

<span class="hljs-comment">// 大端字节序
<span class="hljs-keyword">const v2 = dv.getUint16(<span class="hljs-number">3, <span class="hljs-literal">false);

<span class="hljs-comment">// 大端字节序
<span class="hljs-keyword">const v3 = dv.getUint16(<span class="hljs-number">3);
</span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>DataView 视图提供 8 个方法写入内存。</p>
<ul>
<li><code>setInt8</code>:写入 1 个字节的 8 位整数。</li>
<li><code>setUint8</code>:写入 1 个字节的 8 位无符号整数。</li>
<li><code>setInt16</code>:写入 2 个字节的 16 位整数。</li>
<li><code>setUint16</code>:写入 2 个字节的 16 位无符号整数。</li>
<li><code>setInt32</code>:写入 4 个字节的 32 位整数。</li>
<li><code>setUint32</code>:写入 4 个字节的 32 位无符号整数。</li>
<li><code>setFloat32</code>:写入 4 个字节的 32 位浮点数。</li>
<li><code>setFloat64</code>:写入 8 个字节的 64 位浮点数。</li>
</ul>
<p>这一系列<code>set</code>方法,接受两个参数,第一个参数是字节序号,表示从哪个字节开始写入,第二个参数为写入的数据。对于那些写入两个或两个以上字节的方法,需要指定第三个参数,<code>false</code>或者<code>undefined</code>表示使用大端字节序写入,<code>true</code>表示使用小端字节序写入。</p>
<pre class="hljs"><code><span class="hljs-comment">// 在第1个字节,以大端字节序写入值为25的32位整数
dv.setInt32(<span class="hljs-number">0, <span class="hljs-number">25, <span class="hljs-literal">false);

<span class="hljs-comment">// 在第5个字节,以大端字节序写入值为25的32位整数
dv.setInt32(<span class="hljs-number">4, <span class="hljs-number">25);

<span class="hljs-comment">// 在第9个字节,以小端字节序写入值为2.5的32位浮点数
dv.setFloat32(<span class="hljs-number">8, <span class="hljs-number">2.5, <span class="hljs-literal">true);
</span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>如果不确定正在使用的计算机的字节序,可以采用下面的判断方式。</p>
<pre class="hljs"><code><span class="hljs-keyword">const littleEndian = (<span class="hljs-function"><span class="hljs-keyword">function(<span class="hljs-params">) {
<span class="hljs-keyword">const buffer = <span class="hljs-keyword">new <span class="hljs-built_in">ArrayBuffer(<span class="hljs-number">2);
<span class="hljs-keyword">new <span class="hljs-built_in">DataView(buffer).setInt16(<span class="hljs-number">0, <span class="hljs-number">256, <span class="hljs-literal">true);
<span class="hljs-keyword">return <span class="hljs-keyword">new <span class="hljs-built_in">Int16Array(buffer)[<span class="hljs-number">0] === <span class="hljs-number">256;
})();
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>如果返回<code>true</code>,就是小端字节序;如果返回<code>false</code>,就是大端字节序。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>更多详情参考:https://wangdoc.com/es6/arraybuffer.html</p>
<p>JS DataURL 整理(二) DataURL 和图片</p>
<p>JS DataURL 整理(一)&nbsp;&nbsp;</p>
<p>JavaScript 与 ECMAScript 的关系&nbsp;&nbsp;&nbsp;</p><br><br>
来源:https://www.cnblogs.com/tianma3798/p/13582267.html
頁: [1]
查看完整版本: JavaScript ArrayBuffer 二进制数组(一)