CodeGym /행동 /JAVA 25 SELF /파일 작업 모드: 읽기, 쓰기, 추가

파일 작업 모드: 읽기, 쓰기, 추가

JAVA 25 SELF
레벨 35 , 레슨 3
사용 가능

1. 덮어쓰기로 기록하기

파일을 다룰 때 “파일에 기록한다”는 말이 항상 같은 의미는 아닙니다. 어떤 때는 파일을 완전히 덮어써야 합니다(예를 들어 새 보고서를 저장할 때). 또 어떤 때는 이미 존재하는 파일의 끝에 새 정보를 추가해야 합니다(예: 애플리케이션 이벤트 로그를 남길 때). 어떤 경우에는 파일의 내용을 바꾸지 않고 그냥 읽기만 하면 됩니다.

Java(특히 최신 패키지 java.nio.file)에서는 파일 작업 모드가 기록 메서드(예: Files.write())에 전달하는 특별한 옵션 집합으로 결정됩니다. 이 옵션으로 파일을 덮어쓸지, 끝에 무언가를 추가할지를 명확히 지정할 수 있습니다.

작동 방식

Files.write(path, data)를 호출하면, Java는 기본적으로 새 파일을 만들거나 이미 존재하는 파일을 덮어씁니다. 파일의 예전 내용은 모두 사라지고, 그 자리에 새 데이터만 남게 됩니다.

이 기본 동작은 일종의 파일을 “강제로 초기화”하는 것입니다. 파일에 예전에 1000줄이 있었더라도 한 줄만 기록하면 이전 내용은 흔적 없이 사라집니다.

예: 문자열을 파일에 기록

import java.nio.file.*;
import java.io.IOException;
import java.util.List;

public class OverwriteFileExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");
        List<String> lines = List.of("안녕, 세상!", "파일에 새로 쓰는 내용입니다.");

        // 파일에 문자열을 기록합니다(이전 내용은 삭제됨)
        Files.write(path, lines);

        System.out.println("파일이 성공적으로 덮어써졌습니다!");
    }
}

이 코드를 실행하면 myfile.txt에는 lines 리스트의 두 줄만 남습니다. 이전에 있던 것은 모두 사라집니다(자바가 “미련” 같은 건 남기지 않습니다).

2. 추가(append): 파일 끝에 데이터 더하기

일부 작업(예: 이벤트 로그 관리, 로깅, 데이터 누적)에서는 기존 내용을 지우지 말고 끝에 새로운 줄을 추가해야 합니다. 예전 API에서는 FileWriterappend = true 플래그를 주는 경우가 많았습니다. 최신 API에서는 더 간단하고 명시적으로 처리합니다.

방법

해야 할 일은 StandardOpenOption.APPEND 옵션을 Files.write()에 전달하는 것입니다:

import java.nio.file.*;
import java.io.IOException;
import java.util.List;

public class AppendFileExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");
        List<String> moreLines = List.of("줄을 하나 더 추가했습니다.", "하나 더!");

        // 파일 끝에 줄을 추가합니다(이전 내용은 유지됨)
        Files.write(path, moreLines, StandardOpenOption.APPEND);

        System.out.println("파일 끝에 줄이 추가되었습니다!");
    }
}

중요 사항: 파일이 존재하지 않으면 추가 시도가 실패하여 오류가 발생합니다 — APPEND 모드에서는 Java가 파일을 자동으로 생성하지 않습니다. 파일이 없다면 다음 두 옵션을 함께 사용하세요:

Files.write(path, moreLines, StandardOpenOption.APPEND, StandardOpenOption.CREATE);

실전에서는 이렇게 동작합니다

  • 덮어쓰기로 프로그램을 실행 — 파일에는 두 줄만 남습니다.
  • 추가 모드로 다시 실행 — 기존 줄은 그대로 두고 그 뒤에 새로운 줄이 붙습니다.

3. 옵션 조합: CREATE, APPEND, TRUNCATE_EXISTING

Java에서는 여러 옵션을 조합해 파일 열기/기록 모드를 더 유연하게 제어할 수 있습니다:

  • StandardOpenOption.CREATE — 파일이 없으면 생성.
  • StandardOpenOption.APPEND — 끝에 데이터 추가.
  • StandardOpenOption.TRUNCATE_EXISTING — 파일이 있으면 0바이트로 비우기(초기화).
  • StandardOpenOption.CREATE_NEW — 새 파일을 생성; 이미 있으면 오류.

예: 파일이 없으면 만들고, 있으면 끝에 데이터 추가

Files.write(path, moreLines, StandardOpenOption.CREATE, StandardOpenOption.APPEND);

예: 파일을 만들거나, 완전히 비우고 새 데이터 기록

Files.write(path, lines, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

표: 주요 기록 모드

옵션 동작
(기본값) 파일을 새로 만들거나 기존 파일을 덮어씀
APPEND
끝에 추가, 파일이 없으면 오류
APPEND + CREATE
끝에 추가, 파일이 없으면 생성
TRUNCATE_EXISTING
파일을 0바이트로 비운 뒤 새 데이터 기록
CREATE_NEW
새 파일 생성, 파일이 이미 있으면 오류

4. 바이너리 파일 읽기와 쓰기

지금까지는 문자열로 작업했습니다. 하지만 때로는 “원시” 바이트를 읽거나 써야 합니다(예: 이미지, 아카이브, PDF 파일). 이럴 때는 다음을 사용하세요:

  • Files.readAllBytes(path) — 파일을 바이트 배열로 읽음.
  • Files.write(path, byteArray) — 바이트 배열을 파일에 기록.

예: 파일 복사하기

import java.nio.file.*;
import java.io.IOException;

public class CopyBinaryFileExample {
    public static void main(String[] args) throws IOException {
        Path source = Paths.get("logo.png");
        Path target = Paths.get("logo_copy.png");

        byte[] data = Files.readAllBytes(source);
        Files.write(target, data);

        System.out.println("파일이 복사되었습니다!");
    }
}

예: 바이너리 데이터 추가 기록

byte[] moreData = new byte[] {1, 2, 3, 4, 5};
Files.write(path, moreData, StandardOpenOption.APPEND, StandardOpenOption.CREATE);

주의: 구조화된 데이터(예: 이미지)가 이미 들어 있는 파일에 바이너리 데이터를 덧붙이면 보통 파일이 손상됩니다. 텍스트 또는 특별히 준비한 바이너리 파일(예: 로그)에만 추가 기능을 사용하세요.

5. 스트림이 필요한 때 (FileInputStream, BufferedReader 등)

Files.readAllBytes(), Files.write()는 작은/중간 크기 파일에서 내용을 한 번에 메모리로 읽어도 되는 경우에 편리합니다. 파일이 매우 크다(예: 기가바이트 단위)거나 로그 분석처럼 줄 단위로 읽고 싶다면 스트림을 사용하세요.

BufferedReader로 줄 단위로 읽기

import java.nio.file.*;
import java.io.*;

public class ReadLinesExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");

        try (BufferedReader reader = Files.newBufferedReader(path)) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("줄: " + line);
            }
        }
    }
}

BufferedWriter로 줄 단위 쓰기와 추가

import java.nio.file.*;
import java.io.*;
import java.nio.charset.StandardCharsets;

public class WriteAppendExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");

        try (BufferedWriter writer = Files.newBufferedWriter(
                path,
                StandardCharsets.UTF_8,
                StandardOpenOption.CREATE,
                StandardOpenOption.APPEND)) {
            writer.write("BufferedWriter로 또 한 줄!");
            writer.newLine();
        }
    }
}

6. 실습: 파일을 만들고 그 끝에 줄을 추가하기

지금까지를 하나의 애플리케이션으로 묶어 봅시다. 텍스트 파일에 할 일 목록(todo-list)을 저장하는 프로그램이 있다고 합시다. 사용자가 새 작업을 추가할 때마다 파일 끝에 덧붙입니다.

import java.nio.file.*;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;

public class TodoList {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("todo.txt");
        Scanner scanner = new Scanner(System.in);

        System.out.print("새 작업을 입력하세요: ");
        String task = scanner.nextLine();

        // 작업을 파일 끝에 추가합니다(파일이 없으면 생성)
        Files.write(path, 
            List.of(task), 
            StandardOpenOption.CREATE, 
            StandardOpenOption.APPEND);

        System.out.println("작업이 추가되었습니다!");
    }
}

프로그램을 여러 번 실행하면 각 작업이 파일의 새 줄에 추가됩니다. 이렇게 해서 우리의 첫 “영구” todo 리스트가 완성되었습니다!

7. 오류 처리: 어떤 문제가 발생할 수 있나?

파일 작업에는 항상 오류 위험이 있습니다. 다음과 같은 일이 발생할 수 있습니다:

  • IOException — 모든 입출력 오류의 기본 예외. 파일이 다른 프로그램에서 사용 중이거나, 읽기/쓰기 권한이 없거나, 디스크가 가득 찬 경우 등에 발생할 수 있습니다.
  • NoSuchFileException — 존재하지 않는 파일을 읽거나 추가하려고 하면 발생합니다(그리고 CREATE 옵션을 지정하지 않은 경우).
  • FileAlreadyExistsExceptionCREATE_NEW 옵션을 사용할 때 파일이 이미 있으면 발생합니다.

권장 사항: 항상 파일 작업을 try-catch로 감싸세요:

try {
    // 파일 작업을 수행
} catch (IOException e) {
    System.out.println("파일 작업 중 오류: " + e.getMessage());
}

8. 파일 열기 모드 사용 시 흔한 실수

오류 1: 추가 시 CREATE 옵션을 빼먹음. 존재하지 않는 파일에 추가(APPEND)하려고 하면 NoSuchFileException이 발생합니다. 파일을 자동으로 만들고 싶다면 항상 CREATE를 함께 지정하세요.

오류 2: 파일을 실수로 덮어씀. 추가 옵션 없이 Files.write(path, data)를 호출하면 기존 내용이 모두 사라집니다. 데이터를 추가하고 싶다면 APPEND를 사용하세요.

오류 3: 텍스트 파일에 바이너리 데이터를 추가하려 함(그 반대도 마찬가지). 텍스트와 바이너리 데이터를 한 파일에 섞으면 대개 읽을 수 없는 파일이 됩니다. 한 파일에는 한 가지 형식만 사용하세요.

오류 4: 스트림을 닫지 않음. 스트림(BufferedWriter, BufferedReader)을 사용할 때는 반드시 닫아야 합니다(가장 좋은 방법은 try-with-resources). 닫지 않으면 파일이 잠겨 있을 수 있습니다.

오류 5: 예외를 처리하지 않음. 파일 작업은 언제든지 IOException을 던질 수 있습니다. 예외를 처리하지 않으면 프로그램이 비정상 종료됩니다.

1
과제
JAVA 25 SELF, 레벨 35, 레슨 3
잠금
로그에 새 이벤트를 손실 없이 기록하기 📝
로그에 새 이벤트를 손실 없이 기록하기 📝
1
과제
JAVA 25 SELF, 레벨 35, 레슨 3
잠금
애플리케이션을 위한 스마트 구성기 ⚙️
애플리케이션을 위한 스마트 구성기 ⚙️
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION