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에서는 FileWriter에 append = 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);
표: 주요 기록 모드
| 옵션 | 동작 |
|---|---|
| (기본값) | 파일을 새로 만들거나 기존 파일을 덮어씀 |
|
끝에 추가, 파일이 없으면 오류 |
|
끝에 추가, 파일이 없으면 생성 |
|
파일을 0바이트로 비운 뒤 새 데이터 기록 |
|
새 파일 생성, 파일이 이미 있으면 오류 |
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 옵션을 지정하지 않은 경우).
- FileAlreadyExistsException — CREATE_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을 던질 수 있습니다. 예외를 처리하지 않으면 프로그램이 비정상 종료됩니다.
GO TO FULL VERSION