- Операторы
- Управляющие инструкции
- JS Объекты
- браузер BOM
- HTML DOM
- События
- HTML Объекты
- Промисы, async/await
- Сетевые запросы
- Бинарные данные и файлы
- ArrayBuffer
- TextDecoder и TextEncoder
- Blob
- File и FileReader
- Модули
- Классы
- Разное
ArrayBuffer, бинарные массивы
Работа с бинарными данными в JavaScript реализована нестандартно по сравнению с другими языками программирования.
Базовый объект для работы с бинарными данными имеет тип ArrayBuffer и представляет собой ссылку на непрерывную область памяти фиксированной длины.
let buffer = new ArrayBuffer(16); // создаётся буфер длиной 16 байт Alert(buffer.byteLength); // 16
Инструкция выше выделяет непрерывную область памяти размером 16 байт и заполняет её нулями.
ArrayBuffer – это не массив!
ArrayBuffer не имеет ничего общего с Array:
- его длина фиксирована, нельзя увеличивать или уменьшать её.
- ArrayBuffer занимает ровно столько места в памяти, сколько указывается при создании.
- Для доступа к отдельным байтам нужен вспомогательный объект-представление,
buffer[index]не сработает.
ArrayBuffer – это область памяти. Что там хранится? Этой информации нет. Просто необработанный («сырой») массив байтов.
Для работы с ArrayBuffer нам нужен специальный объект, реализующий «представление» данных.
Такие объекты не хранят какое-то собственное содержимое. Они интерпретируют бинарные данные, хранящиеся в ArrayBuffer.
Например:
- Uint8Array – представляет каждый байт в
ArrayBufferкак отдельное число; возможные значения находятся в промежутке от 0 до 255 (в байте 8 бит, отсюда такой набор). Такое значение называется «8-битное целое без знака». - Uint16Array – представляет каждые 2 байта в
ArrayBufferкак целое число; возможные значения находятся в промежутке от 0 до 65535. Такое значение называется «16-битное целое без знака». - Uint32Array – представляет каждые 4 байта в
ArrayBufferкак целое число; возможные значения находятся в промежутке от 0 до 4294967295. Такое значение называется «32-битное целое без знака». - Float64Array – представляет каждые 8 байт в
ArrayBufferкак число с плавающей точкой; возможные значения находятся в промежутке между5.0x10-324и1.8x10308.
Таким образом, бинарные данные из ArrayBuffer размером 16 байт могут быть представлены как 16 чисел маленькой разрядности или как 8 чисел большей разрядности (по 2 байта каждое), или как 4 числа ещё большей разрядности (по 4 байта каждое), или как 2 числа с плавающей точкой высокой точности (по 8 байт каждое).
Но если мы собираемся что-то записать в него или пройтись по его содержимому, да и вообще для любых действий мы должны использовать какой-то объект-представление («view»), например:
let buffer = new ArrayBuffer(16); // создаётся буфер длиной 16 байт
let view = new Uint32Array(buffer); // интерпретируем содержимое как последовательность 32-битных целых чисел без знака
let s = Uint32Array.BYTES_PER_ELEMENT; // 4 байта на каждое целое число
s += '\n' + view.length; // 4, именно столько чисел сейчас хранится в буфере
s += '\n' + view.byteLength + '\n'; // 16, размер содержимого в байтах
// давайте запишем какое-нибудь значение
view[0] = 123456;
// теперь пройдёмся по всем значениям
for(let num of view) {
s += num+' '; // 123456, потом 0, 0, 0 (всего 4 значения)
}
Alert( s );
TypedArray
Общий термин для всех таких представлений (Uint8Array, Uint32Array и т.д.) – это TypedArray, типизированный массив. У них имеется набор одинаковых свойств и методов.
Они уже намного больше напоминают обычные массивы: элементы проиндексированы, и возможно осуществить обход содержимого.
Конструкторы типизированных массивов (будь то Int8Array или Float64Array, без разницы) ведут себя по-разному в зависимости от типа передаваемого им аргумента.
Синтаксис
new TypedArray(length); new TypedArray(typedArray); new TypedArray(object); new TypedArray(buffer [, byteOffset [, length]]); new TypedArray();TypedArray() это одно из следующих значений:
Uint8Array,Uint16Array,Uint32Array– целые беззнаковые числа по 8, 16 и 32 бита соответственно.Uint8ClampedArray– 8-битные беззнаковые целые, обрезаются по верхней и нижней границе при присвоении (об этом ниже).
Int8Array,Int16Array,Int32Array– целые числа со знаком (могут быть отрицательными).Float32Array,Float64Array– 32- и 64-битные числа со знаком и плавающей точкой.
Параметры
- length
- При вызове в памяти создаётся буфер длины
length*BYTES_PER_ELEMENTбайт, содержащий нулиlet arr = new Uint16Array(4); // создаём типизированный массив для 4 целых 16-битных чисел без знака let s = Uint16Array.BYTES_PER_ELEMENT; // 2 байта на число Alert ( s +'\n' + arr.byteLength ); // 8 (размер массива в байтах)
- typedArray
- Когда вызывается с аргументом
typedArray, который может быть объектом любого из типов типизированных массивов (например, Int32Array), тогда переданный массивtypedArrayкопируется в новый массив. Каждое значение изtypedArrayконвертируется в соответствующий конструктору тип прямо перед копированием. Длина нового объектаtypedArrayбудет такой же как и длина переданного в параметреtypedArraylet arr16 = new Uint16Array([1, 1000]); let arr8 = new Uint8Array(arr16); let s = arr8[0]; // 1 Alert( s+'\n'+arr8[1] ); // 232, потому что 1000 не помещается в 8 бит
- object
- Новый массив создаётся так, как если бы была вызвана функция TypedArray.from()
let arr = new Uint8Array([0, 1, 2, 3]); let s = arr.length; // 4, создан бинарный массив той же длины Alert (s + '\n' + arr[1] ); // 1, заполнен 4-мя байтами с указанными значениями
- buffer, byteOffset, length
Когда происходит вызов с параметрамиbufferи опциональными параметрамиbyteOffsetиlength, то будет создан новый типизированный массив, который будет отражатьbufferтипаArrayBuffer. ПараметрыbyteOffsetиlengthопределяют диапазон (размер) памяти, выводимый данным массивоподобным представлением. Если оба этих параметра (byteOffsetиlength) опущены, то будет использован весь буферbuffer; если опущен толькоlength, то будет выведен весь остаток буфера после смещения начала отсчёта элементов, заданного параметромbyteOffset.
При вызове без аргументов будет создан пустой типизированный массив.
Свойства
Для доступа к ArrayBuffer в TypedArray есть следующие свойства:
buffer– ссылка на объектArrayBuffer.byteLength– размер содержимогоArrayBufferв байтах.
Таким образом, мы всегда можем перейти от одного представления к другому:
let arr8 = new Uint8Array([0, 1, 2, 3]); // другое представление на тех же данных let arr16 = new Uint16Array(arr8.buffer);
Методы TypedArray
Типизированные массивы TypedArray, за некоторыми заметными исключениями, имеют те же методы, что и массивы Array.
Мы можем обходить их, вызывать map, slice, find, reduce и т.д.
Однако, есть некоторые вещи, которые нельзя осуществить:
- Нет метода
splice– мы не можем удалять значения, потому что типизированные массивы – это всего лишь представления данных из буфера, а буфер – это непрерывная область памяти фиксированной длины. Мы можем только записать 0 вместо значения. - Нет метода
concat.
Но зато есть два дополнительных метода:
- arr.set ( fromArr, [offset] )
копирует все элементы из fromArr в arr, начиная с позиции offset (0 по умолчанию).- arr.subarray ( [begin, end] )
создаёт новое представление того же типа для данных, начиная с позиции begin до end (не включая). Это похоже на метод slice (который также поддерживается), но при этом ничего не копируется – просто создаётся новое представление, чтобы совершать какие-то операции над указанными данными.
Эти методы позволяют нам копировать типизированные массивы, смешивать их, создавать новые на основе существующих и т.д.
DataView
DataView – это специальное супергибкое нетипизированное представление данных из ArrayBuffer. Оно позволяет обращаться к данным на любой позиции и в любом формате.
- В случае типизированных массивов конструктор строго задаёт формат данных. Весь массив состоит из однотипных значений. Доступ к i-ому элементу можно получить как
arr[i]. - В случае DataView доступ к данным осуществляется посредством методов типа
.getUint8(i)или.getUint16(i). Мы выбираем формат данных в момент обращения к ним, а не в момент их создания.
Синтаксис:
new DataView(buffer, [byteOffset], [byteLength])
Параметры
- buffer
- ссылка на бинарные данные ArrayBuffer. В отличие от типизированных массивов, DataView не создаёт буфер автоматически. Нам нужно заранее подготовить его самим.
- byteOffset
- начальная позиция данных для представления (по умолчанию 0).
- byteLength
- длина данных (в байтах), используемых в представлении (по умолчанию – до конца
buffer).
Например, извлечение числа в разных форматах из одного и того же буфера двоичных данных:
// бинарный массив из 4х байт, каждый имеет максимальное значение 255 let buffer = new Uint8Array([255, 255, 255, 255]).buffer; let dataView = new DataView(buffer); // получим 8-битное число на позиции 0 let s = dataView.getUint8(0); // 255 // а сейчас мы получим 16-битное число на той же позиции 0, оно состоит из 2-х байт, вместе составляющих число 65535 s += '\n' + dataView.getUint16(0); // 65535 (максимальное 16-битное беззнаковое целое) // получим 32-битное число на позиции 0 Alert( s + '\n' + dataView.getUint32(0) ); // 4294967295 (максимальное 32-битное беззнаковое целое) dataView.setUint32(0, 0); // при установке 4-байтового числа в 0, во все его 4 байта будут записаны нули
Представление DataView отлично подходит, когда мы храним данные разного формата в одном буфере. Например, мы храним последовательность пар, первое значение пары 16-битное целое, а второе – 32-битное с плавающей точкой. DataView позволяет легко получить доступ к обоим.