3주차 Unit 8.1 — System.in 과 한글이 안 되는 이유

Psj·2026년 5월 20일

F-lab

목록 보기
106/240

Unit 8.1 — System.in 과 한글이 안 되는 이유

F-LAB JAVA · 3주차 · Phase 8 · Stream 실전
🚀 Phase 8 시작 — Stream 코드 실전


📌 학습 목표

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

  • System.in 의 정확한 정체와 타입은?
  • System.in, System.out, System.err 의 차이와 활용은?
  • InputStream.read() 의 정확한 동작과 1바이트 한계는?
  • 인코딩의 기초 (ASCII, UTF-8, EUC-KR, MS949) 는?
  • 한글이 깨지는 정확한 메커니즘 은?
  • Reader vs InputStream 의 결정적 차이는?
  • Scanner, BufferedReader, Console 의 비교는?
  • System.in 의 Blocking 특성은?
  • Java 18+ 의 기본 인코딩 변화 (UTF-8) 는?

🎯 핵심 한 문장

System.in 은 표준 입력의 InputStream 인스턴스로 read() 가 1바이트씩 반환하기 때문에 한글 (2~3바이트) 을 그대로 읽으면 깨진다.
인코딩 = 문자 ↔ 바이트 매핑 규칙으로, ASCII (1바이트), UTF-8 (1~4바이트), EUC-KR/MS949 (2바이트) 가 있으며,
한글을 올바르게 읽으려면 InputStream → InputStreamReader (인코딩 명시) → BufferedReader 또는 Scanner 사용.
Java 18+ 부터 기본 인코딩이 UTF-8 로 통일되었지만 OS 별 차이를 명시하는 게 안전.
실무에선 Scanner 의 간편함과 BufferedReader.readLine() 의 효율 + Console 의 비밀번호 처리가 각각 자리가 있다.

비유 — 외국어 책 읽기

System.in.read() (InputStream):
  외국어 책을 글자 하나씩 펴서 그림으로 봄
  - 영어 "A" = 1 그림 = 인식 OK
  - 한글 "가" = 3 그림 = 따로 보면 의미 모름
  - 글자가 깨짐

Reader (with encoding):
  외국어 사전을 가지고 읽음
  - "A" → 'A' (1 글자)
  - "가" (3바이트) → '가' (1 문자)
  - 인코딩 = 사전
  - 문자 단위로 정확히 읽음

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


🧭 9개 섹션 로드맵

1. System.in 의 정체
2. 표준 입출력 3총사 (in, out, err)
3. InputStream.read 의 1바이트 한계
4. 인코딩의 기초 (ASCII, UTF-8, EUC-KR)
5. 한글이 깨지는 정확한 메커니즘
6. Reader/Writer 로 해결
7. Scanner, BufferedReader, Console 비교
8. Java 18+ UTF-8 기본화와 실무
9. 면접 + 자기 점검

1️⃣ System.in 의 정체

1.1 System.in 의 정의

public final class System {
    
    public static final InputStream in;
    public static final PrintStream out;
    public static final PrintStream err;
    
    // 초기화는 native 코드로
}

핵심:

  • InputStream 타입
  • public static final — 전역 접근, 불변 (재할당 X)
  • 표준 입력 (보통 키보드)
  • 블로킹 스트림

1.2 System.in 의 실제 타입

// System.in 의 런타임 타입 확인
public class SystemInDemo {
    
    public static void main(String[] args) {
        InputStream in = System.in;
        
        System.out.println(in.getClass().getName());
        // 예: java.io.BufferedInputStream
        // 내부적으로 BufferedInputStream 으로 감싸짐
        
        System.out.println(in.getClass().getSuperclass().getName());
        // java.io.FilterInputStream
        
        // 실제 핵심: native FileInputStream (fd=0, 표준 입력)
    }
}

// 참고:
// - JVM 시작 시 native 메서드로 초기화
// - 내부 구조는 JVM 구현 의존
// - 표준 입력 파일 디스크립터 (fd=0) 와 연결

1.3 System.in 의 특성

System.in 의 5가지 특성:

1. InputStream (바이트 스트림)
   - 1바이트씩 읽음
   - 문자 변환 X (인코딩 처리 안 함)

2. Blocking
   - 데이터 (사용자 입력) 올 때까지 대기
   - Enter 키 누를 때까지 read 가 안 끝남

3. Buffered (대부분 JVM)
   - 줄 단위 입력 (line-buffered)
   - Enter 키 누르기 전엔 데이터 안 옴

4. 닫지 말 것
   - 표준 입력 닫으면 복구 불가
   - try-with-resources 사용 시 주의

5. JVM 단일 인스턴스
   - 한 JVM 에 하나
   - 모든 스레드 공유

1.4 가장 단순한 사용

// 1바이트 읽기
public class SimpleRead {
    
    public static void main(String[] args) throws IOException {
        System.out.print("Enter: ");
        int b = System.in.read();   // 1바이트 + Enter (line-buffered)
        
        // 결과: int 값 (0~255 또는 -1)
        System.out.println("Got: " + b);
        System.out.println("As char: " + (char) b);
        
        // 영어 입력 OK
        // "A" 입력 → 65 (A) + 13/10 (\r\n 또는 \n)
        
        // 한글 입력
        // "가" 입력 → 3바이트가 큐에 쌓임
        // 첫 read() 는 첫 바이트만 가져옴
    }
}

1.5 System.in 의 리다이렉트

// System.in 을 다른 InputStream 으로 교체 가능
public class RedirectDemo {
    
    public static void main(String[] args) throws IOException {
        // 파일을 표준 입력으로
        System.setIn(new FileInputStream("input.txt"));
        
        // 이제 System.in.read() 가 파일을 읽음
        int b = System.in.read();
        
        // 활용:
        // - 테스트 (입력 자동화)
        // - 배치 처리
    }
}

// 쉘 리다이렉트도 동일
// $ java MyApp < input.txt
// → System.in 이 input.txt

1.6 System.in 의 종료

// 닫으면 복구 불가
// System.in.close();   // ★ 절대 하지 말 것

// 표준 입력 종료 신호:
// - Unix/Linux/Mac: Ctrl+D
// - Windows: Ctrl+Z + Enter

// 종료 시 read() 는 -1 반환
public class EofDetection {
    public static void main(String[] args) throws IOException {
        int b;
        while ((b = System.in.read()) != -1) {
            System.out.write(b);
        }
        // Ctrl+D 또는 Ctrl+Z 누르면 종료
    }
}

1.7 ILIC 의 System.in 활용

// 1. 디버그 도구 — 콘솔 입력
public class ShipmentDebugTool {
    
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        
        System.out.println("=== ILIC Debug Tool ===");
        System.out.println("Commands: info <id> | reset | exit");
        
        String command;
        while ((command = reader.readLine()) != null) {
            if ("exit".equals(command)) break;
            // 처리
        }
    }
}

// 2. 배치 스크립트 — 파일 리다이렉트
// shipment-batch.sh:
// java -jar shipment-batch.jar < input-ids.txt

// 3. 운영 도구 — 비밀번호 입력 (Console)
Console console = System.console();
if (console != null) {
    char[] pass = console.readPassword("DB Password: ");
    // 안전한 비밀번호 입력
}

1.8 자기 점검 답변

System.in 의 정체와 특성은?

:
1. 타입:

  • InputStream
  • 실제 구현은 JVM 의존 (보통 BufferedInputStream)
  1. 본질:

    • 표준 입력 (fd=0)
    • 1바이트씩 읽음
    • 인코딩 처리 X
  2. 특성:

    • Blocking
    • Line-buffered (Enter 까지)
    • 단일 인스턴스
    • 닫지 말 것
  3. 활용:

    • 콘솔 입력
    • 파일 리다이렉트
    • 배치 처리

2️⃣ 표준 입출력 3총사 (in, out, err)

2.1 세 가지 표준 스트림

System.in:
  - 표준 입력 (Standard Input, stdin)
  - 파일 디스크립터 0
  - 보통 키보드
  - InputStream

System.out:
  - 표준 출력 (Standard Output, stdout)
  - 파일 디스크립터 1
  - 보통 콘솔
  - PrintStream

System.err:
  - 표준 에러 (Standard Error, stderr)
  - 파일 디스크립터 2
  - 보통 콘솔 (out 과 별개)
  - PrintStream

2.2 PrintStream 의 특징

// System.out 과 System.err 의 타입
public final static PrintStream out;
public final static PrintStream err;

// PrintStream 은 OutputStream 의 자식
// 편리한 메서드 추가
System.out.println("Hello");
System.out.print("World");
System.out.printf("Value: %d%n", 42);
System.out.write(65);   // 바이트 (low-level)

// flush
System.out.flush();   // 강제 출력

2.3 out vs err 의 차이

out 과 err 는 왜 분리?

이유:
  - 정상 출력 vs 에러 메시지 분리
  - 쉘에서 각각 리다이렉트 가능
  - 로그와 데이터 분리

쉘 활용:
  $ java MyApp 1> output.txt 2> errors.txt
  - out → output.txt
  - err → errors.txt
  
  $ java MyApp 2>/dev/null
  - 에러 메시지 무시
  
  $ java MyApp 2>&1
  - err 를 out 으로

2.4 out 과 err 의 동기화

// out 과 err 는 같은 콘솔로 나가지만 별개 스트림
System.out.println("Output");
System.err.println("Error");

// 순서 보장 안 됨
// 콘솔에서 섞일 수 있음

// 동기화 필요시
synchronized (System.out) {
    System.out.println("...");
}

2.5 PrintStream 의 함정

// println 의 동작
System.out.println("Hello");
// 1. "Hello" 출력
// 2. 시스템 줄바꿈 (line.separator) 추가
//    - Unix: \n
//    - Windows: \r\n

// 인코딩
// PrintStream 은 내부적으로 시스템 인코딩 사용
// Windows: MS949 (CP949)
// Linux/Mac: UTF-8
// → Cross-platform 문제 가능

// 명시적 인코딩
PrintStream out = new PrintStream(
    new BufferedOutputStream(System.out),
    true,
    StandardCharsets.UTF_8);

2.6 리다이렉트

// 표준 스트림을 다른 곳으로
System.setIn(new FileInputStream("input.txt"));
System.setOut(new PrintStream(new FileOutputStream("output.log")));
System.setErr(new PrintStream(new FileOutputStream("errors.log")));

// 이후 모든 println 이 파일로
System.out.println("To file");

// 복원 (원본 저장 후)
PrintStream originalOut = System.out;
System.setOut(new PrintStream(new ByteArrayOutputStream()));
// ... 임시 리다이렉트
System.setOut(originalOut);

2.7 ILIC 의 표준 스트림 활용

// 콘솔 도구
public class ShipmentConsoleTool {
    
    public static void main(String[] args) {
        // out — 정상 메시지
        System.out.println("Shipment Tool v1.0");
        
        try {
            processShipments();
        } catch (Exception e) {
            // err — 에러
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace(System.err);
            System.exit(1);
        }
        
        System.out.println("Done");
    }
}

// 쉘 사용
// $ java ShipmentConsoleTool > result.log 2> error.log

2.8 자기 점검 답변

System.in, out, err 의 차이와 활용은?

:
1. System.in:

  • 표준 입력 (fd=0)
  • InputStream
  • 키보드
  1. System.out:

    • 표준 출력 (fd=1)
    • PrintStream
    • 정상 메시지
  2. System.err:

    • 표준 에러 (fd=2)
    • PrintStream
    • 에러 메시지

왜 분리?:

  • 쉘에서 각각 리다이렉트
  • 정상 vs 에러 구분
  • 로그 분리

리다이렉트:

  • setIn, setOut, setErr
  • 쉘: >, 2>, 2>&1

3️⃣ InputStream.read 의 1바이트 한계

3.1 read() 의 시그니처

public abstract class InputStream {
    
    public abstract int read() throws IOException;
    // - 1바이트 읽기
    // - 반환: 0~255 또는 -1 (EOF)
    // - byte 가 아니라 int 인 이유: -1 표현
    
    public int read(byte[] b) throws IOException {
        return read(b, 0, b.length);
    }
    
    public int read(byte[] b, int off, int len) throws IOException {
        // 여러 바이트 읽기
        // 반환: 실제 읽은 수, -1 (EOF)
    }
}

3.2 read() 의 정확한 동작

// 1바이트씩
InputStream in = System.in;
int b = in.read();

// 사용자가 "A" + Enter 입력:
// - "A" → 65
// - 첫 read(): 65 반환
// - 두 번째 read(): 10 (LF) 또는 13 (CR) — OS 따라

// 사용자가 "가" + Enter 입력 (UTF-8):
// - "가" → 0xEA, 0xB0, 0x80 (3바이트)
// - 첫 read(): 234 (0xEA)
// - 두 번째 read(): 176 (0xB0)
// - 세 번째 read(): 128 (0x80)
// - 네 번째 read(): 10 (LF)

// 각 read() 는 한 바이트만!
// 한글 1글자가 3번 read 로 분리됨

3.3 1바이트 한계의 의미

1바이트 (8비트) 의 표현 범위:
  - 0 ~ 255 (unsigned)
  - 256 가지 가능

문자 표현:
  - ASCII: 0~127 (영문, 숫자, 기호)
  - 한글: 한 글자 = 2~3바이트
  - 중국어, 일본어: 비슷

문제:
  - 1바이트로 모든 문자 X
  - 한글 1글자가 여러 바이트
  - 1바이트씩 읽으면 깨짐

3.4 영문 vs 한글의 차이

// 영문 "Hello" — UTF-8 / ASCII 동일
// H = 72, e = 101, l = 108, l = 108, o = 111
// 5바이트
// 1바이트씩 read → 'H', 'e', 'l', 'l', 'o' 정상

// 한글 "안녕" — UTF-8
// "안" = 0xEC, 0x95, 0x88 (3바이트)
// "녕" = 0xEB, 0x85, 0x95 (3바이트)
// 총 6바이트

// 1바이트씩 read 결과:
// 236, 149, 136, 235, 133, 149
// (char) 변환: 'ì', '•', 'ˆ', 'ë', '…', '•'
// → 완전히 깨진 문자

// 한글 1글자 = 1 read() 로 못 읽음
// 정확히 3번 읽고 합쳐야

3.5 byte[] 로 여러 바이트 읽기

// 여러 바이트 한 번에
byte[] buf = new byte[1024];
int n = System.in.read(buf);

// n = 실제 읽은 바이트 수
// buf 의 0 ~ n-1 에 데이터

// 한글 "안녕" 입력 시:
// n = 6 또는 7 (with \n)
// buf[0..5] = [236, 149, 136, 235, 133, 149]

// 여전히 문자 단위 아님
// 인코딩 디코딩 필요

3.6 1바이트 한계의 회피

// 방법 1: 모든 바이트 모으기 + 인코딩 디코딩
byte[] buf = new byte[1024];
int n = System.in.read(buf);
String s = new String(buf, 0, n, StandardCharsets.UTF_8);
// "안녕\n"

// 방법 2: Reader 사용 (권장)
BufferedReader reader = new BufferedReader(
    new InputStreamReader(System.in, StandardCharsets.UTF_8));
String line = reader.readLine();
// "안녕"

// 방법 3: Scanner
Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8);
String line = scanner.nextLine();

3.7 read 의 반환 int 정밀

// 왜 int 인가? (한 번 더)

// byte 는 -128 ~ 127 (signed)
// EOF (-1) 도 표현 가능하지만
// 정상 데이터 0xFF (= -1 as byte) 와 충돌

// int 로 확장:
// - 0~255: 정상 바이트 (unsigned 처리)
// - -1: EOF

int b = in.read();
if (b == -1) {
    // 스트림 끝
} else {
    // 정상 바이트 (0~255)
    byte data = (byte) b;   // 명시적 캐스트
    char c = (char) b;      // char (16비트로 확장)
}

3.8 read(byte[]) 의 반환

// read(byte[]) 의 반환값
byte[] buf = new byte[10];
int n = in.read(buf);

// n 의 의미:
// - 양수: 실제 읽은 바이트 수
// - -1: EOF (스트림 끝)
// - 0: 가능하지만 드뭄 (Blocking 모드)

// 함정: 다음번 read 호출 시
// 첫 호출: 데이터 일부 (8바이트)
// 두 번째: 남은 데이터 (2바이트)
// 세 번째: -1 (EOF)

// 즉, 한 번에 다 안 읽힘
while ((n = in.read(buf)) != -1) {
    // 0 ~ n-1 처리
    process(buf, 0, n);
}

3.9 자기 점검 답변

InputStream.read() 의 1바이트 한계와 한글 문제는?

:
1. read() 의 동작:

  • 1바이트씩만
  • 0~255 또는 -1 (EOF)
  • int 반환 (byte 의 -1 충돌 회피)
  1. 한글 문제:

    • UTF-8: 한글 1글자 = 3바이트
    • read() 한 번 = 1바이트
    • 한글 1글자 = 3번 read 분리
  2. 해결:

    • byte[] 로 모으기 + 인코딩 디코딩
    • Reader 사용 (권장)
    • Scanner 사용
  3. read(byte[]) 의 반환:

    • 양수: 읽은 수
    • -1: EOF
    • 0: 드뭄

4️⃣ 인코딩의 기초 (ASCII, UTF-8, EUC-KR)

4.1 인코딩의 정의

인코딩 (Encoding):

  문자 (의미) ↔ 바이트 (저장) 의 매핑 규칙.

예:
  - "A" 라는 문자 ↔ 65 (0x41) 바이트
  - "안" 이라는 문자 ↔ 0xEC 0x95 0x88 (UTF-8) 또는 0xBE C8 (EUC-KR)

같은 문자도 인코딩에 따라 다른 바이트.

4.2 ASCII

ASCII (American Standard Code for Information Interchange):

  1963년 표준
  - 7비트 (128 문자)
  - 영문 대소문자, 숫자, 기본 기호
  - 한글, 일본어, 중국어 X

문자 ↔ 바이트:
  'A' = 65 (0x41)
  'B' = 66 (0x42)
  ...
  'Z' = 90 (0x5A)
  
  'a' = 97 (0x61)
  ...
  'z' = 122 (0x7A)
  
  '0' = 48 (0x30)
  ...
  '9' = 57 (0x39)
  
  공백 = 32 (0x20)
  '\n' = 10 (0x0A)
  '\r' = 13 (0x0D)

특징:
  - 1바이트당 1문자
  - 영문만
  - 다른 인코딩의 기반

4.3 EUC-KR

EUC-KR (Extended Unix Code Korean):

  1990년대 한국 표준
  - 영문: ASCII 호환 (1바이트)
  - 한글: 2바이트
  - 한자: 2바이트

한글 표현:
  - 첫 바이트: 0xA1~0xFE
  - 둘째 바이트: 0xA1~0xFE
  - 약 2,350자 (완성형)

문제:
  - 한글 완성형만 (조합 X)
  - "쀍", "긝" 같은 일부 한글 표현 X

확장: CP949 (Microsoft Codepage 949), Windows 949, MS949
  - EUC-KR 확장
  - 더 많은 한글 (11,172자)
  - Windows 한국어의 기본

4.4 UTF-8

UTF-8 (Unicode Transformation Format, 8-bit):

  1993년 표준
  - 가변 길이 (1~4바이트)
  - 모든 유니코드 문자 표현
  - ASCII 호환 (1바이트 영문)
  - 현재 인터넷 표준

가변 길이 규칙:
  1바이트: 0xxxxxxx (영문, ASCII 호환)
  2바이트: 110xxxxx 10xxxxxx (라틴, 그리스 등)
  3바이트: 1110xxxx 10xxxxxx 10xxxxxx (한글, 일본어, 중국어)
  4바이트: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (이모지, 고대 문자)

한글 예:
  "안" → 0xEC 0x95 0x88
  
  이진:
  0xEC = 11101100
  0x95 = 10010101
  0x88 = 10001000
  
  앞 1110 = 3바이트 시그널
  10 = continuation 표시
  나머지: 1100 010101 001000
       = U+C548 (유니코드 '안')

특징:
  - 영문은 1바이트 (효율)
  - 한글은 3바이트
  - 표준

4.5 UTF-16, UTF-32

UTF-16:
  - 2바이트 또는 4바이트
  - 자바의 내부 char 표현
  - String 의 내부도 UTF-16 (까지 Java 8, Java 9+ 는 압축)
  - Windows 의 일부 API

UTF-32:
  - 항상 4바이트
  - 단순 (인덱스 직접)
  - 메모리 비효율
  - 거의 안 사용

비교:
  "A" (UTF-8): 0x41 (1바이트)
  "A" (UTF-16): 0x00 0x41 (2바이트)
  "A" (UTF-32): 0x00 0x00 0x00 0x41 (4바이트)
  
  "안" (UTF-8): 0xEC 0x95 0x88 (3바이트)
  "안" (UTF-16): 0xC5 0x48 (2바이트)
  "안" (UTF-32): 0x00 0x00 0xC5 0x48 (4바이트)

4.6 인코딩 비교 표

인코딩크기영문한글활용
ASCII1바이트X영문 전용
EUC-KR1~2바이트1B2B옛 한국
CP949 (MS949)1~2바이트1B2BWindows 한국어
UTF-81~4바이트1B3B인터넷 표준
UTF-162 또는 4바이트2B2B자바 내부
UTF-324바이트4B4B드물게

4.7 자바의 기본 인코딩

// JVM 의 기본 인코딩
String defaultCharset = Charset.defaultCharset().name();
// Java 17 이하:
//   Windows 한국어: x-windows-949 (MS949)
//   Linux/Mac: UTF-8
// Java 18+:
//   UTF-8 통일 (JEP 400)

// 명시적 인코딩
Charset.forName("UTF-8");
Charset.forName("EUC-KR");
Charset.forName("MS949");

// 표준 인코딩 상수
StandardCharsets.UTF_8;
StandardCharsets.UTF_16;
StandardCharsets.US_ASCII;
StandardCharsets.ISO_8859_1;

4.8 인코딩 변환

// String ↔ byte[]
String s = "안녕하세요";

byte[] utf8 = s.getBytes(StandardCharsets.UTF_8);
// [236, 149, 136, 235, 133, 149, ...]
// 15바이트 (5글자 × 3바이트)

byte[] euckr = s.getBytes(Charset.forName("EUC-KR"));
// 10바이트 (5글자 × 2바이트)

// 디코딩 (바이트 → 문자열)
String s1 = new String(utf8, StandardCharsets.UTF_8);
// "안녕하세요" ✓

// 잘못된 디코딩
String s2 = new String(utf8, Charset.forName("EUC-KR"));
// 깨진 문자열

// 인코딩 변환
byte[] reEncoded = new String(utf8, StandardCharsets.UTF_8)
    .getBytes(Charset.forName("EUC-KR"));
// UTF-8 → String → EUC-KR

4.9 자기 점검 답변

인코딩의 기초와 한글 표현은?

:
1. 인코딩: 문자 ↔ 바이트 매핑

  1. 주요 인코딩:

    • ASCII: 1바이트, 영문만
    • EUC-KR: 2바이트, 한글 (완성형)
    • CP949/MS949: EUC-KR 확장
    • UTF-8: 1~4바이트, 모든 문자 (한글 3바이트)
    • UTF-16: 2~4바이트, 자바 내부
  2. 자바 기본:

    • Java 17 이하: OS 따라 (Windows MS949, Linux UTF-8)
    • Java 18+: UTF-8 통일
  3. 변환:

    • String.getBytes(Charset)
    • new String(bytes, Charset)

5️⃣ 한글이 깨지는 정확한 메커니즘

5.1 깨짐의 시나리오 1 — 1바이트씩 read

// System.in 에서 "안" 입력 (UTF-8 환경)
InputStream in = System.in;

int b1 = in.read();   // 236 (0xEC)
int b2 = in.read();   // 149 (0x95)
int b3 = in.read();   // 136 (0x88)

// 잘못된 처리: 각각 char 로
char c1 = (char) b1;   // 'ì' (Latin-1 의 236번)
char c2 = (char) b2;   // '•'
char c3 = (char) b3;   // 'ˆ'

System.out.println("" + c1 + c2 + c3);
// "안" — 깨진 문자

// 원인:
// - 한글 1글자 = 3바이트
// - 각 바이트를 따로 char 로 변환
// - 의미 잃음

5.2 깨짐의 시나리오 2 — 잘못된 인코딩

// 파일이 UTF-8 인데 EUC-KR 로 읽기
byte[] data = Files.readAllBytes(Path.of("utf8-file.txt"));

String wrong = new String(data, Charset.forName("EUC-KR"));
// 깨진 문자열

String right = new String(data, StandardCharsets.UTF_8);
// 정상

// 반대로
byte[] data2 = Files.readAllBytes(Path.of("euckr-file.txt"));
String s = new String(data2, StandardCharsets.UTF_8);
// 깨짐

// 원인:
// - 인코딩이 다르면 다른 바이트 패턴
// - 잘못 해석하면 의미 잃음

5.3 깨짐의 시나리오 3 — 시스템 인코딩 차이

// Windows (MS949) 에서 작성한 파일
// "안녕" → 0xBE 0xC8 0xB3 0xC4 (4바이트)

// Linux (UTF-8) 에서 읽기 (기본 인코딩)
FileReader reader = new FileReader("hello.txt");
// 기본 인코딩 = UTF-8
// MS949 바이트를 UTF-8 로 해석 → 깨짐

// 해결: 명시적 인코딩
Reader reader = new InputStreamReader(
    new FileInputStream("hello.txt"),
    Charset.forName("MS949"));

5.4 깨짐의 시나리오 4 — 네트워크 (HTTP)

// HTTP 응답의 인코딩
// Content-Type: text/html; charset=UTF-8

InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
// ❌ 기본 인코딩 사용
// 서버가 UTF-8 인데 클라이언트가 다른 인코딩이면 깨짐

// ✓ 명시적
BufferedReader reader = new BufferedReader(
    new InputStreamReader(in, StandardCharsets.UTF_8));

5.5 한글 처리의 흐름

한글 처리의 정확한 흐름:

1. 사용자 입력 (콘솔 또는 파일)
   - OS 의 인코딩으로 저장
   - Windows: MS949
   - Linux/Mac: UTF-8

2. 바이트 스트림 (InputStream)
   - 원본 바이트 그대로 읽음
   - 의미 없음 (그냥 숫자)

3. 인코딩 인식 (InputStreamReader)
   - 바이트 → 문자
   - 인코딩 매핑 적용
   - "안" (UTF-8 3바이트) → '안' (1 char)

4. 자바 내부 (String, char)
   - UTF-16 (BMP) 또는 UTF-16 surrogate (확장)
   - 항상 일관된 표현

5. 출력 (OutputStreamWriter)
   - 문자 → 바이트
   - 다시 인코딩 매핑

핵심:
  - 입력/출력: 바이트 + 인코딩
  - 내부: 항상 char (UTF-16)

5.6 자바 String 의 내부

// Java 8 까지: char[] (UTF-16)
String s = "안녕";
// 내부: ['안', '녕'] (각 char = 2바이트)
// 4바이트

// Java 9+ (JEP 254): Compact Strings
// - 영문만: byte[] (Latin-1, 1바이트)
// - 비영문 포함: byte[] (UTF-16, 2바이트)
// - 동일 API, 내부 최적화

String ascii = "Hello";
// Java 9+: byte[] (Latin-1, 5바이트)

String korean = "안녕";
// Java 9+: byte[] (UTF-16, 4바이트)

5.7 인코딩 깨짐의 일반 패턴

깨짐의 일반 패턴:

1. 시스템 인코딩 차이 (Windows ↔ Linux)
   - 해결: 명시적 인코딩

2. 잘못된 디코딩
   - UTF-8 바이트를 EUC-KR 로
   - 해결: 인코딩 확인

3. 1바이트씩 처리
   - read() 결과를 char 로 직접
   - 해결: Reader 사용

4. 중간 변환
   - 인코딩 변환 시 손실
   - 해결: 단일 인코딩

5. HTTP/XML 의 charset 무시
   - Content-Type 헤더 확인 안 함
   - 해결: 명시적 charset

5.8 ILIC 의 인코딩 처리

// 1. 파일 처리 (명시적 UTF-8)
try (BufferedReader reader = Files.newBufferedReader(
        Path.of("shipments.csv"), StandardCharsets.UTF_8)) {
    String line;
    while ((line = reader.readLine()) != null) {
        // 한글 안전하게 처리
    }
}

// 2. HTTP 응답
@GetMapping(value = "/api/shipments", produces = "application/json;charset=UTF-8")
public ShipmentResponse get() {
    return new ShipmentResponse("한글 데이터");
}

// 3. DB 인코딩
// application.yml
// spring:
//   datasource:
//     url: jdbc:postgresql://localhost:5432/ilic?characterEncoding=UTF-8

// 4. 콘솔 출력
// JVM 옵션
// -Dfile.encoding=UTF-8
// -Dconsole.encoding=UTF-8

5.9 자기 점검 답변

한글이 깨지는 정확한 메커니즘은?

:
1. 1바이트씩 처리:

  • 한글 1글자 = 3바이트 (UTF-8)
  • 각 바이트 → char 로 변환하면 깨짐
  1. 잘못된 인코딩:

    • UTF-8 ↔ EUC-KR 혼동
    • 같은 바이트가 다른 문자
  2. 시스템 차이:

    • Windows MS949 vs Linux UTF-8
    • 기본 인코딩 의존 시 위험
  3. 해결:

    • Reader 사용
    • 명시적 인코딩 (StandardCharsets)
    • 일관된 인코딩 (UTF-8)
  4. 자바 내부:

    • String 은 UTF-16 (Java 8) 또는 byte[] (Java 9+)
    • 입출력 시점만 인코딩 처리

6️⃣ Reader/Writer 로 해결

6.1 Reader 의 정의

public abstract class Reader implements Closeable, Readable {
    
    public abstract int read(char[] cbuf, int off, int len) throws IOException;
    public int read() throws IOException;
    public int read(char[] cbuf) throws IOException;
    public boolean ready() throws IOException;
    public abstract void close() throws IOException;
    
    // skip, mark, reset 등
}

핵심:

  • 문자 (char) 단위 처리
  • 인코딩 인식
  • InputStream 과 비슷한 구조

6.2 InputStreamReader

// InputStream → Reader 변환
InputStreamReader isr = new InputStreamReader(
    System.in,
    StandardCharsets.UTF_8);   // ★ 인코딩 명시

// 한 문자씩 (UTF-16, 한글 1글자 = 1 char)
int c = isr.read();
char ch = (char) c;

// 한글 입력 "안":
// 1. 사용자가 "안" 입력 (UTF-8 3바이트)
// 2. InputStream 이 3바이트 받음
// 3. InputStreamReader 가 UTF-8 디코딩
// 4. char '안' (1 문자) 반환

6.3 BufferedReader

// BufferedReader = Reader + 버퍼링 + readLine
BufferedReader reader = new BufferedReader(
    new InputStreamReader(System.in, StandardCharsets.UTF_8));

// 한 줄씩
String line = reader.readLine();
// 줄바꿈 (\n) 또는 EOF 까지

// 효율:
// - 버퍼링으로 OS 호출 ↓
// - readLine 으로 줄 단위 편의

6.4 Reader 의 read() 와 InputStream 의 read() 비교

// InputStream.read() — 1바이트
InputStream in = ...;
int b = in.read();   // 0~255 (byte), -1 (EOF)

// Reader.read() — 1문자 (UTF-16 단위)
Reader reader = ...;
int c = reader.read();   // 0~65535 (char), -1 (EOF)

// 차이:
// - InputStream: byte
// - Reader: char (16비트)
// - Reader 가 인코딩 처리

// 한글 처리:
// InputStream: 한글 1글자 = 3번 read
// Reader: 한글 1글자 = 1번 read

6.5 Writer 의 정의

public abstract class Writer implements Closeable, Flushable, Appendable {
    
    public abstract void write(char[] cbuf, int off, int len) throws IOException;
    public void write(int c) throws IOException;
    public void write(char[] cbuf) throws IOException;
    public void write(String str) throws IOException;
    public void write(String str, int off, int len) throws IOException;
    
    public abstract void flush() throws IOException;
    public abstract void close() throws IOException;
}

특징:

  • 문자 단위 쓰기
  • 인코딩 적용
  • write(String) 편의

6.6 OutputStreamWriter

// OutputStream → Writer 변환
OutputStreamWriter osw = new OutputStreamWriter(
    System.out,
    StandardCharsets.UTF_8);

osw.write("안녕하세요\n");
osw.flush();   // 강제 출력

// 동작:
// 1. 사용자가 String 쓰기
// 2. OutputStreamWriter 가 UTF-8 인코딩
// 3. 바이트 시퀀스로 변환
// 4. OutputStream 에 쓰기

// 또는 BufferedWriter
BufferedWriter writer = new BufferedWriter(
    new OutputStreamWriter(System.out, StandardCharsets.UTF_8));
writer.write("한글\n");
writer.flush();

6.7 Reader/Writer 계층

java.io 의 Reader/Writer 계층:

Reader (추상)
  ├── InputStreamReader
  │   └── FileReader
  ├── BufferedReader
  ├── StringReader
  ├── CharArrayReader
  ├── PipedReader
  └── FilterReader
      └── PushbackReader

Writer (추상)
  ├── OutputStreamWriter
  │   └── FileWriter
  ├── BufferedWriter
  ├── StringWriter
  ├── CharArrayWriter
  ├── PipedWriter
  ├── PrintWriter
  └── FilterWriter

6.8 InputStream → Reader 변환 종합

// 가장 일반적 패턴
InputStream is = ...;

// 1. InputStreamReader 로 변환
Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8);

// 2. BufferedReader 로 감싸기 (효율 + readLine)
BufferedReader br = new BufferedReader(reader);

// 한 번에
BufferedReader br = new BufferedReader(
    new InputStreamReader(is, StandardCharsets.UTF_8));

// 또는 Files (NIO.2, 가장 권장)
BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8);

6.9 ILIC 의 Reader/Writer 활용

// 1. CSV 파일 한글 읽기
public List<Shipment> readShipments(Path file) throws IOException {
    List<Shipment> shipments = new ArrayList<>();
    
    try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
        String line;
        reader.readLine();   // 헤더
        while ((line = reader.readLine()) != null) {
            shipments.add(parseShipment(line));
            // line 은 한글 정상 처리
        }
    }
    
    return shipments;
}

// 2. CSV 파일 한글 쓰기
public void writeShipments(Path file, List<Shipment> shipments) throws IOException {
    try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) {
        writer.write("ID,선적번호,수하인,중량\n");
        for (Shipment s : shipments) {
            writer.write(s.getId() + ",");
            writer.write(s.getBlNo() + ",");
            writer.write(s.getConsignee() + ",");   // 한글 OK
            writer.write(s.getWeight() + "\n");
        }
    }
}

// 3. HTTP 클라이언트 (한글 응답)
public String fetchData(String url) throws IOException {
    HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
    
    try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
        return reader.lines().collect(Collectors.joining("\n"));
    }
}

6.10 자기 점검 답변

Reader/Writer 의 본질과 활용은?

:
1. 본질:

  • 문자 단위 처리
  • 인코딩 인식
  • byte → char 변환
  1. 계층:

    • InputStreamReader: InputStream → Reader
    • BufferedReader: 버퍼링 + readLine
    • FileReader: File 전용 (인코딩 명시 어려움)
  2. 활용:

    BufferedReader reader = new BufferedReader(
        new InputStreamReader(input, StandardCharsets.UTF_8));
  3. 권장:

    • 텍스트 파일: Files.newBufferedReader
    • 명시적 인코딩 (UTF-8)
    • BufferedReader.readLine
  4. InputStream vs Reader:

    • InputStream: 바이트
    • Reader: 문자 (인코딩 적용)

7️⃣ Scanner, BufferedReader, Console 비교

7.1 세 가지 도구

표준 입력 읽기의 3가지 도구:

1. BufferedReader
   - Reader 기반
   - readLine 으로 한 줄씩
   - 효율적, 표준

2. Scanner (Java 5+)
   - 간편한 API
   - 토큰 단위 (next, nextInt 등)
   - 약간 느림

3. Console (Java 6+)
   - 표준 입력의 특화
   - readPassword 의 안전한 비밀번호
   - IDE 에선 null 가능

7.2 BufferedReader 의 활용

// 가장 표준적
BufferedReader reader = new BufferedReader(
    new InputStreamReader(System.in, StandardCharsets.UTF_8));

System.out.print("Name: ");
String name = reader.readLine();   // 한 줄

System.out.print("Age: ");
int age = Integer.parseInt(reader.readLine());

System.out.print("Items: ");
String[] items = reader.readLine().split(",");

7.3 Scanner 의 활용

// 간편함
Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8);

System.out.print("Name: ");
String name = scanner.nextLine();   // 한 줄

System.out.print("Age: ");
int age = scanner.nextInt();   // 정수 자동 파싱

System.out.print("Price: ");
double price = scanner.nextDouble();   // 실수

System.out.print("Active: ");
boolean active = scanner.nextBoolean();

// 토큰
String token = scanner.next();   // 공백 구분 한 단어

7.4 Scanner 의 함정

// 함정: nextInt + nextLine
Scanner scanner = new Scanner(System.in);

System.out.print("Age: ");
int age = scanner.nextInt();   // 사용자가 "25" + Enter 입력
// nextInt 가 25 만 가져옴, \n 은 버퍼에 남음

System.out.print("Name: ");
String name = scanner.nextLine();   // ★ 빈 문자열 반환!
// 버퍼의 \n 을 읽음

// 해결: nextInt 후 nextLine 한 번 추가
int age = scanner.nextInt();
scanner.nextLine();   // \n 소비
String name = scanner.nextLine();   // 이제 정상

7.5 Console 의 특별 기능

// 비밀번호 안전 입력 (화면에 표시 안 됨)
Console console = System.console();

if (console != null) {
    String username = console.readLine("Username: ");
    char[] password = console.readPassword("Password: ");
    
    // password 는 char[] (보안)
    // 사용 후 명시적 cleanup
    Arrays.fill(password, '\0');
}

// IDE 에서는 null 가능
// 명령줄 (터미널) 에서만 정상

// printf 도 지원
console.printf("Hello, %s!%n", username);

// reader, writer 도
Reader reader = console.reader();
PrintWriter writer = console.writer();

7.6 세 가지 비교

항목BufferedReaderScannerConsole
시기Java 1.1Java 5Java 6
한 줄 읽기readLine()nextLine()readLine()
정수 읽기parseIntnextInt()parseInt
인코딩 명시(시스템)
비밀번호XX✓ (안전)
속도빠름느림보통
패턴표준간편보안
IDEnull 가능

7.7 어느 것을 언제?

선택 가이드:

BufferedReader 권장:
  ✓ 표준적
  ✓ 빠름
  ✓ 인코딩 명시 필요
  ✓ 한 줄 단위 처리

Scanner 권장:
  ✓ 알고리즘 문제 (코딩 테스트)
  ✓ 간편함 우선
  ✓ 다양한 타입 파싱
  ✗ 성능 중요 시 X

Console 권장:
  ✓ 비밀번호 입력
  ✓ 보안
  ✓ 터미널 전용 (IDE 에선 X)

7.8 ILIC 의 활용

// 1. 일반 콘솔 도구 — BufferedReader
public class ShipmentTool {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(System.in, StandardCharsets.UTF_8));
        
        System.out.print("Shipment ID: ");
        Long id = Long.parseLong(reader.readLine());
        
        Shipment s = service.findById(id);
        System.out.println(s);
    }
}

// 2. 알고리즘/배치 — Scanner
public class BatchProcessor {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        for (int i = 0; i < n; i++) {
            Long id = sc.nextLong();
            process(id);
        }
    }
}

// 3. 운영 도구 (비밀번호) — Console
public class AdminTool {
    public static void main(String[] args) {
        Console console = System.console();
        if (console == null) {
            System.err.println("Run in terminal!");
            System.exit(1);
        }
        
        String user = console.readLine("Username: ");
        char[] pass = console.readPassword("Password: ");
        
        try {
            authenticate(user, pass);
        } finally {
            Arrays.fill(pass, '\0');   // 메모리 cleanup
        }
    }
}

7.9 자기 점검 답변

Scanner, BufferedReader, Console 의 비교는?

:
1. BufferedReader:

  • 표준, 빠름
  • readLine
  • 인코딩 명시 가능
  • 가장 권장
  1. Scanner:

    • Java 5+
    • 간편 (next, nextInt 등)
    • 약간 느림
    • 알고리즘 문제
  2. Console:

    • Java 6+
    • readPassword (안전)
    • IDE 에선 null
    • 비밀번호 입력

Scanner 의 함정:

  • nextInt + nextLine
  • \n 버퍼에 남음
  • nextLine 추가 호출

8️⃣ Java 18+ UTF-8 기본화와 실무

8.1 JEP 400 (Java 18+)

JEP 400: UTF-8 by Default

  Java 18 (2022) 부터 기본 인코딩이 UTF-8 로 통일.

이전 (Java 17 이하):
  - Windows 한국어: x-windows-949 (MS949)
  - Linux/Mac: UTF-8
  - 시스템 의존, Cross-platform 문제

이후 (Java 18+):
  - 모든 OS: UTF-8 통일
  - file.encoding=UTF-8 가 기본
  - Cross-platform 일관성

8.2 영향 받는 API

// 기본 인코딩 사용 API
new FileReader("file.txt")           // 기본 인코딩
new FileWriter("file.txt")
new PrintStream(new FileOutputStream(...))
new InputStreamReader(in)            // 기본 인코딩
new OutputStreamWriter(out)
String.getBytes()                    // 기본 인코딩
new String(bytes)                    // 기본 인코딩

Scanner(InputStream)                 // 기본 인코딩

// Java 18+ 부터 이 모두가 UTF-8

8.3 호환성

// Java 18+ 에서 옛 동작 복원
// JVM 옵션
// -Dfile.encoding=COMPAT
// 또는 -Dfile.encoding=MS949

// 시스템 프로퍼티 확인
String enc = System.getProperty("file.encoding");
// Java 18+: "UTF-8" (기본)

Charset.defaultCharset();   // UTF-8

Charset systemDefault = Charset.forName(System.getProperty("file.encoding"));

8.4 명시적 인코딩이 여전히 권장

// Java 18+ 라도 명시적 인코딩 권장

// ❌ 의존성 있음
new FileReader("file.txt");

// ✓ 명시적
new FileReader("file.txt", StandardCharsets.UTF_8);

// ✓ NIO.2 (가장 권장)
Files.newBufferedReader(Path.of("file.txt"), StandardCharsets.UTF_8);

// 이유:
// 1. 다른 JVM 버전 호환
// 2. 의도 명확
// 3. 다른 인코딩 필요 시 대비

8.5 실무 권장 패턴

// 1. 항상 StandardCharsets 사용
import static java.nio.charset.StandardCharsets.UTF_8;

Files.newBufferedReader(path, UTF_8);
Files.newBufferedWriter(path, UTF_8);
Files.writeString(path, content, UTF_8);
Files.readString(path, UTF_8);

// 2. HTTP 통신
@RestController
public class Controller {
    
    @GetMapping(value = "/api", produces = "application/json;charset=UTF-8")
    public Response get() { ... }
    
    @PostMapping(consumes = "application/json;charset=UTF-8")
    public void post(@RequestBody Request req) { ... }
}

// 3. DB 인코딩
// application.yml
// spring:
//   datasource:
//     url: jdbc:postgresql://localhost:5432/db?characterEncoding=UTF-8

// 4. JVM 옵션 (확실히)
// -Dfile.encoding=UTF-8
// -Dconsole.encoding=UTF-8

8.6 자주 발생하는 인코딩 문제 종합

실무 시나리오:

1. CSV 파일 한글 깨짐
   - 원인: Excel 이 BOM 없는 UTF-8 못 읽음
   - 해결: BOM 추가 또는 EUC-KR

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

3. DB 조회 한글 깨짐
   - 원인: DB 인코딩 vs 클라이언트 인코딩
   - 해결: 통일 (UTF-8 권장)

4. 로그 파일 한글 깨짐
   - 원인: 로그 프레임워크의 기본 인코딩
   - 해결: 명시적 인코딩 설정

5. 파일명 한글 깨짐
   - 원인: 파일 시스템의 인코딩
   - 해결: -Dfile.encoding=UTF-8

8.7 BOM (Byte Order Mark)

BOM:
  - UTF-8 BOM: 0xEF 0xBB 0xBF
  - 파일이 UTF-8 임을 표시
  - 일부 도구 (Excel 등) 가 인식

장단점:
  + Excel 에서 UTF-8 한글 정상
  - 일부 도구 (스크립트 파서 등) 가 BOM 을 데이터로 인식

자바에서:
// BOM 쓰기
Files.write(path, new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF});
Files.write(path, content.getBytes(UTF_8), StandardOpenOption.APPEND);

// BOM 처리 라이브러리: Apache Commons IO 의 BOMInputStream

8.8 ILIC 의 인코딩 종합 설정

// 1. JVM 옵션 (모든 서비스 통일)
// -Dfile.encoding=UTF-8

// 2. application.yml
spring:
  http:
    encoding:
      charset: UTF-8
      enabled: true
      force: true
  datasource:
    url: jdbc:postgresql://...?characterEncoding=UTF-8

// 3. 코드에서 명시
public class Constants {
    public static final Charset CHARSET = StandardCharsets.UTF_8;
}

// 4. 일관된 사용
Files.writeString(path, content, Constants.CHARSET);
new String(bytes, Constants.CHARSET);

// 5. Excel 호환 (BOM)
public void exportForExcel(Path path, String csv) throws IOException {
    try (OutputStream os = Files.newOutputStream(path)) {
        os.write(new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF});   // BOM
        os.write(csv.getBytes(StandardCharsets.UTF_8));
    }
}

8.9 자기 점검 답변

Java 18+ 의 인코딩 변화와 실무 권장은?

:
1. JEP 400 (Java 18+):

  • 기본 인코딩 UTF-8 통일
  • 모든 OS
  • Cross-platform 일관성
  1. 호환성:

    • 옛 동작: -Dfile.encoding=COMPAT
    • 명시적 인코딩 여전히 권장
  2. 실무 권장:

    • StandardCharsets.UTF_8 명시
    • HTTP charset=UTF-8
    • DB 인코딩 UTF-8
    • JVM 옵션 -Dfile.encoding=UTF-8
  3. 흔한 문제:

    • CSV Excel (BOM 필요)
    • HTTP charset 누락
    • DB ↔ 클라이언트 차이
    • 로그 인코딩

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
System.in 의 타입?InputStream
System.in 의 특성?Blocking, line-buffered, 단일
read() 의 반환 int?byte (-1) 충돌 회피, 0~255
한글이 깨지는 이유?1바이트씩 + 인코딩 무시
UTF-8 한글 크기?3바이트
EUC-KR vs UTF-8?2바이트 (한국 전용) vs 1~4바이트 (글로벌)
InputStream vs Reader?byte vs char, 인코딩 처리
BufferedReader 의 효과?버퍼링 + readLine
Scanner 의 nextInt 함정?\n 버퍼 남음
Console 의 특별 기능?readPassword (안전)
Java 18+ 인코딩?UTF-8 통일
자바 String 의 내부?UTF-16 (Java 8), byte[] (Java 9+)

9.2 자기 점검 체크리스트

System.in

  • System.in 의 타입
  • Blocking + line-buffered
  • System.in/out/err 차이
  • 리다이렉트

InputStream.read

  • 1바이트 한계
  • int 반환 이유
  • read(byte[]) 의 반환

인코딩

  • ASCII, EUC-KR, UTF-8 차이
  • 한글 표현 크기
  • 자바 기본 인코딩

한글 처리

  • 깨짐의 메커니즘
  • Reader/Writer 의 역할
  • InputStreamReader 의 인코딩 명시

도구

  • BufferedReader
  • Scanner + 함정
  • Console + 비밀번호

Java 18+

  • UTF-8 기본화
  • 명시적 인코딩 권장
  • 실무 패턴

9.3 추가 심화 질문

Q1: System.in 을 close 하면?

답:

  • 표준 입력 종료
  • 다시 못 엺
  • 자바의 다른 입력 도구도 못 씀
  • 절대 close 하지 말 것
// ❌ 위험
try (InputStream in = System.in) {
    // ...
}
// try-with-resources 가 close 호출
// 표준 입력 영구 종료

Q2: Scanner 가 느린 이유?

답:

  • 정규 표현식 기반 토큰 파싱
  • 매 호출마다 패턴 매칭
  • BufferedReader 가 단순 줄 단위
  • 알고리즘 문제에서 BufferedReader 권장

Q3: 자바 String 의 length() 의 단위?

답:

String s = "안녕";
s.length();   // 2 (문자 수, UTF-16 unit)

String emoji = "😀";
emoji.length();   // 2 (서로게이트 쌍, 4바이트 문자)

// 진짜 문자 수
emoji.codePointCount(0, emoji.length());   // 1

Q4: 한글 1글자가 항상 3바이트?

답:

  • UTF-8 기준: 한글 일반적으로 3바이트
  • BMP 외 문자 (드문 한자 등): 4바이트
  • UTF-16 기준: 2바이트 (BMP)
  • EUC-KR: 2바이트
  • 인코딩마다 다름

Q5: 시스템 인코딩 확인 방법?

답:

// 1. 자바 코드
System.getProperty("file.encoding");   // JVM 의 기본 인코딩
Charset.defaultCharset();
System.console().charset();   // Console 의 charset (Java 17+)

// 2. 쉘
$ locale   # Linux/Mac
$ chcp     # Windows

// 3. JVM 옵션
-Dfile.encoding=UTF-8
-Dconsole.encoding=UTF-8

🎯 핵심 요약 — 3줄 정리

1. System.in 의 정체

  • InputStream 인스턴스 (표준 입력)
  • 1바이트씩 read, Blocking
  • 닫지 말 것

2. 한글이 깨지는 이유

  • read() 가 1바이트
  • 한글 = 3바이트 (UTF-8) 인데 따로 처리
  • 해결: Reader + 명시적 인코딩

3. 권장 패턴

  • BufferedReader + InputStreamReader + UTF-8
  • Files.newBufferedReader (NIO.2)
  • Java 18+ 부터 UTF-8 기본, 그래도 명시적

📚 다음으로...

Unit 8.2 — FileInputStream

이번 Unit에서 System.in 과 인코딩을 봤다면, 다음은 파일 바이트 스트림.

  • FileInputStream 의 정의
  • read() 의 동작
  • 파일 끝 (-1)
  • try-with-resources 활용

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 (한글 쓰기)

3주차 누적 진행

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

총: 32/43 Unit (약 74%)
profile
Software Developer

0개의 댓글