Skip to content

ArrayBuffer, binary arrays #529

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

Merged
merged 15 commits into from
Dec 5, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
arraybuffer: update against review comments
  • Loading branch information
bemself committed Dec 4, 2019
commit 192760a3a1c4a25dbb9b80f79da9ce3728292a8b
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function concat(arrays) {
// ...您的代码...
// ...你的代码...
}

let chunks = [
Expand Down
74 changes: 37 additions & 37 deletions 4-binary/01-arraybuffer-binary-arrays/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ JavaScript 中同样也会遇到,而且二进制操作性能也高。

与其他语言相比,JavaScript 中二进制的实现方式不是很标准。但当我们理清楚以后,一切就变得相当简单了。

**基本的二进制对象是 `ArrayBuffer` -- 对固定长度的连续内存空间的引用。**
**基本的二进制对象是 `ArrayBuffer` 对固定长度的连续内存空间的引用。**

我们如下创建一个 ArrayBuffer:
```js run
Expand All @@ -19,27 +19,27 @@ alert(buffer.byteLength); // 16

它会分配一个 16 字节的连续内存区域,并预先用 0 填充。

```warn header ="`ArrayBuffer` 不是某种数组"
```warn header="`ArrayBuffer` 不是某种数组"
让我们来澄清一个可能的误区。‎`ArrayBuffer` 与 `Array` 没有任何共同之处:
- 它长度固定,无法增加或减少。
- 它正好占用了内存中那么多的空间。
- 如要访问单个字节,需要另一个"视图"对象,而不是 `buffer[index]`。
- 如要访问单个字节,需要另一个“视图”对象,而不是 `buffer[index]`。
```

`ArrayBuffer` 是一个内存区域。它里面存储了什么?无从判断。只是一个原始的字节序列。

**如要操作 `ArrayBuffer`,我们需要使用"视图"对象。**
**如要操作 `ArrayBuffer`,我们需要使用“视图”对象。**

视图对象本身并不存储任何元素。它是一副”眼镜“,透过它来解析存储在 `ArrayBuffer` 中的字节。
视图对象本身并不存储任何元素。它是一副“眼镜”,透过它来解析存储在 `ArrayBuffer` 中的字节。

例如:

- **`Uint8Array`** -- 将 "ArrayBuffer" 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位,因此只能容纳那么多)。此类值称为 "8 位无符号整数"
- **`Uint16Array`** -- 将每 2 个字节视为一个 0 到 65535 的整数。此类值称为"16 位无符号整数"
- **`Uint32Array`** -- 将每 4 个字节视为一个 0 到 4294967295 之间的整数。此类值称为"32 位无符号整数"
- **`Float64Array`** -- 将每 8 个字节视为一个 <code>5.0x10<sup>-324</sup></code> 到 <code>1.8x10<sup>308</sup></code> 之间的浮点数。
- **`Uint8Array`** 将 "ArrayBuffer" 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位,因此只能容纳那么多)。此类值称为8 位无符号整数
- **`Uint16Array`** 将每 2 个字节视为一个 0 到 65535 的整数。此类值称为16 位无符号整数
- **`Uint32Array`** 将每 4 个字节视为一个 0 到 4294967295 之间的整数。此类值称为32 位无符号整数
- **`Float64Array`** 将每 8 个字节视为一个 <code>5.0x10<sup>-324</sup></code> 到 <code>1.8x10<sup>308</sup></code> 之间的浮点数。

因此,一个 16 字节 `ArrayBuffer` 中的二进制数据可以表示为 16 个"小数字",或 8 个较大的数字(每个数字 2 个字节),或 4 个更大的数字 (每个数字 4 个字节),或 2 个高精度的浮点数(每个数字 8 个字节)。
因此,一个 16 字节 `ArrayBuffer` 中的二进制数据可以表示为 16 个小数字,或 8 个较大的数字(每个数字 2 个字节),或 4 个更大的数字每个数字 4 个字节,或 2 个高精度的浮点数(每个数字 8 个字节)。

![](arraybuffer-views.svg)

Expand All @@ -64,14 +64,14 @@ view[0] = 123456;

// 遍历值
for(let num of view) {
alert(num); // 123456,然后是 0,0,0 (一共 4 个值)
alert(num); // 123456,然后是 0,0,0一共 4 个值
}

```

## 类型化数组(TypedArray)

所有这些视图(`Uint8Array`, `Uint32Array` 等)有一个通用术语是 [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects)。它们都享有同一组方法和属性。
所有这些视图(`Uint8Array``Uint32Array` 等)有一个通用术语是 [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects)。它们都享有同一组方法和属性。

它们更像普通数组:有索引,可遍历。

Expand All @@ -90,7 +90,7 @@ new TypedArray();

1. 如果给定的是 `ArrayBuffer` 参数,则在其上创建视图。我们已经用过该语法了。

根据需要,我们可以给定起始位置 `byteOffset`(默认为 0)以及 `length`(默认至缓存区的末尾),这样视图就会只涵盖 `buffer` 的一部分。
根据需要,我们可以给定起始位置 `byteOffset`(默认为 0)以及 `length`(默认至缓存区的末尾),这样视图就会只涵盖 `buffer` 的一部分。

2. 如果给定的是 `Array`、或任何类似数组的对象,则创建一个相同长度的类型化数组,并复制值。

Expand All @@ -112,20 +112,20 @@ new TypedArray();
alert( arr8[1] ); // 232(试图复制 1000,但无法将 1000 放进 8 位字节中。)
```

4. 对于整型参数 `length` -- 创建包含 `length` 这么多元素的类型化数组。它的字节长度将是 `length` 乘以单个 `TypedArray.BYTES_PER_ELEMENT` 中的字节数:
4. 对于整型参数 `length` 创建包含 `length` 这么多元素的类型化数组。它的字节长度将是 `length` 乘以单个 `TypedArray.BYTES_PER_ELEMENT` 中的字节数:
```js run
let arr = new Uint16Array(4); // 为 4 个整数创建类型化数组
alert( Uint16Array.BYTES_PER_ELEMENT ); // 每个整数 2 个字节
alert( arr.byteLength ); // 8 (大小,以字节为单位)
alert( arr.byteLength ); // 8大小,以字节为单位)
```

5. 不带参数的情况下,创建零长度的类型化数组。

我们可以直接创建一个 `TypedArray`,而无需提及 `ArrayBuffer`。但是,视图离不开底层的 `ArrayBuffer`,因此除第一种情况(已提供 `ArrayBuffer`)外,其他所有情况都会自动创建 `ArrayBuffer`。
我们可以直接创建一个 `TypedArray`,而无需提及 `ArrayBuffer`。但是,视图离不开底层的 `ArrayBuffer`,因此在所有这些情况下(第一个除外)都会自动创建 `ArrayBuffer`(如果提供的话)

如要访问 `ArrayBuffer`,可以用以下属性:
- `arr.buffer` -- 引用 `ArrayBuffer`。
- `arr.byteLength` -- `ArrayBuffer` 的长度。
- `arr.buffer` 引用 `ArrayBuffer`。
- `arr.byteLength` `ArrayBuffer` 的长度。

因此,我们总是可以从一个视图转到另一个视图:
```js
Expand All @@ -138,10 +138,10 @@ let arr16 = new Uint16Array(arr8.buffer);

下面是类型化数组的列表:

- `Uint8Array`, `Uint16Array`, `Uint32Array` -- 用于 8、16 和 32 位的整数。
- `Uint8ClampedArray` -- 对于 8 位整数,在赋值时便"固定"其值(见下文)。
- `Int8Array`, `Int16Array`, `Int32Array` -- 用于有符号整数(可以为负数)。
- `Float32Array`, `Float64Array` -- 用于 32 位和 64 位的有符号浮点数。
- `Uint8Array``Uint16Array``Uint32Array` 用于 8、16 和 32 位的整数。
- `Uint8ClampedArray` 对于 8 位整数,在赋值时便“固定“其值(见下文)。
- `Int8Array``Int16Array``Int32Array` 用于有符号整数(可以为负数)。
- `Float32Array``Float64Array` 用于 32 位和 64 位的有符号浮点数。

```warn header="无 `int8` 或类似的单值类型"
请注意,尽管有类似 `Int8Array` 这样的名称,JavaScript 中并没有像 `int`,或 `int8` 这样的单值类型。
Expand All @@ -153,19 +153,19 @@ let arr16 = new Uint16Array(arr8.buffer);

如果我们尝试将越界值写入类型化数组会出现什么情况?不会报错。但是多余的位被截断。

例如,我们试着将 256 放入 `Uint8Array`。256 的二进制格式是 `100000000` (9 位),但 `Uint8Array` 每个值只有 8 位,因此可用范围为 0 到 255。
例如,我们试着将 256 放入 `Uint8Array`。256 的二进制格式是 `100000000`9 位,但 `Uint8Array` 每个值只有 8 位,因此可用范围为 0 到 255。

对于更大的数字,仅存储最右边的(低位有效)8 位,其余部分被截断:

![](8bit-integer-256.svg)

因此结果是 0。

257 的二进制格式是 `100000001` (9 位),最右边的 8 位会被存储,因此数组中会有 `1`
257 的二进制格式是 `100000001`9 位,最右边的 8 位会被存储,因此数组中会有 `1`:

![](8bit-integer-257.svg)

换句话说,该数字对 2 取余<sup>8</sup> 被保存
换句话说,保存的是该数字对 2<sup>8</sup> 取模的结果

示例如下:

Expand All @@ -188,17 +188,17 @@ alert(uint8array[1]); // 1

`TypedArray` 有普通的 `Array` 方法,但有个明显的例外。

我们可以遍历(iterate)、`map`、`slice`、`find``reduce`等等。
我们可以遍历iterate、`map`、`slice`、`find``reduce`等等。

但有几件事我们不能做:

- 无 `splice` -- 我们不能"删除"一个值,因为类型化数组是缓存区上的视图,并且是固定的、连续的内存区域。我们所能做的就是分配一个零值。
- 无 `splice` 我们不能“删除”一个值,因为类型化数组是缓存区上的视图,并且是固定的、连续的内存区域。我们所能做的就是分配一个零值。
- 无 `concat` 方法。

还有两种其他方法:

- `arr.set(fromArr, [offset])` 将 `fromArr` 中从 `offset`(默认为 0)开始的所有元素复制到 `arr`。
- `arr.subarray([begin, end])` 创建一个从 `begin` 到 `end`(不包括)相同类型的新视图。这类似于 `slice` 方法(同样也支持),但是不复制任何内容 -- 只是创建一个新视图,对给定的数据进行操作。
- `arr.subarray([begin, end])` 创建一个从 `begin` 到 `end`(不包括)相同类型的新视图。这类似于 `slice` 方法(同样也支持),但是不复制任何内容 - 只是创建一个新视图,对给定的数据进行操作。

有了这些方法,我们可以复制、混合类型化数组,从现有数组创建新数组,等等。

Expand All @@ -217,9 +217,9 @@ alert(uint8array[1]); // 1
new DataView(buffer, [byteOffset], [byteLength])
```

- **`buffer`** -- 底层的 `ArrayBuffer`. 与类型化数组不同,`DataView`不会自行创建缓存区。我们需要事先准备好。
- **`byteOffset`** -- 视图的起始字节位置(默认为 0)。
- **`byteLength`** -- 视图的字节长度(默认至 `buffer` 的末尾)。
- **`buffer`** 底层的 `ArrayBuffer`与类型化数组不同,`DataView` 不会自行创建缓存区。我们需要事先准备好。
- **`byteOffset`** 视图的起始字节位置(默认为 0)。
- **`byteLength`** 视图的字节长度(默认至 `buffer` 的末尾)。

例如,这里我们从同一缓存区中提取不同格式的数字:

Expand Down Expand Up @@ -249,13 +249,13 @@ dataView.setUint32(0, 0); // 将 4 个字节的数字设为 0
几乎任何对 `ArrayBuffer` 的操作,都需要一个视图。

- 它可以是 `TypedArray`:
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- 用于 8 位、16 位和 32 位无符号整数。
- `Uint8ClampedArray` -- 用于 8 位整数,在赋值时便"固定"其值。
- `Int8Array`, `Int16Array`, `Int32Array` -- 用于有符号整数(可以为负数)。
- `Float32Array`, `Float64Array` -- 用于 32 位和 64 位的有符号浮点数。
- 或 `DataView` -- 通过方法(methods)来指定格式的视图,例如,`getUint8(offset)`。
- `Uint8Array``Uint16Array``Uint32Array` 用于 8 位、16 位和 32 位无符号整数。
- `Uint8ClampedArray` 用于 8 位整数,在赋值时便“固定”其值。
- `Int8Array``Int16Array``Int32Array` 用于有符号整数(可以为负数)。
- `Float32Array``Float64Array` 用于 32 位和 64 位的有符号浮点数。
- 或 `DataView` 通过方法(methods)来指定格式的视图,例如,`getUint8(offset)`。

在多数情况下,我们直接对类型化数组进行创建和操作,而将ArrayBuffer”作为“普通区分器”隐藏起来。我们可以通过 `.buffer` 来访问它,并在需要时创建另一个视图。
在多数情况下,我们直接对类型化数组进行创建和操作,而将ArrayBuffer” 作为“普通区分器”隐藏起来。我们可以通过 `.buffer` 来访问它,并在需要时创建另一个视图。

还有另外两个术语:
- `ArrayBufferView` 是所有这些视图的总称。
Expand Down