Queue를 사용할 때 자주 사용하는 구현체로 ArrayDeque와 LinkedList를 사용하는데 언제 어떻게 사용하면 좋을까?
우선 Queue의 대표적인 특징은 선입선출 방식이에요.
Queue 대기열에 데이터를 추가하는 것을 Enqueue, 데이터를 꺼내는 것을 Dequeue라고 하지만, java에서는 추가는 두 메서드 add()와 offer() 존재하며 , 꺼내는 메서드는 poll() 메서드가 존재해요.

출처: https://medium.com/depayse/kotlin-data-structure-collections-4-stack-queue-deque-4c383efebee9
ArrayDeque는 Queue의 서브인터페이스인 Deque의 구현체이며, 배열의 특성을 가지고 있어요.
그리고 데이터 삽입 및 삭제가 큐의 앞, 뒤 모두에서 가능해요.

출처: https://medium.com/depayse/kotlin-data-structure-collections-4-stack-queue-deque-4c383efebee9

우선 ArrayDeque를 기본생성자를 통해 생성하면 16개의 공간이 만들어져요.
public ArrayDeque() {
elements = new Object[16];
}
그리고 값을 추가 할 때, 기존의 큐의 크기를 초과하게 되면 resize가 다음과 같은 순서로 이루어져요.
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
final Object[] es = elements;
es[head = dec(head, es.length)] = e;
if (head == tail)
grow(1); // resize 관련된 부분
}
grow() 함수를 보면 다음과 같이 진행되고 있어요.
private void grow(int needed) {
// overflow-conscious code
final int oldCapacity = elements.length;
int newCapacity;
// Double capacity if small; else grow by 50%
int jump = (oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1);
// 여기서 newCapacity에 값 할당이 이루어집니다.
if (jump < needed
|| (newCapacity = (oldCapacity + jump)) - MAX_ARRAY_SIZE > 0)
newCapacity = newCapacity(needed, jump);
final Object[] es = elements = Arrays.copyOf(elements, newCapacity);
// Exceptionally, here tail == head needs to be disambiguated
if (tail < head || (tail == head && es[head] != null)) {
// wrap around; slide first leg forward to end of array
int newSpace = newCapacity - oldCapacity;
System.arraycopy(es, head,
es, head + newSpace,
oldCapacity - head);
for (int i = head, to = (head += newSpace); i < to; i++)
es[i] = null;
}
}
// newCapacity를 다시 계산하는 이유는 배열 크기가 MAX_ARRAY_SIZE를 초과할 수 없기 때문입니다.
private int newCapacity(int needed, int jump) {
final int oldCapacity = elements.length, minCapacity;
if ((minCapacity = oldCapacity + needed) - MAX_ARRAY_SIZE > 0) {
if (minCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
return Integer.MAX_VALUE;
}
if (needed > jump)
return minCapacity;
return (oldCapacity + jump - MAX_ARRAY_SIZE < 0)
? oldCapacity + jump
: MAX_ARRAY_SIZE;
}
LinkedList는 List와 Queue의 구현체에요. 따라서 LinkedList는 List의 특징을 가지고 있고, 다음 그림을 통해 자신의 노드와 다음 데이터를 참조하는 데이터를 갖고 있어요.


테스트 하고 있는 환경은 다음과 같아요.
public static void main(String[] args) {
ArrayDeque<Event> arrayDeque = new ArrayDeque<>();
int size = 변경되는 크기 부분
long dequeStart = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
arrayDeque.offer(new Event("test", i));
}
long dequeEnd = System.currentTimeMillis();
System.out.printf("Array Deque Times: %dms\n", dequeEnd - dequeStart);
Queue<Event> linkedListQue = new LinkedList<>();
long linkedListStart = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
linkedListQue.offer(new Event("test", i));
}
long linkedListEnd = System.currentTimeMillis();
System.out.printf("LinkedList Que Times: %dms\n", linkedListEnd - linkedListStart);
}
데이터를 추가하는 상황만 테스트 해보았어요. Deque resize를 자주 하기 때문에 성능이 더 안좋을 것이라고 생각했는데, 300,000 번 부터는 Deque가 성능이 더 좋았어요.
Deque에 값을 넣을 때는 단순히 값을 넣지만, LinkedList Que는 새로운 노드 추가 뿐만 아니라다음 노드에 대한 메모리도 참조해야 하기에 더 많은 연산이 필요해서 그런 것 같아요.
다음은 추가와 삭제가 빈번한 상황을 테스트하려고 해요. 이전 테스트와 큰 차이가 없네요.
자바 공식문서에 다음과 같이 설명이 되어 있어요.
This class is likely to be faster than
Stackwhen used as a stack, and faster thanLinkedListwhen used as a queue.
위의 공식문서를 토대로 다음과 같은 결론을 내렸어요.
cache locality-friendly 하다.정리하다보니 다음과 같은 생각도 하게 되었어요.
ArrayList vs ArrayDeque vs LinkedList: which one is best?