Клавиша / esc

DataView

Объект для нетипизированного представления бинарных данных из буфера в нужном формате и порядке байтов

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

Кратко

Скопировано

DataView — это специальный объект для обращения к нетипизированному представлению данных из ArrayBuffer. Он содержит уникальные методы, позволяющие обратиться к данным, задавая их тип в момент обращения с возможностью явно указать порядок байтов.

Поскольку DataView предоставляет низкоуровневое API для записи и чтения данных из ArrayBuffer, это делает его очень гибким инструментом для работы с бинарными форматами данных — изображениями, аудио и видеофайлами, сетевыми запросами. Если требуется взаимодействовать с буфером, где хранятся данные разного типа, то DataView просто необходим.

Пример

Скопировано
        
          
          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]);

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

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

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

        
        
          
        
      

Или создать представление для его части, указав в аргументах byteOffset и byteLength:

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

        
        
          
        
      

Свойства

Скопировано

Свойства DataView аналогичны аргументам его конструктора:

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

Методы

Скопировано

Имена методов DataView удобно связаны с числовыми типами (uint8, float64, ...), при этом методы начинающиеся с get читают данные из буфера, а начинающиеся с set их записывают. Например: .getInt8(), .getUint8(), .setFloat64(), .setBigInt64() и т. д.

        
          
          // Создаем бинарный массив размером 4 байтаconst buffer = new Uint8Array([0, 42, 0, 42]).buffer;const dataView = new DataView(buffer);// Получим 8-битное число на позиции 0console.log(dataView.getUint8(0));// 0// Теперь на той же позиции 0, получим 16-битное число, но оно уже состоит из двух байтconsole.log(dataView.getUint16(0));// 42// Теперь на той же позиции 0, получим 32-битное число, но оно уже состоит из четырех байтconsole.log(dataView.getUint32(0));// 2752554// Запишем нули на позицию 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

// Теперь на той же позиции 0, получим 16-битное число, но оно уже состоит из двух байт
console.log(dataView.getUint16(0));
// 42

// Теперь на той же позиции 0, получим 32-битное число, но оно уже состоит из четырех байт
console.log(dataView.getUint32(0));
// 2752554

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

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

        
        
          
        
      

Список всех методов DataView.

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

Скопировано

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

DataView дает возможность явно указывать порядок байтов в момент чтения или записи данных. В обычных TypedArray такой возможности нет. Возможность указания порядка байтов особенна важна когда порядок байтов в данных отличается от используемого в операционной системе. Подробнее о порядке байтов.

        
          
          const buffer = new ArrayBuffer(4);const dataView = new DataView(buffer);// Запишем число 123 с явным указанием big-endian порядка байтов, для этого укажем falsedataView.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);  new DataView(buffer).setInt16(0, 256, true);  return new Int16Array(buffer)[0] === 256;})();console.log(littleEndian);// true или false
          const littleEndian = (() => {
  const buffer = new ArrayBuffer(2);
  new DataView(buffer).setInt16(0, 256, true);

  return new Int16Array(buffer)[0] === 256;
})();

console.log(littleEndian);
// true или false

        
        
          
        
      

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

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

Скопировано

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 см. секции GetViewValue и SetViewValue) — например, проверки выхода за границы буфера, проверка значения endianness и т. п.

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