CodeGym /행동 /JAVA 25 SELF /인코딩 불일치 문제와 흔한 오류

인코딩 불일치 문제와 흔한 오류

JAVA 25 SELF
레벨 37 , 레슨 3
사용 가능

1. 오류의 증상

이상적인 세계에서는 프로그래머가 항상 파일의 인코딩을 알고, 읽을 때 올바르게 지정합니다. 하지만 현실에서는 파일이 Windows, Linux, 서버, 각종 편집기 사이를 오가며 각자 다른 방식으로 바이트를 해석합니다. 그 결과 다음과 같은 증상을 마주하게 됩니다.

  • “깨진 문자” — 기대한 텍스트 대신 낯선 기호, 물음표, 네모 박스, 혹은 어떤 언어로도 보이지 않는 글자 조합이 나타납니다.
  • 문자 손실 — 텍스트의 일부가 사라지거나 ?로 대체됩니다.
  • 예외 발생 — 예를 들어 MalformedInputException처럼 Java가 선택한 인코딩으로 바이트를 “소화”하지 못할 때 발생합니다.
  • 파싱 오류 — 텍스트가 훼손되어 키워드나 구조가 깨지면 프로그램이 파일을 제대로 처리하지 못합니다.

다음은 키릴 문자(Cyrillic)가 들어 있는 파일을 잘못된 인코딩으로 읽을 때의 전형적인 “깨진 문자” 예시입니다.

Ozhidali:  Privet, mir!
Poluchili: Привет, мир

이것은 새로운 언어가 아니라, 필요한 “사전”(인코딩)과 다르게 바이트가 해석된 결과입니다.

2. 왜 오류가 발생하는가: 근본 원인

파일은 한 인코딩으로 저장되고, 다른 인코딩으로 읽힐 때

누군가가 파일을 Windows-1251로 저장했는데, 당신이 이를 UTF-8로 연다고 가정해 봅시다. Java는 UTF-8 규칙에 따라 바이트를 해독하려고 애쓰지만, 바이트 값이 기대와 맞지 않기 때문에 결국 의미 없는 결과가 됩니다.

시스템 “기본값” 인코딩 사용

인코딩을 명시하지 않으면, Java는 시스템 인코딩 — 즉 컴퓨터에 설정된 기본 인코딩을 사용합니다. Windows의 러시아 로캘에서는 Windows-1251, Linux에서는 UTF-8, Mac에서도 UTF-8일 수 있습니다. 내 컴퓨터에서 잘 열리던 파일이 다른 OS를 쓰는 동료의 컴퓨터에서는 읽히지 않을 수 있습니다.

구식 생성자 사용

오래된 Java 버전(그리고 일부 교재)에서는 FileReader/FileWriter 같은 구문을 자주 볼 수 있는데, 이들은 시스템 인코딩을 사용해 제어할 수 없게 만듭니다 — 이는 함정이며 “깨진 문자”의 원인이 됩니다.

FileReader reader = new FileReader("file.txt");
FileWriter writer = new FileWriter("file.txt");

BOM(Byte Order Mark)의 유무

일부 인코딩(예: UTF-8BOM 또는 UTF-16)은 파일 시작에 특수 바이트를 추가해 자신의 특성을 알립니다. 프로그램이 BOM을 기대하지 않는데 존재하거나, 반대로 기대하는데 없으면 문제가 생길 수 있습니다. 파일의 처음 몇 글자가 깨지거나, 아예 파일을 인식하지 못하는 상황이 벌어집니다.

3. 어떻게 나타나는가: 실전 분석

예제 1: Windows-1251로 저장된 키릴 문자 파일을 UTF-8로 읽기

import java.nio.file.*;
import java.nio.charset.*;

public class EncodingDemo {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("russian.txt");
        // 파일은 Windows-1251로 저장되었는데 UTF-8로 읽음 — 글자가 깨집니다!
        try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            System.out.println(reader.readLine());
        }
    }
}

결과적으로 “Privet, mir!” 대신 이상한 기호들이 보이게 됩니다.

예제 2: 파일은 UTF-8로 저장되고 ISO-8859-1로 읽을 때

try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.ISO_8859_1)) {
    System.out.println(reader.readLine());
}

결과: ASCII가 아닌 모든 문자는 쓰레기로 바뀌거나 ?로 대체됩니다.

예제 3: 파일 읽기 중 예외

바이트가 선택한 인코딩의 규칙에 맞지 않으면, Java는 다음과 같이 예외를 던질 수 있습니다.

Exception in thread "main" java.nio.charset.MalformedInputException: Input length = 1
    at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
    ...

이는 Java가 선택한 인코딩에서 정상적으로 해석할 수 없는 바이트를 만났다는 뜻입니다.

4. 진단: 인코딩 문제를 파악하는 방법

파일 인코딩을 확인하세요

  • Notepad++, VS Code, Sublime Text 같은 편집기에서는 보통 파일 인코딩을 확인하거나 변경할 수 있습니다(대개 하단 상태표시줄).
  • Linux에서는 다음 명령으로 인코딩에 대한 단서를 얻을 수 있습니다(항상 100% 정확하진 않음):
file imya_fayla.txt

Java의 시스템 인코딩을 확인하세요

콘솔에 file.encoding 시스템 속성 값을 출력합니다:

System.out.println(System.getProperty("file.encoding"));

테스트 데이터를 사용하세요

키릴 문자, 라틴 문자, 특수기호, 이모지 등 다양한 문자를 넣은 작은 파일을 만들고, 여러 인코딩으로 읽어 보면서 기대한 결과와 언제 일치하는지 확인하세요.

항상 인코딩을 명시하세요

코드에서 인코딩을 지정하지 않은 채 파일을 읽거나 쓴다면 — 의심해야 합니다. 예를 들어 “기본값” 대신 Files.newBufferedReader(..., StandardCharsets.UTF_8)을 사용하세요.

5. Best practices: 함정 피하기

규칙 №1:
항상 파일 작업 시 인코딩을 명시하세요. 특히 파일이 서로 다른 컴퓨터, OS에서 사용되거나 네트워크로 전송될 가능성이 있다면 더욱 그렇습니다.

규칙 №2:
가능하면 현대적이고 “표현력 있는” 인코딩 — 우선적으로 UTF-8(StandardCharsets.UTF_8)을 사용하세요. 특별한 요구사항(예: 레거시 시스템과의 연동)이 있는 경우에만 다른 인코딩을 사용하세요.

규칙 №3:
FileReaderFileWriter 클래스는 피하세요(인코딩을 지정할 수 없음). 대신 InputStreamReader, OutputStreamWriter 또는 Files의 메서드와 명시적 Charset을 사용하세요.

규칙 №4:
결과를 확인하세요! 다양한 인코딩을 지원하는 편집기에서 기록한 파일을 열어 텍스트가 올바르게 보이는지 검증하세요.

6. 특이 사항과 뉘앙스: BOM, XML, JSON 및 기타 “재밌는” 사례

BOM(Byte Order Mark): UTF-8 파일은 때때로 “보이지 않는” 바이트(EF BB BF)로 시작합니다. 대부분의 현대 프로그램은 이를 무시하지만, 일부는 줄 시작에 깨진 문자를 보여 주거나 파일을 거부할 수 있습니다(예: 구형 XML/JSON 파서).

XML/HTML: 파일 시작에 <?xml version="1.0" encoding="UTF-8"?> 같은 라인이 있을 수 있습니다. 이는 프로그램에 어떤 인코딩을 기대해야 하는지 알려 줍니다. 그러나 실제 인코딩이 선언과 다르면 — 다시 “깨진 문자”가 나타납니다.

JSON: 표준상 UTF-8이어야 합니다. 하지만 파일이 Windows-1251로 만들어졌다면 파서는 오류를 내거나 데이터를 왜곡시킬 것입니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION