Клавиша / esc

DataView

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

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

Кратко

Скопировано

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) — это последовательность байтов которая используется для хранения чисел в памяти. По умолчанию DataView использует порядок от старшего к младшему (big-endian). Подробнее о памяти можно узнать из статьи «Как устроена память».

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

        
          
          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)  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)
  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.