- Операторы
- Управляющие инструкции
- 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
будет такой же как и длина переданного в параметреtypedArray
let 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 позволяет легко получить доступ к обоим.