JavaScript Typed Arrays


발생일: 2013.06.19

문제:

노드에서 파일을 읽어 Buffer 객체에 저장한다던가, 브라우저에서 File API로 파일 처리를 하는 등,
최근에는 자바스크립트에서도 바이너리 데이터를 처리할 수 있게 됐다.

지금까지는 주로 래핑한 객체의 API만 사용해왔었는데,
오늘은 시간을 내서 JavaScript Typed Array (자바스크립트 타입 배열)에 대해 자세히 살펴봤다.



해결책:


MDN의 Typed arrays 페이지를 보면서 메모한 거라 원문의 내용과 거의 동일하다.


개요

웹 애플리케이션이 점점 더 파워풀 해지면서, 오디오나 비디오 처리, 웹소켓으로 로우 데이터에 접근하는 등
자바스크립트에서 바이너리 데이터에 접근할 필요가 생기게 되었다.

과거에는 데이터 버퍼로부터 로우 데이터를 문자열로 읽어서 charCodeAt() 메서드로 변환해 사용해야 했다.
그러나, 여러 번 변환해야 하기 때문에 느리고 에러가 발생하기도 쉬웠다.
특히, 바이너리 데이터가 바이트 단위의 포맷이 아닌 경우(예를 들어, 32-bit integer나 floats 등과 같이) 더욱 그랬다.

자바스크립트의 타입 배열은 로우 바이너리 데이터에 좀 더 효과적으로 접근할 수 있는 방법을 제공한다.



Buffers and Views: typed array architecture

유연성과 효율성을 위해, 자바스크립트 타입 배열은 `buffer`와  `view`로 나뉘어 구현되어 있다.

`ArrayBuffer 클래스`로 구현되어 있는 `buffer`는, 데이터 청크를 나타내는 객체이다.
특별히 저장 포맷을 제한하지 않기 때문에 유연하다.
데이터에 직접 컨텐츠에 접근할 수 있는 방법을 제공하지 않으며, 저장된 메모리에 접근하려면, `view`를 사용해야 한다.

`view`는 데이터 타입, 시작 위치, 엘리먼트의 크기 등의 컨텍스트를 제공하며, 데이터를 실제 타입 배열로 변경한다.
`view``ArrayBufferView 클래스`와 그 하위 클래스로 구현되어 있다.



Typed array superclasses

DataView : `DataView` 뷰는 `ArrayBuffer`로부터 값을 읽거나 쓸 수 있도록 로우 레벨의 인터페이스를 제공한다.
StringView : 문자열에 대한 C 스타일의 인터페이스를 제공한다.



Typed array subclasses

 `DavaView`를 상속한 아래 목록의 서브 클래스가,
각 버퍼에 존재하는 데이터에 접근할 수 있는 기능을 제공한다.

Uint8Array : 1바이트 크기의 부호없는 정수. C의 unsigned char 형과 동일하다.
Float64Array : 8바이트 크기로 64-bit IEEE 부동소수점을 표현한다. C의 double 형과 동일하다.

여기서는 대표적인 클래스만 기재했고, 전체 목록은 아래 링크에서 조회할 수 있다.



ArrayBuffer

buffer의 구현체로 `ArrayBuffer 클래스`가 있으며, 고정 크기의 바이너리 데이터이다.
`ArrayBuffer`의 컨텐츠를 직접 조작할 순 없지만,
특정 타입의 `ArrayBufferView` 객체를 생성해서 데이터를 읽거나 쓸 수 있다.

아래와 같이 32 바이트 크기의 버퍼를 생성할 수 있다.

    var buffer = new ArrayBuffer(32);



버퍼와 뷰 사용하기

16바이트 크기의 버퍼를 만들어보자.

    var buffer = new ArrayBuffer(16);

이 시점에서 우리는 0으로 초기화된 16바이트 크기의 메모리 공간을 확보했다.
아직 할 수 있는 것 별로 없지만, 일단 16바이트가 맞는지 확인해보자.

    buffer.byteLength; //--> 16

이제 버퍼로 뭔가 작업을 하기 위해 뷰를 만들어야 한다.
버퍼의 데이터를 8-bit 부호없는 정수처럼 사용할 수 있도록 뷰를 만들어보자.

    var uint8View = new Uint8Array(buffer);


이제 일반적인 배열을 다루는 방식으로 메모리에 접근할 수 있다.

    for (var i = 0; i < uint8View.length; i++) {
      uint8View[i] = i;
    }



같은 데이터에 여러 뷰를 적용하기

아까 만든 buffer에 다른 뷰를 적용할 수도 있다.
C의 Union을 떠올리면 좋다.

이전에 실행했던 코드에 의해 uint8View의 두 번째 인덱스엔 1가 저장되어 있다.

    uint8View[1]; //--> 1


이제 16-bit 단위의 정수로 뷰를 만들어보자.

    var int16View = new Int16Array(buffer);

2바이트를 차지하고 있는 첫 번째 배열을 0으로 설정하면,

    int16View[0] = 0;

uint8View의 두 번째 인덱스도 0으로 설정된다.

    uint8View[1]; //--> 0



복잡한 데이터 구조를 사용하기

하나의 버퍼에 여러 뷰와 버퍼의 오프셋을 조합해서 다양한 타입으로 처리할 수도 있다.
C의 구조체를 떠올리면 좋다.

아래와 같은 C 구조체가 있다고 가정해보자.

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

버퍼와 여러 개의 뷰를 생성해서 구조체처럼 처리할 수 있다.

    var buffer = new ArrayBuffer(24);
    var idView = new Uint32Array(buffer, 0, 1); // 0~3바이트까지
    var usernameView = new Uint8Array(buffer, 4, 16); // 4~19바이트까지
    var amountDueView = new Float32Array(buffer, 20, 1); // 20~23바이트까지

C 구조체의 데이터 정렬은 플랫폼마다 다르기 때문에, 위와 같이 사용할 때 패딩에 주의하도록 한다.



일반 배열로 변환하기

타입 배열을 사용하다가 일반 배열로 변환해야할 때도 있을 것이다.
이 때는 다음 코드와 같이 빈 배열을 컨텍스트로 타입 배열을 매개변수로 전달해 생성하면 된다.

    var typedArray = new Uint8Array( [ 1, 2, 3, 4, 5 ] );
    var normalArray = Array.apply( [], typedArray);

    normalArray; //--> [1, 2, 3, 4, 5]



호환성

IE10~, iOS4.2~, Android4.0~, 이외 모던 브라우저에서 제공한다.
자세한 지원 범위는 아래 링크를 참고한다.


사파리에서 타입 배열을 제공하긴 하지만, 일반 배열에 비해 엄청 느리다는 버그 제보가 있다.
수정되었는지 여부는 아직 잘 모르겠다.



활용하기

Base64로 인코딩한 데이터를 8-bit 타입 배열로 변경할 수도 있고,


FileReadAPI를 사용해서 버퍼로 읽어올 수도 있다.


카테고리

분류 전체보기 (683)
About me. (6)
Daylogs (648)
비공개 (0)
영어공부 (0)
My works - 추억 (29)