자바스크립트 기본 배열의 문제점
- 자바스크립트의 배열은 컴퓨터 과학에서 정의하는 일반적 배열이 아니다.
- 자바스크립트의 배열은 특수처리가 된 객체이다.
- 배열 인덱스,
length
,Array.prototype
에서 상속한 메서드들을 가진 객체이다.
- 배열 인덱스,
타입이 있는 배열 (TypedArray
)
- ES2015 에 생겨났다.
- 파일 읽기/쓰기, 그래픽 작업, 수학 API 등에 쓰인다.
TypedArray
와 기존 배열과의 차이
- 값이 항상 프리미티브 숫자 값이다.
- 8비트 정수 혹은 32비트 부동 소수점 등이다.
- 배열 내부의 모든 원소는 동일한 타입이다.
- 배열을 한번 만들면 길이를 변경할 수 없다.
- 지정된 바이너리 양식으로 연속 메모리 버퍼에 저장된다.
- 중간에 공백이 있을 수 없다.
- 성긴 배열 (sparse array) 이 아니다.
- 다른 타입의 배열과 메모리를 공유할 수 있다.
- 스레드 간에 전송되거나 공유될 수 있다.
- 브라우저의 웹 워커나 워커 스레드에서 공유될 수 있다.
- 타입이 있는 배열을 가져올 때
Float64
타입이 아니라면, 형변환이 수반된다.
onst a = [];
a[9] = "nine";
console.log(a);
성긴 배열을 만드는 간단한 예제 코드.
TypedArray
에 대해 오해하면 안되는 점
- 기존 배열과 마찬가지로 객체이다.
- 포인터가 있는 미가공 데이터 블록이 아니다.
- 기존 자바스크립트 배열처럼 엔트리가 아닌 속성을 넣을 수 있다.
TypedArray
의 종류
총 11
가지가 존재한다.
이름
,값타입
,엔트리 사이즈
,변환 작업
순이다.Int8Array
Int8
1
ToInt8
Uint8Array
Uint8
1
ToUint8
Uint8ClampedArray
Uint8Clamped
1
ToUint8Clamped
Int16Array
Int16
2
ToInt16
Uint16Array
Uint16
2
ToUint16
Int32Array
Int32
4
ToInt32
Uint32Array
Uint32
4
ToUint32
Float32Array
Float32
4
반올림 모드 (IEEE-754-2008)
Float64Array
Float64
8
변환이 필요하지 않다.
BigInt32Array
BigInt32
8
ToBigInt32
BigUInt64Array
BigUInt64
8
ToBigUInt64
TypedArray
사용법
- 생성자 혹은 생성자의
of
혹은from
메서드를 통해 생성한다.- 배열 리터럴로 생성할 수 없다.
TypedArray
의 타입이 달라도 생성법은 같다.
생성자를 통한 생성 방법
new TypedArray()
: 길이가 0으로 설정된 배열을 생성한다.new TypedArray(length)
: 길이 만큼의 엔트리가 있는 배열을 생성한다.new TypedArray(object)
:array-like
객체나iterable
객체만 인자로 올 수 있다.- TypedArray.from() 메서드와 동일하다.
new TypedArray(typedArray)
:TypedArray
의 메모리를 복사하여 생성한다. 이터레이터를 거치지 않는다.new TypedArray(buffer[, start[, length]])
: ArrayBuffer 혹은 SharedArrayBuffer 를 이용하여 생성한다.
TypedArray
에 다른 타입을 할당했을 때
- 다른 타입의 값을 할당하면, 자동으로 변환 함수를 수행한다.
- 평가된 값이
undefined
일 때는0
이 할당된다.
const a1 = new Int8Array(3);
a1[0] = 1;
a1[1] = "2";
a1[2] = 3;
console.log(a1); // Int8Array(3): [1, 2, 3]
const a2 = Int8Array.of(1, 2, "3");
console.log(a2); // Int8Array(3): [1, 2, 3]
const a3 = Int8Array.from({ length: 3, 0: 1, 1: "2" });
console.log(a3); // Int8Array(3): [1, 2, 0]
const a4 = Int8Array.from([1, 2, 3]);
console.log(a4); // Int8Array(3): [1, 2, 3]
TypedArray
에 범위를 벗어나는 숫자를 입력했을 때
- 범위를 초과하는 숫자를 입력했을 때 반응은 타입마다 조금씩 다르다.
부동 소수점 배열에 할당하는 경우
Float64
의 경우엔 자바스크립트Number
와 동일하기 때문에 변화가 없다.Float32
의 경우엔IEEE-754-2008
사양의 "가장 가까운 값으로 반올림, 짝수에 연결" 모드를 사용하여Float32
로 변환된다.
일반 정수 배열에 할당하는 경우 1: 범위를 초과하는 경우
- 최대 값이나 최소 값 범위를 넘어서는 경우
- 래핑이 일어나서
Int8
범위의 값으로 변경된다.
const i1 = Int8Array.of(127, -128, 128, -129);
console.log(i1); // Int8Array(4) [127, -128, -128, 127]
127
,-128
은 범위 내에 있는 값이므로 그대로 표현된다.128
은1000 0000
이고, 부호 비트가1
(음수) 이라서 1의 보수를 취하면0111 1111
이 되고 2의 보수를 취하면1000 0000
이 되어-128
인 것을 확인할 수 있다.
const i2 = Int8Array.of(500, -500);
console.log(i2); // Int8Array(2) [-12, 12]
500
은 2진수로 바꾸면0001 1111 0100
이 된다.- 우측 끝에서 8비트만 남기면
1111 0100
이 된다. 1111 0100
에서 1의 보수를 취하면0000 1011
이 된다.0000 1011
에서 2의 보수로 만들면0000 1100
이 된다.- 즉
-12
가 되는 것이다.
- 우측 끝에서 8비트만 남기면
-500
은 2진수로 바꾸면0010 0000 1100
이 된다.- 8비트만 남기면
0000 1100
이 된다. - 즉
12
가 되는 것이다.
- 8비트만 남기면
일반 정수 배열에 할당하는 경우 2: 소수점을 넣는 경우
const a = new Uint8Array(1);
a[0] = -25.4;
console.log(a[0]); // 231
-25.4
를 넣으면 먼저 소수점에 대한 부분이 날아간다.-25
는 8bit 로 표현하면128 - 25 = 103
이라-128 + 103
을 해주면 될 것이다.1110 0111
이-25
일 것이다.- 그런데 이번엔
Int8Array
가 아니라Uint8Array
여서1110 0111
은231
이 된다.
ArrayBuffer
: TypedArray
에 사용되는 저장소
ArrayBuffer
는 모든 타입의TypedArray
를 품을 수 있다.- 비트를 저장해둘 수 있고, 몇비트씩 짤라서 볼 것이냐에 따라
TypedArray
가 보는 관점이 되는 것이다. - 단, 데이터에 직접 접근할 수는 없고
TypedArray
혹은DataView
를 통해서만 접근 가능하다.
ArrayBuffer
를 이용해 TypedArray
공간 할당해보기
const arrayBuffer = new ArrayBuffer(20);
const intArray = new Int32Array(arrayBuffer);
console.log(arrayBuffer.byteLength); // 20
console.log(intArray.length); // 5
new ArrayBuffer(20)
는 20바이트의ArrayBuffer
를 생성한다.new Int32Array(arrayBuffer)
는 20바이트의ArrayBuffer
를 이용해 생성된다.- 32 비트는 4 바이트고, 총 5개의 공간이 있는
Int32Array
배열이 된다.
- 32 비트는 4 바이트고, 총 5개의 공간이 있는
const intArray = new Int32Array(5);
console.log(intArray.length); // 5
- 위의 코드와 같다.
적합하지 않은 공간을 할당한다면?
- 적합하지 않은 공간이 1개의 공간에 4바이트가 드는데 총 공간이 7바이트로 떨어지거나 하는 경우이다.
- 배열 1개도 2개도 될 수 없다.
const arrayBuffer = new ArrayBuffer(7);
const intArray = new Int32Array(arrayBuffer);
/*
Uncaught RangeError: byte length of Int32Array should be a multiple of 4
at new Int32Array (<anonymous>)
at <anonymous>:2:18
*/
- 에러가 발생한다.
- 이러한 경우를 방지하기 위해
TypedArray
에는BYTES_PER_ELEMENT
라는 속성을 제공한다.
const arrayBuffer = new ArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 5);
const intArray = new Int32Array(arrayBuffer);
- 보통은 이렇게 코드를 작성할 일은 거의 없고 어떠한 사정에 의해
ArrayBuffer
를 따로 작성하는 경우에 필요하다.
PNG
확장자인지 체크하는 로직 만들어보기
- 읽은 파일의 데이터를 8바이트씩 읽어
PNG_HEADER
패턴인지 확인한다. FileReader
는 범용적이어서 몇비트 단위로 접근할지 미리 정해두지 않는다.
const PNG_HEADER = Uint8Array.of(
0x89,
0x50,
0x4e,
0x47,
0x0d,
0x0a,
0x1a,
0x0a
);
const isPNG = (byteData) =>
byteData.length >= PNG_HEADER.length &&
PNG_HEADER.every((b, i) => b === byteData[i]);
const show = (msg) => {
const p = document.createElement("p");
p.appendChild(document.createTextNode(msg));
document.body.appendChild(p);
};
document
.getElementById("file-input")
.addEventListener("change", ({ target }) => {
const file = target.files[0];
if (!file) {
return;
}
const fr = new FileReader();
fr.readAsArrayBuffer(file);
fr.onload = () => {
const byteData = new Uint8Array(fr.result);
show(`${file.name} ${isPNG(byteData) ? "is" : "is not"} a PNG file.`);
};
fr.onerror = (error) => {
show(`File read failed: ${error}`);
};
});
DataView
로 ArrayBuffer
접근하기
ArrayBuffer
에 접근하는 여러 방식을 제공한다.- 빅 엔디언 혹은 리틀 엔디언 방식을 지정하여 읽어낼 수도 있다.
dv.getUint32(0, true)
처럼 두번째 인자가true
면littleEndian
으로 읽는 것이다.
const PNG_HEADER1 = 0x89504e47;
const PNG_HEADER2 = 0x0d0a1a0a;
const TYPE_IHDR = 0x49484452;
const isPNG = (byteData) =>
byteData.length >= PNG_HEADER.length &&
PNG_HEADER.every((b, i) => b === byteData[i]);
const show = (msg) => {
const p = document.createElement("p");
p.appendChild(document.createTextNode(msg));
document.body.appendChild(p);
};
document
.getElementById("file-input")
.addEventListener("change", ({ target }) => {
const file = target.files[0];
if (!file) {
return;
}
const fr = new FileReader();
fr.readAsArrayBuffer(file);
fr.onload = () => {
const dv = new DataView(fr.result);
if (
dv.byteLength >= 24 &&
dv.getUint32(0) === PNG_HEADER_1 &&
dv.getUint32(4) === PNG_HEADER_2 &&
dv.getUint32(12) === TYPE_IHDR
) {
const width = dv.getUint32(16);
const height = dv.getUint32(20);
show(`${file.name}은 ${width} x ${height} 픽셀`);
} else {
show(`${file.name}은 PNG file 이 아니다.`);
}
};
fr.onerror = (error) => {
show(`File read failed: ${error}`);
};
});
ArrayBuffer
의 공유
ArrayBuffer
는 모든TypedArray
에서 공통으로 사용 가능하기 때문에 공유도 가능하다.
겹침 없는 공유
- 겹침 없는 공유는 별다른 문제가 없다.
const buf = new ArrayBuffer(20);
const bytes = new Uint8Array(buf, 0, 8);
const words = new Uint16Array(buf, 8);
console.log(buf.byteLength); // 20 바이트
console.log(bytes.length); // 8 바이트
console.log(words.length); // (16 bit * 6) -> 12 바이트
겹침 있는 공유
- 겹침 있는 공유는 서로를 침범하므로 주의해야 한다.
- 아래 코드는
Uint8Array
의 배열이 각각 리틀 엔디언의 상위 바이트 하위 바이트를 가리키고 있음을 인지해야 한다.
const buf = new ArrayBuffer(12);
const bytes = new Uint8Array(buf);
const words = new Uint16Array(buf);
console.log(words[0]); // 0
bytes[0] = 1;
bytes[1] = 2;
console.log(bytes[0]); // 1
console.log(bytes[1]); // 1
console.log(words[0]); // 513 -> 리틀 엔디언 사용
TypedArray
의 메서드
- 배열의 길이가 고정이라 배열의 길이를 변화시키는 메서드는 없다.
pop()
,push()
,shift()
,unshift()
,splice()
- 중첩된 배열이 포함되지 않으므로 아래 메서드도 없다.
flat()
,flatMap()
,concat()
map()
, slice()
, filter()
메서드
map()
,slice()
,filter()
는 지원하지만 같은 타입의 배열만 반환한다.
const a1 = Uint8Array.of(50, 100, 150, 200);
const a2 = a1.map((v) => v * 2);
console.log(a2); // Uint8Array(4) [50, 100, 150, 200]
TypedArray.prototype.set()
메서드
- setter 와 비슷한 역할을 한다.
- 배열 내용과 길이를 주면
set()
된다.
const source = new Uint8Array([1, 2, 3, 4, 5]);
const dest = new Uint8Array(5);
dest.set(source);
console.log(dest); // Output: Uint8Array [ 1, 2, 3, 4, 5 ]
TypedArray.prototype.subarray()
메서드
- 메모리를 공유하는 하위
TypedArray
를 생성한다. begin
과end
를 인자로 받아 범위를 지정 가능하다.- 아래 예제에서 값을 바꾸니 공유하는 메모리 값이 같이 바뀐 것을 알 수 있다.
const wholeArray = Uint8Array.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
const firstHalf = wholeArray.subarray(0, 5);
console.log(wholeArray); // Uint8Array(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(firstHalf); // Uint8Array(5) [0, 1, 2, 3, 4]
firstHalf[0] = 100;
console.log(wholeArray); // Uint8Array(10) [100, 1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(firstHalf); // Uint8Array(5) [100, 1, 2, 3, 4]
반응형
'자바스크립트 > 모던 자바스크립트' 카테고리의 다른 글
모던 자바스크립트, 4가지 동등성 비교 알고리즘 (0) | 2023.03.22 |
---|---|
모던 자바스크립트, Reflect (리플렉트) 객체란? (0) | 2023.03.20 |
모던 자바스크립트, ES2019 의 stable 내장 정렬 (Array.prototype.sort) (0) | 2023.03.15 |
모던 자바스크립트, 편의 유틸 배열 메서드 (0) | 2023.03.13 |
모던 자바스크립트, 편의 유틸 문자열 메서드 (0) | 2023.03.12 |