CodeGym /행동 /JAVA 25 SELF /인코딩이란 무엇이며 왜 필요한가

인코딩이란 무엇이며 왜 필요한가

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

1. 인코딩의 개념 (encoding)

가장 중요한 질문부터 시작해 봅시다: 인코딩이란 무엇일까요?

국제 학회에 왔다고 상상해 보세요. 모두가 각자 다른 언어로 말하지만 서로 이해해야 합니다. 이를 위해 “번역가”가 필요합니다. 예를 들어 "hello"는 영어, "privet"는 러시아어 인사말의 라틴 표기, "hola"는 스페인어 인사말이죠. 컴퓨터 세계에서 인코딩은 바로 그 번역가와 같습니다.

인코딩은 문자를 바이트로 표현하는 방법입니다

컴퓨터는 단순합니다. 0과 1, 즉 비트와 바이트만 이해합니다. 하지만 사람들은 글자, 숫자, 이모지, 심지어 — 오, 이런! — 한자까지 보고 싶어 하죠. 컴퓨터가 문자를 “기록”하려면 각 문자에 어떤 바이트들의 조합을 대응시킬지 합의해야 합니다.

인코딩(encoding)은 문자(글자, 숫자, 구두점, 이모지 등)를 저장·전송을 위한 바이트로 바꾸는 규칙 집합이며, 그 반대로 바이트를 화면에 표시할 문자로 되돌리는 규칙이기도 합니다.

예: 글자 'A' (Cyrillic, U+0410)를 다양한 인코딩에서

  • UTF-8 인코딩에서 'A' (Cyrillic, U+0410)는 두 바이트로 인코딩됩니다: 0xD0 0x90.
  • Windows-1251에서는 같은 글자가 1바이트입니다: 0xC0.
  • 반면 라틴 문자 'A'는 거의 모든 인기 인코딩에서 0x41입니다.

파일을 잘못된 인코딩으로 읽으면 문자가 “문자 깨짐”으로 보이거나 물음표로 바뀝니다.

2. 왜 인코딩이 필요한가

그냥 글자를 있는 그대로 저장하면 안 되나요?

컴퓨터는 숫자(0과 1)만 이해합니다. 어떤 숫자가 어떤 글자에 대응하는지가 바로 인코딩의 핵심입니다.

예: 디스크에 있는 “Privet”

파일에 "Privet"라는 단어를 쓸 때, 컴퓨터에게 그것은 단지 바이트의 나열일 뿐입니다. 그 바이트를 어떻게 해석할지는 인코딩에 달려 있습니다.

  • 파일이 UTF-8로 저장되었다면, 키릴 문자 ‘P’(U+041F)는 2바이트이고, ‘r’(U+0440)도 2바이트입니다.
  • Windows-1251이라면 각 글자가 1바이트지만 바이트 값은 다릅니다.

인코딩이 필요한 곳

  • 텍스트를 파일에 기록할 때: 나중에 바르게 읽을 수 있도록.
  • 파일에서 텍스트를 읽을 때: 바이트를 다시 글자로 되돌리기 위해.
  • 네트워크로 텍스트를 보낼 때(예: HTTP, e‑mail).
  • 데이터베이스를 사용할 때: 텍스트가 어떤 인코딩으로 저장되는지 알아야 합니다.

인코딩을 지정하지 않으면...

낯선 언어의 텍스트를 펼쳐놓고 읽으려는 것과 같습니다. 그 언어를 알면 확률이 올라가지만, 모르고 시도하면 — 잘해야 아무것도 이해하지 못하고, 최악의 경우 엉망인 결과를 얻습니다.

3. 올바른 인코딩이 없을 때의 문제

문자 깨짐과 데이터 손실

초보(그리고 숙련자도) 프로그래머가 가장 자주 하는 하소연: “왜 "Privet" 대신 "Привет" 또는 물음표만 보이나요?”

이것은 파일이 한 인코딩으로 기록되었는데 다른 인코딩으로 읽히기 때문입니다. 예를 들어 오래된 Windows에서 Windows-1251로 만든 파일을 기본값이 UTF-8인 Linux에서 열거나, 그 반대의 경우입니다.

예

  • Windows-1251로 파일을 저장: 키릴 문자 ‘P’(U+041F)에 대한 바이트는 0xCF.
  • UTF-8로 열기: 프로그램은 키릴 문자가 두 바이트일 거라 기대하지만 실제로는 한 바이트를 받습니다. 모든 것이 꼬입니다.

데이터 손실

선택한 인코딩이 특정 문자를 지원하지 않으면(예: ASCII에 이모지를 저장하려는 경우) 그 문자는 사라지거나 물음표로 대체됩니다. 인코딩에 “들어가지 않는” 것은 모두 잃어버리게 됩니다.

파일 교환 시 문제

한 인코딩으로 저장된 파일은 기본 인코딩이 다른 컴퓨터에서 올바르게 표시되지 않을 수 있습니다. 특히 Windows와 Linux 사이에서 파일을 주고받거나 오래된 파일을 열 때 자주 발생합니다.

4. Java에서의 인코딩: 내부와 외부 표현

JVM 내부: 항상 Unicode (UTF-16)

Java에서 문자열(String)은 프로그램 내부에 항상 Unicode(정확히는 UTF-16)으로 저장됩니다. 즉, 어떤 언어의 문자열이든 변수에 안심하고 할당할 수 있으며, Java는 이를 잘 처리합니다.

String hello = "안녕, 세계! 😀";

JVM 메모리에서는 이 텍스트가 16비트 정수(char)들의 집합으로 저장되며, 각 문자에는 Unicode 표의 고유 코드가 대응됩니다.

흥미로운 사실
Java에서 char는 16비트(2바이트)입니다. 하지만 일부 문자(희귀 한자나 이모지 등)는 char 두 개가 필요합니다 — 이를 “서로게이트 페어”라고 합니다.

입출력 시: 인코딩이 중요합니다!

문자열을 읽거나 기록하여 외부 세계(파일, 네트워크)와 주고받을 때, Java는 내부 표현(UTF-16)을 바이트 시퀀스로 변환해야 합니다. 이때 인코딩이 필요합니다.

  • 인코딩을 명시하지 않으면 Java는 시스템 기본값을 사용합니다(러시아어 Windows에서는 Windows-1251, Linux에서는 UTF-8일 수 있음).
  • 이는 위험합니다. 다른 컴퓨터에서는 결과가 달라질 수 있습니다.

예: 인코딩을 지정하지 않고 파일을 읽고 쓰기

// 나쁜 예! 인코딩을 지정하지 않았습니다.
FileReader reader = new FileReader("data.txt");
FileWriter writer = new FileWriter("data.txt");

이 경우 Java는 시스템 인코딩을 사용합니다. 파일이 다른 시스템에서 기록되었다면 문자 깨짐을 보게 됩니다.

좋은 예: 항상 인코딩을 명시하세요

// 좋습니다! 인코딩을 명시했습니다.
BufferedReader reader = new BufferedReader(
    new InputStreamReader(new FileInputStream("data.txt"), StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(
    new OutputStreamWriter(new FileOutputStream("data.txt"), StandardCharsets.UTF_8));

5. 간단한 그림: 인코딩이 동작하는 과정

도식: 파일에서 프로그램으로, 다시 파일로

[디스크의 파일(바이트, 인코딩 X)]
        |
        V
[Java가 바이트를 읽고 인코딩 X로 String(UTF-16)으로 변환]
        |
        V
[프로그램에서 문자열로 작업]
        |
        V
[Java가 인코딩 Y를 사용해 String을 바이트로 기록]
        |
        V
[디스크의 파일(바이트, 인코딩 Y)]

X와 Y가 같으면 — 모든 것이 좋습니다. 다르면 — 문제가 생길 수 있습니다.

6. 인코딩의 간단한 역사(호기심 많은 분들을 위해)

ASCII

ASCII는 가장 오래된 인코딩 중 하나로, 문자당 1바이트이며 영어 알파벳, 숫자, 기본 기호만 포함합니다. 다른 문자 체계는 포함하지 않습니다.

Windows-1251, ISO-8859-1 등 ‘올드’ 인코딩

이는 서로 다른 문자 집합(키릴 문자, 라틴 문자, 그리스 문자 등)을 위한 단일 바이트 인코딩입니다. 각자 자기 인코딩을 선택했고, 그 결과 혼란이 시작되었습니다.

Unicode와 UTF 계열

  • Unicode는 전 세계 문자를 위한 전역 테이블입니다.
  • UTF-8, UTF-16, UTF-32는 Unicode 문자를 바이트로 표현하는 서로 다른 방식입니다.
  • UTF-8은 웹, 파일, 시스템 간 교환의 표준이 되었습니다.

7. 실습: 인코딩이 파일 작업에 미치는 영향

서로 다른 인코딩으로 문자열을 기록하고 읽는 간단한 예제를 보겠습니다.

예: 서로 다른 인코딩으로 기록하고 읽기

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class EncodingDemo {
    public static void main(String[] args) throws IOException {
        String text = "안녕, 세계! 😀";

        // UTF-8로 파일을 기록합니다
        try (Writer writer = new OutputStreamWriter(
                new FileOutputStream("utf8.txt"), StandardCharsets.UTF_8)) {
            writer.write(text);
        }

        // 이제 잘못된 인코딩으로 읽어 봅시다
        try (Reader reader = new InputStreamReader(
                new FileInputStream("utf8.txt"), Charset.forName("Windows-1251"))) {
            int c;
            while ((c = reader.read()) != -1) {
                System.out.print((char) c);
            }
        }
        // 화면에는 깨진 문자가 출력됩니다!
    }
}

결론: 인코딩이 일치하지 않으면 텍스트가 훼손됩니다.

8. 인코딩과 타 시스템 통합

현실의 프로젝트에서는 서로 다른 언어와 운영체제를 사용하는 다양한 프로그램 사이에 파일을 주고받는 일이 흔합니다. 각 프로그램은 자신만의 인코딩을 기대할 수 있습니다. 사전에 합의하지 않으면 “문자 깨짐”과 잡기 어려운 버그를 맞이하게 됩니다. 전형적인 사례: 데이터베이스는 텍스트를 UTF-8로 저장하는데, 프로그램은 원본 파일을 Windows-1251로 읽어 DB에 적재—문자 훼손이 보장됩니다.

9. 인코딩 작업에서 흔한 실수

실수 №1: 파일을 읽고/쓸 때 인코딩을 지정하지 않음.
결과적으로 프로그램은 “내 컴퓨터에서는 잘 되는데”, 동료의 컴퓨터에서는 “문자 깨짐”이 발생합니다.

실수 №2: 오래된 생성자(FileReader, FileWriter) 사용.
항상 시스템 인코딩을 사용하므로 — 초보자에게는 함정입니다.

실수 №3: 소스 파일의 인코딩이 잘못됨.
파일이 한 인코딩으로 기록되었는데 다른 인코딩으로 읽히면 일부 문자가 훼손되거나 물음표로 대체됩니다.

실수 №4: 인코딩 변환 중 문자 손실.
대상 인코딩이 모든 문자를 지원하지 않으면(예: UTF-8 대신 ASCII) 텍스트 일부가 그대로 사라집니다.

1
과제
JAVA 25 SELF, 레벨 37, 레슨 0
잠금
전송을 위한 비밀 메시지 준비 🕵️‍♂️
전송을 위한 비밀 메시지 준비 🕵️‍♂️
1
과제
JAVA 25 SELF, 레벨 37, 레슨 0
잠금
마법의 두루마리 저장 및 복원 📜
마법의 두루마리 저장 및 복원 📜
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION