Stack
- LIFO -> 처음 넣었던 원소는, 마지막에 꺼낼 수 있다. "끝" 에 대한 삽입과, 제거만을 허용한다
Stack 은 Array, LinkedList 등등을 사용하여 구현할 수 있다.
사실 자료구조상, 양 쪽(out,in 하는 곳) 이 모두 뚫려 있더라도, 제공하는 작업이 pop, push 이기만 해도 stack 으로 제공가능하다.
따라서 Queue 도 스택으로 사용이 가능하다.
Java 에서 Stack 을 위해 어떤 구현체를 사용해야할까?
그렇다면 Java 에서 Stack 으로 사용할 자료구조에는 무엇이 있을까?
그냥 Stack 사용하면 안됨??
흠.. 가장 먼저, "Stack" 클래스를 살펴보자!
Java 의 Stack 에 대한 문서에는 이런 구절이 존재한다
A more complete and consistent set of LIFO stack operations is provided by the Deque interface and its implementations, which should be used in preference to this class. For example
Deque<Integer> stack = new ArrayDeque<Integer>();
Deque 는 새로운 원소는 "시작" 또는 "끝" 에만 추가되거나, 삭제 될 것을 요구한다. 여기에 적합한 자료구조는 무엇일까?
가장 먼저 떠오르는 것은 LinkedList 다. 연결리스트는 랜덤한 위치에 대한 삽입과 삭제가 가능하다 (위치를 찾는데에는 O(n) 이 걸리기는 한다 ) . 그리고 Java 의 LinkedList 는 Deque 를 구현하고 있다.
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
또한 이중연결리스트이기 때문에( Node 클래스를 살펴보면 next, prev 를 저장하고 있다) Last, First 에 대한 추적을 하고 있음을 확인할 수도 있다. ( Deque )
하지만 Java 에서는 ArrayDeque 라는 자료구조 또한 제공해주고 있다. 이 자료구조는 ArrayList 와 유사한 특징을 가지고 있다
- Array 라는 이름에서 볼 수 있듯이, "배열을 기반" 으로 하고 있다
- 고정 길이 자료구조가 아니다 -> 필요시 (ex, add) 저장공간을 늘린다
- 64보다 작은 크기였던 경우에는 2를 증가시키고, 그 외의 상황에서는 2배 증가한다.
This class is likely to be faster than Stack when used as a stack, and faster than LinkedList when used as a queue.
- stack 으로 사용시 Stack 클래스보다 빠르다
- queue 로 사용시 LinkedList 보다 빠르다.
- 단, Stack 은 스레드 세이프한 반면, ArrayDeque 는 non-thread-safe 하다.
Stack 자료구조가 존재하는데... 문서에서도 Stack 을 사용하느니 Deque 를 사용하라고 한다. 왜 그럴까?
추측건데 "Stack 은 Vector 를 상속하는 구체 클래스" 라는 이유도 존재할 것 같다.
Stack 은 Vector 를 "상속" 하고 있다. Vector 에서는 index 를 사용한 랜덤 접근(상수시간 으로 특정 원소에 대한 탐색이 가능) 이 가능하다. Deque 에서는 단지 front, end 에 대한 insert, remove 만을 public 하게 제공하고 있다. 위치 기반 접근을 public 하게 제공하지는 않는다. 그리고 Stack 자료구조를 사용하려고 하는 상황이라면, 이런 위치 기반 접근 기능이 필요하지는 않을 것이다.
하지만 Stack 은 JDK1.0 부터 존재했기에 "자바의 하위 호환성" 때문에 이러한 상속관계를 계속 유지하고 있다. 사실상 Java에 새롭게 등장했던 Collection 프레임워크로 인해, 잔재로 남은 클래스라고 볼 수 있다.
또한 Stack 은 인터페이스가 아니다.
Stack 에 대한 연산이 필요할 경우, 구체클래스인 Stack 을 사용하게 되는 것이다. 반면 Deque 는 인터페이스이기 때문에 여러가지 구현체를 사용할 수 있다.
Deque<Integer> queue = new LinkedList<>();
Deque<Integer> queue = new ArrayDeque<>();
참고로, Deque 와 Stack 의 iteration 순서는 정 반대이다
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println(new ArrayList<>(stack)); // prints 1, 2, 3
Deque<Integer> deque = new ArrayDeque<>();
deque.push(1);
deque.push(2);
deque.push(3);
System.out.println(new ArrayList<>(deque)); // prints 3, 2, 1
그렇다면 어떤 구현체를 사용하는 것이 좋을까?
이 주제에 대한 시작은 사실 "코딩테스트"에서 어떤 자료구조를 사용할지에 대한 고민이었다.
Stack 연산이 필요한 상황이라면 어떤 것을 사용하는 것이 좋을까?
사실 코딩테스트만을 위해서라면, 동시성을 고려하지 않는 경우가 대부분이라고 생각한다. 이 경우에는 non-thread-safe 하지만,queue, stack 이 필요한 상황에서 더 빠른 속도를 제공하는 ArrayDeque 를 사용하는 것이 좋을 것 같다.
성능
아직 성능테스트를 직접 해보진 않았으나, 코딩테스트 문제를 풀 때 구현체를 변경하여 제출해보았다
ArrayDeque, LinkedList, Stack 을 사용해보았는데, LinkedList, Stack 은 비슷한 성능을 보여주었다. 아무리 다른 자료구조를 사용해도 변하지 않던 성능이 ArrayDeque 를 사용하니 확연히 빠른 속도를 보여주었다.
'Java' 카테고리의 다른 글
Aggregation 과 Composition 개념 (0) | 2023.03.24 |
---|---|
타입 및 클래스들 사이에 존재하는 각 종 관계들과 리스코프 치환 원칙 (0) | 2023.03.24 |
record 의 생성자를 private 으로 만드는 것이 불가능한 이슈 (0) | 2022.08.23 |
[Java] method final parameter ? (0) | 2022.07.27 |
[Java] ImmutableCollection 과 UnmodifiableCollection 과 Immutability (0) | 2022.05.31 |