Книга: JavaScript. Подробное руководство, 6-е издание

22.5. Типизированные массивы и буферы

22.5. Типизированные массивы и буферы

Как говорилось в главе 7, массивы в языке JavaScript являются многоцелевыми объектами с числовыми именами свойств и специальным свойством length. Элементами массива могут быть любые JavaScript-значения. Массивы могут увеличиваться и уменьшаться в размерах и быть разреженными. Реализации JavaScript выполняют множество оптимизаций, благодаря которым типичные операции с массивами в языке JavaScript выполняются очень быстро. Типизированные массивы - это объекты, подобные массивам (раздел 7.11), которые имеют несколько важных отличий от обычных массивов:

• Все элементы типизированного массива являются числами. Конструктор, используемый для создания массива, определяет тип (целые числа со знаком или без знака или вещественные числа) и размер (в битах) чисел.

• Типизированные массивы имеют фиксированную длину.

• Элементы типизированного массива всегда инициализируются значением 0 при его создании.

Всего существует восемь разновидностей типизированных массивов, каждый с различным типом элементов. Создавать их можно с помощью следующих конструкторов:


При создании типизированного массива конструктору передается размер массива. Вместо размера можно передать массив или типизированный массив, который будет использован для инициализации элементов нового массива. После создания типизированного массива его элементы можно читать или изменять с помощью обычной формы записи с квадратными скобками, как и при работе с любым другим объектом, подобным массиву:

var bytes = new Uint8Array(1024);     // Один килобайт байтов
for(var і = 0; і < bytes.length; і++) // Для каждого элемента массива
  bytes[i] = і & 0xFF;                // Записать 8 младших бит индекса
var copy = new Uint8Array(bytes);     // Создать копию массива
var ints = new Int32Array([0,1,2,3]); // Типизированный массив с 4 целыми

Современные реализации JavaScript оптимизируют операции с массивами и делают их очень эффективными. Однако типизированные массивы могут быть еще более эффективными, как по времени выполнения операций с ними, так и по использованию памяти. Следующая функция вычисляет наибольшее простое число, которое меньше указанного значения. Она использует алгоритм «Решето Эратосфена», который основан на использовании большого массива, в котором запоминается, какие числа являются простыми, а какие составными. Так как в каждом элементе массива требуется сохранять всего один бит информации, объект Int8Array может оказаться более эффективным в использовании, чем обычный массив JavaScript:

// Возвращает наибольшее целое простое число меньше п.
// Использует алгоритм "Решето Эратосфена"
function sieve(n) {
  var а = new Int8Array(n+1);        // в a[x] записывается 1, если х - составное число
  var max = Math.floor(Math.sqrt(n));// Множитель не может быть выше этого значения
  var р = 2;                         // 2 - первое простое число
  while(p <= max) {                  // Для простых чисел меньше max
    for(var і = 2*р; і <= n; і += p) // Пометить числа, кратные р,
      a[i] = 1;                      // как составные
    while(a[++p]) /* пустое тело */; // Следующий непомеченный индекс -
  }                                  // простое число
  while(a[n]) n--; // Цикл в обр. напр., чтобы отыскать последнее простое
  return n;        // И вернуть его
}

Функция sieve() будет работать, если вызов конструктора Int8Array() заменить вызовом традиционного конструктора Аггау(), но выполняться она будет в два-три раза дольше и будет расходовать гораздо больше памяти при больших значениях параметра n. Типизированные массивы могут также пригодиться при обработке графических изображений и для математических вычислений:

var matrix = new Float64Array(9); // Матрица 3x3
var 3dPoint = new Int16Array(3); // Точка в 3-мерном пространстве
var rgba = new Uint8Array(4); // 4-байтовое значение RGBA пиксела
var sudoku = new Uint8Array(81); // Доска 9x9 для игры в судоку

Форма записи с квадратными скобками, используемая в языке JavaScript, позволяет читать и записывать значения отдельных элементов типизированного массива. Но типизированные массивы определяют дополнительные методы для записи и чтения целого фрагмента массива. Метод set() копирует элементы обычного или типизированного массива в типизированный массив:

var bytes = new Uint8Aгray(1024)         // Буфер размером 1Кбайт
var pattern = new Uint8Array([0,1,2,3]); // Массив из 4 байтов
bytes.set(pattern);         // Скопировать их в начало другого массива байтов
bytes.set(pattern, 4);      // Скопировать их же в другое место массива
bytes.set([0,1,2,3], 8);    // Просто скопировать значения из обычного массива

Типизированные массивы имеют также метод subarray(), возвращающий фрагмент массива, относительно которого он был вызван:

var ints = new Int16Array([0,1.2,3.4,5,6,7,8,9]);      // 10 коротких целых
var last3 = ints.subarray(ints.length-3, ints.length); // Последние 3 из них
last3[0]           // => 7: то же самое, что и ints[7]

Обратите внимание, что метод subarray() не создает копии данных. Он просто возвращает новое представление тех же самых значений:

ints[9] = -1; // Изменить значение в оригинальном массиве и...
last3[2]      // => -1: изменения коснулись фрагмента массива

Тот факт, что метод subarray() возвращает новое представление существующего массива, раскрывает важную особенность типизированных массивов: все они являются представлениями участка памяти, который называется ArrayBuffег. Каждый типизированный массив имеет три свойства, связывающие его с лежащим в его основе буфером:

last3.buffer                 // => вернет объект ArrayBuffer
last3.buffer == ints.buffer  // => true: оба - представления одного буфера
last3.byteOffset    // => 14: это представление начинается с 14-го байта в буфере
last3.byteLength    // => 6: размер представления 6 байт (3 16-битных целых)

Сам объект ArrayBuffer имеет только одно свойство, возвращающее его длину:

last3.byteLength        // => 6: размер представления 6 байт
last3.buffer.byteLength // => 20: но буфер имеет размер 20 байт

Типизированные массивы, элемент <canvas> и базовый JavaScript

Типизированные массивы являются важной частью прикладного интерфейса создания трехмерной графики WebGL в элементе <canvas>, и в броузерах они реализованы как часть прикладного интерфейса WebGL. WebGL не рассматривается в этой книге, но типизированные массивы весьма полезны сами по себе, и поэтому обсуждаются здесь. В главе 21 говорилось, что прикладной интерфейс объекта Canvas определяет метод getlmageData(), возвращающий объект ImageData. Свойство data объекта ImageData является массивом байтов. В спецификации HTML он называется CanvasPixelArray, но, по сути, это то же самое, что описываемый здесь Uint8Array, за исключением способа обработки значений, выходящих за диапазон 0-255.

Имейте в виду, что эти типы не являются частью базового языка. Будущие версии языка JavaScript, возможно, будут включать поддержку типизированных массивов, подобных этим, но на момент написания этих строк еще было неясно, примет ли язык прикладной интерфейс, описываемый здесь, или будет создан новый прикладной интерфейс.

*****************************************************

Объект АгrayВuffers - это всего лишь последовательность байтов. К этим байтам можно обращаться с помощью типизированных массивов, но сам объект ArrayBuffer не является типизированным массивом. Однако будьте внимательны: объект ArrayBuffer можно индексировать числами, как любой другой объект JavaScript, но это не обеспечивает доступ к байтам в буфере:

var bytes = new Uint8Array(8); // Разместить 8 байтов
bytes[0] =1;                   // Записать в первый байт значение 1
bytes.buffer[0]                // => undefined: буфер не имеет индекса 0
bytes.buffer[1] = 255;         // Попробовать некорректно записать значение в байт буфера
bytes.buffer[1]                // => 255: это обычное JavaScript-свойство
bytes[1]                       // => 0: строка выше не изменила байт

Имеется возможность создавать объекты ArrayBuffer непосредственно, вызовом конструктора ArrayBuffer(), и на основе имеющегося объекта ArrayBuffer можно создать любое число представлений типизированного массива:

var buf = new ArrayBuffeг(1024*1024); // Один Мбайт
var asbytes = new Uint8Array(buf);    // Представление в виде байтов
var asints = new Int32Array(buf);     // В виде 32-битных целых со знаком
var lastK = new Uint8Array(buf,1023*1024);  // Последний Кбайт в виде байтов
var ints2 = new Int32Array(buf, 1024, 256); // 2-й Кбайт в виде 256 целых чисел

Типизированные массивы позволяют представлять одну и ту же последовательность байтов в виде целых чисел размером 8, 16, 32 или 64 бита. Это поднимает проблему «порядка следования байтов», т. е. порядка, в каком следуют байты при объединении в более длинные слова. Для эффективности типизированные массивы используют порядок следования байтов, определяемый аппаратным обеспечением. В системах с обратным порядком следования байтов байты числа располагаются в буфере ArrayBuffer в порядке от младшего к старшему. На платформах с прямым порядком следования байтов байты располагаются в порядке от старшего к младшему. Определить порядок следования байтов на текущей платформе, где выполняется сценарий, можно следующим образом:

// Если целое число 0x00000001 располагается в памяти в виде
// последовательности байтов 01 00 00 00, следовательно, сценарий выполняется
// на платформе с обратным порядком следования байтов. На платформе с прямым
// порядком следования байтов мы получим байты 00 00 00 01.
var little_endian = new Int8Array(new Int32Array([1]).buffer)[0] === 1;

В настоящее время наибольшее распространение получили процессорные архитектуры с обратным порядком следования байтов. Однако многие сетевые протоколы и некоторые двоичные форматы файлов требуют, чтобы байты следовали в прямом порядке. В разделе 22.6 вы узнаете, как использовать объекты ArrayBuffer для хранения байтов, прочитанных из файлов или полученных из сети. В подобных ситуациях нельзя просто полагаться на то, что порядок следования байтов, поддерживаемый аппаратной частью, совпадает с порядком следования байтов в данных. Вообще, при работе с внешними данными, для представления данных в виде массива отдельных байтов можно использовать Int8Array и Uint8Array, но нельзя использовать другие виды типизированных массивов для представления данных в виде массивов многобайтовых слов. Вместо этого можно использовать класс DataView, который определяет методы чтения и записи значений из буфера ArrayBuffer, использующие явно указанный порядок следования байтов:

var data;                     // Предположим, что данные в ArrayBuffer получены из сети
var view = DataView(data);    // Создать представление буфера
var int = view.getlnt32(0);   // 32-битное* целое со знаком с прямым порядком
                              // следования байтов, начиная с 0-го байта
int = view.getInt32(4,false); // Следующее 32-битное целое, также с прямым
                              // порядком следования байтов
int = view.getInt32(8,true)   // Следующие 4 байта как целое со знаком
                              // и с обратным порядком следования байтов
view.setInt32(8,int,false);   // Записать его обратно, в формате с прямым
                              // порядком следования байтов

Класс DataView определяет восемь методов get для каждого из восьми видов типизированных массивов. Они имеют такие имена, как getlnt16(), getUint32() и getFloat64(). В первом аргументе они принимают смещение значения в байтах в буфере ArrayBuffer. Все эти методы чтения, кроме getlnt8() и getUint8(), принимают логическое значение во втором необязательном аргументе. Если второй аргумент отсутствует или имеет значение false, используется прямой порядок следования байтов. Если второй аргумент имеет значение true, используется обратный порядок следования байтов.

Класс DataView определяет восемь соответствующих методов set, которые записывают значения в буфер ArrayBuffer. В первом аргументе этим методам передается смещение начала значения. Во втором аргументе - записываемое значение. Все методы, кроме setlnt8() и setUint8(), принимают необязательный третий аргумент. Если аргумент отсутствует или имеет значение false, значение записывается в формате с прямым порядком следования байтов, когда первым следует старший байт. Если аргумент имеет значение true, значение записывается в формате с обратным порядком следования байтов, когда первым записывается младший байт.

Оглавление книги


Генерация: 3.490. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз