3주차 Unit 8.5 — 한글 처리 (FileReader, InputStreamReader)

Psj·2026년 5월 20일

F-lab

목록 보기
110/230

Unit 8.5 — 한글 처리 (FileReader, InputStreamReader)

F-LAB JAVA · 3주차 · Phase 8 · Stream 실전


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • Reader 의 정의와 InputStream 과의 결정적 차이 는?
  • FileReader 의 정의와 한계는?
  • InputStreamReader 의 정의와 활용은?
  • FileReader vs InputStreamReader 의 선택 기준은?
  • 인코딩 명시의 중요성 과 명시 안 하면 어떻게 되나?
  • BufferedReader 와 결합의 효과는?
  • char 의 한계 (Supplementary 문자, 이모지) 는?
  • Java 11+ 의 FileReader(File, Charset) 의 의미는?
  • 실무 권장 패턴 은?

🎯 핵심 한 문장

Reader 는 문자 (char) 단위로 읽는 추상 클래스로, 인코딩을 인식 하여 바이트 시퀀스를 문자로 변환한다.
FileReader 는 파일 전용 Reader 지만 Java 10 까지 인코딩 명시 불가 (시스템 기본만) — Cross-platform 위험.
InputStreamReaderInputStreamReader 로 변환하며 인코딩 명시 가능 — 실무 권장.
BufferedReader 를 결합하면 readLine 으로 줄 단위 처리 + 버퍼링 효율.
Java 11+ 부터 FileReader(File, Charset) 생성자 추가로 FileReader 도 안전하게 사용 가능, 하지만 Files.newBufferedReader(path, UTF_8) 가 가장 권장.

비유 — 외국어 책 번역가

InputStream:
  외국어 책의 글자 사진을 한 장씩 보는 사람
  - 사진 1장 = 1바이트
  - 한글 "안" = 사진 3장 따로
  - 의미 모름

Reader:
  사전 (인코딩) 을 가진 번역가
  - 사진 1~4장을 보고 1 문자
  - 한글 "안" = "안" 으로 인식
  - 의미 있음

FileReader (옛):
  사전을 항상 한국에서 가져옴 (시스템 기본)
  - 외국 가면 사전이 달라짐
  - Cross-platform 위험

InputStreamReader (안전):
  사전을 명시 ("UTF-8" 사전 사용)
  - 어디서든 같은 사전
  - 안전

→ Reader = 인코딩 인식 + 문자 단위.


🧭 9개 섹션 로드맵

1. Reader 의 정의와 InputStream 과의 차이
2. FileReader 의 정의와 한계
3. InputStreamReader 의 정의와 활용
4. FileReader vs InputStreamReader 비교
5. 인코딩 명시의 중요성
6. BufferedReader 와의 결합
7. char 의 한계 (Supplementary 문자)
8. Java 11+ 의 개선과 실무 패턴
9. 면접 + 자기 점검

1️⃣ Reader 의 정의와 InputStream 과의 차이

1.1 Reader 의 정의

public abstract class Reader implements Readable, Closeable {
    
    // 핵심 메서드
    public int read() throws IOException;
    public int read(char[] cbuf) throws IOException;
    public abstract int read(char[] cbuf, int off, int len) throws IOException;
    
    // 추가
    public long skip(long n) throws IOException;
    public boolean ready() throws IOException;
    public boolean markSupported();
    public void mark(int readAheadLimit) throws IOException;
    public void reset() throws IOException;
    public abstract void close() throws IOException;
}

핵심:

  • 문자 (char) 단위 처리
  • 인코딩 인식
  • java.io 패키지
  • Java 1.1 부터

1.2 Reader 의 계층

Reader (추상)
  ├── InputStreamReader      ← InputStream 을 Reader 로
  │   └── FileReader          ← 파일 전용
  ├── BufferedReader          ← 버퍼링 + readLine
  ├── StringReader            ← String 을
  ├── CharArrayReader        ← char[] 를
  ├── PipedReader             ← 파이프
  └── FilterReader
      └── PushbackReader

핵심:
  - 모두 char 처리
  - InputStream 의 byte 와 대응

1.3 InputStream vs Reader 결정적 차이

항목InputStreamReader
단위byte (8비트)char (16비트)
인코딩X (raw)✓ (자동 변환)
반환 (read)0~255, -10~65535, -1
한글 처리불가 (1바이트)OK (인코딩 매핑)
패키지java.iojava.io
자식FileInputStream 등FileReader 등

1.4 read() 의 비교

// InputStream.read()
InputStream is = new FileInputStream("file.txt");
int b = is.read();
// 반환: 0~255 (byte 의 unsigned 값) 또는 -1
// 1바이트 (한글이면 분리됨)

// Reader.read()
Reader r = new InputStreamReader(new FileInputStream("file.txt"), "UTF-8");
int c = r.read();
// 반환: 0~65535 (char 의 값) 또는 -1
// 1문자 (한글 1글자 = 1 read)

1.5 한글 처리 비교

// 파일 내용: "안" (UTF-8: EC 95 88)

// InputStream — 3번 read
try (FileInputStream fis = new FileInputStream("an.txt")) {
    int b1 = fis.read();   // 236 (0xEC)
    int b2 = fis.read();   // 149 (0x95)
    int b3 = fis.read();   // 136 (0x88)
    // 각각 따로 → 의미 잃음
}

// Reader — 1번 read
try (Reader r = new InputStreamReader(
        new FileInputStream("an.txt"), "UTF-8")) {
    int c = r.read();   // 51008 (0xC548 = '안')
    char ch = (char) c;   // '안'
    // 한 글자 완성
}

1.6 시각적 비교

파일: "안녕" (UTF-8, 6바이트)

InputStream.read() 시퀀스:
  236, 149, 136,    ← "안" 의 3바이트
  235, 133, 149,    ← "녕" 의 3바이트
  -1                 ← EOF

Reader.read() 시퀀스:
  '안' (51008),       ← "안" 1문자
  '녕' (45397),       ← "녕" 1문자
  -1                  ← EOF

1.7 ILIC 의 활용 비교

// ❌ InputStream 으로 한글 (잘못)
public String readKoreanBad(Path path) throws IOException {
    StringBuilder sb = new StringBuilder();
    try (FileInputStream fis = new FileInputStream(path.toFile())) {
        int b;
        while ((b = fis.read()) != -1) {
            sb.append((char) b);
            // 한글 깨짐!
        }
    }
    return sb.toString();
}

// ✓ Reader 로 한글
public String readKoreanGood(Path path) throws IOException {
    StringBuilder sb = new StringBuilder();
    try (Reader r = new InputStreamReader(
            new FileInputStream(path.toFile()),
            StandardCharsets.UTF_8)) {
        int c;
        while ((c = r.read()) != -1) {
            sb.append((char) c);
        }
    }
    return sb.toString();
}

// ✓✓ BufferedReader (더 효율적)
public String readKoreanBest(Path path) throws IOException {
    try (BufferedReader r = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
        return r.lines().collect(Collectors.joining("\n"));
    }
}

1.8 자기 점검 답변

Reader 와 InputStream 의 결정적 차이는?

:
1. 단위:

  • InputStream: byte (1바이트)
  • Reader: char (1문자)
  1. 인코딩:

    • InputStream: raw, 인코딩 X
    • Reader: 자동 변환
  2. 한글 처리:

    • InputStream: 불가 (3바이트 분리)
    • Reader: 가능 (1 read = 1글자)
  3. 반환:

    • InputStream.read(): 0~255
    • Reader.read(): 0~65535 (char 범위)
  4. 권장:

    • 텍스트: Reader
    • 바이너리: InputStream

2️⃣ FileReader 의 정의와 한계

2.1 FileReader 의 정의

package java.io;

public class FileReader extends InputStreamReader {
    
    public FileReader(String fileName) throws FileNotFoundException;
    public FileReader(File file) throws FileNotFoundException;
    public FileReader(FileDescriptor fd);
    
    // Java 11+ 추가
    public FileReader(String fileName, Charset charset) throws IOException;
    public FileReader(File file, Charset charset) throws IOException;
}

핵심:

  • InputStreamReader 의 자식
  • 파일 전용
  • Java 10 까지는 인코딩 명시 불가
  • Java 11+ 부터 Charset 생성자 추가

2.2 기본 사용 (Java 10 이하)

// Java 10 이하 — 인코딩 명시 불가
try (FileReader reader = new FileReader("file.txt")) {
    int c;
    while ((c = reader.read()) != -1) {
        System.out.print((char) c);
    }
}

// 문제:
// - 시스템 기본 인코딩 사용
// - Windows: MS949
// - Linux/Mac: UTF-8
// - Cross-platform 위험!

2.3 시스템 기본 인코딩의 함정

// 시나리오: UTF-8 파일을 Windows 에서 읽기
// 파일: "안녕" (UTF-8 으로 저장됨)

// Windows 의 자바 (MS949)
try (FileReader reader = new FileReader("hello.txt")) {
    // 기본 인코딩 = MS949
    // UTF-8 파일을 MS949 로 해석
    // → 깨짐!
}

// Linux 의 자바 (UTF-8)
try (FileReader reader = new FileReader("hello.txt")) {
    // 기본 인코딩 = UTF-8
    // 정상 처리
}

// 같은 코드, 다른 결과
// → FileReader 의 큰 함정

2.4 Java 11+ 의 개선

// Java 11+ 부터 인코딩 명시 가능
try (FileReader reader = new FileReader("file.txt", StandardCharsets.UTF_8)) {
    int c;
    while ((c = reader.read()) != -1) {
        System.out.print((char) c);
    }
}

// 이제 안전:
// - 모든 OS 에서 UTF-8
// - Cross-platform 일관성

2.5 FileReader 의 한계 (여전히)

한계:

1. 인코딩 명시 (Java 10 이하) 불가
   - Java 11+ 에서 해결

2. NIO.2 API 활용 어려움
   - Path 대신 String/File 만

3. BufferedReader 와 결합해야 효율
   - 단독으로는 1문자씩

4. Files.newBufferedReader 가 더 권장
   - 더 간결, NIO.2 통합

2.6 FileReader 의 실제 활용

// 짧은 텍스트 파일 — 간단할 때
try (FileReader reader = new FileReader("config.txt", StandardCharsets.UTF_8)) {
    // 간단한 한 문자씩 처리
}

// 더 권장 — BufferedReader 결합
try (BufferedReader reader = new BufferedReader(
        new FileReader("file.txt", StandardCharsets.UTF_8))) {
    String line;
    while ((line = reader.readLine()) != null) {
        // 한 줄씩 처리
    }
}

// 가장 권장 — NIO.2
try (BufferedReader reader = Files.newBufferedReader(
        Path.of("file.txt"), StandardCharsets.UTF_8)) {
    String line;
    while ((line = reader.readLine()) != null) {
        // 처리
    }
}

2.7 ILIC 의 FileReader 활용

public class ShipmentTextReader {
    
    // Java 11+ 에서 안전
    public String readShipmentText(Path path) throws IOException {
        try (FileReader reader = new FileReader(path.toFile(), StandardCharsets.UTF_8);
             BufferedReader br = new BufferedReader(reader)) {
            return br.lines().collect(Collectors.joining("\n"));
        }
    }
    
    // 권장 (NIO.2)
    public String readShipmentTextModern(Path path) throws IOException {
        return Files.readString(path, StandardCharsets.UTF_8);
    }
    
    // Stream
    public List<Shipment> readShipmentsStream(Path path) throws IOException {
        try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
            return lines.skip(1).map(this::parse).toList();
        }
    }
}

2.8 자기 점검 답변

FileReader 의 정의와 한계는?

:
1. 정의:

  • InputStreamReader 의 자식
  • 파일 전용 Reader
  • java.io 패키지
  1. 한계 (Java 10 이하):

    • 인코딩 명시 불가
    • 시스템 기본 사용
    • Cross-platform 위험
  2. Java 11+ 개선:

    • FileReader(File, Charset) 추가
    • 안전한 사용 가능
  3. 실무 권장:

    • Files.newBufferedReader(path, UTF_8) 가 더 좋음
    • NIO.2 통합
  4. 활용:

    • 짧은 텍스트
    • BufferedReader 와 결합

3️⃣ InputStreamReader 의 정의와 활용

3.1 InputStreamReader 의 정의

package java.io;

public class InputStreamReader extends Reader {
    
    // 생성자
    public InputStreamReader(InputStream in);
    public InputStreamReader(InputStream in, String charsetName) 
        throws UnsupportedEncodingException;
    public InputStreamReader(InputStream in, Charset cs);
    public InputStreamReader(InputStream in, CharsetDecoder dec);
}

핵심:

  • Reader 의 직접 자식
  • InputStream 을 Reader 로 변환 (Bridge)
  • 인코딩 명시 가능
  • Java 1.1 부터

3.2 기본 사용

// InputStream → Reader 변환
try (FileInputStream fis = new FileInputStream("file.txt");
     InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
    
    int c;
    while ((c = isr.read()) != -1) {
        System.out.print((char) c);
    }
}

// 줄 단위 (BufferedReader 결합)
try (InputStreamReader isr = new InputStreamReader(
        new FileInputStream("file.txt"), StandardCharsets.UTF_8);
     BufferedReader br = new BufferedReader(isr)) {
    
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}

3.3 다양한 InputStream 활용

// 1. 파일
new InputStreamReader(new FileInputStream("file.txt"), UTF_8);

// 2. 표준 입력
new InputStreamReader(System.in, UTF_8);

// 3. URL (네트워크)
URL url = new URL("https://example.com");
new InputStreamReader(url.openStream(), UTF_8);

// 4. 메모리
byte[] data = "안녕".getBytes(UTF_8);
new InputStreamReader(new ByteArrayInputStream(data), UTF_8);

// 5. 소켓
new InputStreamReader(socket.getInputStream(), UTF_8);

// 모든 InputStream 을 Reader 로

3.4 인코딩 명시의 4가지 방법

// 1. 인코딩 안 함 (위험)
new InputStreamReader(in);
// 시스템 기본 인코딩

// 2. 문자열 인코딩 이름
new InputStreamReader(in, "UTF-8");
// UnsupportedEncodingException 가능

// 3. Charset 객체 (권장)
new InputStreamReader(in, StandardCharsets.UTF_8);
// 컴파일 타임 검증

// 4. CharsetDecoder (고급)
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
decoder.onMalformedInput(CodingErrorAction.REPLACE);
new InputStreamReader(in, decoder);
// 에러 처리 옵션

3.5 인코딩 변환 활용

// 같은 InputStream 을 다른 인코딩으로
public void readMultipleEncodings(byte[] data) throws IOException {
    // 데이터가 어떤 인코딩인지 모를 때
    
    Charset[] candidates = {
        StandardCharsets.UTF_8,
        Charset.forName("MS949"),
        Charset.forName("EUC-KR"),
        StandardCharsets.ISO_8859_1
    };
    
    for (Charset cs : candidates) {
        try (InputStreamReader isr = new InputStreamReader(
                new ByteArrayInputStream(data), cs)) {
            
            CharBuffer buffer = CharBuffer.allocate(1024);
            isr.read(buffer);
            
            System.out.println("As " + cs + ": " + buffer.flip());
        }
    }
}

3.6 InputStreamReader 의 내부 동작

사용자 호출:
  isr.read()

내부 동작:
  1. InputStream 에서 N 바이트 읽음 (필요한 만큼)
     - UTF-8 의 첫 바이트로 길이 판단
     - 1바이트 (ASCII), 2/3/4 바이트 (다국어)
  
  2. CharsetDecoder 가 디코딩
     - 바이트 시퀀스 → char
  
  3. char 반환

복잡한 처리를 추상화
사용자는 단순히 char 받음

3.7 ILIC 의 활용

public class ShipmentEncodingReader {
    
    // 1. 단일 인코딩
    public String readUtf8(Path path) throws IOException {
        try (InputStreamReader isr = new InputStreamReader(
                new FileInputStream(path.toFile()), StandardCharsets.UTF_8);
             BufferedReader br = new BufferedReader(isr)) {
            return br.lines().collect(Collectors.joining("\n"));
        }
    }
    
    // 2. 옛 데이터 (EUC-KR)
    public String readEucKr(Path path) throws IOException {
        try (InputStreamReader isr = new InputStreamReader(
                new FileInputStream(path.toFile()), Charset.forName("EUC-KR"));
             BufferedReader br = new BufferedReader(isr)) {
            return br.lines().collect(Collectors.joining("\n"));
        }
    }
    
    // 3. HTTP 응답 (Content-Type 기반)
    public String readHttpResponse(URL url) throws IOException {
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        String contentType = conn.getContentType();
        // "text/html; charset=UTF-8"
        
        Charset charset = extractCharset(contentType);
        
        try (InputStreamReader isr = new InputStreamReader(
                conn.getInputStream(), charset);
             BufferedReader br = new BufferedReader(isr)) {
            return br.lines().collect(Collectors.joining("\n"));
        }
    }
    
    private Charset extractCharset(String contentType) {
        // "text/html; charset=UTF-8" → UTF-8
        if (contentType != null && contentType.contains("charset=")) {
            String charsetName = contentType.split("charset=")[1].trim();
            return Charset.forName(charsetName);
        }
        return StandardCharsets.UTF_8;   // 기본
    }
}

3.8 자기 점검 답변

InputStreamReader 의 정의와 활용은?

:
1. 정의:

  • Reader 의 직접 자식
  • InputStream → Reader 변환
  • Bridge 패턴
  1. 본질:

    • 인코딩 명시 가능
    • 다양한 InputStream 활용
    • 파일, 네트워크, 메모리 등
  2. 인코딩 명시:

    • String (UnsupportedEncodingException)
    • Charset (권장)
    • CharsetDecoder (고급)
  3. 활용:

    • 다양한 InputStream
    • BufferedReader 결합 권장
    • HTTP 응답 (Content-Type)

4️⃣ FileReader vs InputStreamReader 비교

4.1 비교 표

항목FileReaderInputStreamReader
정의InputStreamReader 의 자식Reader 의 직접 자식
입력파일만모든 InputStream
인코딩 (Java 10-)X
인코딩 (Java 11+)
유연성↑↑
코드 길이짧음약간 길음
활용단순 파일다양

4.2 같은 작업의 두 방식

// FileReader (Java 11+)
try (FileReader fr = new FileReader("file.txt", StandardCharsets.UTF_8)) {
    int c;
    while ((c = fr.read()) != -1) {
        System.out.print((char) c);
    }
}

// InputStreamReader (Java 1.1+)
try (FileInputStream fis = new FileInputStream("file.txt");
     InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
    int c;
    while ((c = isr.read()) != -1) {
        System.out.print((char) c);
    }
}

// 결과 동일
// 코드는 FileReader 가 조금 짧음
// InputStreamReader 가 더 유연 (네트워크 등도 OK)

4.3 FileReader 의 내부

// FileReader 는 사실상 InputStreamReader + FileInputStream

// Java 11+ FileReader 구현 (대략)
public class FileReader extends InputStreamReader {
    
    public FileReader(File file, Charset charset) throws IOException {
        super(new FileInputStream(file), charset);
    }
    
    public FileReader(String fileName, Charset charset) throws IOException {
        super(new FileInputStream(fileName), charset);
    }
}

// 즉:
// FileReader = InputStreamReader + FileInputStream 의 편의 래퍼

4.4 언제 어느 것을?

FileReader 권장:
  ✓ 파일만 처리
  ✓ Java 11+ 환경
  ✓ 짧은 코드 우선
  ✓ 인코딩 명시

InputStreamReader 권장:
  ✓ 파일 외에도 처리 (네트워크, 메모리 등)
  ✓ Java 10 이하 호환
  ✓ 다양한 InputStream
  ✓ 학습/이해 (Bridge 패턴)

가장 권장 (NIO.2):
  ✓ Files.newBufferedReader(path, UTF_8)
  ✓ 가장 간결
  ✓ Path 활용

4.5 가장 권장 패턴

// 가장 권장 — Files.newBufferedReader
try (BufferedReader reader = Files.newBufferedReader(
        Path.of("file.txt"), StandardCharsets.UTF_8)) {
    String line;
    while ((line = reader.readLine()) != null) {
        process(line);
    }
}

// 또는 Stream
try (Stream<String> lines = Files.lines(
        Path.of("file.txt"), StandardCharsets.UTF_8)) {
    lines.forEach(this::process);
}

// 또는 한 번에 (작은 파일)
String content = Files.readString(Path.of("file.txt"), StandardCharsets.UTF_8);

4.6 변환 표

같은 효과의 표현:

Files.newBufferedReader(path, UTF_8)
  ≡ new BufferedReader(new InputStreamReader(new FileInputStream(path.toFile()), UTF_8))
  ≡ new BufferedReader(new FileReader(path.toFile(), UTF_8))   // Java 11+

내부 동작:
  1. FileInputStream — 바이트 스트림
  2. InputStreamReader — 인코딩 적용
  3. BufferedReader — 버퍼링 + readLine

4.7 ILIC 의 패턴 선택

public class ShipmentReader {
    
    private final Charset charset = StandardCharsets.UTF_8;
    
    // 1. 가장 권장 — NIO.2
    public List<Shipment> readAllModern(Path path) throws IOException {
        try (Stream<String> lines = Files.lines(path, charset)) {
            return lines.skip(1).map(this::parse).toList();
        }
    }
    
    // 2. Java 11+ FileReader (간결)
    public List<Shipment> readAllFileReader(Path path) throws IOException {
        try (BufferedReader br = new BufferedReader(
                new FileReader(path.toFile(), charset))) {
            return br.lines().skip(1).map(this::parse).toList();
        }
    }
    
    // 3. InputStreamReader (전통)
    public List<Shipment> readAllInputStreamReader(Path path) throws IOException {
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(
                    new FileInputStream(path.toFile()),
                    charset))) {
            return br.lines().skip(1).map(this::parse).toList();
        }
    }
    
    // 4. 네트워크 (InputStreamReader 만 가능)
    public List<Shipment> readFromUrl(URL url) throws IOException {
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(url.openStream(), charset))) {
            return br.lines().skip(1).map(this::parse).toList();
        }
    }
}

4.8 자기 점검 답변

FileReader 와 InputStreamReader 의 선택?

:
1. FileReader:

  • 파일 전용
  • 짧은 코드
  • Java 11+ 부터 안전
  1. InputStreamReader:

    • 모든 InputStream
    • 유연
    • Bridge 패턴
  2. 차이:

    • FileReader = InputStreamReader + FileInputStream 래퍼
  3. 선택:

    • 파일만: FileReader (Java 11+)
    • 다양: InputStreamReader
    • 가장 권장: Files.newBufferedReader
  4. 실무:

    • NIO.2 활용
    • 인코딩 명시 (UTF-8)
    • BufferedReader 결합

5️⃣ 인코딩 명시의 중요성

5.1 명시 안 하면

// ❌ 인코딩 없음
new InputStreamReader(is);
new FileReader("file.txt");   // Java 10 이하

// 시스템 기본 인코딩 사용
// - Windows 한국어: MS949
// - Linux: UTF-8
// - Mac: UTF-8

// 문제:
// - 환경 따라 다름
// - Cross-platform 위험
// - 디버깅 어려움

5.2 시나리오 — 서버와 로컬

시나리오:
  - 개발자 로컬: Mac (UTF-8)
  - 운영 서버: Linux (UTF-8)
  - 옛 윈도우 서버: Windows (MS949)

UTF-8 로 저장된 파일을 읽기:

new FileReader("data.txt"):
  - Mac: UTF-8 → OK
  - Linux: UTF-8 → OK
  - Windows: MS949 로 해석 → 깨짐!

같은 코드, 다른 결과

5.3 Java 18+ 의 변화

JEP 400 (Java 18+):
  - 기본 인코딩 UTF-8 통일
  - 모든 OS

영향:
  - Java 18+ 에선 FileReader 가 UTF-8 기본
  - Cross-platform 일관성

하지만:
  - 명시적 인코딩이 여전히 권장
  - 의도 명확
  - 다른 JVM 버전 호환
  - 다른 인코딩 필요 시 대비

5.4 인코딩 명시의 효과

// ✓ 명시적
try (Reader r = new InputStreamReader(is, StandardCharsets.UTF_8)) {
    // 안전:
    // - 모든 OS
    // - 모든 Java 버전
    // - 의도 명확
}

5.5 잘못된 인코딩의 결과

// UTF-8 파일을 EUC-KR 로 읽기
byte[] utf8 = "안녕".getBytes(StandardCharsets.UTF_8);
// [0xEC, 0x95, 0x88, 0xEB, 0x85, 0x95]

try (Reader r = new InputStreamReader(
        new ByteArrayInputStream(utf8), Charset.forName("EUC-KR"))) {
    int c;
    while ((c = r.read()) != -1) {
        System.out.print((char) c);
    }
}

// 출력: 깨진 한자 또는 ?
// EUC-KR 의 0xEC95 가 어떤 한자에 매핑되든
// 정상 한글 아님

5.6 인코딩 자동 감지 (한계)

// 인코딩 추정 라이브러리 활용
// 예: ICU4J, juniversalchardet

import org.mozilla.universalchardet.UniversalDetector;

public Charset detectCharset(byte[] data) throws IOException {
    UniversalDetector detector = new UniversalDetector(null);
    detector.handleData(data, 0, data.length);
    detector.dataEnd();
    
    String charsetName = detector.getDetectedCharset();
    return charsetName != null 
        ? Charset.forName(charsetName) 
        : StandardCharsets.UTF_8;
}

// 한계:
// - 100% 정확 X
// - 통계적 추정
// - 짧은 데이터 부정확
// - 명시적 인코딩이 더 안전

5.7 BOM (Byte Order Mark)

// UTF-8 BOM: 0xEF 0xBB 0xBF
// UTF-16 BE BOM: 0xFE 0xFF
// UTF-16 LE BOM: 0xFF 0xFE

// 파일 첫 부분에 BOM 이 있으면 인코딩 추정 가능
// 자바 표준은 BOM 자동 처리 X
// BOMInputStream (Apache Commons IO) 활용

import org.apache.commons.io.input.BOMInputStream;

try (InputStream is = new FileInputStream("file.txt");
     BOMInputStream bis = new BOMInputStream(is, false);   // BOM 제거
     InputStreamReader isr = new InputStreamReader(bis, StandardCharsets.UTF_8);
     BufferedReader br = new BufferedReader(isr)) {
    
    // BOM 자동 제거
    String line;
    while ((line = br.readLine()) != null) {
        // BOM 없는 깨끗한 데이터
    }
}

5.8 ILIC 의 인코딩 관리

public class EncodingConstants {
    public static final Charset DEFAULT = StandardCharsets.UTF_8;
    public static final Charset LEGACY_KOREAN = Charset.forName("MS949");
    public static final Charset LATIN = StandardCharsets.ISO_8859_1;
}

@Service
public class ShipmentEncodingService {
    
    // 항상 명시적 인코딩
    public String readFile(Path path) throws IOException {
        return Files.readString(path, EncodingConstants.DEFAULT);
    }
    
    // 옛 시스템 데이터
    public String readLegacy(Path path) throws IOException {
        return Files.readString(path, EncodingConstants.LEGACY_KOREAN);
    }
    
    // 변환
    public void convertEncoding(Path src, Path dest, 
                               Charset srcCharset, Charset destCharset) throws IOException {
        String content = Files.readString(src, srcCharset);
        Files.writeString(dest, content, destCharset);
    }
    
    // HTTP 응답
    public String fetchUrl(String url) throws IOException {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build();
        
        try {
            HttpResponse<String> response = client.send(request, 
                BodyHandlers.ofString(EncodingConstants.DEFAULT));
            return response.body();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IOException(e);
        }
    }
}

5.9 자기 점검 답변

인코딩 명시의 중요성은?

:
1. 명시 안 하면:

  • 시스템 기본 사용
  • Windows MS949 vs Linux UTF-8
  • Cross-platform 위험
  1. Java 18+:

    • UTF-8 기본화 (JEP 400)
    • 하지만 여전히 명시 권장
  2. 잘못된 인코딩 결과:

    • 한글 깨짐
    • 데이터 손실
    • 디버깅 어려움
  3. 권장:

    • StandardCharsets.UTF_8 명시
    • 일관된 인코딩
    • 의도 명확
  4. BOM:

    • 파일 첫 부분 인코딩 표시
    • Apache Commons IO 활용

6️⃣ BufferedReader 와의 결합

6.1 BufferedReader 의 정의

public class BufferedReader extends Reader {
    
    public BufferedReader(Reader in);
    public BufferedReader(Reader in, int sz);
    
    // 핵심 메서드
    public String readLine() throws IOException;
    
    // 상속
    public int read();
    public int read(char[] cbuf, int off, int len);
    
    // Java 8+
    public Stream<String> lines();
}

특징:

  • Reader 의 자식 + Reader 받음 (Decorator)
  • 내부 버퍼 (기본 8KB)
  • readLine (한 줄씩)
  • Stream API (lines)

6.2 결합 패턴

// 기본 결합
try (BufferedReader br = new BufferedReader(
        new InputStreamReader(
            new FileInputStream("file.txt"),
            StandardCharsets.UTF_8))) {
    // ...
}

// Java 11+ FileReader
try (BufferedReader br = new BufferedReader(
        new FileReader("file.txt", StandardCharsets.UTF_8))) {
    // ...
}

// NIO.2 (가장 권장)
try (BufferedReader br = Files.newBufferedReader(
        Path.of("file.txt"), StandardCharsets.UTF_8)) {
    // ...
}

// 모두 같은 효과

6.3 readLine — 한 줄씩

// readLine 의 동작
public String readLine() throws IOException;

// 반환:
// - 한 줄 (\n, \r, \r\n 제외)
// - null (EOF)

// 사용
try (BufferedReader br = Files.newBufferedReader(path)) {
    String line;
    while ((line = br.readLine()) != null) {
        process(line);
    }
}

// readLine 의 구분자:
// \n (LF, Unix)
// \r (CR, Old Mac)
// \r\n (CRLF, Windows)
// 모두 처리, 결과에서 제외

6.4 BufferedReader 의 버퍼링

일반 Reader (8KB 버퍼 없음):
  read() → InputStreamReader → InputStream → OS
  매 호출이 system call

BufferedReader (8KB 버퍼):
  read() → BufferedReader 의 char[8192]
  버퍼 비면 InputStreamReader 에서 채움
  대부분 메모리 접근
  
효과:
  - read 호출 빠름
  - readLine 효율적

6.5 Stream API (Java 8+)

// lines() 메서드
try (BufferedReader br = Files.newBufferedReader(path)) {
    br.lines()
        .filter(l -> !l.isBlank())
        .map(String::trim)
        .forEach(System.out::println);
}

// Files.lines (더 간단)
try (Stream<String> lines = Files.lines(path)) {
    lines.filter(...)
        .map(...)
        .forEach(...);
}

// 차이:
// - BufferedReader.lines(): 자기가 만든 reader
// - Files.lines(): NIO.2 의 정적 메서드
// - 둘 다 try-with-resources 필수

6.6 readLine vs read 비교

// 한 줄씩 — readLine
String line;
while ((line = br.readLine()) != null) {
    process(line);
}
// 가독성 ↑
// 메모리: 한 줄 정도

// 한 문자씩 — read
int c;
while ((c = br.read()) != -1) {
    process((char) c);
}
// 더 세밀한 제어
// 토큰 단위 파싱 등

// 청크 단위 — read(char[])
char[] buf = new char[1024];
int n;
while ((n = br.read(buf)) != -1) {
    process(buf, 0, n);   // ★ n 만큼만!
}
// 효율 ↑
// 마지막 함정 주의

6.7 ILIC 의 BufferedReader 활용

public class ShipmentLogReader {
    
    // 1. 한 줄씩 처리
    public List<String> readLogLines(Path logFile) throws IOException {
        List<String> lines = new ArrayList<>();
        try (BufferedReader br = Files.newBufferedReader(logFile, StandardCharsets.UTF_8)) {
            String line;
            while ((line = br.readLine()) != null) {
                lines.add(line);
            }
        }
        return lines;
    }
    
    // 2. Stream + 필터링
    public List<String> findErrorLogs(Path logFile) throws IOException {
        try (Stream<String> lines = Files.lines(logFile, StandardCharsets.UTF_8)) {
            return lines
                .filter(l -> l.contains("ERROR"))
                .toList();
        }
    }
    
    // 3. 그룹핑
    public Map<String, Long> countByLevel(Path logFile) throws IOException {
        try (Stream<String> lines = Files.lines(logFile, StandardCharsets.UTF_8)) {
            return lines
                .map(this::extractLevel)
                .collect(Collectors.groupingBy(
                    Function.identity(),
                    Collectors.counting()));
        }
    }
    
    // 4. 대용량 처리
    public long countLines(Path logFile) throws IOException {
        try (Stream<String> lines = Files.lines(logFile, StandardCharsets.UTF_8)) {
            return lines.count();
        }
    }
    
    private String extractLevel(String line) {
        // "2026-05-19 [INFO] message"
        int start = line.indexOf('[');
        int end = line.indexOf(']');
        return (start >= 0 && end > start) ? line.substring(start + 1, end) : "UNKNOWN";
    }
}

6.8 자기 점검 답변

BufferedReader 의 효과는?

:
1. 정의:

  • Reader + 버퍼링
  • Decorator 패턴
  • 8KB 기본 버퍼
  1. 핵심 메서드:

    • readLine: 한 줄씩
    • lines: Stream
    • read: 효율적 (버퍼)
  2. 결합:

    • BufferedReader + InputStreamReader + FileInputStream
    • 또는 BufferedReader + FileReader (Java 11+)
    • 또는 Files.newBufferedReader (NIO.2)
  3. 효과:

    • readLine 의 편의
    • Stream API
    • 효율적 read
  4. 권장:

    • Files.newBufferedReader 가 가장 간결

7️⃣ char 의 한계 (Supplementary 문자)

7.1 char 의 본질

Java 의 char:
  - 16비트 (2바이트)
  - 0 ~ 65535
  - UTF-16 의 한 단위 (code unit)

표현 가능 범위:
  - BMP (Basic Multilingual Plane): 0x0000 ~ 0xFFFF
  - 65,536 코드 포인트

7.2 BMP 와 Supplementary Plane

유니코드의 분류:

BMP (Basic Multilingual Plane):
  - 0x0000 ~ 0xFFFF
  - 대부분의 문자
  - 영어, 한글, 한자 (기본), 일본어, 그리스어 등
  - char 1개로 표현

Supplementary Planes:
  - 0x10000 ~ 0x10FFFF
  - 이모지 😀
  - 고대 문자
  - 일부 한자 (확장)
  - char 2개로 표현 (Surrogate Pair)

7.3 Surrogate Pair

Surrogate Pair:
  
  Supplementary 문자 (BMP 밖) 을
  char 2개로 표현하는 방법.

High Surrogate: 0xD800 ~ 0xDBFF
Low Surrogate:  0xDC00 ~ 0xDFFF

예: 😀 (U+1F600, GRINNING FACE)
  UTF-16: 0xD83D 0xDE00 (2 char)
  
"😀".length() == 2   // char 길이
"😀".codePointCount(0, 2) == 1   // 코드 포인트 수

7.4 Reader.read() 의 한계

// Reader.read() 는 1 char 반환
// Supplementary 문자는 두 번 read 필요

try (Reader r = new InputStreamReader(
        new ByteArrayInputStream("😀".getBytes(UTF_8)),
        UTF_8)) {
    
    int c1 = r.read();   // 0xD83D (High Surrogate)
    int c2 = r.read();   // 0xDE00 (Low Surrogate)
    int c3 = r.read();   // -1 (EOF)
    
    // 단일 char 로는 불완전
    String s = "" + (char) c1 + (char) c2;
    // "😀" (정상)
}

7.5 codePoint 활용

// Code Point 단위 처리
String s = "Hello 😀 World";

// 잘못 — char 기반
for (int i = 0; i < s.length(); i++) {
    char c = s.charAt(i);
    System.out.println(c);
    // 😀 가 두 char 로 나옴 (깨진 표시)
}

// 올바름 — Code Point
s.codePoints().forEach(cp -> {
    String character = new String(Character.toChars(cp));
    System.out.println(character);
    // 😀 가 한 문자
});

// 또는 iterator
for (int i = 0; i < s.length(); ) {
    int cp = s.codePointAt(i);
    String character = new String(Character.toChars(cp));
    System.out.println(character);
    i += Character.charCount(cp);   // 1 또는 2
}

7.6 BufferedReader 와 이모지

// 이모지가 있는 파일
String text = "Hello 😀\n안녕 🌍";
Files.writeString(Path.of("emoji.txt"), text, StandardCharsets.UTF_8);

// 읽기 — 정상
try (BufferedReader br = Files.newBufferedReader(
        Path.of("emoji.txt"), StandardCharsets.UTF_8)) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
        // 이모지 포함 정상 출력
    }
}

// BufferedReader.readLine 은 String 반환
// String 은 Surrogate Pair 안전하게 처리
// 단, length(), charAt() 사용 시 주의

7.7 일반 한글 vs 한자 확장

일반 한글 (BMP):
  '안' = U+C548 (49992)
  char 1개

한자 확장 (Supplementary):
  '𠮷' (U+20BB7) — 일부 한자
  char 2개 (Surrogate Pair)

영향:
  - 일반 한글: char 안전
  - 확장 한자, 이모지: Surrogate Pair 필요
  - Code Point 기반 처리 권장

7.8 ILIC 의 활용 (확장 문자 처리)

public class TextProcessor {
    
    // 정확한 문자 수 계산
    public int countCharacters(String text) {
        return text.codePointCount(0, text.length());
        // Surrogate Pair 를 1로 계산
    }
    
    // 안전한 길이 자르기
    public String truncate(String text, int maxChars) {
        if (text.codePointCount(0, text.length()) <= maxChars) {
            return text;
        }
        
        StringBuilder sb = new StringBuilder();
        int count = 0;
        int i = 0;
        while (i < text.length() && count < maxChars) {
            int cp = text.codePointAt(i);
            sb.appendCodePoint(cp);
            i += Character.charCount(cp);
            count++;
        }
        return sb.toString();
    }
    
    // 이모지 제거 (예시)
    public String removeEmoji(String text) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < text.length(); ) {
            int cp = text.codePointAt(i);
            if (!isEmoji(cp)) {
                sb.appendCodePoint(cp);
            }
            i += Character.charCount(cp);
        }
        return sb.toString();
    }
    
    private boolean isEmoji(int cp) {
        // 이모지 범위 (대략)
        return cp >= 0x1F600 && cp <= 0x1F64F;
    }
}

7.9 자기 점검 답변

char 의 한계와 Supplementary 문자는?

:
1. char 의 크기:

  • 16비트 (0~65535)
  • UTF-16 의 한 단위
  • BMP 만 표현
  1. BMP:

    • 0x0000 ~ 0xFFFF
    • 대부분의 일반 문자
    • 영어, 일반 한글 OK
  2. Supplementary:

    • 0x10000 ~ 0x10FFFF
    • 이모지, 확장 한자
    • char 2개 (Surrogate Pair)
  3. 처리:

    • codePoint 기반
    • Character.charCount
    • codePoints().forEach
  4. 영향:

    • 일반 한글: 안전
    • 이모지/확장: 주의

8️⃣ Java 11+ 의 개선과 실무 패턴

8.1 Java 11+ 의 개선

Java 11 의 FileReader 개선:

이전 (Java 10 이하):
  new FileReader("file.txt")   // 시스템 기본 인코딩

Java 11+:
  new FileReader("file.txt")                          // 시스템 기본 (호환)
  new FileReader("file.txt", StandardCharsets.UTF_8)  // 인코딩 명시 ✓

새 생성자:
  FileReader(String, Charset)
  FileReader(File, Charset)

이제 FileReader 도 안전하게 사용 가능

8.2 Java 18+ 의 변화

JEP 400 (Java 18+):

기본 인코딩 UTF-8 통일.

영향:
  - new FileReader("file.txt") 가 UTF-8 (모든 OS)
  - new InputStreamReader(is) 가 UTF-8
  - 시스템 기본 의존 사라짐

호환:
  - -Dfile.encoding=COMPAT (옛 동작)
  - 명시적 인코딩 여전히 권장

8.3 실무 권장 패턴 종합

// 1. 가장 권장 — Files.newBufferedReader
<try (BufferedReader reader = Files.newBufferedReader(
        Path.of("file.txt"), StandardCharsets.UTF_8)) {
    String line;
    while ((line = reader.readLine()) != null) {
        process(line);
    }
}

// 2. Stream 처리
try (Stream<String> lines = Files.lines(
        Path.of("file.txt"), StandardCharsets.UTF_8)) {
    lines.filter(...)
        .map(...)
        .forEach(...);
}

// 3. 한 번에 읽기 (작은 파일)
String content = Files.readString(
    Path.of("file.txt"), StandardCharsets.UTF_8);

List<String> allLines = Files.readAllLines(
    Path.of("file.txt"), StandardCharsets.UTF_8);

// 4. 다양한 InputStream
try (Reader r = new InputStreamReader(
        url.openStream(), StandardCharsets.UTF_8);
     BufferedReader br = new BufferedReader(r)) {
    // URL 응답 처리
}

// 5. 표준 입력
try (BufferedReader br = new BufferedReader(
        new InputStreamReader(System.in, StandardCharsets.UTF_8))) {
    String line = br.readLine();
}

8.4 인코딩 상수의 활용

// Constants 클래스
public class IoConstants {
    public static final Charset UTF8 = StandardCharsets.UTF_8;
    public static final Charset MS949 = Charset.forName("MS949");
    public static final Charset EUC_KR = Charset.forName("EUC-KR");
    
    public static final int BUFFER_SIZE = 8192;
}

// 활용
try (BufferedReader br = Files.newBufferedReader(path, IoConstants.UTF8)) {
    // ...
}

// 일관성:
// - 모든 코드에서 같은 인코딩
// - 변경 용이
// - 의도 명확

8.5 ILIC 의 권장 패턴

@Service
public class ShipmentTextService {
    
    private static final Charset CHARSET = StandardCharsets.UTF_8;
    
    // 1. CSV 읽기
    public List<Shipment> importCsv(Path path) throws IOException {
        try (Stream<String> lines = Files.lines(path, CHARSET)) {
            return lines
                .skip(1)   // 헤더
                .map(this::parseShipment)
                .toList();
        }
    }
    
    // 2. 큰 파일 청크 처리
    public void processLargeFile(Path path, LineProcessor processor) throws IOException {
        try (BufferedReader br = Files.newBufferedReader(path, CHARSET)) {
            String line;
            long lineNum = 0;
            while ((line = br.readLine()) != null) {
                processor.process(lineNum++, line);
            }
        }
    }
    
    // 3. 한글 검증
    public boolean isValidKorean(Path path) throws IOException {
        try (BufferedReader br = Files.newBufferedReader(path, CHARSET)) {
            String line;
            while ((line = br.readLine()) != null) {
                if (containsCorruptedChars(line)) {
                    return false;
                }
            }
            return true;
        }
    }
    
    // 4. HTTP 응답 처리
    public String fetchShipmentData(String url) throws IOException {
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        
        // Content-Type 의 charset 활용
        String contentType = conn.getContentType();
        Charset charset = extractCharset(contentType, CHARSET);
        
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(conn.getInputStream(), charset))) {
            return br.lines().collect(Collectors.joining("\n"));
        }
    }
    
    private boolean containsCorruptedChars(String line) {
        // U+FFFD (replacement character) 검사
        return line.contains("\uFFFD");
    }
    
    private Charset extractCharset(String contentType, Charset defaultCharset) {
        if (contentType != null && contentType.contains("charset=")) {
            try {
                return Charset.forName(contentType.split("charset=")[1].trim());
            } catch (Exception e) {
                // 무시
            }
        }
        return defaultCharset;
    }
    
    @FunctionalInterface
    interface LineProcessor {
        void process(long lineNum, String line);
    }
}

8.6 자주 발생하는 인코딩 버그

실무 인코딩 버그 5가지:

1. CSV Excel 에서 한글 깨짐
   - 원인: Excel 의 BOM 요구
   - 해결: UTF-8 BOM 추가

2. HTTP 응답 한글 깨짐
   - 원인: Content-Type charset 누락
   - 해결: 명시 또는 기본 UTF-8

3. DB 조회 한글 깨짐
   - 원인: DB 인코딩 ↔ 클라이언트 인코딩 불일치
   - 해결: 모두 UTF-8

4. 파일 이름 한글 깨짐
   - 원인: 파일 시스템 인코딩
   - 해결: -Dfile.encoding=UTF-8

5. 컴파일러 인코딩
   - 원인: 소스 파일 인코딩
   - 해결: -encoding UTF-8 (javac)

8.7 디버깅 팁

// 인코딩 확인
String defaultCharset = Charset.defaultCharset().name();
String fileEncoding = System.getProperty("file.encoding");
String consoleEncoding = System.getProperty("console.encoding");

System.out.println("Default: " + defaultCharset);
System.out.println("File: " + fileEncoding);
System.out.println("Console: " + consoleEncoding);

// 바이트 hex 출력
byte[] bytes = "안녕".getBytes(StandardCharsets.UTF_8);
for (byte b : bytes) {
    System.out.printf("%02X ", b);
}
// EC 95 88 EB 85 95

// 다양한 인코딩으로 해석
byte[] data = ...;
for (Charset cs : List.of(UTF_8, UTF_16, Charset.forName("MS949"), Charset.forName("EUC-KR"))) {
    System.out.println(cs + ": " + new String(data, cs));
}

8.8 자기 점검 답변

Java 11+ 의 개선과 권장 패턴은?

:
1. Java 11+ 개선:

  • FileReader(File, Charset) 추가
  • 인코딩 명시 가능
  1. Java 18+ 변화:

    • JEP 400: UTF-8 기본
    • 모든 OS 통일
  2. 가장 권장:

    • Files.newBufferedReader(path, UTF_8)
    • NIO.2 활용
    • Stream API
  3. 명시적 인코딩:

    • StandardCharsets.UTF_8
    • 의도 명확
    • 호환성
  4. 실무 5가지 버그:

    • CSV Excel, HTTP, DB, 파일명, 컴파일러

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
Reader 정의?문자 단위, 인코딩 인식
Reader vs InputStream?char vs byte, 인코딩
FileReader 한계?Java 10 이하 인코딩 명시 X
InputStreamReader 역할?Bridge (InputStream → Reader)
인코딩 명시 안 하면?시스템 기본, Cross-platform 위험
BufferedReader 효과?버퍼링 + readLine
readLine 의 줄 구분?\n, \r, \r\n
Reader.read() 반환?0~65535 또는 -1
char 16비트?UTF-16 한 단위
Supplementary 문자?이모지, char 2개 (Surrogate)
Java 18+ 변화?UTF-8 기본
가장 권장?Files.newBufferedReader(path, UTF_8)

9.2 자기 점검 체크리스트

Reader

  • Reader 의 정의
  • InputStream 과 차이
  • char vs byte
  • 인코딩 인식

FileReader

  • 정의 (InputStreamReader 자식)
  • Java 10 이하 한계
  • Java 11+ 개선
  • 시스템 인코딩 위험

InputStreamReader

  • 정의 (Bridge)
  • 인코딩 명시 4가지
  • 다양한 InputStream
  • BufferedReader 결합

BufferedReader

  • 8KB 버퍼
  • readLine
  • lines() (Stream)
  • 줄 구분자 처리

인코딩

  • StandardCharsets.UTF_8
  • Java 18+ 기본
  • 명시적 권장
  • BOM 처리

char 한계

  • 16비트 한도
  • BMP vs Supplementary
  • Surrogate Pair
  • Code Point 처리

실무

  • Files.newBufferedReader 권장
  • Stream API 활용
  • 인코딩 상수
  • 흔한 버그 5가지

9.3 추가 심화 질문

Q1: Reader 가 char 인데 한자 (확장) 처리?

답:

  • 일반 한자: BMP, char 1개 OK
  • 확장 한자 (Supplementary): char 2개
  • BufferedReader.readLine 은 String 반환
  • String 은 Surrogate Pair 안전
  • length() 는 char 단위 (확장은 2)
  • codePointCount() 가 진짜 문자 수

Q2: 잘못된 UTF-8 바이트 만나면?

답:

// 기본 동작
Charset.forName("UTF-8");
// 잘못된 시퀀스 → U+FFFD (replacement character) 로 치환

// 엄격하게
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
decoder.onMalformedInput(CodingErrorAction.REPORT);   // 예외
decoder.onUnmappableCharacter(CodingErrorAction.REPORT);

try (Reader r = new InputStreamReader(is, decoder)) {
    // MalformedInputException
}

Q3: 매우 큰 파일을 readLine?

답:

  • BufferedReader.readLine 은 한 줄을 String 으로
  • 줄이 매우 길면 메모리 부담
  • 1줄에 1GB 같은 극단적 경우 OOM
  • 해결: 청크 단위 read(char[])

Q4: read(char[]) 의 마지막 함정?

답:

char[] buf = new char[1024];
int n = br.read(buf);
// n < buf.length 가능

// ❌ for-each
for (char c : buf) { ... }

// ✓ n 까지만
for (int i = 0; i < n; i++) { ... }

// InputStream 의 read(byte[]) 와 동일한 함정

Q5: Reader 와 NIO 의 Channel?

답:

  • Reader: java.io, char 단위
  • Channel: java.nio, ByteBuffer 단위
  • 변환:
    • Channels.newReader(channel, charset)
    • Channels.newChannel(reader)
  • 큰 파일: Channel + DirectBuffer 가 효율

🎯 핵심 요약 — 3줄 정리

1. Reader vs InputStream

  • Reader: char (문자), 인코딩 인식
  • InputStream: byte, raw
  • 한글은 Reader 필수

2. FileReader vs InputStreamReader

  • FileReader: 파일 전용 (Java 11+ 안전)
  • InputStreamReader: 모든 InputStream (Bridge)
  • 가장 권장: Files.newBufferedReader

3. 권장 패턴

  • 항상 인코딩 명시 (StandardCharsets.UTF_8)
  • BufferedReader 결합
  • Stream API (lines)
  • NIO.2 의 Files

📚 다음으로...

Unit 8.6 — FileWriter (한글 쓰기)

이번 Unit에서 한글 읽기를 봤다면, 다음은 한글 쓰기 + Phase 8 완주.

  • FileWriter 의 정의
  • OutputStreamWriter 와의 비교
  • 다양한 write 메서드
  • BufferedWriter 결합
  • Phase 8 완주

Phase 8 진행 상황

🚀 Phase 8 — Stream 실전
  ✅ Unit 8.1 System.in (한글 안 되는 이유)
  ✅ Unit 8.2 FileInputStream
  ✅ Unit 8.3 byte[] 배열로 효율적 읽기
  ✅ Unit 8.4 FileOutputStream
  ✅ Unit 8.5 한글 처리 (FileReader, InputStreamReader) ← 여기
  ⏭ Unit 8.6 FileWriter (한글 쓰기) — Phase 8 완주

3주차 누적 진행

✅ Phase 1 ~ 7 완주 (31 Unit)
🚀 Phase 8 — Stream 실전 (5/6 진행)

총: 36/43 Unit (약 84%)
profile
Software Developer

0개의 댓글