데이터 엔지니어링

패스트캠퍼스 환급챌린지 32일차: 데이터엔지니어링 초격차 강의 후기

Big Byte 2025. 5. 2. 23:30

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.

 

자바 컬렉션 여정 2탄! 🥞 FIFO & LIFO 마스터하기 (Queue, Stack, ArrayDeque)

 

안녕하세요, 여러분! ✨

 

지난 시간에는 자바 컬렉션 프레임워크의 기본기를 다졌죠! 마치 마법 가방처럼 데이터를 유연하게 담을 수 있는 List, Set, Map 인터페이스와 그 친구들(ArrayList, HashSet, HashMap)을 만나보았습니다. 🎒 순서대로 목록을 만들고(List), 중복 없이 특별한 보석만 모으고(Set), 이름표를 붙여 빠르게 찾아내는(Map) 마법들을 익혔어요! 🧙‍♀️

 

이제 우리는 데이터들을 그냥 '담는 것'을 넘어, 데이터를 넣고 꺼내는 방식에 특별한 규칙을 부여하는 컬렉션들을 만나볼 시간입니다! 마치 마법 물약을 순서대로 제조하거나, 방금 사용한 마법 주문을 되돌리는 것처럼요! 🧪🔙

 

"가장 최근에 추가된 작업을 먼저 처리해야 해!", "처리 요청이 들어온 순서대로 공평하게 처리해야지!", "앞에서도 뒤에서도 데이터를 넣고 뺄 수 있으면 좋겠는데..." 🤔 이런 고민들이 있다면, 오늘 배울 내용이 딱 맞을 거예요!

 

그래서 오늘은! 데이터 처리의 흐름을 제어하는 특별한 컬렉션 구조, Stack, Queue, 그리고 만능 재주꾼 ArrayDeque에 대해 알아볼 거예요! 데이터 입출구에 질서를 부여하는 마법을 함께 배워봅시다!

 

오늘 우리가 탐험할 데이터 흐름 제어 세계는 다음과 같아요:

  1. 나중에 온 게 먼저! - 스택 (Stack) 🥞 (LIFO)
  2. 먼저 온 순서대로! - 큐 (Queue) 🚶‍♂️🚶‍♀️🚶 (FIFO)
  3. 양쪽 문이 활짝! - 덱 (Deque) & ArrayDeque ↔️

자, 데이터 처리 순서 마법을 배울 준비되셨나요? 출발~! 💨

 

1. 나중에 온 게 먼저! - 스택 (Stack) 🥞 (LIFO: Last-In, First-Out)

스택(Stack)은 이름 그대로, 마치 접시를 쌓아 올리는 것처럼 데이터를 관리하는 구조입니다. 🍽️ 가장 마지막에 들어온 데이터(Last-In)가 가장 먼저 나가는(First-Out) 특징을 가지고 있죠. '후입선출'이라고도 불러요.

컴퓨터 세상에서는 이런 LIFO 방식이 꽤 유용하게 쓰입니다:

 

 

  • 함수 호출 관리: 함수를 호출할 때마다 스택에 쌓고, 함수가 끝나면 스택에서 꺼내 원래 위치로 돌아가죠.
  • 웹 브라우저 뒤로 가기: 방문한 페이지를 스택에 쌓아두고, '뒤로' 버튼을 누르면 가장 최근 페이지(스택의 맨 위)를 꺼내 보여줍니다. 🔙
  • 실행 취소 (Undo): 작업 내용을 스택에 저장해두고, 'Undo'를 누르면 가장 마지막 작업을 꺼내 취소합니다.

자바에서는 Stack이라는 클래스를 직접 제공합니다.

import java.util.Stack;

public class StackExample {
    public static void main(String[] args) {
        Stack<String> browserHistory = new Stack<>();

        // 페이지 방문 (push - 데이터를 스택 맨 위에 쌓음)
        browserHistory.push("google.com");
        browserHistory.push("fastcampus.co.kr");
        browserHistory.push("github.com");

        System.out.println("현재 스택 상태: " + browserHistory); // [google.com, fastcampus.co.kr, github.com]

        // 가장 마지막 페이지 확인 (peek - 맨 위 데이터를 확인만 하고 제거하진 않음)
        System.out.println("현재 보고 있는 페이지: " + browserHistory.peek()); // github.com

        // 뒤로 가기 (pop - 맨 위 데이터를 꺼내고 제거)
        String previousPage = browserHistory.pop();
        System.out.println("뒤로 가기 -> " + previousPage); // github.com
        System.out.println("현재 스택 상태: " + browserHistory); // [google.com, fastcampus.co.kr]

        // 스택이 비었는지 확인 (empty)
        System.out.println("스택이 비었나요? " + browserHistory.empty()); // false

        browserHistory.pop();
        browserHistory.pop();
        System.out.println("모두 뒤로 간 후 스택 상태: " + browserHistory); // []
        System.out.println("스택이 비었나요? " + browserHistory.empty()); // true
    }
}

 

잠깐! ✋ Stack 클래스는 사실 조금 오래된 방식(Vector를 상속받아 약간 무거움)이에요. 요즘은 뒤에서 배울 Deque 인터페이스와 그 구현체(ArrayDeque)를 스택처럼 사용하는 것을 더 권장합니다! 하지만 기본적인 LIFO 개념 이해를 위해 알아두면 좋아요!

2. 먼저 온 순서대로! - 큐 (Queue) 🚶‍♂️🚶‍♀️🚶 (FIFO: First-In, First-Out)

 

큐(Queue)는 우리가 놀이공원에서 줄을 서는 것처럼, 가장 먼저 들어온 데이터(First-In)가 가장 먼저 나가는(First-Out) 구조입니다. '선입선출'이라고 부르죠. 공평함이 생명! 😉

큐는 이런 상황에 딱입니다:

  • 작업 대기열: 프린터 인쇄 요청, 메시지 처리 등 요청이 들어온 순서대로 처리해야 할 때. 📨
  • 너비 우선 탐색 (BFS): 그래프나 트리 구조를 탐색할 때 방문할 노드를 순서대로 저장하는 용도로 사용됩니다. 🌳

자바에서는 Queue 인터페이스를 제공하고, 주로 LinkedList 클래스가 이 인터페이스를 구현하여 큐로 사용됩니다. (LinkedList는 List 인터페이스도 구현하지만, Queue 인터페이스의 메소드를 사용하면 큐처럼 동작해요!)

Queue 인터페이스의 핵심 메소드:

  • offer(E e): 큐의 맨 뒤에 데이터를 추가합니다. (큐가 꽉 찼을 때 false 반환)
  • poll(): 큐의 맨 앞에서 데이터를 꺼내고 제거합니다. (큐가 비었을 때 null 반환)
  • peek(): 큐의 맨 앞에서 데이터를 확인만 하고 제거하지 않습니다. (큐가 비었을 때 null 반환)

(💡 팁: add(), remove(), element() 메소드도 있지만, 이들은 큐가 꽉 찼거나 비었을 때 예외(Exception)를 발생시켜요. 좀 더 안전한 프로그램을 위해 offer(), poll(), peek() 사용을 권장합니다!)

import java.util.LinkedList;
import java.util.Queue;

public class QueueExample {
    public static void main(String[] args) {
        // LinkedList를 Queue 인터페이스 타입으로 사용
        Queue<String> printerQueue = new LinkedList<>();

        // 인쇄 요청 추가 (offer)
        printerQueue.offer("문서1.hwp");
        printerQueue.offer("사진2.jpg");
        printerQueue.offer("보고서3.docx");

        System.out.println("현재 인쇄 대기열: " + printerQueue); // [문서1.hwp, 사진2.jpg, 보고서3.docx]

        // 다음에 인쇄될 문서 확인 (peek)
        System.out.println("다음 인쇄될 문서: " + printerQueue.peek()); // 문서1.hwp

        // 인쇄 시작! (poll)
        while (!printerQueue.isEmpty()) { // 큐가 비어있지 않다면 계속 반복
            String printingDoc = printerQueue.poll();
            System.out.println("'" + printingDoc + "' 인쇄 중...");
            System.out.println(" 남은 대기열: " + printerQueue);
        }

        System.out.println("모든 인쇄 완료!");
        System.out.println("큐가 비었나요? " + printerQueue.isEmpty()); // true
        System.out.println("비어있을 때 peek: " + printerQueue.peek()); // null
        System.out.println("비어있을 때 poll: " + printerQueue.poll()); // null
    }
}

3. 양쪽 문이 활짝! - 덱 (Deque) & ArrayDeque ↔️

 

덱(Deque, "덱" 또는 "디큐"라고 읽어요)은 'Double-Ended Queue'의 줄임말로, 이름처럼 양쪽 끝에서 데이터를 추가하고 제거할 수 있는 아주 유연한 구조입니다. 🔄

이게 왜 좋냐구요?

  • 스택(LIFO)으로 사용 가능: 한쪽 끝(예: 뒤)으로만 데이터를 넣고(push) 빼면(pop) 스택처럼 동작해요!
  • 큐(FIFO)로 사용 가능: 한쪽 끝(예: 뒤)으로 데이터를 넣고(offer), 반대쪽 끝(예: 앞)에서 데이터를 빼면(poll) 큐처럼 동작해요!
  • 덱 고유 기능: 양쪽 끝에서 자유롭게 데이터를 추가/삭제할 수 있어 더 복잡한 로직 구현에 유리해요.

 

자바에서는 Deque 인터페이스를 제공하고, 가장 대표적인 구현 클래스가 바로 ArrayDeque 입니다! ArrayDeque는 내부적으로 배열을 사용하여 데이터를 관리하기 때문에, 스택이나 큐의 동작을 Stack 클래스나 LinkedList보다 더 효율적으로 처리하는 경우가 많습니다. 👍 (단, Stack 클래스와 달리 동기화(thread-safe) 기능은 없으니 멀티스레딩 환경에서는 주의가 필요해요!)

ArrayDeque를 스택처럼 사용하기 (주요 메소드):

  • push(E e): 덱의 맨 앞에 요소 추가 (스택의 push와 동일)
  • pop(): 덱의 맨 앞에서 요소 제거 및 반환 (스택의 pop과 동일)
  • peek(): 덱의 맨 앞에서 요소 확인 (스택의 peek과 동일)

ArrayDeque를 큐처럼 사용하기 (주요 메소드):

  • offer(E e) 또는 offerLast(E e): 덱의 맨 뒤에 요소 추가
  • poll() 또는 pollFirst(): 덱의 맨 앞에서 요소 제거 및 반환
  • peek() 또는 peekFirst(): 덱의 맨 앞에서 요소 확인
import java.util.ArrayDeque;
import java.util.Deque;

public class ArrayDequeExample {
    public static void main(String[] args) {
        // ArrayDeque 생성
        Deque<Integer> numbers = new ArrayDeque<>();

        // --- 스택(LIFO)처럼 사용 ---
        System.out.println("--- 스택 테스트 ---");
        numbers.push(1); // 앞에 추가 [1]
        numbers.push(2); // 앞에 추가 [2, 1]
        numbers.push(3); // 앞에 추가 [3, 2, 1]
        System.out.println("스택 상태: " + numbers);

        System.out.println("Peek: " + numbers.peek()); // 3 (앞에서 확인)
        System.out.println("Pop: " + numbers.pop()); // 3 (앞에서 제거)
        System.out.println("Pop 후 스택 상태: " + numbers); // [2, 1]

        numbers.clear(); // 내용 비우기

        // --- 큐(FIFO)처럼 사용 ---
        System.out.println("\n--- 큐 테스트 ---");
        numbers.offer(10); // 뒤에 추가 [10]
        numbers.offer(20); // 뒤에 추가 [10, 20]
        numbers.offer(30); // 뒤에 추가 [10, 20, 30]
        System.out.println("큐 상태: " + numbers);

        System.out.println("Peek: " + numbers.peek()); // 10 (앞에서 확인)
        System.out.println("Poll: " + numbers.poll()); // 10 (앞에서 제거)
        System.out.println("Poll 후 큐 상태: " + numbers); // [20, 30]

        // --- 덱(Deque) 고유 기능 맛보기 ---
        System.out.println("\n--- 덱 테스트 ---");
        numbers.offerFirst(5); // 앞에 추가 [5, 20, 30]
        numbers.offerLast(40); // 뒤에 추가 [5, 20, 30, 40]
        System.out.println("덱 상태: " + numbers);
        System.out.println("Poll Last: " + numbers.pollLast()); // 40 (뒤에서 제거)
        System.out.println("Poll Last 후 덱 상태: " + numbers); // [5, 20, 30]
    }
}

ArrayDeque 정말 만능 재주꾼 같지 않나요? 😄 특별한 이유가 없다면, 스택이나 큐가 필요할 때 ArrayDeque를 우선적으로 고려해보는 것이 좋습니다!

정리하며 📝

오늘은 데이터의 입출력 순서를 제어하는 특별한 컬렉션들을 배웠습니다! 🔄

  • 스택 (Stack - LIFO): 나중에 들어온 것이 먼저 나가는 🥞 구조! Stack 클래스(레거시) 또는 ArrayDeque로 구현 가능. (push, pop, peek)
  • 큐 (Queue - FIFO): 먼저 들어온 것이 먼저 나가는 🚶‍♂️🚶‍♀️🚶 구조! Queue 인터페이스, 주로 LinkedListArrayDeque로 구현. (offer, poll, peek)
  • 덱 (Deque) & ArrayDeque: 양쪽 끝에서 데이터 입출력이 가능한 ↔️ 만능 구조! 스택과 큐의 기능을 모두 효율적으로 수행 가능. 👍

이제 여러분은 데이터를 단순히 저장하는 것을 넘어, 특정 순서나 규칙에 따라 처리해야 할 때 어떤 자료구조를 사용해야 할지 판단할 수 있는 눈을 갖게 되셨습니다! 👀 웹 브라우저의 뒤로 가기 버튼이나 은행의 대기표 시스템이 어떻게 동작하는지 상상해보는 것도 재미있을 거예요! 😉

이야~ 오늘도 새로운 개념들을 배우느라 머리가 핑핑 도는 느낌이지만, 정말 뿌듯하지 않나요? 😊 이 지식들이 여러분의 코드를 더욱 논리적이고 효율적으로 만들어 줄 강력한 무기가 될 거예요! 

 

다음에 시간에 다시 봐요~

 

https://abit.ly/lisbva