“This is the first day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021”
Binary data is the most basic data in a computer. However, in the front-end development, we are more exposed to the advanced data type number, and there are few scenarios that require binary data directly. Here are some typical scenarios that require binary data in the front-end:
- Canvas image data processing
- Communicates with the graphics card in WebGL
- String encoding TextEncoder
- Ajax responseType arraybuffer
- Communicate with other programming languages in WebAssembly
ArrayBuffer is provided in JS to control binary data. ArrayBuffer represents a fixed length of binary data buffer. New ArrayBuffer(8) creates an 8-byte binary array. But we can’t manipulate this data directly. An ArrayBuffer is a buffer of binary data that simply stores the data itself, and how you control it really depends on how you read it. ArrayBuffer can be read and written using TypedArray and DataView.
TypedArray
TypedArray is a set of built-in types of JS. There are currently 11 types: Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, BigInt64A Rray, BigUint64Array. TypedArray as the name implies, TypedArray is associated with the type. From the name of each type, you can easily identify the corresponding data type. Using the corresponding TypedArray class, you can read or write the corresponding data type to the ArrayBuffer.
Type | Value Range | Size in bytes | Description | Web IDL type | Equivalent C type |
---|---|---|---|---|---|
Int8Array |
- 128. to 127 |
1 | 8-bit two’s complement signed integer | byte |
int8_t |
Uint8Array |
0 to 255 |
1 | 8-bit unsigned integer | octet |
uint8_t |
Uint8ClampedArray |
0 to 255 |
1 | 8-bit unsigned integer (clamped) | octet |
uint8_t |
Int16Array |
- 32768. to 32767 |
2 | 16-bit two’s complement signed integer | short |
int16_t |
Uint16Array |
0 to 65535 |
2 | 16-bit unsigned integer | unsigned short |
uint16_t |
Int32Array |
- 2147483648. to 2147483647 |
4 | 32-bit two’s complement signed integer | long |
int32_t |
Uint32Array |
0 to 4294967295 |
4 | 32-bit unsigned integer | unsigned long |
uint32_t |
Float32Array |
3.4 e38 to 3.4 e38 and 1.2 e-38 is the min positive number |
4 | 32-bit IEEE floating point number (7 significant digits e.g., 1.234567 ) |
unrestricted float |
float |
Float64Array |
1.8 e308 to 1.8 e308 and 5E-324 is the min positive number |
8 | 64-bit IEEE floating point number (16 significant digits e.g., 1.23456789012345 ) |
unrestricted double |
double |
BigInt64Array |
- 2 ^ 63 to 2^63 - 1 |
8 | 64-bit two’s complement signed integer | bigint |
int64_t (signed long long) |
BigUint64Array |
0 to 2^64 - 1 |
8 | 64-bit unsigned integer | bigint |
uint64_t (unsigned long long) |
Constructor:
new TypedArray(); New in ES2017, create an empty TypedArray
new TypedArray(length); // Create a TypedArray of length langth
new TypedArray(typedArray); // Copy a new typedArray from a typedArray
new TypedArray(object); // Create TypedArray by calling TypeDarray. from with the same effect as array. from
new TypedArray(buffer [, byteOffset [, length]]); // Create typedArray with a portion of the specified buffer or buffe. The buffer length must be divisible by the number of bytes occupied by a single element
Copy the code
Here’s an example:
const buffer = new ArrayBuffer(16);
const typedArray = new Uint8Array(buffer);
console.log(typedArray);
// output: Uint8Array(16) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
typedArray[0] = 10;
typedArray[1] = 16;
console.log(typedArray[0]);
// output: 10
console.log(typedArray);
// output: Uint8Array(16) [10, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Copy the code
In the table above: Uint8Array each element takes up one byte, so we can create a Uint8Array of 16 bytes from the ArrayBuffer. We can use subscript values like arrays, we can set values for them, and we can use most of the methods on arrays. TypedArray differs from regular arrays in that it is fixed length and memory space is continuous, so push, POP, and splice methods that change the Array length cannot be used on TypedArray.
The Uint8Array in the table can store data ranging from 0 to 255, so we try to store a larger data:
const buffer = new ArrayBuffer(2);
const uint8Array = new Uint8Array(buffer);
uint8Array[0] = 300;
console.log(uint8Array);
// output: Uint8Array(2) [44, 0]
Copy the code
We write 300, but read 44, which is obviously not in line with expectations, so in order to avoid exceeding the memory limit, we need to choose the appropriate data format, which can be solved by replacing it with Uint16Array.
const buffer = new ArrayBuffer(2);
const uint16Array = new Uint16Array(buffer);
uint16Array[0] = 300;
console.log(uint16Array);
// output: Uint16Array [300]
Copy the code
Since the Uint16Array takes up two bytes, the ArrayBuffer of length 2 here creates a Uint16Array with only one value. The following diagram illustrates how an ArrayBuffer is controlled using TypedArray:
As can be seen from the figure, the memory space represented by ArrayBuffer is actually the same. TypedArray provides a unit to read memory. When we use Uint8Array, subscript 0 controls the first byte in memory. The subscript 0 of the Uint16Array controls the first two bytes, and so on.
What if we write two bytes to the Uint8Array?
const buffer = new ArrayBuffer(2);
const uint8Array = new Uint8Array(buffer);
uint8Array[0] = 10;
uint8Array[1] = 16;
const uint16Array = new Uint16Array(buffer);
Copy the code
What is the uint16Array?
Since we wrote 10 in the first byte and 16 in the second byte, each byte is 8 bits, which looks like (10 << 8) + 16 = 2576, let’s try printing:
console.log(uint16Array); // output: Uint16Array [4106]
Copy the code
As if not as expected, we see that instead of 2576, we see 4106. What went wrong?
There is a problem about the order of byte storage. Computers store multi-byte data in two ways: low bits are stored at low addresses called small endian, and high bits are stored at low addresses called big endian.
The values set by TypedArray are stored in the small endian mode, so the data in the low address of Uint16Array is used as the low one and the data in the high address as the high one, that is, 10 is the low one and 16 is the high one, so the value above is (16 << 8) + 10 = 4106.
Let’s go back and see why this 300 is 44. So according to the way of small heap, the low part should be put into the first byte, i.e. 00101100, and the high part 1 should be put into the second byte. As the Uint8Array has only one byte, the 1 of the high part is lost. We’re just left with 0010, 1100, which is 44.
We can verify this:
const buffer = new ArrayBuffer(2);
const uint16Array = new Uint16Array(buffer);
uint16Array[0] = 300;
const uint8Array = new Uint8Array(buffer);
console.log(uint8Array);
// output: Uint8Array(2) [44, 1]
Copy the code
Uint8ClampedArray is special, it does not discard data when it exceeds the limit, but directly stores 0 or 255. RGB data values are between 0 and 255, so it is often used for imageData.
The same type of data is written to buffer using TypedArray, but as we have seen, what is actually written is undifferentiated binary information, depending on how we read memory. Therefore, we can write and read data in many different formats for the same piece of bFFER. However, due to the size side problem, it will be troublesome to write different types of data, so we can use DataView for different types of data.
DataView
Using DataView to read and write an ArrayBuffer is easy, we only need to care about the byte size and offset of each write. DataView is stored in big-endian mode by default. We can specify the use of small-endian by setting the last parameter to true when writing values. Note that if you specify the little endian read when writing, you must specify the little endian read as well.
const buffer = new ArrayBuffer(20);
const dataView = new DataView(buffer);
dataView.setInt8(0.10);
dataView.setInt16(1.300);
dataView.setInt16(3.400.true); // Use the small end
dataView.setInt8(5.20);
console.log(dataView.getInt8(0), dataView.getInt16(1), dataView.getInt16(3.true /* Use little endian */), dataView.getInt8(5));
// output: 10 300 400 20
Copy the code
Why does TypedArray use the small end and DataView defaults to the big end?
TypedArray provides a set of interfaces to manipulate binary data directly, which can lead to better performance. DataView can assemble a variety of types of data, which is mostly used for data serialization deserialization and transmission. In transmission, for streaming data, we only need to store the data in memory order to restore the big-end sequence. At this time, the big-end is obviously more efficient, and the big-end is also used in TCP/IP. Of course, DataView can specify the use of small ends, so you can actually use DataView to do things that TypedArray does.
reference
Hacks.mozilla.org/2017/01/typ…
Developer.mozilla.org/en-US/docs/…