공부 기록/Java with ChatGPT

[Java/ChatGPT] Day 21: 파일 기반 데이터 저장 및 로딩 (실제 시스템처럼)

dev.jelee 2025. 7. 25. 16:01

[ 학습 목표 ]

  1. books.txt에 저장된 내용을 프로그램 시작 시 읽어와서 List<Book>으로 만들기
  2. 프로그램 종료 시 List<Book>의 모든 내용을 다시 파일에 저장하기
  3. 도서 등록, 검색, 삭제 등 모든 작업을 파일과 연동

 

[ 실습 ]

1. 프로잭트 개념 요약

  • 시작 시: books.txt 읽어서 List<Book>로 변환
  • 도서 등록/삭제: 리스트에 추가/삭제만 함 (List<Book>)
  • 종료 시: List<Book>의 내용을 books.txt에 덮어쓰기

2. 구조 미리 보기

📂 src/
├── domain/
│   └── Book.java
├── service/
│   └── LibraryService.java
├── io/
│   └── FileHandler.java     ✅ NEW
├── Main.java
└── books.txt

3. 코드

1) Book.java

package studyjavachatgpt.day21.domain;

import java.time.LocalDate;

public class Book {
  private String title;
  private String author;
  private LocalDate publishedDate;

  public Book (String title, String author, LocalDate publishedDate) {
    this.title = title;
    this.author = author;
    this.publishedDate = publishedDate;
  }

  public String getTitle() {
    return title;
  }
  public String getAuthor() {
    return author;
  }
  public LocalDate getPublishedDate() {
    return publishedDate;
  }

  @Override
  public String toString() {
    return "[" + title + " | " + author + " | " + publishedDate + "]";
  }
}

2) FileHandler.java

package studyjavachatgpt.day21.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

import studyjavachatgpt.day21.domain.Book;

public class FileHandler {
  // 파일 경로
  private static final String FILE_PATH = "./studyjavachatgpt/day21/books.txt";

  // 도서 불러오기
  public static List<Book> loadBooks() {
    // 책 리스트로 관리
    List<Book> books = new ArrayList<>();

    try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(FILE_PATH), "UTF-8"))) {
      String line;
      while ( (line = br.readLine()) != null) {
        String[] parts = line.split(", "); // , 기준으로 나누어 parts에 배열형태로 저장
        if (parts.length == 3) {
          books.add(new Book(parts[0], parts[1], LocalDate.parse(parts[2])));
        }
      }
    } catch (IOException e) {
      System.out.println("파일 로딩 실패: " + e.getMessage());
    }

    return books;
  }

  // 도서 저장
  public static void saveBooks(List<Book> books) {
    try (BufferedWriter bw = new BufferedWriter(new FileWriter(FILE_PATH))) {
      for (Book b : books) {
        bw.write(b.getTitle() + ", " + b.getAuthor() + ", " + b.getPublishedDate());
        bw.newLine(); // 줄바꿈
      }
    } catch (IOException e) {
      System.out.println("파일 저장 실패: " + e.getMessage());
    }
  }
}

3) LibraryService.java

package studyjavachatgpt.day21.service;

import java.time.LocalDate;
import java.util.List;

import studyjavachatgpt.day21.domain.Book;

public class LibraryService {
  private List<Book> books;

  // 생성자
  public LibraryService(List<Book> books) {
    this.books = books;
  }

  // 도서 등록
  public void register(String title, String author, LocalDate date) {
    books.add(new Book(title, author, date));
    System.out.println("도서 등록 완료!");
  }

  // 도서 목록
  public void list() {
    books.forEach(System.out::println);
  }

  // 도서 키워드 검색
  public void searchByKeyword(String keyword) {
    for (Book b : books) {
      if (b.getTitle().contains(keyword) || b.getAuthor().contains(keyword)) {
        System.out.println(b);
      }
    }
  }

  // 도서 제목 기준 삭제
  public void deleteByTitle(String title) {
    books.removeIf(b -> b.getTitle().equals(title));
    System.out.println("삭제 완료!");
  }

  // 도서 전체 목록
  public List<Book> getBooks() {
    return books;
  }
}

4. Main.java

package studyjavachatgpt.day21;

import java.time.LocalDate;
import java.util.List;
import java.util.Scanner;

import studyjavachatgpt.day21.domain.Book;
import studyjavachatgpt.day21.io.FileHandler;
import studyjavachatgpt.day21.service.LibraryService;

public class Main {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    List<Book> bookList = FileHandler.loadBooks(); // 파일에 있는 도서 목록 전체 가져와서 리스트로 저장
    LibraryService service = new LibraryService(bookList);

    while (true) {
      System.out.println("1. 도서 등록 | 2. 도서 목록 | 3. 검색 | 4. 삭제 | 5. 종료");
      String input = sc.nextLine();
      
      switch (input) {
        case "1":
          System.out.print("제목: ");
          String title = sc.nextLine();
          System.out.print("저자: ");
          String author = sc.nextLine();
          System.out.print("출판일(yyyy-MM-dd): ");
          LocalDate date = LocalDate.parse(sc.nextLine());
          service.register(title, author, date);
          break;

        case "2":
          service.list();
          break;
  
        case "3":
          System.out.print("검색 키워드: ");
          service.searchByKeyword(sc.nextLine());
          break;
  
        case "4":
          System.out.print("삭제할 제목 입력: ");
          service.deleteByTitle(sc.nextLine());
          break;
  
        case "5":
          FileHandler.saveBooks(service.getBooks());
          System.out.println("저장 후 종료합니다.");
          return;
      }
    }

  }
}

[ 메모 ]

  • books.txt 파일에 미리 데이터를 입력하여 저장하였는데, 터미널을 통해 도서 목록을 전체 불러오는데 글자가 깨져서 출력이 되었다. UTF-8로 저장했는데 왜 그런지 이해가 안 갔다. 찾아보니 new FileReader(...)는 플랫폼 기본 인코딩을 사용하는데 이게 Windows에선 CP949와 같은 형식을 사용하기 때문이라고 했다. 그래서 인코딩을 명시해줘야한다.
  • new FileReader(...) 를 new InputStreamReader(new FileInputStream(파일 경로 입력), "UTF-8") 로 작성했다.
    • new FileReader(...) : 시스템 기본 인코딩 사용 (권장 x)
    • new InputStreamReader(..., "UTF-8") : UTF-8로 강제 지정 (권장 O)