Java 입출력 | File, BufferedReader, NIO
이 글의 핵심
BufferedReader·Files, NIO Channel·Buffer·Selector, 직렬화, 성능 팁, CSV·JSON·로그 실전까지 한 번에 정리합니다.
들어가며
java.io·java.nio 계열은 바이트·문자 스트림으로 파일과 네트워크를 다루는 출발점입니다. 읽기/쓰기 경로를 열고 닫는 책임을 try-with-resources로 묶는 패턴이 흔합니다.
1. 파일 읽기 (IO)
BufferedReader
텍스트 파일을 효율적으로 읽는 가장 일반적인 방법입니다:
import java.io.*;
public class FileReadExample {
public static void main(String[] args) {
// try-with-resources 구문: 자동으로 리소스를 닫아줌
// () 안에 선언된 리소스는 try 블록이 끝나면 자동으로 close() 호출
try (BufferedReader br = new BufferedReader(
new FileReader("file.txt"))) {
// BufferedReader: 내부 버퍼(보통 8KB)를 사용해 I/O 횟수 감소
// FileReader를 감싸서 성능 향상
String line;
// readLine(): 한 줄씩 읽어옴 (줄바꿈 문자 제외)
// 파일 끝에 도달하면 null 반환
// (line = br.readLine()): 읽으면서 동시에 line 변수에 할당
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (FileNotFoundException e) {
// 파일이 존재하지 않을 때 발생
System.out.println("파일을 찾을 수 없습니다");
} catch (IOException e) {
// 파일 읽기 중 발생하는 기타 입출력 오류
System.out.println("파일 읽기 오류: " + e.getMessage());
}
// try 블록이 끝나면 br.close()가 자동 호출됨
}
}
성능 비교:
FileReader단독: 한 문자씩 읽음 → 느림BufferedReader+FileReader: 버퍼 단위로 읽음 → 빠름 (10~100배)
FileReader vs BufferedReader
두 방식의 성능 차이를 명확히 이해해봅시다:
// FileReader: 한 문자씩 읽기 (느림)
try (FileReader fr = new FileReader("file.txt")) {
int ch;
// read(): 한 문자를 읽어서 int로 반환 (0~65535)
// 파일 끝이면 -1 반환
// 문제점: 파일에서 한 문자 읽을 때마다 디스크 I/O 발생
while ((ch = fr.read()) != -1) {
// int를 char로 캐스팅해서 출력
System.out.print((char) ch);
}
}
// 1000자 파일 → 1000번의 디스크 I/O 발생
// BufferedReader: 버퍼링 사용 (빠름)
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
String line;
// readLine(): 한 줄 전체를 읽어서 String으로 반환
// 내부적으로 버퍼(보통 8KB)에 미리 읽어놓고 사용
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// 1000자 파일 → 약 1~2번의 디스크 I/O만 발생 (버퍼 크기에 따라)
성능 차이 예시:
- 1MB 텍스트 파일 읽기
FileReader: 약 1,000,000번의 I/O → 수 초 소요BufferedReader: 약 128번의 I/O (8KB 버퍼) → 0.1초 미만
결론: 파일 I/O는 항상 BufferedReader/BufferedWriter 사용 권장
2. 파일 쓰기 (IO)
BufferedWriter
import java.io.*;
public class FileWriteExample {
public static void main(String[] args) {
try (BufferedWriter bw = new BufferedWriter(
new FileWriter("output.txt"))) {
bw.write("첫 번째 줄");
bw.newLine();
bw.write("두 번째 줄");
bw.newLine();
} catch (IOException e) {
e.printStackTrace();
}
}
}
추가 모드
// 덮어쓰기 (기본)
try (BufferedWriter bw = new BufferedWriter(
new FileWriter("output.txt"))) {
bw.write("새 내용");
}
// 추가 모드
try (BufferedWriter bw = new BufferedWriter(
new FileWriter("output.txt", true))) {
bw.write("추가 내용");
}
3. NIO (Java 7+)
Files 클래스
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class NIOExample {
public static void main(String[] args) throws IOException {
Path path = Path.of("file.txt");
// 파일 읽기 (전체)
String content = Files.readString(path);
System.out.println(content);
// 파일 읽기 (줄 단위)
List<String> lines = Files.readAllLines(path);
for (String line : lines) {
System.out.println(line);
}
// 파일 쓰기
Files.writeString(Path.of("output.txt"), "Hello, NIO!");
// 여러 줄 쓰기
List<String> linesToWrite = List.of("라인 1", "라인 2", "라인 3");
Files.write(Path.of("output.txt"), linesToWrite);
}
}
파일 조작
import java.nio.file.*;
// 파일 존재 확인
boolean exists = Files.exists(Path.of("file.txt"));
// 파일 삭제
Files.deleteIfExists(Path.of("temp.txt"));
// 파일 복사
Files.copy(
Path.of("source.txt"),
Path.of("dest.txt"),
StandardCopyOption.REPLACE_EXISTING
);
// 파일 이동
Files.move(
Path.of("old.txt"),
Path.of("new.txt"),
StandardCopyOption.REPLACE_EXISTING
);
// 디렉토리 생성
Files.createDirectories(Path.of("dir/subdir"));
4. 실전 예제
예제: 로그 파일 처리
import java.io.*;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
public class LogProcessor {
public static void processLog(String inputPath, String outputPath) {
try {
List<String> lines = Files.readAllLines(Path.of(inputPath));
List<String> errors = lines.stream()
.filter(line -> line.contains("ERROR"))
.collect(Collectors.toList());
Files.write(Path.of(outputPath), errors);
System.out.println("에러 로그 " + errors.size() + "개 추출");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
processLog("app.log", "errors.log");
}
}
정리
핵심 요약
- BufferedReader/Writer: 버퍼링으로 성능 향상
- try-with-resources: 자동 리소스 닫기
- NIO Files: 간결한 현대적 API
- Path: 파일 경로 표현
- 파일 조작: 복사, 이동, 삭제
다음 단계
- Java 멀티스레딩
- Java Spring Boot
관련 글
- Java 시작하기 | JDK 설치부터 Hello World까지