JavaScript 형식화 배열

JavaScript 형식화 배열(typed array)은 배열같은 객체이고 원시(raw) 이진 데이터에 액세스하기 위한 메커니즘을 제공합니다. 이미 아시다시피, Array 객체는 동적으로 늘었다 줄고 어떤 JavaScript 값이든 가질 수 있습니다. JavaScript 엔진은 이러한 배열이 빨라지도록 최적화를 수행합니다. 그러나, audio 및 video 조작과 같은 기능 추가, WebSocket을 사용한 원시 데이터에 액세스 등 웹 어플리케이션이 점점 더 강력해짐에 따라, 빠르고 쉽게 형식화 배열의 원시 이진 데이터를 조작할 수 있게 하는 것이 JavaScript 코드에 도움이 될 때가 있음이 분명해 졌습니다.

그러나, 형식화 배열은 보통 배열과 혼동되지는 않습니다, 형식화 배열에 Array.isArray() 호출은 false를 반환하기에. 게다가, 보통 배열에 이용할 수 있는 모든 메서드가 형식화 배열에 의해 지원되지는 않습니다(가령 push 및 pop).

버퍼 및 뷰: 형식화 배열 구조

최대 유연성 및 효율을 달성하기 위해, JavaScript 형식화 배열은 구현을 버퍼로 나눕니다. 버퍼 (ArrayBuffer 객체에 의해 구현됨)는 데이터 부분(chunk, 덩어리)을 나타내는 객체입니다; 이야기 할 형식이 없으며, 그 콘텐츠에 액세스하기 위한 메커니즘을 제공하지 않습니다. 버퍼에 포함된 메모리에 액세스하기 위해, 뷰를 사용할 필요가 있습니다. 뷰는 문맥(context, 즉 데이터 형, 시작 오프셋 및 요소 수)을 제공해 데이터를 실제 형식화 배열로 바꿉니다.

Typed arrays in an ArrayBuffer

ArrayBuffer

ArrayBuffer는 일반 고정 길이 이진 데이터 버퍼를 나타내는 데 사용되는 데이터 형입니다. ArrayBuffer 콘텐츠를 직접 조작할 수는 없습니다; 대신에, 형식화 배열 뷰 또는 특정 형식으로 버퍼를 나타내는 DataView를 만들어 버퍼의 콘텐츠를 읽고 쓰기 위해 사용합니다.

형식화 배열 뷰

형식화 배열 뷰는 자체 설명형 이름이 있으며 Int8, Uint32, Float64 등과 같은 모든 일반 숫자 형을 위한 뷰를 제공합니다. 특별한 형식화 배열 뷰가 하나 있습니다, Uint8ClampedArray. 값은 0에서 255 사이로 단속(제한)합니다. 예를 들어, 이는 Canvas 데이터 처리에 유용합니다.

Type Value Range Size in bytes Description Web IDL type Equivalent C type
Int8Array -128 to 127 1 8-bit two's complement signed integer byte int8_t
Uint8Array 0 to 255 1 8-bit unsigned integer octet uint8_t
Uint8ClampedArray 0 to 255 1 8-bit unsigned integer (clamped) octet uint8_t
Int16Array -32768 to 32767 2 16-bit two's complement signed integer short int16_t
Uint16Array 0 to 65535 2 16-bit unsigned integer unsigned short uint16_t
Int32Array -2147483648 to 2147483647 4 32-bit two's complement signed integer long int32_t
Uint32Array 0 to 4294967295 4 32-bit unsigned integer unsigned long uint32_t
Float32Array -3.4E38 to 3.4E38 and 1.2E-38 is the min positive number 4 32-bit IEEE floating point number (7 significant digits e.g., 1.123456) unrestricted float float
Float64Array -1.8E308 to 1.8E308 and 5E-324 is the min positive number 8 64-bit IEEE floating point number (16 significant digits e.g., 1.123...15) unrestricted double double
BigInt64Array -2^63 to 2^63 - 1 8 64-bit two's complement signed integer bigint int64_t (signed long long)
BigUint64Array 0 to 2^64 - 1 8 64-bit unsigned integer bigint uint64_t (unsigned long long)

DataView

DataView는 버퍼에 임의 데이터를 읽고 쓰기 위해 getter/setter API를 제공하는 저레벨 인터페이스입니다. 예를 들어, 서로 다른 유형의 데이터를 처리하는 경우 유용합니다. 형식화 배열 뷰는 플랫폼의 본디(native) 바이트 순서(byte-order, Endianness 참조)에 속합니다. DataView로 바이트 순서를 제어할 수 있습니다. 기본으로 big-endian이고 getter/setter 메서드로 little-endian으로 설정될 수 있습니다.

형식화 배열을 사용하는 웹 API

FileReader.prototype.readAsArrayBuffer()

FileReader.prototype.readAsArrayBuffer() 메서드는 지정된 Blob 또는 File의 콘텐츠를 읽기 시작합니다.

XMLHttpRequest.prototype.send()

XMLHttpRequest 인스턴스의 send() 메서드는 이제 형식화 배열 및 인수로 ArrayBuffer 객체를 지원합니다.

ImageData.data

0에서 255까지 정수값으로 RGBA 순 데이터를 포함하는 1차원 배열을 나타내는 Uint8ClampedArray입니다.

버퍼와 뷰 사용하기

우선, 여기서 16바이트 고정 길이로 버퍼를 만들어야 합니다:

js
var buffer = new ArrayBuffer(16);

이 시점에서, 바이트가 모두 0으로 미리 초기화된 메모리 덩어리가 있습니다. 그렇지만 이거 가지고 할 수 있는 게 많지 않습니다. 실제 16바이트 길이인지 확인할 수 있고 대략 그런 정도입니다:

js
if (buffer.byteLength === 16) {
  console.log("Yes, it's 16 bytes.");
} else {
  console.log("Oh no, it's the wrong size!");
}

정말 이 버퍼로 작업할 수 있기 전에, 뷰를 만들어야 합니다. 32비트 부호있는 정수 배열로 버퍼의 데이터를 다루는 뷰를 만듭시다:

js
var int32View = new Int32Array(buffer);

이제 보통 배열처럼 배열 내 필드에 액세스할 수 있습니다:

js
for (var i = 0; i < int32View.length; i++) {
  int32View[i] = i * 2;
}

이는 배열의 4항목을 값 0, 2, 4 및 6으로 채웁니다 (4항목이 각각 4바이트에 총 16바이트가 됩니다).

같은 데이터에 여러 뷰

상황은 당신이 같은 데이터에 여러 뷰를 만들 수 있음을 고려하는 경우 정말 흥미로워지기 시작합니다. 예를 들어, 위 코드가 주어지면 다음과 같이 계속할 수 있습니다:

js
var int16View = new Int16Array(buffer);

for (var i = 0; i < int16View.length; i++) {
  console.log("Entry " + i + ": " + int16View[i]);
}

여기서 우리는 기존 32비트 뷰와 같은 버퍼를 공유하는 16비트 정수 뷰를 만들고 16비트 정수로 버퍼 내 모든 값을 출력합니다. 이제 우리는 출력 0, 0, 2, 0, 4, 0, 6, 0을 얻습니다.

그렇지만 한 단계 더 앞으로 갈 수 있습니다. 다음을 생각해 보세요:

js
int16View[0] = 32;
console.log("Entry 0 in the 32-bit array is now " + int32View[0]);

이로부터 출력은 "Entry 0 in the 32-bit array is now 32"입니다. 즉, 두 배열은 확실히 그저 같은 데이터 버퍼 상의 뷰입니다, 버퍼를 서로 다른 형식으로 다루는. 모든 뷰 유형으로 이를 할 수 있습니다.

복잡한 데이터 구조(체)와 작업하기

단일 버퍼를 서로 다른 형인 여러 뷰(버퍼 내 서로 다른 오프셋에서 시작하는)와 결합시켜, 여러 데이터 형을 포함하는 데이터 객체와 상호 작용할 수 있습니다. 예를 들어, 이는 WebGL, 데이터 파일 또는 js-ctypes를 쓰는 동안 사용해야 하는 C 구조체에서 복잡한 데이터 구조와 상호 작용케 합니다.

이 C 구조체를 생각해 보세요:

cpp
struct someStruct {
  unsigned long id;
  char username[16];
  float amountDue;
};

이 같은 형식으로 데이터를 포함하는 버퍼에 액세스할 수 있습니다:

js
var buffer = new ArrayBuffer(24);

// ... 버퍼 내의 데이터를 읽어들임 ...

var idView = new Uint32Array(buffer, 0, 1);
var usernameView = new Uint8Array(buffer, 4, 16);
var amountDueView = new Float32Array(buffer, 20, 1);

그런 뒤, 예를 들어 amountDueView[0]로 지불할 요금(amount due)을 액세스할 수 있습니다.

참고 : C 구조체에서 데이터 구조 정렬은 플랫폼에 의존합니다. 이러한 패딩(padding) 차이에 대해 조심하고 고려하세요.

보통 배열로 변환

형식화 배열을 처리한 뒤, 때때로 Array 프로토타입의 도움을 받기 위해 보통 배열로 다시 변환하는 게 유용합니다. 이는 Array.from 또는 Array.from이 지원되지 않는 경우 다음 코드를 사용하여 수행할 수 있습니다.

js
var typedArray = new Uint8Array([1, 2, 3, 4]),
  normalArray = Array.prototype.slice.call(typedArray);
normalArray.length === 4;
normalArray.constructor === Array;

참조