본문 바로가기

Web

JavaScript 자료 구조와 메모리 관리

자료구조란?

자료구조는 데이터를 저장하고 조직화하여, 데이터의 접근 및 수정, 검색 등을 효율적으로 수행할 수 있도록 돕는 방법

알고리즘이란?

자료구조에 저장된 데이터를 처리하고 조작하는 과정이나 절차

 

1. 단순구조 (Primitive Data Structure)

  • 프로그래밍에서 사용하는 기본 데이터 타입으로 구성된 구조
  • 하나의 값만 저장하며, 복잡한 데이터 관계를 표현하지 않음
  • 예시 (JavaScript 원시 타입):
    • string, number, boolean, null, undefined
  • 프로그램에서 변수, 상수 등 단순 데이터를 저장하는 데 사용

2. 비단순구조 (Non-Primitive Data Structure)

  • 여러 데이터를 목적에 맞게 효과적으로 저장하는 구조
  • 다양한 데이터 간의 관계를 표현할 수 있음
  • 예시 (JavaScript 참조 타입):
    • object, array, function
  • 여러 데이터의 상호 관계를 저장 및 조작

3. 선형구조 (Linear Data Structure)

  • 데이터가 일렬로 연결된 구조로, 항목 간의 관계가 1:1
  • 데이터를 순차적으로 탐색하거나 조작하기에 적합
  • 예시:
    • 배열(Array), 스택(Stack), 큐(Queue), 연결 리스트(Linked List)
  • 배열에서 요소를 검색하거나, 스택을 이용한 호출 순서 관리

4. 비선형구조 (Non-Linear Data Structure)

  • 데이터 항목 간의 관계가 1또는 N으로 연결된 구조
  • 계층적 구조나 복잡한 데이터 관계를 표현하기 적합
  • 예시:
    • 트리(Tree), 그래프(Graph)
  • 트리를 사용한 디렉토리 구조 관리, 그래프를 통한 네트워크 연결

 

자료 구조

1. Set

  • 특징:
    • 중복되지 않는 값을 저장하는 데이터 구조
    • 순서가 중요하지 않은 고유한 값의 컬렉션
  • 주요 메서드:
    • add(value): 값을 추가
    • delete(value): 값을 삭제
    • has(value): 값의 존재 여부 확인
  • 사용 사례:
    • 데이터의 중복 제거
    • 빠른 값 검색 및 저장
const mySet = new Set();
mySet.add(1);
mySet.add(2);
mySet.add(2); // 중복은 추가되지 않음
console.log(mySet); // Set { 1, 2 }

 

2. Map

  • 특징:
    • 키-값(Key-Value) 쌍으로 데이터를 저장
    • 키는 원시값뿐만 아니라 객체도 가능
  • 주요 메서드:
    • set(key, value): 키와 값을 추가
    • get(key): 키에 대응하는 값 가져오기
    • has(key): 키의 존재 여부 확인
  • 사용 사례:
    • 키 기반 데이터 저장과 검색
    • 객체 키가 아닌 데이터 저장이 필요할 때
const myMap = new Map();
myMap.set('name', 'Alice');
myMap.set({ id: 1 }, 'Data');
console.log(myMap.get('name')); // 'Alice'

 

3. Object

  • 특징:
    • 키-값(Key-Value) 쌍을 저장하는 기본 자료구조
    • 키는 반드시 문자열 또는 심볼(Symbol)이어야 함
  • 주요 메서드:
    • Object.keys(obj): 객체의 키를 배열로 반환
    • Object.values(obj): 객체의 값을 배열로 반환
  • 사용 사례:
    • 고정된 속성 데이터 저장
    • 데이터를 JSON 형태로 관리
const obj = { name: 'Alice', age: 25 };
console.log(obj.name); // 'Alice'
console.log(Object.keys(obj)); // ['name', 'age']

 

4. Array

  • 특징:
    • 순서가 있는 데이터의 컬렉션
    • 인덱스를 사용해 값에 접근
  • 주요 메서드:
    • push(value): 값 추가
    • pop(): 마지막 값을 제거
    • filter(), map(), reduce(): 고차 함수 제공
  • 사용 사례:
    • 순서가 중요한 데이터 관리
    • 리스트, 스택, 큐와 같은 선형 자료구조 구현
const arr = [1, 2, 3];
arr.push(4);
console.log(arr); // [1, 2, 3, 4]

 

 

Set vs Array

특징 Set Array
데이터 저장 고유한 값만 저장 중복 데이터 저장 가능
순서 순서가 중요하지 않음 순서 유지
접근 방식 값 기반으로 접근 인덱스 기반으로 접근

 

Map vs Object

특징 Map Object
키 유형 모든 데이터 타입 가능 문자열 또는 심볼만 가능
순서 입력 순서 유지 순서 보장되지 않음
크기 확인 size 속성 사용 직접 키 개수를 확인해야 함
사용 사례 동적 키-값 데이터 관리 고정된 키-값 데이터 관리

 

심볼 vs 문자열 키

특징 Symbol String
고유성 고유 값, 중복 불가 중복 가능
은닉성 일반적인 방법으로 접근 불가 접근 가능
사용 용도 키 충돌 방지, 은닉 속성 일반적인 속성 키
예시 사용 숨겨진 속성, 라이브러리 내부 구현 JSON, 전역 키 사용

 

Symbol은 고유성을 가지고 변경이 불가능하고, 은닉성을 가지고 있지만, 문자열로 암묵적 변환이 불가해서 보안상으로는 유리하지만 디버깅시 값 확인이 어려울 수 있다. 일반적인 키는 문자열로도 충분히 표현 가능하여, 심볼을 굳이 사용할 필요가 없는 경우가 많다. 심볼은 라이브러리, 프레임워크, 메타프로그래밍에서 필수적이며, 코드의 안전성과 모듈화를 보장하는 데 유용하다.

 

  • Set: 중복 없는 데이터 저장 , 중복 제거시 사용
  • Map: 키-값 쌍 관리, 객체 키도 허용 , 효율적인 키-값 데이터 관리
  • Object: 기본 키-값 데이터 관리, JSON 구조 활용, 간단한 키-값 데이터 관리
  • Array: 순서가 중요한 데이터 관리, 순서가 중요한 데이터 관리

 

JavaScript에서 해시 테이블이 사용되는 곳

  1. Object와 Map
    • Object: JavaScript의 가장 기본적인 키-값 데이터 구조이다.
      • 문자열과 Symbol만 키로 사용할 수 있다.
      • 내부적으로 해시 테이블을 사용하여 키를 관리한다.
    • Map: ES6에서 도입된 데이터 구조로, 키에 모든 데이터 타입(number, object, 등)을 사용할 수 있다.
      • 내부적으로 해시 테이블을 기반으로 구현되며, 더 효율적인 키-값 매핑을 제공한다.
  2. Set
    • Set은 중복되지 않는 값의 집합을 관리하며, 내부적으로 값 자체를 키로 사용하는 해시 테이블 구조를 활용한다.
  3. 웹 브라우저 (JavaScript 엔진):
    • JavaScript 엔진(V8, SpiderMonkey 등)은 많은 데이터 구조에서 해시 테이블을 사용해 성능을 최적화한다.
    • 예: DOM 속성 관리, 캐싱, 이벤트 핸들러 저장 등

 

하지만, 이러한 해시테이블도 충돌이 발생하기 때문에 JavaScript의 ObjectMap은 해시 충돌을 처리하기 위해 다양한 전략을 사용한다. 대표적으로 체이닝(Linked List)오픈 어드레싱(Open Addressing)을 사용한다.

 

1. 체이닝(Linked List): Object의 충돌 처리 방식

  • 어떻게 작동하나?
    • JavaScript의 Object는 체이닝 방식을 사용한다.
    • 동일한 해시 값을 가진 키가 존재할 경우, 충돌된 값을 연결 리스트(Linked List)로 관리한다.
    • 즉, 같은 버킷(bucket)에 들어온 여러 값을 연결하여 저장한다.
  • 장점:
    • 테이블이 가득 차더라도 데이터를 추가할 수 있음
    • 충돌이 적은 경우 빠른 검색/삽입 가능
  • 단점:
    • 충돌이 많아질수록 리스트를 순회해야 하므로 성능 저하

2. 오픈 어드레싱(Open Addressing): Map의 충돌 처리 방식

  • 어떻게 작동하나?
    • Map은 오픈 어드레싱 방식을 사용한다.
    • 충돌이 발생하면 동일한 버킷에 데이터를 저장하지 않고, 테이블 내 다른 빈 슬롯을 찾아 데이터를 저장한다.
  • 선형 탐사(Linear Probing):
    • 충돌이 발생하면 다음 빈 슬롯을 순차적으로 탐색
  • 제곱 탐사(Quadratic Probing):
    • 충돌 발생 시, 슬롯을 제곱 간격으로 탐색
  • 장점:
    • 메모리를 효율적으로 사용
    • 작은 데이터 집합에서는 빠르게 동작
  • 단점:
    • 테이블이 가득 차면 성능 저하
    • 연속된 빈 슬롯을 탐색하므로 충돌이 많아질수록 속도가 느려질 수 있음

 

데이터  구조데이터 저장 방식 내부 데이터 구조 해시 테이블  충돌 해결 방식
Object 키-값(Key-Value) 해시 테이블 기반 사용 체이닝(Chaining)
Map 키-값(Key-Value), 키는 모든 타입 가능 해시 테이블 기반 사용 오픈 어드레싱(Open Addressing)
Set 값(Value), 중복 없음 해시 테이블 기반 사용 오픈 어드레싱(Open Addressing)
Array 순서가 있는 값(Value) 인덱스 기반의 배열 사용하지 않음 충돌 처리 없음(충돌문제 없음)

 

 

그렇다면, 배열말고는 해시 테이블 구조인데 그럼 힙에 저장되는 메모리에 문제가 발생하지 않을까?

 

참조 타입 데이터가 모두 힙에 저장되므로, 관리가 잘못되면 메모리 누수나 성능 문제가 발생할 수 있다. JavaScript는 메모리 관리를 자동으로 수행하며, 프로그램에서 메모리의 할당과 해제는 가비지 컬렉터(Garbage Collector)에 의해 처리된다. 아래는 힙과 스택의 구조, 가비지 컬렉터의 동작 방식, 그리고 힙 메모리에 발생할 수 있는 문제와 해결 방법에 대해 설명하고 있다.

 

 

1. 스택(Stack)

  • 특징:
    • LIFO(Last In, First Out) 구조로, 데이터가 쌓이고 제거되는 순서를 따름.
    • 고정 크기로, 간단하고 빠른 메모리 할당/해제를 처리.
  • 저장되는 데이터:
    • 원시값(Primitive Types): number, string, boolean, null, undefined, symbol.
    • 함수 호출 시 생성되는 호출 스택(Call Stack)의 함수 실행 컨텍스트.
  • 작동 방식:
    • 데이터를 호출 스택에 저장하고, 함수 실행이 끝나면 메모리에서 제거.

2. 힙(Heap)

  • 특징:
    • 동적으로 할당된 참조 타입 데이터(Object, Array, Map, Set, Function 등)가 저장.
    • 크기가 유동적이며, 메모리 블록이 자유롭게 할당/해제됨.
  • 저장되는 데이터:
    • 객체, 배열, 함수 등 참조 타입 데이터.
    • 스택에는 힙에 저장된 데이터의 참조(Reference)만 저장.
  • 문제점:
    • 힙은 크기가 제한되지 않으므로, 데이터가 계속 쌓이면 메모리 부족이나 성능 저하 발생 가능.

가비지 컬렉터(Garbage Collector)의 역할

JavaScript 엔진(V8 등)에는 가비지 컬렉터가 포함되어, 더 이상 사용되지 않는 메모리를 자동으로 회수한다. 이는 프로그래머가 직접 메모리를 관리하지 않아도 되는 이유이다. 가비지 컬렉터는 도달 가능한 객체(Reachable Object)를 기준으로 메모리 회수를 결정하는데, 도달 가능한 객체란 루트(Root)에서 접근할 수 있는 모든 객체를 의미한다. 루트에서 시작해 도달 가능한 객체를 마킹하고, 도달 불가능한 객체를 메모리에서 제거한다. 또한 참조가 끊어진 데이터를 탐지하여 메모리를 회수하고 정리한다.

 

하지만, 가비지 컬렉터는 즉각적으로 동작하지 않고, 주기적으로 실행되므로 메모리 누수가 완전히 방지되지는 않는다. 또한 참조가 남아 있는 데이터(ex: 클러저, 전역 변수)는 제거되지 않는다.

 

즉, 힙 메모리는 크기가 유동적이고 많은 데이터를 저장할 수 있지만, 관리하지 않으면 메모리 누수로 이어질 수 있다. 가비지 컬렉터는 더 이상 참조되지 않는 데이터를 자동으로 제거하지만, 메모리 누수를 완전히 방지하지는 못한다. 메모리를 효과적으로 관리하기 위해 참조 제거, 전역 변수 최소화, 이벤트 리스너 정리 등 최적화 전략을 도입하는 것이 중요하다.

 


참고자료

https://velog.io/@blackb0x/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0

https://kim-mj.tistory.com/235