Day 20: 파일 입출력
Java NIO(New I/O)의 Files와 Path 클래스는 파일 입출력을 간편하고 안전하게 처리합니다. 기존 java.io 패키지보다 현대적이며, 코드도 훨씬 간결합니다.
Path와 Files 기본
파일 경로를 다루고 기본적인 파일 작업을 수행합니다.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class PathBasic {
public static void main(String[] args) throws IOException {
// Path 생성
Path currentDir = Path.of(".");
Path filePath = Path.of("test.txt");
Path absolutePath = filePath.toAbsolutePath();
System.out.println("현재 디렉토리: " + currentDir.toAbsolutePath());
System.out.println("파일 경로: " + absolutePath);
System.out.println("파일 이름: " + filePath.getFileName());
System.out.println("부모 경로: " + absolutePath.getParent());
// 파일 존재 여부
System.out.println("존재? " + Files.exists(filePath));
// 파일 생성 및 쓰기
Path newFile = Path.of("hello.txt");
Files.writeString(newFile, "안녕하세요, Java 파일 입출력!\n오늘도 좋은 하루!");
System.out.println("파일 생성됨: " + Files.exists(newFile));
// 파일 정보
System.out.println("크기: " + Files.size(newFile) + " bytes");
System.out.println("읽기 가능? " + Files.isReadable(newFile));
System.out.println("쓰기 가능? " + Files.isWritable(newFile));
// 정리
Files.deleteIfExists(newFile);
}
}
파일 읽기
다양한 방식으로 파일 내용을 읽는 방법입니다.
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
public class FileReadExample {
public static void main(String[] args) throws IOException {
// 테스트 파일 생성
Path path = Path.of("sample.txt");
String content = """
첫 번째 줄입니다.
두 번째 줄입니다.
세 번째 줄입니다.
Java 파일 입출력을 배워봅시다.
마지막 줄입니다.
""";
Files.writeString(path, content, StandardCharsets.UTF_8);
// 방법 1: 전체 문자열로 읽기
String allContent = Files.readString(path);
System.out.println("=== 전체 읽기 ===");
System.out.println(allContent);
// 방법 2: 줄 단위 리스트로 읽기
List<String> lines = Files.readAllLines(path);
System.out.println("=== 줄 단위 ===");
for (int i = 0; i < lines.size(); i++) {
System.out.println((i + 1) + ": " + lines.get(i));
}
// 방법 3: Stream으로 읽기 (대용량 파일에 적합)
System.out.println("=== Stream 읽기 ===");
try (Stream<String> lineStream = Files.lines(path)) {
lineStream
.filter(line -> !line.isBlank())
.map(String::trim)
.forEach(System.out::println);
}
// 방법 4: 바이트 단위 읽기
byte[] bytes = Files.readAllBytes(path);
System.out.println("바이트 크기: " + bytes.length);
// 정리
Files.deleteIfExists(path);
}
}
파일 쓰기
파일에 데이터를 쓰는 다양한 방법입니다.
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
public class FileWriteExample {
public static void main(String[] args) throws IOException {
// 방법 1: 문자열 한 번에 쓰기
Path path1 = Path.of("output1.txt");
Files.writeString(path1, "한 줄로 쓰기\n두 번째 줄");
System.out.println("output1.txt 생성됨");
// 방법 2: 줄 단위 리스트 쓰기
Path path2 = Path.of("output2.txt");
List<String> lines = List.of(
"이름: 홍길동",
"나이: 25",
"직업: 개발자",
"언어: Java"
);
Files.write(path2, lines, StandardCharsets.UTF_8);
System.out.println("output2.txt 생성됨");
// 방법 3: 기존 파일에 추가 (APPEND)
Files.writeString(path1, "\n추가된 내용!",
StandardOpenOption.APPEND);
System.out.println("output1.txt에 내용 추가됨");
// 방법 4: BufferedWriter 사용 (대용량)
Path path3 = Path.of("output3.txt");
try (BufferedWriter writer = Files.newBufferedWriter(path3)) {
for (int i = 1; i <= 100; i++) {
writer.write("줄 번호 " + i);
writer.newLine();
}
}
System.out.println("output3.txt 생성됨 (" + Files.size(path3) + " bytes)");
// 출력 확인 후 정리
System.out.println("\n=== output1.txt 내용 ===");
System.out.println(Files.readString(path1));
Files.deleteIfExists(path1);
Files.deleteIfExists(path2);
Files.deleteIfExists(path3);
}
}
디렉토리 작업과 파일 탐색
디렉토리 생성, 파일 복사/이동, 디렉토리 내용 탐색을 다룹니다.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.stream.Stream;
public class DirectoryExample {
public static void main(String[] args) throws IOException {
// 디렉토리 생성
Path dir = Path.of("test-dir");
Path subDir = Path.of("test-dir", "sub", "deep");
Files.createDirectories(subDir); // 중간 경로도 자동 생성
System.out.println("디렉토리 생성: " + subDir);
// 파일 생성
Path file1 = dir.resolve("file1.txt");
Path file2 = dir.resolve("file2.java");
Path file3 = subDir.resolve("file3.txt");
Files.writeString(file1, "파일 1 내용");
Files.writeString(file2, "public class Test {}");
Files.writeString(file3, "중첩된 파일");
// 파일 복사
Path copied = dir.resolve("file1_copy.txt");
Files.copy(file1, copied, StandardCopyOption.REPLACE_EXISTING);
System.out.println("복사됨: " + copied);
// 디렉토리 내용 나열 (1단계만)
System.out.println("\n=== 디렉토리 내용 ===");
try (Stream<Path> entries = Files.list(dir)) {
entries.forEach(path -> {
String type = Files.isDirectory(path) ? "[DIR]" : "[FILE]";
System.out.println(type + " " + path.getFileName());
});
}
// 재귀적 파일 탐색 (모든 하위 포함)
System.out.println("\n=== 전체 트리 ===");
try (Stream<Path> walk = Files.walk(dir)) {
walk.forEach(path -> {
int depth = dir.relativize(path).getNameCount();
String indent = " ".repeat(depth);
System.out.println(indent + path.getFileName());
});
}
// 특정 확장자 파일 찾기
System.out.println("\n=== .txt 파일만 ===");
try (Stream<Path> found = Files.find(dir, 10,
(path, attrs) -> path.toString().endsWith(".txt") && attrs.isRegularFile())) {
found.forEach(System.out::println);
}
// 정리: 역순으로 삭제 (파일 먼저, 디렉토리 나중에)
try (Stream<Path> walk = Files.walk(dir)) {
walk.sorted((a, b) -> b.compareTo(a))
.forEach(path -> {
try { Files.deleteIfExists(path); }
catch (IOException ignored) {}
});
}
System.out.println("\n정리 완료");
}
}
오늘의 연습문제
-
CSV 파서: CSV 파일을 읽어 각 행을 파싱하고, 특정 열의 합계/평균을 계산하는 프로그램을 작성하세요. (CSV 파일을 먼저 프로그래밍으로 생성하세요)
-
로그 분석기: 로그 파일을 Stream으로 읽어서 ERROR 레벨의 로그만 필터링하고, 시간대별로 에러 수를 집계하는 프로그램을 작성하세요.
-
파일 백업 유틸리티: 지정한 디렉토리의 모든
.txt파일을backup/디렉토리에 날짜 접미사를 붙여 복사하는 프로그램을 작성하세요. (예:file.txt->backup/file_20260422.txt)