본문으로 건너뛰기
김신건의 로그

[Java] Deque

· 수정 · 📖 약 2분 · 633자/단어 #java #collection #queue #deque #interface #arraydeque #stack
Deque, java.util.Deque, Java Deque, double-ended queue, 양방향 큐, 덱

정의

java.util.Deque<E> (Double-Ended Queue) 는 Queue 의 확장 인터페이스. 양쪽 끝 (head, tail) 모두에서 add/remove/examine 이 가능한 자료구조.

JDK 1.6 도입. Queue, Stack, 일반 양 끝 연산을 모두 한 인터페이스로 통합한다.

시각화: Deque 구조

flowchart LR
    direction LR
    PF["addFirst / pollFirst\n(head)"] <-->|"head"| A["A"] --- B["B"] --- C["C"] <-->|"tail"| PL["addLast / pollLast\n(tail)"]
  • head: Stack 의 top, Queue 의 front
  • tail: Queue 의 back
  • 양 방향 모두 O(1) 삽입/삭제 (구현체에 따라 다름)

메서드 매트릭스

위치insert (예외)insert (특수값)remove (예외)remove (특수값)examine
headaddFirst(e)offerFirst(e)removeFirst()pollFirst()peekFirst()
tailaddLast(e)offerLast(e)removeLast()pollLast()peekLast()
Queue aliasadd(e)offer(e)remove()poll()peek()
Stack aliaspush(e)(= addFirst)pop()(= removeFirst)peek()
  • push / pophead (앞) 에 동작 → Stack 으로 쓸 때 head 가 top
  • offer / polltail 추가, head 제거 → FIFO Queue 동작

구현체 선택

구현내부 구조특징
ArrayDeque원형 배열권장 기본, null 불가, GC 친화
LinkedList양방향 연결리스트null 허용, 메모리 많이 씀, 중간 삽입 O(1)
ConcurrentLinkedDeque이중 연결리스트 (lock-free)thread-safe, non-blocking
LinkedBlockingDeque이중 연결리스트thread-safe + blocking, bounded 지원

IMPORTANT

새 코드의 Stack 은 Deque<E> stack = new ArrayDeque<>() 가 권장. 레거시 java.util.StackVector 상속이라 모든 메서드가 동기화돼 비효율적. 새 코드에서 Stack 클래스는 쓰지 말 것.

시각화: ArrayDeque 내부 (원형 배열)

flowchart LR
    subgraph "원형 배열 (capacity=8)"
        direction LR
        I0["[0]"] --- I1["[1] head=1\nA"] --- I2["[2]\nB"] --- I3["[3]\nC"] --- I4["[4] tail=4"] --- I5["[5]"] --- I6["[6]"] --- I7["[7]"]
    end
    H["head 포인터\n(index 1)"] --> I1
    T["tail 포인터\n(index 4)"] --> I4
  • head: 다음에 꺼낼 인덱스
  • tail: 다음에 넣을 인덱스
  • addFirst 는 head 를 감소 (mod capacity), addLast 는 tail 를 증가
  • 꽉 차면 2배 배열로 복사 (grow)

Stack 패턴

import java.util.ArrayDeque;
import java.util.Deque;

Deque<Integer> stack = new ArrayDeque<>();

stack.push(1);     // addFirst(1)
stack.push(2);     // addFirst(2)
stack.push(3);     // addFirst(3)

stack.peek();      // 3 (top, 제거 안 함)
stack.pop();       // 3 (removeFirst)
stack.pop();       // 2
stack.pop();       // 1
stack.pop();       // EmptyStackException 대신 NoSuchElementException

// 비어 있음 확인
boolean empty = stack.isEmpty();

Queue 패턴 (FIFO)

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Queue;

// Queue 인터페이스로 잡으면 FIFO 만 사용 가능
Queue<Integer> queue = new ArrayDeque<>();
queue.offer(1);
queue.offer(2);
queue.offer(3);

queue.peek();     // 1 (head, 제거 안 함)
queue.poll();     // 1
queue.poll();     // 2

// 또는 Deque 그대로 사용
Deque<Integer> deque = new ArrayDeque<>();
deque.offerLast(1);    // tail 에 추가
deque.offerLast(2);
deque.pollFirst();     // 1 (head 에서 제거)

Palindrome 검사 (양쪽 끝 동시 비교)

import java.util.ArrayDeque;
import java.util.Deque;

boolean isPalindrome(String s) {
    Deque<Character> deque = new ArrayDeque<>();
    for (char c : s.toCharArray()) deque.addLast(c);

    while (deque.size() > 1) {
        if (!deque.pollFirst().equals(deque.pollLast())) return false;
    }
    return true;
}

// 테스트
isPalindrome("racecar");   // true
isPalindrome("hello");     // false

Java 17+ 실전: 슬라이딩 윈도우 최댓값

import java.util.ArrayDeque;
import java.util.Deque;

// 크기 k 의 슬라이딩 윈도우에서 각 위치의 최댓값 (Monotonic Deque)
int[] maxSlidingWindow(int[] nums, int k) {
    int n = nums.length;
    int[] result = new int[n - k + 1];
    // deque 는 인덱스를 저장, 항상 감소 순서 유지
    Deque<Integer> deque = new ArrayDeque<>();

    for (int i = 0; i < n; i++) {
        // 윈도우 범위 밖 인덱스 제거
        while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
            deque.pollFirst();
        }
        // 현재 원소보다 작은 후보 제거
        while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
            deque.pollLast();
        }
        deque.offerLast(i);

        if (i >= k - 1) {
            result[i - k + 1] = nums[deque.peekFirst()];
        }
    }
    return result;
}

// maxSlidingWindow([3,1,4,1,5,9,2,6], 3) = [4,4,5,9,9,9]

Java 17+ 실전: 수식 괄호 검사

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;

boolean isBalanced(String expr) {
    Deque<Character> stack = new ArrayDeque<>();
    Map<Character, Character> pairs = Map.of(')', '(', ']', '[', '}', '{');

    for (char c : expr.toCharArray()) {
        if ("([{".indexOf(c) >= 0) {
            stack.push(c);
        } else if (pairs.containsKey(c)) {
            if (stack.isEmpty() || stack.pop() != pairs.get(c)) return false;
        }
    }
    return stack.isEmpty();
}

ArrayDeque vs LinkedList 성능

// ArrayDeque: 원형 배열 → 캐시 라인 친화, 객체 생성 최소화
Deque<Integer> arrayDeque = new ArrayDeque<>();

// LinkedList: 노드마다 힙 할당 → GC 부담, 캐시 미스
Deque<Integer> linkedList = new java.util.LinkedList<>();
항목ArrayDequeLinkedList
addFirst / addLastamortized O(1)O(1)
pollFirst / pollLastO(1)O(1)
중간 삽입/삭제O(n)O(1) (노드 보유 시)
메모리 per 원소낮음 (배열 슬롯)높음 (Node 객체)
null 허용
캐시 효율높음낮음
권장 상황대부분중간 삽입 多, null 필요

LinkedBlockingDeque (blocking 패턴)

import java.util.concurrent.LinkedBlockingDeque;

LinkedBlockingDeque<String> bq = new LinkedBlockingDeque<>(100);

// Producer
new Thread(() -> {
    try {
        bq.putLast("work item");    // 꽉 차면 block
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}).start();

// Consumer
new Thread(() -> {
    try {
        String item = bq.takeFirst();   // 비어 있으면 block
        System.out.println("Got: " + item);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}).start();

함정

1. null 삽입 금지 (ArrayDeque)

Deque<String> deque = new ArrayDeque<>();
deque.push(null);   // NullPointerException

// null 이 필요하면 LinkedList (비권장) 또는 Optional 래핑

2. Stack 클래스 사용 금지

// 잘못: Stack 은 Vector 상속 (synchronized + 비효율)
java.util.Stack<Integer> stack = new java.util.Stack<>();

// 올바름: Deque 로 Stack 패턴
Deque<Integer> stack = new ArrayDeque<>();

3. poll vs pop 혼동

Deque<Integer> d = new ArrayDeque<>();
d.push(1);

d.pop();    // 비어 있으면 NoSuchElementException
d.poll();   // 비어 있으면 null (예외 없음)

pop() 은 Queue/Deque 에서 예외 던지는 버전, poll() 은 특수값 반환 버전.

4. 동시성 주의

ArrayDequeLinkedList 는 thread-safe 하지 않다. 동시성이 필요하면 ConcurrentLinkedDeque 또는 LinkedBlockingDeque.

5. Deque 를 Queue 로 쓸 때 메서드 혼용

Deque<Integer> deque = new ArrayDeque<>();
deque.offer(1);       // tail 에 추가 (Queue 관행)
deque.addFirst(0);    // head 에 추가 (Deque)
deque.poll();         // head 에서 제거 (Queue = pollFirst)

코드 의도를 명확히 하려면 Queue<E> 인터페이스 타입으로 선언하거나, Stack/Queue 전용 메서드만 사용.

관련 위키

이 글의 용어 (8개)
[Java] ArrayDequejava
정의 는 원형 배열 (circular buffer) 기반의 구현. JDK 1.6 도입. 으로 쓰면 보다 빠르고, 로 쓰면 보다 빠르다. Stack / Queue / Deque 가…
[Java] BlockingQueuejava
정의 는 요소를 가져올 때 비어 있으면 대기, 넣을 때 가득 차 있으면 대기 하는 thread-safe 큐 인터페이스. 생산자-소비자 (producer-consumer) 패턴의 …
[Java] Collectionjava
정의 는 그룹으로 묶인 객체들을 표현하는 최상위 인터페이스. JCF (Java Collections Framework) 의 입구이자, / / / 모두 이를 확장한다. 자체는 직접…
[Java] ConcurrentLinkedDequejava
정의 는 lock-free . 의 양방향 버전. JDK 1.7 추가. 양 끝 모두에서 add/remove 가 가능하며, 모든 연산이 CAS (Compare-And-Swap) 기반…
[Java] Iterablejava
정의 는 루프로 순회 가능한 모든 타입의 최상위 인터페이스. 단 하나의 추상 메서드, 를 정의한다. 인터페이스가 을 extends 하므로 , , , 등 모든 컬렉션이 자동으로 대…
[Java] LinkedListjava
정의 는 양방향 연결 리스트 (doubly linked list) 로 구현된 이자 . 각 원소는 와 포인터를 가진 노드로 보관된다. 와 같은 List 이지만, 메모리 레이아웃과 …
[Java] Objectjava
정의 는 Java 의 모든 클래스의 최상위 부모 (root) 클래스. 가 명시되지 않은 클래스는 컴파일러가 자동으로 를 붙인다. 인터페이스는 클래스가 아니라 를 직접 상속하지는 …
큐 (Queue)algorithm
정의 큐 (Queue) 는 FIFO (First In, First Out) 순서로 원소를 관리하는 추상 자료구조입니다. (또는 ), (또는 ), 세 연산만 제공하며, 가장 먼저 …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기