Java programming

μžλ°” ν”„λ‘œκ·Έλž˜λ°μ„ ν•˜λ©΄μ„œ Vector 클래슀 와 Stack 클래슀 ν™œμš©μ„ μ§€μ–‘ν•˜λŠ” 이유

ν”„λ‘œκ·Έλž˜λ¨Έ μ˜€μ›” 2024. 5. 27.

κ°œμš”

μžλ°” Collection μΈν„°νŽ˜μ΄μŠ€λ₯Ό 깊게 κ³΅λΆ€ν•˜κΈ° μ „μ—” μ½”λ”© ν…ŒμŠ€νŠΈ λ¬Έμ œμ—μ„œ Stack 자료ꡬ쑰λ₯Ό μ‚¬μš©ν•΄μ„œ 문제λ₯Ό ν’€μ–΄μ•Όν•  λ•Œ, μžλ°”μ—μ„œ μ œκ³΅ν•΄μ£ΌλŠ” Stack 클래슀λ₯Ό ν™œμš©ν•΄μ„œ 문제λ₯Ό ν’€μ—ˆλ‹€.

ν•˜μ§€λ§Œ, CS와 자료ꡬ쑰 및 μžλ°” 곡뢀λ₯Ό 깊게 ν•΄λ³΄λ‹ˆ 싀무적인 ν”„λ‘œκ·Έλž˜λ°μ‹œμ—” Stack ν΄λž˜μŠ€μ™€ Vector 클래슀 ν™œμš©μ„ μ§€μ–‘ν•΄μ•Όν•œλ‹€λŠ” κ±Έ ν•™μŠ΅ν•˜κ²Œ 됐닀.

μ΄μœ λŠ” λ‹€μŒκ³Ό κ°™λ‹€.

 

1. λΉ„νš¨μœ¨μ μΈ 동기화

Vector와 Stack은 λͺ¨λ‘ λ™κΈ°ν™”λœ(synchronized) ν΄λž˜μŠ€μ΄λ‹€. μ΄λŠ” λ©€ν‹°μŠ€λ ˆλ“œ ν™˜κ²½μ—μ„œ μ•ˆμ „ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆλ‹€λŠ” μž₯점이 μžˆμœΌλ‚˜, λΆˆν•„μš”ν•œ λ™κΈ°ν™”λ‘œ 인해 μ„±λŠ₯ μ €ν•˜κ°€ λ°œμƒν•  수 μžˆλ‹€. λŒ€λΆ€λΆ„μ˜ 경우, 동기화가 ν•„μš”ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— μ΄λŸ¬ν•œ ν΄λž˜μŠ€λ“€μ„ μ‚¬μš©ν•˜λŠ” 것은 λΉ„νš¨μœ¨μ μ΄λ‹€. λŒ€μ‹ , λΉ„λ™κΈ°ν™”λœ ArrayList와 Deque(κ΅¬ν˜„μ²΄λ‘œλŠ” ArrayDequeλ₯Ό 많이 μ‚¬μš©ν•¨)λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 더 λ‚«λ‹€.

 

벑터 클래슀의 λ‚΄λΆ€ κ΅¬ν˜„

 

Vector 클래슀의 λ™κΈ°ν™”λŠ” 클래슀 λ‚΄μ˜ 거의 λͺ¨λ“  λ©”μ„œλ“œκ°€ synchronized ν‚€μ›Œλ“œλ‘œ 보호되기 λ•Œλ¬Έμ— λ°œμƒν•œλ‹€. μ΄λŠ” Vector의 λͺ¨λ“  λ©”μ„œλ“œκ°€ λ™κΈ°ν™”λœ 블둝 λ‚΄μ—μ„œ μ‹€ν–‰λœλ‹€λŠ” 것을 μ˜λ―Έν•˜λ©°, λ™μ‹œμ— μ—¬λŸ¬ μŠ€λ ˆλ“œκ°€ μ ‘κ·Όν•  경우 μŠ€λ ˆλ“œ μ•ˆμ „μ„±μ„ 보μž₯ν•˜μ§€λ§Œ, μ„±λŠ₯ μ €ν•˜λ₯Ό μ΄ˆλž˜ν•œλ‹€.

λ‹¨μˆœνžˆ Vector에 Iteratorλ₯Ό λΆ™μ—¬ 순차적으둜 item듀을 νƒμƒ‰ν•˜κΈ°λ§Œ 해도 μ›μ†Œνƒμƒ‰ μ‹œλ§ˆλ‹€ get() λ©”μ„œλ“œμ˜ 싀행을 μœ„ν•΄ 계속 lock을 κ±Έκ³  λ‹«μœΌλ―€λ‘œ Iteratorμ—°μ‚°κ³Όμ • 전체에 1번만 κ±Έμ–΄μ£Όλ©΄ 될 locking에 μ“Έλ°μ—†λŠ” μ˜€λ²„ν—€λ“œκ°€ μ—„μ²­λ‚˜κ²Œ λ°œμƒν•œλ‹€.

λ˜ν•œ 단일 μŠ€λ ˆλ“œ ν™˜κ²½μ—μ„  동기화 μ²˜λ¦¬λŠ” λΆˆν•„μš”ν•˜κΈ° λ•Œλ¬Έμ— λΆˆν•„μš”ν•œ μ˜€λ²„ν—€λ“œκ°€ 생긴닀.

μ½”λ”© ν…ŒμŠ€νŠΈ 문제 풀이 λ•Œλ„ λ©€ν‹° μŠ€λ ˆλ”© ν™˜κ²½μ΄ μ•„λ‹ˆκΈ° λ•Œλ¬Έμ— 벑터 ν΄λž˜μŠ€λ‚˜ μŠ€νƒ 클래슀λ₯Ό μ‚¬μš©ν•˜λŠ” 것은 λΆˆν•„μš”ν•œ μ˜€λ²„ν—€λ“œλ₯Ό μ•ΌκΈ°μ‹œν‚¨λ‹€.

 

 

μŠ€νƒ 클래슀 λ‚΄λΆ€ κ΅¬ν˜„

 

μŠ€νƒ ν΄λž˜μŠ€λŠ” 벑터 클래슀λ₯Ό 상속받아 ν™•μž₯된 ν΄λž˜μŠ€μ΄λ‹€. μŠ€νƒμ˜ λ©”μ†Œλ“œ λ˜ν•œ 동기화 μ²˜λ¦¬κ°€ λ˜μ–΄ μžˆμ–΄μ„œ, 벑터 클래슀 와 λ˜‘κ°™μ΄ λΆˆν•„μš”ν•œ μ˜€λ²„ν—€λ“œλ₯Ό μ•ΌκΈ°μ‹œν‚¨λ‹€.

 

 

2. 더 λ‚˜μ€ λŒ€μ•ˆ 쑴재

  • Vector λŒ€μ‹  ArrayList μ‚¬μš©: ArrayListλŠ” Vector와 거의 λ™μΌν•œ κΈ°λŠ₯을 μ œκ³΅ν•˜μ§€λ§Œ, λ™κΈ°ν™”λ˜μ§€ μ•Šμ•„ 더 가볍고 λΉ λ₯΄λ‹€. 동기화가 ν•„μš”ν•œ κ²½μš°μ—λŠ” Collections.synchronizedList λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ ArrayListλ₯Ό 동기화할 수 μžˆλ‹€.
List<Integer> synchronizedList = Collections.synchronizedList(new ArrayList<>());
  • Stack λŒ€μ‹  Deque μ‚¬μš©: Stack ν΄λž˜μŠ€λŠ” 였래된 클래슀이며, 더 μœ μ—°ν•˜κ³  κ°•λ ₯ν•œ Deque μΈν„°νŽ˜μ΄μŠ€(κ΅¬ν˜„μ²΄λ‘œλŠ” ArrayDeque, LinkedList λ“±)λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ’‹λ‹€. DequeλŠ” μŠ€νƒκ³Ό 큐의 κΈ°λŠ₯을 λͺ¨λ‘ μ§€μ›ν•˜λ©°, 더 λ‚˜μ€ μ„±λŠ₯을 μ œκ³΅ν•œλ‹€.
    μžλ°”μ—μ„œ Dequeλ₯Ό κ΅¬ν˜„ν•˜λŠ” 방법은 크게 LinkedList와 ArrayDequeκ°€ μžˆλŠ”λ°, μŠ€νƒμ˜ sizeκ°€ μ—„μ²­ 컀질 κ°€λŠ₯성이 있고 size의 변동성이 맀우 큰 경우 즉각적인 λ©”λͺ¨λ¦¬ 곡간 확보λ₯Ό μœ„ν•΄μ„  LinkedList방식이 μ μ ˆν•˜κ³ , 그렇지 μ•Šμ€ κ²½μš°λŠ” 자체 λ©”λͺ¨λ¦¬ μ†Œλͺ¨λŸ‰μ΄ 적고 iterate의 효율이 쒋은 ArrayDequeλ₯Ό μ‚¬μš©ν•˜λ©΄ λœλ‹€.
    κ·Έ μ™Έ νŠΉμˆ˜ν•œ 상황을 더 κ³ λ €ν•΄λ³΄μžλ©΄, λ©€ν‹°μŠ€λ ˆλ“œ ν™˜κ²½μ—μ„œλŠ” java.util.concurrent νŒ¨ν‚€μ§€μ˜ ConcurrentLinkedDeque클래슀λ₯Ό μ‚¬μš©ν•˜λŠ”κ²Œ μ’‹κ³ (Non-blocking을 μ‚¬μš©ν•˜μ—¬ 높은 λ™μ‹œμ„±μ„ 제곡, ν΄λž˜μŠ€λŠ” λ‚΄λΆ€μ μœΌλ‘œ 락을 μ‚¬μš©ν•˜μ§€ μ•Šκ³  CAS(compare-and-swap) 같은 μ €μˆ˜μ€€ 동기화 기법을 μ‚¬μš©ν•œλ‹€.)
    μŠ€νƒμ˜ sizeκ°€ μ œν•œλ˜μ–΄μžˆλŠ” κ²½μš°μ— LinkedBlockingDeque 클래슀(λΈ”λ‘œν‚Ή 큐둜, 큐가 가득 μ°¨κ±°λ‚˜ λΉ„μ–΄ μžˆμ„ λ•Œ μŠ€λ ˆλ“œλ₯Ό μ°¨λ‹¨ν•˜μ—¬ μ•ˆμ „ν•œ 동기화λ₯Ό 제곡 ν•œλ‹€.)λ₯Ό μ‚¬μš©ν•˜λŠ” 것을 κ³ λ €ν•΄λ³Όλ§Œ ν•˜λ‹€.
Deque<Integer> stack = new ConcurrentLinkedDeque<>();
Deque<Integer> anotherStack = new LinkedBlockingDeque<>();

 

 

3. μ„€κ³„μƒμ˜ 문제

Stack ν΄λž˜μŠ€λŠ” Vectorλ₯Ό 상속받아 κ΅¬ν˜„λ˜μ—ˆκΈ° λ•Œλ¬Έμ—, 상속을 ν†΅ν•œ μ½”λ“œ μž¬μ‚¬μš©μ΄λΌλŠ” μΈ‘λ©΄μ—μ„œ 쒋은 섀계가 μ•„λ‹ˆλ‹€. μ΄λŠ” Liskov Substitution Principle을 μœ„λ°˜ν•˜λŠ” κ²½μš°κ°€ λ§Žμ•„μ§„λ‹€. Deque μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œ ν΄λž˜μŠ€λ“€μ€ μ΄λŸ¬ν•œ 문제λ₯Ό ν”Όν•  수 μžˆλ„λ‘ μ„€κ³„λ˜μ—ˆλ‹€.

 

Stack ν΄λž˜μŠ€λŠ” Vector 클래슀λ₯Ό 상속받아 κ΅¬ν˜„λ˜μ—ˆκΈ° λ•Œλ¬Έμ—, Vector의 λͺ¨λ“  λ©”μ„œλ“œλ₯Ό μƒμ†λ°›λŠ”λ‹€. μ΄λŠ” Stack ν΄λž˜μŠ€κ°€ Vector 클래슀의 λ©”μ„œλ“œλ₯Ό λΆˆν•„μš”ν•˜κ²Œ 많이 κ°€μ§€κ²Œ 됨을 μ˜λ―Έν•œλ‹€. 예λ₯Ό λ“€μ–΄, Vector ν΄λž˜μŠ€μ—λŠ” insertElementAt, removeElementAt 같은 λ©”μ„œλ“œκ°€ μžˆλŠ”λ°, μ΄λŸ¬ν•œ λ©”μ„œλ“œλŠ” μŠ€νƒμ˜ LIFO(Last In First Out) νŠΉμ„±μ— μ ν•©ν•˜μ§€ μ•Šλ‹€.

Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println(stack.get(1));  // 2
stack.set(1, 99);
System.out.println(stack);  // [1, 99, 3]

 

VectorλŠ” 랜덀 μ•‘μ„ΈμŠ€λ₯Ό μ§€μ›ν•˜κΈ° λ•Œλ¬Έμ— Stack ν΄λž˜μŠ€κ°€ 이λ₯Ό μƒμ†λ°›μ•˜μ„ λ•Œ μŠ€νƒμ˜ κΈ°λ³Έ 원칙(LIFO)을 μœ„λ°˜ν•  κ°€λŠ₯성이 μžˆλ‹€. μ΄λŠ” Liskov Substitution Principle(LSP)의 "μ„œλΈŒνƒ€μž…μ€ 기반 νƒ€μž…μœΌλ‘œ λŒ€μ²΄ κ°€λŠ₯ν•΄μ•Ό ν•œλ‹€"λŠ” 원칙을 μœ„λ°˜ν•˜κ²Œ 되며, Stack ν΄λž˜μŠ€κ°€ 쒋은 섀계가 μ•„λ‹˜μ„ λ‚˜νƒ€λ‚Έλ‹€. 반면, DequeλŠ” μŠ€νƒκ³Ό 큐의 νŠΉμ„±μ„ λͺ…ν™•ν•˜κ²Œ μœ μ§€ν•˜λ©°, 인덱슀λ₯Ό ν†΅ν•œ 랜덀 μ•‘μ„ΈμŠ€λ₯Ό μ§€μ›ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— μ΄λŸ¬ν•œ 문제λ₯Ό ν”Όν•  수 μžˆλ‹€. κ·Έλž˜μ„œ, μžλ°” ν”„λ‘œκ·Έλž˜λ°μ—μ„œ Stack λŒ€μ‹  Dequeλ₯Ό μ‚¬μš©ν•˜λŠ” 것이 λ°”λžŒμ§ν•˜λ‹€.

 

κ²°λ‘ 

μžλ°” ν”„λ‘œκ·Έλž˜λ°μ—μ„œ Vector와 Stack을 지양해야 ν•˜λŠ” μ΄μœ λŠ” λΉ„νš¨μœ¨μ μΈ 동기화, 더 λ‚˜μ€ λŒ€μ•ˆμ˜ 쑴재, 그리고 μ„€κ³„μƒμ˜ λ¬Έμ œλ“€ λ•Œλ¬Έμ΄λ‹€. μ΄λŸ¬ν•œ 이유둜, 더 ν˜„λŒ€μ μΈ ArrayList와 Dequeλ₯Ό μ‚¬μš©ν•˜λŠ” 것이 λ°”λžŒμ§ν•˜λ‹€.

λŒ“κΈ€