Клавиша / esc

DataView

Читаем и записываем байты в буфере, как душе угодно.

Время чтения: 6 мин

Кратко

Скопировано

DataView помогает работать с содержимым ArrayBuffer. С его помощью можно читать и записывать данные разных типов, а также указывать порядок интерпретации байтов.

Пример

Скопировано
        
          
          const buffer = new ArrayBuffer(4)const dataView = new DataView(buffer)dataView.setUint8(0, 255)dataView.setUint8(1, 255)console.log(dataView.getUint8(0))// 255console.log(dataView.getUint16(0))// 65535
          const buffer = new ArrayBuffer(4)
const dataView = new DataView(buffer)

dataView.setUint8(0, 255)
dataView.setUint8(1, 255)

console.log(dataView.getUint8(0))
// 255

console.log(dataView.getUint16(0))
// 65535

        
        
          
        
      

Как пишется

Скопировано

Создать DataView можно с помощью оператора new:

        
          
          new DataView(buffer, [byteOffset], [byteLength])
          new DataView(buffer, [byteOffset], [byteLength])

        
        
          
        
      

DataView при создании принимает следующие аргументы:

  • buffer — объект ArrayBuffer;
  • byteOffset — смещение по данным ArrayBuffer (в байтах), с учётом которого будет создаваться представление DataView. По умолчанию — 0;
  • byteLength — количество байтов, доступных в создаваемом представлении. По умолчанию — все данные до конца буфера.

Создание представления

Скопировано

Можно создать DataView для всего буфера:

        
          
          // Создаём буфер размером 16 байтconst buffer = new ArrayBuffer(16)// Создаём представление для всего буфераconst dataView = new DataView(buffer)
          // Создаём буфер размером 16 байт
const buffer = new ArrayBuffer(16)

// Создаём представление для всего буфера
const dataView = new DataView(buffer)

        
        
          
        
      

Или для части буфера, указав смещение и длину:

        
          
          const buffer = new ArrayBuffer(16)// Создаём представление, которое начинается// со смещением на 2 байта от начала buffer и занимает 4 байтаconst dataView = new DataView(buffer, 2, 4)
          const buffer = new ArrayBuffer(16)

// Создаём представление, которое начинается
// со смещением на 2 байта от начала buffer и занимает 4 байта
const dataView = new DataView(buffer, 2, 4)

        
        
          
        
      

Свойства

Скопировано

У DataView есть три основных свойства:

  • buffer — ссылка на исходный ArrayBuffer;
  • byteOffset — смещение представления от начала буфера в байтах;
  • byteLength — размер представления в байтах.

Методы

Скопировано

Методы DataView начинаются с get (чтение) или set (запись), а дальше указывается тип данных: Uint8, Int16, Float32 и так далее.

Рассмотрим на примере:

        
          
          // Создаем бинарный массив размером 4 байтаconst buffer = new Uint8Array([0, 42, 0, 42]).bufferconst dataView = new DataView(buffer)// Читаем 8-битное беззнаковое число с позиции 0console.log(dataView.getUint8(0))// 0// Читаем 16-битное беззнаковое число с позиции 0 (оно состоит из двух байт)console.log(dataView.getUint16(0))// 42// Читаем 32-битное беззнаковое число с позиции 0 (оно состоит из четырех байт)console.log(dataView.getUint32(0))// 2752554// Записываем 0 на позицию 0 для 16-битного беззнакового числаdataView.setUint16(0, 0)// Теперь 32-битное число изменилосьconsole.log(dataView.getUint32(0))// 42
          // Создаем бинарный массив размером 4 байта
const buffer = new Uint8Array([0, 42, 0, 42]).buffer
const dataView = new DataView(buffer)

// Читаем 8-битное беззнаковое число с позиции 0
console.log(dataView.getUint8(0))
// 0

// Читаем 16-битное беззнаковое число с позиции 0 (оно состоит из двух байт)
console.log(dataView.getUint16(0))
// 42

// Читаем 32-битное беззнаковое число с позиции 0 (оно состоит из четырех байт)
console.log(dataView.getUint32(0))
// 2752554

// Записываем 0 на позицию 0 для 16-битного беззнакового числа
dataView.setUint16(0, 0)

// Теперь 32-битное число изменилось
console.log(dataView.getUint32(0))
// 42

        
        
          
        
      

Посмотреть список всех методов DataView можно в спецификации.

Указание порядка байтов

Скопировано

Порядок байтов (endianness) — это правило, определяющее, в каком порядке байты интерпретируются при формировании числа. Например, для двух байтов [0x01, 0x02] при порядке от старшего к младшему (big-endian) получится число 0x0102 (258), а при порядке от младшего к старшему (little-endian) — 0x0201 (513). По умолчанию DataView использует big-endian. Подробнее о памяти можно узнать из статьи «Как устроена память».

DataView позволяет явно указать порядок интерпретации байтов т. к. принимает флаг little-endian при чтении или записи. В обычных типизированных массивах такой возможности нет. Это особенно важно, когда вы читаете данные из внешнего источника (например, из файла или по сети), который использует порядок интерпретации байтов, отличный от порядка, принятого в вашей операционной системе. Например, в сетевых протоколах часто используется big-endian, а в операционных системах персональных компьютеров — little-endian и если не указать правильный порядок, числа будут прочитаны неверно. DataView позволяет легко подстроиться под нужный формат.

        
          
          const buffer = new ArrayBuffer(4)const dataView = new DataView(buffer)// Записываем беззнаковое число 123 с указанием порядка big-endian (false)dataView.setUint16(0, 123, false)// Читаем беззнаковое число с указанием порядка big-endianconsole.log(dataView.getUint16(0, false))// 123 - все верно// Читаем беззнаковое число, как будто оно записано в порядке little-endianconsole.log(dataView.getUint16(0, true))// 31488 - получили другое число!
          const buffer = new ArrayBuffer(4)
const dataView = new DataView(buffer)

// Записываем беззнаковое число 123 с указанием порядка big-endian (false)
dataView.setUint16(0, 123, false)

// Читаем беззнаковое число с указанием порядка big-endian
console.log(dataView.getUint16(0, false))
// 123 - все верно

// Читаем беззнаковое число, как будто оно записано в порядке little-endian
console.log(dataView.getUint16(0, true))
// 31488 - получили другое число!

        
        
          
        
      
Порядок байтов в используемом окружении

Вот пример, как с помощью DataView можно узнать порядок интерпретации байтов в используемом окружении:

        
          
          const littleEndian = (() => {  const buffer = new ArrayBuffer(2)  // Указываем, что данные записываются в формате little-endian (true) для знакового 16-битного числа  new DataView(buffer).setInt16(0, 256, true)  // Типизированные массивы используют порядок интерпритации байтов платформы  return new Int16Array(buffer)[0] === 256})()// Вернет true для little-endian или false для big-endianconsole.log(littleEndian)
          const littleEndian = (() => {
  const buffer = new ArrayBuffer(2)
  // Указываем, что данные записываются в формате little-endian (true) для знакового 16-битного числа
  new DataView(buffer).setInt16(0, 256, true)

  // Типизированные массивы используют порядок интерпритации байтов платформы
  return new Int16Array(buffer)[0] === 256
})()

// Вернет true для little-endian или false для big-endian
console.log(littleEndian)

        
        
          
        
      

По умолчанию DataView использует big-endian, но многие платформы работают и с little-endian.

Например, little-endian используется в x86 процессорах (Intel, AMD). Архитектура ARM тоже чаще использует little-endian.

При этом популярные сетевые протоколы (TCP/IP, UDP) и форматы файлов (JPEG, PNG) используют big-endian. Поэтому важно контролировать порядок байтов.

Как понять

Скопировано

DataView — это низкоуровневый инструмент. Он пригодится, когда нужно работать с бинарными данными: изображениями, аудио, видео или сетевыми запросами.

Его особенность — возможность указать порядок интерпретации байтов при чтении и записи. Это позволяет работать с данными, у которых порядок интерпретации байтов отличается от порядка интерпретации байтов в системе. А ещё DataView полезен, если в буфере хранятся данные разного типа (например, сами данные и служебная информация к ним).

Выход за границы буфера

Скопировано

DataView сам следит за тем, чтобы вы не вышли за границы буфера. Если попытаться прочитать данные за пределами ArrayBuffer, возникнет ошибка RangeError.

        
          
          const buffer = new ArrayBuffer(2)const dataView = new DataView(buffer)console.log(dataView.getUint32(0))// Ошибка: Uncaught RangeError: Offset is outside the bounds of the DataViewconsole.log(dataView.getUint16(1))// Ошибка: Uncaught RangeError: Offset is outside the bounds of the DataView
          const buffer = new ArrayBuffer(2)
const dataView = new DataView(buffer)

console.log(dataView.getUint32(0))
// Ошибка: Uncaught RangeError: Offset is outside the bounds of the DataView
console.log(dataView.getUint16(1))
// Ошибка: Uncaught RangeError: Offset is outside the bounds of the DataView

        
        
          
        
      

На практике

Скопировано

Светлана Маркова советует

Скопировано

DataView даёт гибкость и контроль, но это достигается ценой производительности. При каждом чтении или записи DataView выполняет дополнительные проверки, (согласно спецификации ECMAScript) — например, проверяет границы буфера и порядок байтов.

Для большинства задач разница в скорости незаметна. Но в высокопроизводительных сценариях (например, обработка каждого пикселя изображения) использование типизированных массивов через uint8array[i] или uint8array.map() сработает значительно быстрее, чем методы DataView.