В JavaScript есть много понятий, связанных с двоичным кодом, таких как Buffer
, TypedArray
, ArrayBuffer
, Blob
, Stream
и так далее. Так как же соотносятся эти понятия друг с другом? Каковы соответствующие сценарии использования? Это будет в центре внимания этой статьи.
типизированный массив
Во-первых, давайте представим TypedArray. TypedArray — это специализированный массив для обработки данных числовых типов (не всех типов), ArrayBuffer
— это лишь одно из понятий.
История TypedArray
TypedArray впервые были использованы в WebGL. WebGL — это трансплантированная версия OpenGL ES 2.0. В ранних версиях WebGL из-за разницы между массивами JavaScript и нативными массивами между ними существует несоответствие, поэтому возникает проблема с производительностью.
Формат массивов JavaScript в памяти — это формат с плавающей запятой двойной точности (IEEE 754 64-разрядный), но API-интерфейсы графических драйверов обычно не ожидают, что значения будут переданы им в стандартном формате JavaScript с плавающей запятой двойной точности. Таким образом, каждый раз, когда массив передается между WebGL и средой выполнения JavaScript, WebGL необходимо перераспределить массив в целевой среде, выполнить итерацию по массиву в его текущем формате и преобразовать значения в правильный формат в новом массиве, что требует много времени.
Чтобы решить вышеуказанную проблему, Mozilla внедрила CanvasFloatArray
. Он предоставляет интерфейс JavaScript для массивов значений с плавающей запятой в стиле C. В конце концов этот тип стал Float32Array
, одним из типов TypedArray.
ArrayBuffer
ArrayBuffer
— это основа всего TypedArray, это кусок адреса памяти, который может содержать определенное количество байтов, который на других языках называется 'Byte Array'. Процесс создания ArrayBuffer
аналогичен вызову malloc()
в C для выделения памяти, за исключением того, что нет необходимости указывать тип данных, содержащихся в блоке памяти.
let buffer = new ArrayBuffer(10); // allocate 10 bytes in memory
Примечание. Размер ArrayBuffer нельзя изменить после создания.
Просто создавать блок хранения бесполезно, нам нужно записать данные в блок хранения, поэтому нам также нужно создать представление для реализации функции записи.
ArrayBuffer — это адрес в памяти, а представление используется для управления интерфейсом в памяти. Представления могут манипулировать буферами массивов или подмножествами буферов, считывая и записывая данные в соответствии с одним из числовых типов данных.
Просмотр данных
Первым представлением, позволяющим читать и записывать ArrayBuffer
, является DataView
, представляющее собой универсальное представление буфера массива. Это представление предназначено для файлового ввода-вывода и сетевого ввода-вывода, и его API поддерживает высокую степень контроля над буферизованными данными, но производительность ниже, чем у других типов представлений.
Пример использования следующий:
let buffer = new ArrayBuffer(10) let view = new DataView(buffer)
DataView имеет следующие свойства:
- буфер: ArrayBuffer, привязанный к представлению;
- byteOffset: второй параметр конструктора DataView, значение по умолчанию равно 0 и имеет значение только при передаче параметра;
- byteLength: третий параметр конструктора DataView, значение по умолчанию — длина буфера в байтах.
DataView не имеет предустановленного значения для типа данных, хранящегося в буфере. Его API заставляет разработчиков указывать ElementType при чтении и записи, а затем DataView преобразуется в соответствии с указанным типом. Существует 8 типов ElementType, поддерживаемых DataView:
typebytesdescriptionInt818-битное целое число со знакомUint818-битное целое число без знакаInt16216-битное целое число со знакомUint16216-битное целое число без знакаInt32432-битное целое число со знакомUint32432-битное целое число без знакаFloat32432-бит IEEE-754 число с плавающей запятойFloat64864-бит IEEE-754 с плавающей запятой
Каждый из перечисленных выше типов предоставляет методы get
и set
, такие как getInt8(byteOffset, littleEndian)
, setFloat32(byteOffset, value ,littleEndian)
. Для более подробного ознакомления см.: DataView.
типизированный массив
TypedArray — это еще одна форма представления ArrayBuffer
, которое представляет собой конкретное представление для ArrayBuffer, которое может напрямую принудительно использовать определенный тип данных вместо универсального объекта DataView
для управления буфером массива. TypedArray следует собственному порядку байтов.
Существует несколько типов TypedArray:
имя конструктораbytesdescriptionInt8Array18-разрядное целое число со знакомUint8Array18-разрядное целое число без знакаUint8ClampedArray18-разрядное целое число без знака (приведение)Int16Array216-разрядное целое число со знакомUint16Array216-разрядное целое число без знакаInt32Array432-разрядное целое число со знакомUint32Array432-разрядное целое число без знакаFloat32Array432-разрядное плавающее IEEE номер точкиFloat64Array864-битные числа IEEE с плавающей запятой
Приведенный выше Uint8ClampedArray
примерно такой же, как Uint8Array
, с той лишь разницей, что если значение в буфере массива меньше 0 или больше 255, Uint8ClampedArray
преобразует его в 0 или 255 соответственно. Например, -1 становится 0, а 300 становится 255.
По словам Брендана Эйха, отца JavaScript: «Uint8ClampedArray
— это полностью историческая реликвия HTML5-элемента canvas
. Если вы действительно не занимаетесь разработкой, связанной с canvas
, не используйте ее».
Подробнее об использовании TypedArray смотрите в документации: TypedArray.
порядок следования байтов
8-, 16-, 32- или 64-битное представление одной и той же последовательности байтов можно просмотреть с помощью стереотипного массива. Это связано с проблемой «порядка байтов». Так называемый «порядок байтов» относится к соглашению о порядке байтов, поддерживаемому компьютерной системой. Он делится на два типа: с прямым порядком байтов и с прямым порядком байтов:
- Big-endian: старший байт идет первым, а младший байт следует за ним. Именно так люди читают и записывают значения.
- little-endian: младший байт идет первым, а старший байт следует за ним. Например, значение
0x2211
хранится в двух байтах: старший байт —0x22
, младший —0x11
, поэтому соответствующий порядок байтов с прямым порядком байтов —0x1122
.
Порядок байтов базовой платформы можно определить с помощью следующего кода:
// If the integer 0x00000001 is arranged in memory as 01 00 00 00 // The bottom layer uses little-endian byte order. // On big-endian platforms it should be 00 00 00 01 let littleEndian = new Int8Array(new Int32Array([1]).buffer)[0] === 1
Обычные ЦП, представленные в настоящее время на рынке, имеют прямой порядок следования байтов. Многие сетевые протоколы и некоторые форматы двоичных файлов требуют порядка байтов с прямым порядком байтов.
Для эффективности TypedArray использует собственный порядок следования байтов базового оборудования. Упомянутый выше DataView
не подчиняется этому соглашению. DataView
— это нейтральный интерфейс к части памяти, он будет следовать указанному вами порядку байтов. Все методы DataView
API используют обратный порядок байтов в качестве значения по умолчанию, но их можно включить, получив сообщение true
.
const buf = new ArrayBuffer(2) const view = new DataView(buf) // read Uint16 in little endian byte order view. getUint16(0, true)
Транслировать
API Steam был создан для решения проблемы веб-приложений, которые упорядоченно потребляют небольшие фрагменты информации, а не более крупные. Сценарии применения этой возможности следующие:
- Большие порции информации могут быть недоступны сразу: типичным примером являются ответы на сетевые запросы. Сетевая нагрузка передается последовательными пакетами, а потоковая передача позволяет приложениям потреблять данные по мере их поступления, а не ждать загрузки всех данных.
- Большие блоки данных, возможно, придется обрабатывать небольшими частями. Такие как обработка видео, сжатие данных и т.д.
Проблема, непосредственно решаемая Stream API, заключается в обработке сетевых запросов и чтении и записи дисков, что определяет три потока:
- Потоки для чтения: поток, который считывает фрагменты данных через общедоступный интерфейс. Данные поступают в поток внутренне из основного источника, а затем обрабатываются потребителем;
- Потоки с возможностью записи: поток, который записывает блоки данных через общедоступный интерфейс. Производитель записывает данные в поток, и данные передаются внутри базового приемника данных;
- Потоки преобразования. Он состоит из двух потоков: поток с возможностью записи используется для получения данных (конец с возможностью записи), а поток с возможностью чтения используется для вывода данных (конец с возможностью чтения). Между этими двумя потоками находится преобразователь, который проверяет и изменяет содержимое потока по мере необходимости.
Основной единицей потока является чанк. Чанки могут иметь любой тип данных, но обычно это TypedArray. Каждый чанк представляет собой отдельный фрагмент потока, который можно обрабатывать как единое целое. Куски не имеют фиксированного размера и не обязательно поступают через равные промежутки времени.
капля
Blob связан с чтением файла. В некоторых случаях нам нужно прочитать часть файла, а не весь файл. Для этой цели объект File предоставляет метод с именем slice()
. Метод slice()
возвращает экземпляр BLOB-объекта. Интерфейс File основан на Blob, наследуя функциональные возможности BLOB-объектов и расширяя их для поддержки файлов в системе пользователя.
Blob означает двоичный большой объект, который является типом инкапсуляции JavaScript для неизменяемых двоичных данных. Массивы, содержащие строки, ArrayBuffer
, ArrayBufferView
и даже другие большие двоичные объекты, можно использовать для создания больших двоичных объектов. Его данные могут быть прочитаны в текстовом или двоичном формате, а также могут быть преобразованы в ReadableStream для манипулирования данными.
У BLOB-объектов есть два свойства:
Blob.prototype.size
: указывает размер (в байтах)** **данных, содержащихся в объекте Blob;Blob.prototype.type
: строка, указывающая тип MIME, который содержит этот объект Blob. Если тип неизвестен, значение пустое.
Методы экземпляра Blob следующие:
Blob.prototype.arrayBuffer()
: возвращает обещание, и после разрешения результат содержитArrayBuffer
в двоичном формате, содержащий все содержимое BLOB-объекта;Blob.prototype.slice()
: возвращает новый объект Blob, содержащий данные в указанном диапазоне исходного объекта Blob;Blob.prototype.stream()
: возвращает ReadableStream, который может читать содержимое BLOB-объекта;Blob.prototype.text()
: возвращает обещание, которое разрешается в строку UTF-8, содержащую все содержимое большого двоичного объекта.
Буфер
Наконец, давайте поговорим о Buffer. Отличие от вышесказанного в том, что Buffer уникален для Node.js, но на самом деле класс Buffer
является подклассом Uint8Array
в JavaScript, и он расширен.
Экземпляры Buffer
также являются экземплярами JavaScript Uint8Array
и TypedArray
. Все методы TypedArray
доступны на Buffer
s. Однако между Buffer
API и TypedArray
API есть тонкая несовместимость.
В частности:
- В то время как
TypedArray.prototype.slice()
создает копию частиTypedArray
,Buffer.prototype.slice()
создает представление поверх существующегоBuffer
без копирования. Такое поведение может быть удивительным и существует только для устаревшей совместимости.TypedArray.prototype.subarray()
можно использовать для достижения поведенияBuffer.prototype.slice()
как наBuffer
, так и на другихTypedArray
, и его следует отдавать предпочтение. buf.toString()
несовместим со своим эквивалентомTypedArray
.- Ряд методов, например.
buf.indexOf()
, поддержите дополнительные аргументы.
Подведем итог
Выше приведены некоторые концепции, связанные с двоичным кодом в JS. Наконец, используйте картинку, чтобы обобщить взаимосвязь между понятиями, упомянутыми выше: