공공부하자개발 · 영어 학습 노트
자바
실무 배치대용량 데이터 처리0/6 완료
  • 01배치 프로세스 개념과 아키텍처
  • 02대용량 파일 I/O (NIO, Buffered)
  • 03Chunk 단위 처리와 OOM 방지
  • 04트랜잭션: 커밋과 롤백 시뮬레이션
  • 05Skip과 Retry 로직 구현
  • 06메서드 활용 패턴 (배치 유틸)
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 배치 › 02 / 6

대용량 파일 I/O (NIO, Buffered)

섹션 7진행 0 / 6
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

5. 자주 하는 실수 (Tip)

❌ 실수 1: FileReader / FileWriter 를 버퍼 없이 단독 사용

java
FileWriter w = new FileWriter("out.txt");
for (int i = 0; i < 1_000_000; i++) w.write("line " + i + "\n");   // 매번 커널 호출에 가까움
w.close();

100만 번의 write 가 각각 인코딩과 (거의) 시스템 콜을 유발합니다. 벤치마크에서 버퍼 유무는 5~10배 차이가 납니다.

✅ Files.newBufferedWriter 또는 new BufferedWriter(new FileWriter(...)).

java
try (BufferedWriter w = Files.newBufferedWriter(Path.of("out.txt"), StandardCharsets.UTF_8)) {
    for (int i = 0; i < 1_000_000; i++) { w.write("line " + i); w.newLine(); }
}

❌ 실수 2: 인코딩을 지정하지 않는다

java
new FileReader("members.csv");             // OS 기본 인코딩
Files.readAllLines(path);                  // JDK 17 이하: OS 기본, 18+: UTF-8

개발 PC(Windows, MS949) 에서 잘 되던 코드가 Linux 서버(UTF-8) 에서 한글을 깨뜨립니다. 반대 경우도 마찬가지입니다. 배포 후 발견되면 이미 깨진 파일이 외부로 나간 뒤입니다.

✅ 항상 Charset 을 명시합니다. 파일 형식 문서에 인코딩이 없으면 만든 쪽에 확인합니다.

java
Files.newBufferedReader(path, StandardCharsets.UTF_8);
Files.newBufferedReader(path, Charset.forName("MS949"));   // Excel 저장 CSV

❌ 실수 3: Files.lines 를 닫지 않는다

java
long n = Files.lines(path).filter(l -> l.contains("ERROR")).count();   // 스트림이 닫히지 않음

Files.lines 는 파일을 열어 두고 스트림을 돌려줍니다. 닫지 않으면 파일 핸들이 GC 될 때까지 열려 있고, 배치가 수천 개 파일을 처리하면 "Too many open files" 가 납니다. Windows 에서는 열린 파일을 삭제·이동할 수 없어 다음 단계가 실패합니다.

✅ try-with-resources 로 감쌉니다.

java
long n;
try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
    n = lines.filter(l -> l.contains("ERROR")).count();
}

❌ 실수 4: split(",") 으로 CSV 를 파싱한다

java
String[] f = line.split(",");
long amount = Long.parseLong(f[4]);     // memo 에 쉼표가 있으면 f[4] 가 밀림 → NumberFormatException 또는 엉뚱한 값

더 나쁜 것은 예외 없이 엉뚱한 값으로 파싱되는 경우입니다. 금액 컬럼이 옆 컬럼 값으로 바뀌어도 숫자면 통과합니다.

✅ 따옴표를 이해하는 파서를 씁니다(예제 2), 또는 검증된 라이브러리. 그리고 필드 수를 검증합니다.

java
List<String> f = CsvParser.parseLine(line);
if (f.size() != 6) throw new IllegalArgumentException("필드 수 " + f.size() + ": " + line);

❌ 실수 5: 결과 파일을 정식 이름으로 바로 쓴다

java
try (BufferedWriter w = Files.newBufferedWriter(Path.of("settlement.csv"))) {
    ... // 10분 소요, 5분에 OOM
}
// 반쪽짜리 settlement.csv 가 남고 새벽 4시 전송 배치가 그것을 가져감

✅ .tmp 에 쓰고 완료 후 ATOMIC_MOVE. 소비자 쪽에서는 .tmp 를 무시하도록 약속합니다.

java
Path tmp = dest.resolveSibling(dest.getFileName() + ".tmp");
// ... tmp 에 쓰기 ...
Files.move(tmp, dest, StandardCopyOption.ATOMIC_MOVE);

❌ 실수 6: 한글 고정길이 전문을 substring 으로 자른다

java
String name = line.substring(20, 30);   // 레이아웃: 이름 10바이트

MS949 에서 한글은 2바이트입니다. "홍길동" 은 6바이트인데 substring(20, 30) 은 10문자를 자르므로 뒤 필드가 섞여 들어옵니다.

✅ 바이트 배열로 읽어 바이트 오프셋으로 자릅니다.

java
byte[] rec = line.getBytes(Charset.forName("MS949"));
String name = new String(Arrays.copyOfRange(rec, 20, 30), Charset.forName("MS949")).trim();
자주 하는 실수 (Tip)
  • ❌ 실수 1: FileReader / FileWriter 를 버퍼 없이 단독 사용
  • ❌ 실수 2: 인코딩을 지정하지 않는다
  • ❌ 실수 3: Files.lines 를 닫지 않는다
  • ❌ 실수 4: split(",") 으로 CSV 를 파싱한다
  • ❌ 실수 5: 결과 파일을 정식 이름으로 바로 쓴다
  • ❌ 실수 6: 한글 고정길이 전문을 substring 으로 자른다
이전 섹션4 응용 변형 예제5 / 7다음 섹션6 연습 문제