This commit is contained in:
Ilya Kantor 2019-03-20 15:09:06 +03:00
parent 31f83a5e1b
commit 4ffd79e337
65 changed files with 1481 additions and 28000 deletions

View file

@ -1,17 +1,15 @@
# ArrayBuffer, binary arrays
Binary data appears when we work with arbitrary files (uploading, downloading, creation). Or when we want to do image/audio processing.
In web-development we meet binary data mostly while dealing with files (create, upload, download). Another typical use case is image processing.
That's all possible in Javascript, and binary operations are high-performant.
Although, there's a bit of confusion, because there are many classes. To name a few:
- `ArrayBuffer`, `Uint8Array`, `DataView`, etc.
- `ArrayBuffer`, `Uint8Array`, `DataView`, `Blob`, `File`, etc.
It may seem complex, but in fact it's not. Everything's fairly simple.
Binary data in Javascript is implemented in a non-standard way, compared to other languages. But when we sort things out, everything becomes fairly simple.
Let's sort things out.
**The basic binary object is `ArrayBuffer` -- a fixed-length raw sequence of bytes.**
**The basic binary object is `ArrayBuffer` -- a reference to a fixed-length contiguos memory area.**
We create it like this:
```js run
@ -21,62 +19,66 @@ alert(buffer.byteLength); // 16
This allocates a contiguous memory area of 16 bytes and pre-fills it with zeroes.
```warn header="`ArrayBuffer` is not `Array` at all"
`ArrayBuffer` has nothing in common with `Array`:
```warn header="`ArrayBuffer` is not an array of something"
Let's eliminate a possible source of confusion. `ArrayBuffer` has nothing in common with `Array`:
- It has a fixed length, we can't increase or decrease it.
- It takes exactly that much space in the memory.
- It can't store any value: only bytes, the exact number of them.
- To access individual bytes, another "view" object is needed, not `buffer[index]`.
```
`ArrayBuffer` is a raw sequence of bytes. What's stored in it? It has no clue.
`ArrayBuffer` is a memory area. What's stored in it? It has no clue. Just a raw sequence of bytes.
**To manipulate an `ArrayBuffer`, we need to use a "view" object.**
A view object does not store anything on it's own. It's just an "eyeglasses" that gives an interpretation of the bytes stored in the `ArrayBuffer`.
A view object does not store anything on it's own. It's the "eyeglasses" that give an interpretation of the bytes stored in the `ArrayBuffer`.
For instance:
- **`Uint8Array`** -- treats each byte as a separate number, with possible values are from 0 to 255 (a byte is 8-bit, so can hold only that much). That's called a "8-bit unsigned integer".
- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values are from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer".
- **`Uint16Array`** -- treats every 2 bytes as an integer, with possible values from 0 to 65535. That's called a "16-bit unsigned integer".
- **`Uint32Array`** -- treats every 4 bytes as an integer, with possible values from 0 to 4294967295. That's called a "32-bit unsigned integer".
- **`Float64Array`** -- treats every 8 bytes as a floating point number with possible values from <code>5.0x10<sup>-324</sup></code> to <code>1.8x10<sup>308</sup></code>.
So, the binary data in an `ArrayBuffer` of 16 bytes can be interpreted as 16 "tiny numbers", or 8 bigger numbers, or 4 even bigger, or 2 floating-point values with high precision.
So, the binary data in an `ArrayBuffer` of 16 bytes can be interpreted as 16 "tiny numbers", or 8 bigger numbers (2 bytes each), or 4 even bigger (4 bytes each), or 2 floating-point values with high precision (8 bytes each).
![](arraybuffer-views.png)
`ArrayBuffer` is the core object, the root of everything, the raw binary data. But if we're going to write into it, or iterate over it, basically for almost any operation we must use a view, e.g:
`ArrayBuffer` is the core object, the root of everything, the raw binary data.
But if we're going to write into it, or iterate over it, basically for almost any operation we must use a view, e.g:
```js run
let buffer = new ArrayBuffer(16); // create a buffer of length 16
*!*
let view = new Uint32Array(buffer); // treat every 4 bytes as an integer number
let view = new Uint32Array(buffer); // treat buffer as a sequence of 32-bit integers
alert(Uint32Array.BYTES_PER_ELEMENT); // 4 bytes per integer
*/!*
alert(view.length); // 4, it stores that many numbers
alert(view.byteLength); // 16, the length in bytes
alert(view.length); // 4, it stores that many integers
alert(view.byteLength); // 16, the size in bytes
// let's write a value
view[0] = 123456;
// iterate over bytes
// iterate over values
for(let num of view) {
alert(num); // 123456, then 0, 0, 0 (4 numbers total)
alert(num); // 123456, then 0, 0, 0 (4 values total)
}
alert(Uint32Array.BYTES_PER_ELEMENT); // 4 (that many bytes per number)
```
The common term for all these views is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properities.
They are much more like regular arrays: have indexes and iterable.
## TypedArray
The common term for all these views (`Uint8Array`, `Uint32Array`, etc) is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properities.
They are much more like regular arrays: have indexes and iterable.
A typed array constructor (be it `Int8Array` or `Float64Array`, doesn't matter) behaves differently depending on argument types.
There are 5 variants:
There are 5 variants of arguments:
```js
new TypedArray(buffer, [byteOffset], [length]);
@ -86,13 +88,11 @@ new TypedArray(length);
new TypedArray();
```
A view cannot exist without an underlying `ArrayBuffer`, so gets created automatically in all these calls except the first one.
1. If an `ArrayBuffer` argument is supplied, the view is created over it. We used that syntax already.
Optionally we can provide `byteOffset` to start from (0 by default) and the `length` (till the end of the buffer by default).
Optionally we can provide `byteOffset` to start from (0 by default) and the `length` (till the end of the buffer by default), then the view will cover only a part of the `buffer`.
2. If an `Array`, or any array-like object is given, it behaves as `Array.from`: creates a typed array of the same length and copies the content.
2. If an `Array`, or any array-like object is given, it creates a typed array of the same length and copies the content.
We can use it to pre-fill the array with the data:
```js run
@ -102,7 +102,7 @@ A view cannot exist without an underlying `ArrayBuffer`, so gets created automat
alert( arr.length ); // 4
alert( arr[1] ); // 1
```
3. If another `TypedArray` is supplied, it's contents is also copied. Values are converted to the new type in the process. The new array will have the same length.
3. If another `TypedArray` is supplied, it does the same: creates a typed array of the same length and copies values. Values are converted to the new type in the process.
```js run
let arr16 = new Uint16Array([1, 1000]);
*!*
@ -112,16 +112,31 @@ A view cannot exist without an underlying `ArrayBuffer`, so gets created automat
alert( arr8[1] ); // 232 (tried to copy 1000, but can't fit 1000 into 8 bits)
```
4. For a numeric argument `length` -- creates an `ArrayBuffer` to contain that many elements. Its byte length will be `length` multiplied by the number of bytes in a single item `TypedArray.BYTES_PER_ELEMENT`:
4. For a numeric argument `length` -- creates the typed array to contain that many elements. Its byte length will be `length` multiplied by the number of bytes in a single item `TypedArray.BYTES_PER_ELEMENT`:
```js run
let arr = new Uint16Array(4);
alert( Uint16Array.BYTES_PER_ELEMENT ); // 2 bytes per number
alert( arr.byteLength ); // 8 bytes in the buffer, to contain four 2-byte numbers
let arr = new Uint16Array(4); // create typed array for 4 integers
alert( Uint16Array.BYTES_PER_ELEMENT ); // 2 bytes per integer
alert( arr.byteLength ); // 8 (size in bytes)
```
5. Without arguments, creates an zero-length empty `ArrayBuffer`.
5. Without arguments, creates an zero-length typed array.
Here's the full list of typed arrays:
We can create a `TypedArray` directly, without mentioning `ArrayBuffer`. But a view cannot exist without an underlying `ArrayBuffer`, so gets created automatically in all these cases except the first one (when provided).
To access the `ArrayBuffer`, there are properties:
- `arr.buffer` -- references the `ArrayBuffer`.
- `arr.byteLength` -- the length of the `ArrayBuffer`.
So, we can always move from one view to another:
```js
let arr8 = new Uint8Array([0, 1, 2, 3]);
// another view on the same data
let arr16 = new Uint16Array(arr8.buffer);
```
Here's the list of typed arrays:
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- for integer numbers of 8, 16 and 32 bits.
- `Uint8ClampedArray` -- for 8-bit integers, "clamps" them on assignment (see below).
@ -129,14 +144,14 @@ Here's the full list of typed arrays:
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
```warn header="No `int8` or similar single-valued types"
Please note, despite of the names like `Int8Array`, there's no type like `int8`, or similar in Javascript.
Please note, despite of the names like `Int8Array`, there's no single-value type like `int`, or `int8` in Javascript.
That's logical, as `Int8Array` is technically not an array, but rather a view on `ArrayBuffer`.
That's logical, as `Int8Array` is not an array of these individual values, but rather a view on `ArrayBuffer`.
```
### Out-of-bounds behavior
What if we attempt to write an out-of-bounds value into a typed array? There will be no error. But the result may be not what we want.
What if we attempt to write an out-of-bounds value into a typed array? There will be no error. But extra bits are cut-off.
For instance, let's try to put 256 into `Uint8Array`. In binary form, 256 is `100000000` (9 bits), but `Uint8Array` only provides 8 bits per value, that makes the available range from 0 to 255.
@ -155,8 +170,7 @@ In other words, the number modulo 2<sup>8</sup> is saved.
Here's the demo:
```js run
let buffer = new ArrayBuffer(16);
let uint8array = new Uint8Array(buffer);
let uint8array = new Uint8Array(16);
let num = 256;
alert(num.toString(2)); // 100000000 (binary representation)
@ -168,7 +182,7 @@ alert(uint8array[0]); // 0
alert(uint8array[1]); // 1
```
`Uint8ClampedArray` is special in this aspect, its behavior is different. It saves 255 for any number that is greater than 255, and 0 for any negative number. That behavior is sometimes useful for image processing.
`Uint8ClampedArray` is special in this aspect, its behavior is different. It saves 255 for any number that is greater than 255, and 0 for any negative number. That behavior is useful for image processing.
## TypedArray methods
@ -181,28 +195,21 @@ There are few things we can't do though:
- No `splice` -- we can't "delete" a value, because typed arrays are views on a buffer, and these are fixed, contiguous areas of memory. All we can do is to assign a zero.
- No `concat` method.
There are two additional properties:
- `arr.buffer` -- the reference to the underlying `ArrayBuffer`.
- `arr.byteLength` -- the byte size of the underlying `ArrayBuffer`.
...And methods:
There are two additional methods:
- `arr.set(fromArr, [offset])` copies all elements from `fromArr` to the `arr`, starting at position `offset` (0 by default).
- `arr.subarray([begin, end])` creates a new view of the same type from `begin` to `end` (exclusive). That's similar to `arr.slice` method, but doesn't copy anything -- just creates a new view.
- `arr.subarray([begin, end])` creates a new view of the same type from `begin` to `end` (exclusive). That's similar to `slice` method (that's also supported), but doesn't copy anything -- just creates a new view, to operate on the given piece of data.
These methods allow us to copy typed arrays one onto another, create new arrays from existing ones.
These methods allow us to copy typed arrays, mix them, create new arrays from existing ones, and so on.
## DataView
[DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`.
[DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`. It allows to access the data on any offset in any format.
It allows to access the data on any offset in any format.
- For typed arrays, the constructor dictates what the format is, then we access i-th number as `arr[i]`. All array members are assumed to have the same format.
- With `DataView` we access the data with methods like `.getUint8(i)`. So we decide what the format is at method call time instead of the construction time.
- For typed arrays, the constructor dictates what the format is. The whole array is supposed to be uniform. The i-th number is `arr[i]`.
- With `DataView` we access the data with methods like `.getUint8(i)` or `.gteUint16(i)`. We choose the format at method call time instead of the construction time.
The syntax:
@ -233,11 +240,11 @@ alert( dataView.getUint32(0) ); // 4294967295 (biggest 32-bit unsigned int)
dataView.setUint32(0, 0); // set 4-byte number to zero
```
`DataView` is great when we store mixed-format data in the same buffer. E.g 8-bit integers followed by 32-bit floats, or more complex.
`DataView` is great when we store mixed-format data in the same buffer. E.g we store a sequence of pairs (16-bit integer, 32-bit float). Then `DataView` allows to access them easily.
## Summary
`ArrayBuffer` is the core object, a raw byte sequence.
`ArrayBuffer` is the core object, a reference to the fixed-length contiguous memory area.
To do almost any operation on `ArrayBuffer`, we need a view.
@ -254,7 +261,8 @@ There are also two additional terms:
- `ArrayBufferView` is an umbrella term for all these kinds of views.
- `BufferSource` is an umbrella term for `ArrayBuffer` or `ArrayBufferView`.
These are used in descriptions of methods that operate on binary data. `BufferSource` is one of the most common words, as it means "`ArrayBuffer` or a view over it"
These are used in descriptions of methods that operate on binary data. `BufferSource` is one of the most common teerms, as it means "any kind of binary data" -- an `ArrayBuffer` or a view over it.
Here's a cheatsheet: