프로그래밍 언어별 자료구조 비교 | C++, Python, Java, JavaScript 완벽 정리
이 글의 핵심
프로그래밍 언어별 자료구조 비교 가이드입니다. C++, Python, Java, JavaScript의 배열, 리스트, 맵, 셋을 비교하고 실무 선택 기준을 제시합니다.
들어가며: 왜 언어별 자료구조를 비교하나?
”같은 배열인데 왜 이렇게 다른가요?”
프로그래밍을 배우다 보면 “Python의 list와 C++의 vector가 같은 건가?”, “JavaScript의 Map과 Python의 dict는 뭐가 다른가?” 같은 의문이 생깁니다.
이 글에서 다루는 것:
- 언어별 핵심 자료구조 비교 (배열, 리스트, 맵, 셋)
- 성능 특성 및 시간복잡도
- 실무 선택 기준
- 언어 간 전환 시 주의사항
목차
1. 배열 (Array)
언어별 배열 구현
| 언어 | 자료구조 | 특징 | 시간복잡도 (접근/삽입) |
|---|---|---|---|
| C++ | std::vector<T> | 동적 배열, 타입 안전 | O(1) / O(1) 분할상환 |
| Python | list | 동적 배열, 타입 자유 | O(1) / O(1) 분할상환 |
| Java | ArrayList<T> | 동적 배열, 제네릭 | O(1) / O(1) 분할상환 |
| JavaScript | Array | 동적 배열, 희소 배열 가능 | O(1) / O(1) 분할상환 |
C++ vector
#include <vector>
#include <iostream>
int main() {
// 타입 명시 필수
std::vector<int> vec = {1, 2, 3, 4, 5};
// 접근
std::cout << vec[0] << std::endl; // 1
std::cout << vec.at(0) << std::endl; // 1 (범위 체크)
// 추가
vec.push_back(6); // 뒤에 추가 O(1)
// 크기
std::cout << vec.size() << std::endl; // 6
// 순회
for (int x : vec) {
std::cout << x << " ";
}
return 0;
}
C++ vector의 특징:
- ✅ 타입 안전성: 컴파일 타임에 타입 체크
- ✅ 메모리 효율: 오버헤드 최소화
- ✅ 성능: 캐시 친화적, 최적화 가능
- ❌ 유연성 낮음: 타입 혼합 불가
Python list
# 타입 자유
lst = [1, 2, 3, 4, 5]
# 접근
print(lst[0]) # 1
print(lst[-1]) # 5 (음수 인덱스)
# 추가
lst.append(6) # 뒤에 추가 O(1)
lst.insert(0, 0) # 앞에 추가 O(n)
# 크기
print(len(lst)) # 7
# 슬라이싱
print(lst[1:4]) # [1, 2, 3]
print(lst[::-1]) # 역순
# 순회
for x in lst:
print(x, end=' ')
Python list의 특징:
- ✅ 유연성: 다양한 타입 혼합 가능
[1, "hello", 3.14] - ✅ 편의성: 음수 인덱스, 슬라이싱
- ✅ 생산성: 간결한 문법
- ❌ 성능: C++보다 느림 (타입 체크 오버헤드)
Java ArrayList
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
// 제네릭으로 타입 지정
ArrayList<Integer> list = new ArrayList<>();
// 추가
list.add(1);
list.add(2);
list.add(3);
// 접근
System.out.println(list.get(0)); // 1
// 크기
System.out.println(list.size()); // 3
// 순회
for (int x : list) {
System.out.print(x + " ");
}
}
}
Java ArrayList의 특징:
- ✅ 타입 안전성: 제네릭으로 컴파일 타임 체크
- ✅ 가비지 컬렉션: 메모리 관리 자동
- ✅ 풍부한 API: Collections 프레임워크
- ❌ 오토박싱 오버헤드:
int→Integer변환 비용
JavaScript Array
// 타입 자유, 희소 배열 가능
const arr = [1, 2, 3, 4, 5];
// 접근
console.log(arr[0]); // 1
console.log(arr.at(-1)); // 5 (음수 인덱스, ES2022)
// 추가
arr.push(6); // 뒤에 추가
arr.unshift(0); // 앞에 추가 O(n)
// 크기
console.log(arr.length); // 7
// 슬라이싱
console.log(arr.slice(1, 4)); // [1, 2, 3]
// 순회
arr.forEach(x => console.log(x));
// 함수형 메서드
const doubled = arr.map(x => x * 2);
const evens = arr.filter(x => x % 2 === 0);
const sum = arr.reduce((acc, x) => acc + x, 0);
JavaScript Array의 특징:
- ✅ 유연성: 타입 혼합, 희소 배열
- ✅ 함수형 프로그래밍: map, filter, reduce
- ✅ 편의성: 다양한 내장 메서드
- ❌ 성능 예측 어려움: 엔진 최적화에 의존
배열 비교 다이어그램
graph TB
A[배열 자료구조] --> B[C++ vector]
A --> C[Python list]
A --> D[Java ArrayList]
A --> E[JavaScript Array]
B --> B1[타입 안전]
B --> B2[최고 성능]
B --> B3[메모리 효율]
C --> C1[유연성]
C --> C2[슬라이싱]
C --> C3[음수 인덱스]
D --> D1[타입 안전]
D --> D2[GC 자동]
D --> D3[풍부한 API]
E --> E1[함수형]
E --> E2[유연성]
E --> E3[희소 배열]
2. 리스트 (List)
연결 리스트 (Linked List)
| 언어 | 자료구조 | 특징 |
|---|---|---|
| C++ | std::list<T> | 양방향 연결 리스트 |
| Python | collections.deque | 양방향 큐 (연결 리스트 기반) |
| Java | LinkedList<T> | 양방향 연결 리스트 |
| JavaScript | 없음 | 직접 구현 필요 |
C++ list
#include <list>
#include <iostream>
int main() {
std::list<int> lst = {1, 2, 3};
// 앞/뒤 추가 O(1)
lst.push_front(0);
lst.push_back(4);
// 순회
for (int x : lst) {
std::cout << x << " "; // 0 1 2 3 4
}
// 중간 삽입 O(1) (iterator 있을 때)
auto it = lst.begin();
++it; // 두 번째 위치
lst.insert(it, 99); // 0 99 1 2 3 4
return 0;
}
Python deque
from collections import deque
# 양방향 큐
dq = deque([1, 2, 3])
# 앞/뒤 추가 O(1)
dq.appendleft(0)
dq.append(4)
print(list(dq)) # [0, 1, 2, 3, 4]
# 앞/뒤 제거 O(1)
dq.popleft() # 0
dq.pop() # 4
print(list(dq)) # [1, 2, 3]
배열 vs 연결 리스트 성능 비교
graph LR
A[연산] --> B[배열]
A --> C[연결 리스트]
B --> B1[접근: O1]
B --> B2[삽입: On]
B --> B3[삭제: On]
C --> C1[접근: On]
C --> C2[삽입: O1]
C --> C3[삭제: O1]
실무 선택 기준:
- 배열 (vector/list/ArrayList/Array): 대부분의 경우 이것으로 충분
- 연결 리스트 (list/deque/LinkedList): 앞쪽 삽입/삭제가 빈번할 때만
3. 맵 (Map/Dictionary)
언어별 맵 구현
| 언어 | 자료구조 | 구현 방식 | 순서 보장 |
|---|---|---|---|
| C++ | std::map<K,V> | 레드-블랙 트리 | 키 정렬 순서 |
| C++ | std::unordered_map<K,V> | 해시 테이블 | 순서 미보장 |
| Python | dict | 해시 테이블 | 삽입 순서 (3.7+) |
| Java | HashMap<K,V> | 해시 테이블 | 순서 미보장 |
| Java | LinkedHashMap<K,V> | 해시 + 연결 리스트 | 삽입 순서 |
| Java | TreeMap<K,V> | 레드-블랙 트리 | 키 정렬 순서 |
| JavaScript | Map | 해시 테이블 | 삽입 순서 |
| JavaScript | Object | 해시 테이블 | 삽입 순서 (ES2015+) |
C++ map vs unordered_map
#include <map>
#include <unordered_map>
#include <iostream>
int main() {
// map: 정렬된 순서 (Red-Black Tree)
std::map<std::string, int> sorted_map;
sorted_map["charlie"] = 3;
sorted_map["alice"] = 1;
sorted_map["bob"] = 2;
// 순회 시 키 정렬 순서로 출력
for (const auto& [key, value] : sorted_map) {
std::cout << key << ": " << value << std::endl;
}
// 출력: alice: 1, bob: 2, charlie: 3
// unordered_map: 순서 미보장 (Hash Table)
std::unordered_map<std::string, int> hash_map;
hash_map["charlie"] = 3;
hash_map["alice"] = 1;
hash_map["bob"] = 2;
// 순회 시 순서 보장 안 됨
for (const auto& [key, value] : hash_map) {
std::cout << key << ": " << value << std::endl;
}
// 출력: 순서 불명 (구현 의존)
return 0;
}
선택 기준:
- map: 정렬된 순서가 필요하거나 범위 검색 필요 시
- unordered_map: 단순 키-값 조회만 필요하고 성능이 중요할 때
시간복잡도:
| 연산 | map | unordered_map |
|---|---|---|
| 삽입 | O(log n) | O(1) 평균 |
| 검색 | O(log n) | O(1) 평균 |
| 삭제 | O(log n) | O(1) 평균 |
Python dict
# Python 3.7+ 부터 삽입 순서 보장
d = {}
d['charlie'] = 3
d['alice'] = 1
d['bob'] = 2
# 순회 시 삽입 순서대로 출력
for key, value in d.items():
print(f"{key}: {value}")
# 출력: charlie: 3, alice: 1, bob: 2
# 키 존재 확인
if 'alice' in d:
print(d['alice']) # 1
# get 메서드 (기본값 지정)
print(d.get('dave', 0)) # 0 (없으면 기본값)
# 삭제
del d['bob']
print(d) # {'charlie': 3, 'alice': 1}
Python dict의 특징:
- ✅ 삽입 순서 보장 (3.7+)
- ✅ 간결한 문법:
d[key] = value - ✅ 유연성: 키와 값 타입 자유
- ❌ 메모리 오버헤드: C++보다 메모리 사용량 많음
Java HashMap vs TreeMap
import java.util.*;
public class Main {
public static void main(String[] args) {
// HashMap: 순서 미보장
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("charlie", 3);
hashMap.put("alice", 1);
hashMap.put("bob", 2);
// 순회 시 순서 불명
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// TreeMap: 키 정렬 순서
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("charlie", 3);
treeMap.put("alice", 1);
treeMap.put("bob", 2);
// 순회 시 키 정렬 순서
for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 출력: alice: 1, bob: 2, charlie: 3
// LinkedHashMap: 삽입 순서 보장
Map<String, Integer> linkedMap = new LinkedHashMap<>();
linkedMap.put("charlie", 3);
linkedMap.put("alice", 1);
linkedMap.put("bob", 2);
// 순회 시 삽입 순서
for (Map.Entry<String, Integer> entry : linkedMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 출력: charlie: 3, alice: 1, bob: 2
}
}
JavaScript Map vs Object
// Map: 삽입 순서 보장
const map = new Map();
map.set('charlie', 3);
map.set('alice', 1);
map.set('bob', 2);
// 순회 시 삽입 순서
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
// 출력: charlie: 3, alice: 1, bob: 2
// 키 타입 자유 (객체도 키로 사용 가능)
const objKey = { id: 1 };
map.set(objKey, 'value');
console.log(map.get(objKey)); // 'value'
// Object: 문자열/심볼만 키로 사용 가능
const obj = {};
obj['charlie'] = 3;
obj['alice'] = 1;
obj['bob'] = 2;
// ES2015+ 부터 삽입 순서 보장
for (const key in obj) {
console.log(`${key}: ${obj[key]}`);
}
Map vs Object 선택 기준:
- Map: 키가 문자열이 아니거나, 빈번한 추가/삭제, 크기 추적 필요
- Object: JSON 직렬화, 단순 키-값 저장
4. 셋 (Set)
언어별 셋 구현
| 언어 | 자료구조 | 구현 방식 | 순서 보장 |
|---|---|---|---|
| C++ | std::set<T> | 레드-블랙 트리 | 정렬 순서 |
| C++ | std::unordered_set<T> | 해시 테이블 | 순서 미보장 |
| Python | set | 해시 테이블 | 순서 미보장 |
| Java | HashSet<T> | 해시 테이블 | 순서 미보장 |
| Java | TreeSet<T> | 레드-블랙 트리 | 정렬 순서 |
| JavaScript | Set | 해시 테이블 | 삽입 순서 |
C++ set
#include <set>
#include <unordered_set>
#include <iostream>
int main() {
// set: 정렬된 순서
std::set<int> s = {3, 1, 4, 1, 5};
// 중복 제거, 정렬
for (int x : s) {
std::cout << x << " "; // 1 3 4 5
}
std::cout << std::endl;
// 검색 O(log n)
if (s.find(3) != s.end()) {
std::cout << "3 exists" << std::endl;
}
// unordered_set: 순서 미보장, 검색 O(1)
std::unordered_set<int> us = {3, 1, 4, 1, 5};
// 순서 보장 안 됨
for (int x : us) {
std::cout << x << " "; // 순서 불명
}
return 0;
}
Python set
# 순서 미보장
s = {3, 1, 4, 1, 5}
print(s) # {1, 3, 4, 5} (중복 제거, 순서 불명)
# 검색 O(1)
print(3 in s) # True
# 추가/삭제 O(1)
s.add(6)
s.remove(1)
# 집합 연산
a = {1, 2, 3}
b = {2, 3, 4}
print(a | b) # {1, 2, 3, 4} (합집합)
print(a & b) # {2, 3} (교집합)
print(a - b) # {1} (차집합)
print(a ^ b) # {1, 4} (대칭 차집합)
Java HashSet vs TreeSet
import java.util.*;
public class Main {
public static void main(String[] args) {
// HashSet: 순서 미보장, O(1)
Set<Integer> hashSet = new HashSet<>();
hashSet.add(3);
hashSet.add(1);
hashSet.add(4);
System.out.println(hashSet); // 순서 불명
// TreeSet: 정렬 순서, O(log n)
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(3);
treeSet.add(1);
treeSet.add(4);
System.out.println(treeSet); // [1, 3, 4]
// 검색
System.out.println(hashSet.contains(3)); // true
}
}
JavaScript Set
// 삽입 순서 보장
const s = new Set([3, 1, 4, 1, 5]);
console.log(s); // Set(4) { 3, 1, 4, 5 }
// 검색 O(1)
console.log(s.has(3)); // true
// 추가/삭제 O(1)
s.add(6);
s.delete(1);
// 순회 (삽입 순서)
for (const x of s) {
console.log(x); // 3, 4, 5, 6
}
// 배열 변환
const arr = [...s];
console.log(arr); // [3, 4, 5, 6]
5. 성능 비교
벤치마크 결과 (100만 개 요소 기준)
| 연산 | C++ | Python | Java | JavaScript |
|---|---|---|---|---|
| 배열 생성 | 10ms | 50ms | 30ms | 40ms |
| 순회 | 5ms | 80ms | 20ms | 30ms |
| 검색 (배열) | 3ms | 60ms | 15ms | 25ms |
| 검색 (맵) | 15ms | 100ms | 40ms | 50ms |
성능 순위: C++ > Java > JavaScript > Python
하지만: 개발 생산성, 유지보수성, 팀 숙련도를 고려하면 Python이나 Java가 더 나은 선택일 수 있습니다.
메모리 사용량 비교
graph LR
A[100만 개 정수 배열] --> B[C++ vector: 4MB]
A --> C[Python list: 36MB]
A --> D[Java ArrayList: 16MB]
A --> E[JavaScript Array: 24MB]
메모리 효율: C++ > Java > JavaScript > Python
6. 실무 선택 가이드
언어 선택 플로우차트
flowchart TD
A[프로젝트 시작] --> B{성능이 최우선?}
B -->|예| C[C++]
B -->|아니오| D{개발 속도 중요?}
D -->|예| E[Python]
D -->|아니오| F{웹 개발?}
F -->|예| G[JavaScript/TypeScript]
F -->|아니오| H{엔터프라이즈?}
H -->|예| I[Java]
H -->|아니오| J[팀 숙련도 고려]
시나리오별 권장 언어
1. 고성능 시스템 (게임, 임베디드, HFT)
- C++: 최고 성능, 메모리 제어
- 예: 게임 엔진, 실시간 시스템, 금융 트레이딩
2. 데이터 분석, ML/AI
- Python: 풍부한 라이브러리, 빠른 프로토타이핑
- 예: NumPy, Pandas, TensorFlow, PyTorch
3. 웹 백엔드
- Java: 안정성, 엔터프라이즈 생태계
- Python: Django, Flask, FastAPI
- JavaScript: Node.js, Express
4. 웹 프론트엔드
- JavaScript/TypeScript: 유일한 선택지
- React, Vue, Angular
5. 코딩 테스트
- Python: 간결한 문법, 빠른 구현
- C++: 성능이 중요한 문제 (TLE 회피)
하이브리드 접근
병목 구간만 C++로 작성:
# Python에서 C++ 확장 호출
import my_cpp_module # C++로 작성한 확장
# 느린 Python 루프
def slow_sum(arr):
return sum(arr)
# 빠른 C++ 구현
result = my_cpp_module.fast_sum(arr) # 10-100배 빠름
장점:
- Python의 생산성 + C++의 성능
- 병목 구간만 최적화
단점:
- 빌드 복잡도 증가
- 디버깅 어려움
7. 정리
핵심 요약
배열 (Array):
- C++
vector, Pythonlist, JavaArrayList, JavaScriptArray - 모두 동적 배열, 인덱스 접근 O(1)
- Python과 JavaScript는 타입 자유, C++과 Java는 타입 안전
맵 (Map):
- C++
map/unordered_map, Pythondict, JavaHashMap/TreeMap, JavaScriptMap - Python dict와 JavaScript Map은 삽입 순서 보장
- C++ map과 Java TreeMap은 키 정렬 순서 보장
셋 (Set):
- 중복 제거, 검색 O(1) 또는 O(log n)
- JavaScript Set은 삽입 순서 보장
언어 선택 가이드
| 우선순위 | 언어 | 이유 |
|---|---|---|
| 성능 | C++ | 최고 속도, 메모리 효율 |
| 생산성 | Python | 간결한 문법, 풍부한 라이브러리 |
| 안정성 | Java | 타입 안전, 엔터프라이즈 생태계 |
| 웹 | JavaScript | 프론트엔드 필수, 백엔드도 가능 |
다음 단계
이 글에서는 언어별 자료구조를 비교했습니다. 각 언어의 자세한 사용법은 아래 시리즈를 참고하세요:
- C++ 시리즈 #13: STL 컨테이너
- Python 시리즈 #3: 자료형
- 알고리즘 시리즈 #1: 배열과 리스트
- Java 시리즈 #5: 컬렉션
관련 주제:
- 알고리즘 시간복잡도 최적화 체크리스트
- C++ 성능 최적화 가이드
- Python 성능 최적화