CodeGym /행동 /JAVA 25 SELF /주요 인코딩: UTF-8, UTF-16, ISO-8859-1

주요 인코딩: UTF-8, UTF-16, ISO-8859-1

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

1. UTF-8 — 현대 인코딩의 왕

프로그래머의 업무에서는 파일, 네트워크 프로토콜, 데이터베이스, 웹‑서비스 등 다양한 인코딩이 쓰입니다. UTF-8과 ISO-8859-1의 차이를 모르면 쉽게 함정에 빠질 수 있습니다: 코드는 “내 컴퓨터에서는 잘 되지만”, 프랑스의 동료나 기본값이 다른 클라우드 환경에서는 깨질 수 있죠.

오늘 우리는 Java‑개발자의 실무에서 만나는 세 가지 주요 인코딩을 살펴봅니다:

  • UTF-8 — 현대의 범용 표준.
  • UTF-16 — Java에서 문자열의 내부 형식이며 Windows에서 자주 보입니다.
  • ISO-8859-1 (Latin-1) — 오래됐지만 여전히 쓰이는 서유럽 언어용 단일 바이트 인코딩.

이제 더 자세히 알아봅시다!

무엇인가요?

UTF-8(Unicode Transformation Format, 8-bit)은 방대한 Unicode 표의 모든 문자를 표현할 수 있는 인코딩입니다(키릴 문자, 한자, 이모지, 드문 고대 문자까지 포함). 게다가 매우 영리하게 동작합니다: 가장 널리 쓰이는 문자(영문자, 숫자, 구두점)는 단 1바이트만 사용하고, 그 밖의 문자들은 2, 3 또는 4바이트를 사용합니다.

사실: 모든 현대 웹사이트, 대부분의 JSON·XML 파일, 심지어 기본 설정의 Java 소스까지 UTF-8을 사용합니다.

문자 하나에 필요한 바이트 수는?

  • ASCII 문자(영문자, 숫자, 구두점) — 1바이트.
  • 키릴 문자, 발음 구별 기호가 있는 유럽 문자 — 2바이트.
  • 중국어·일본어·한국어의 한자 — 3바이트.
  • 매우 희소한 문자, 이모지 — 4바이트.

예:

문자 Unicode 코드 UTF-8(바이트)
A
U+0041
41
Ya
U+042F
D0 AF
€
U+20AC
E2 82 AC
😀
U+1F600
F0 9F 98 80

왜 UTF-8이 좋은가요?

  • ASCII와 하위 호환: 파일에 영어 문자만 있으면 ASCII와 완전히 동일하게 보입니다.
  • 컴팩트함: 영어로 된 짧은 텍스트라면 UTF-8이 가장 작은 파일 크기를 제공합니다.
  • 국제성: 이모지를 포함해 전 세계 모든 언어를 지원합니다.
  • 웹 표준: HTML, CSS, JSON, XML, JavaScript — 기본값이 모두 UTF-8입니다.

Java에서의 사용 예

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

public class Utf8Demo {
    public static void main(String[] args) throws Exception {
        // UTF-8로 문자열을 파일에 기록
        String text = "안녕, 세상! 😀";
        Files.write(Paths.get("utf8.txt"), text.getBytes(StandardCharsets.UTF_8));

        // UTF-8로 파일 읽기
        String read = Files.readString(Paths.get("utf8.txt"), StandardCharsets.UTF_8);
        System.out.println(read); // 출력: 안녕, 세상! 😀
    }
}

여기서는 기록 전에 문자열을 명시적으로 UTF-8로 인코딩하고, 읽을 때도 동일한 인코딩을 명시합니다. 덕분에 텍스트에 이모지나 여러 언어의 문자가 있어도 왜곡 없이 저장되고 복원됩니다.

언제 사용할까요 UTF-8?

항상 사용하세요. 다른 것을 선택해야 할 아주 타당한 이유가 있는 경우를 제외하면, 모든 현대 애플리케이션의 보편적 선택입니다.

2. UTF-16 — Java의 내부 형식

UTF-16은 Unicode 계열의 또 다른 인코딩입니다. UTF-8과 달리 보통 문자당 2바이트를 사용합니다. Java는 내부적으로 문자열(String)과 문자(char)를 JVM 안에서 이렇게 저장합니다. 하지만 주의할 점이 있습니다. 일부 문자(드문 한자나 이모지 등)는 4바이트(두 개의 ‘서러게이트 쌍’)가 필요합니다.

UTF-16은 어디에서 쓰이나요?

  • Java 내부: 모든 문자열과 문자는 UTF-16입니다.
  • Windows: Windows의 많은 시스템 파일이 UTF-16을 사용합니다(예: 메모장은 기본적으로 UTF-16 LE로 저장).
  • 일부 네트워크 프로토콜과 포맷: 예를 들어 XML은 UTF-16일 수 있습니다.

예: UTF-16에서 문자열은 어떻게 보이나요

문자 Unicode 코드 UTF-16(바이트) 설명
A
U+0041
00 41
2바이트
Ya
U+042F
04 2F
2바이트
€
U+20AC
20 AC
2바이트
😀
U+1F600
D8 3D DE 00
4바이트(서러게이트 쌍)

Java에서의 사용 예

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

public class Utf16Demo {
    public static void main(String[] args) throws Exception {
        String text = "안녕, 세상! 😀";

        // UTF-16 인코딩을 사용해 파일에 문자열 쓰기
        Files.write(Paths.get("utf16.txt"), text.getBytes(StandardCharsets.UTF_16));

        // UTF-16 인코딩을 지정해 파일 내용 읽기
        String read = Files.readString(Paths.get("utf16.txt"), StandardCharsets.UTF_16);
        System.out.println(read); // 원본 문자열이 올바르게 출력됨
    }
}

핵심 포인트: 기록과 읽기에는 동일한 인코딩을 지정해야 합니다. UTF-16으로 기록하고 기본값(예: UTF-8)으로 읽으면 문자가 ‘깨짐’ 현상이 발생합니다.

UTF-16의 특징

  • 대부분의 문자에 대해 고정 길이: 2바이트.
  • 서러게이트 쌍: 일부 문자(예: 이모지)는 4바이트가 필요합니다.
  • Byte Order Mark (BOM): 파일 시작에 바이트 순서를 나타내는 특별한 ‘헤더’가 자주 추가됩니다(LE — little endian, BE — big endian). Java는 보통 BOM을 자동으로 인식합니다.
  • ASCII와 호환되지 않음: ASCII나 UTF-8을 기대하는 에디터에서 UTF-16 파일을 열면 0 바이트와 이상한 기호가 잔뜩 보입니다.

언제 사용할까요 UTF-16?

  • 특정 시스템이나 파일이 반드시 UTF-16을 요구하는 경우(예: Windows‑프로그램과의 통합).
  • 일반적인 애플리케이션과 파일 — UTF-8이 더 좋습니다.

4. ISO-8859-1 (Latin-1) — 과거에서 온 인사

ISO-8859-1, 또는 Latin-1은 지난 80년대에 만들어진 단일 바이트 인코딩입니다. 256개의 문자를 지원합니다: 영문 알파벳, 서유럽어의 발음 구별 기호가 있는 문자(é, ü, ç 등), 구두점, 특수 기호.

사실: 이 인코딩에는 키릴 문자, 그리스 문자, 아랍 문자, 중국어 등 비라틴 문자가 없습니다.

ISO-8859-1은 어디에서 쓰이나요?

  • 오래된 파일과 프로그램(특히 유럽).
  • 일부 데이터베이스와 프로토콜의 “기본값”.
  • HTTP‑헤더(표준상 인코딩이 지정되지 않은 텍스트 데이터는 ISO-8859-1로 간주되지만, 실제로는 드뭅니다).

예: ISO-8859-1에서 문자열은 어떻게 보이나요

문자 Unicode 코드 ISO-8859-1(바이트) 설명
A
U+0041
41
ASCII와 동일
é
U+00E9
E9
프랑스어
ü
U+00FC
FC
독일어
Ya
U+042F
— 해당 문자가 없음!

Java에서의 사용 예

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

public class Latin1Demo {
    public static void main(String[] args) throws Exception {
        String text = "Bonjour, ça va? Café!"; // 프랑스어 텍스트
        Files.write(Paths.get("latin1.txt"), text.getBytes(StandardCharsets.ISO_8859_1));

        // ISO-8859-1로 파일 읽기
        String read = Files.readString(Paths.get("latin1.txt"), StandardCharsets.ISO_8859_1);
        System.out.println(read); // 정상적으로 읽힙니다!

        // 키릴 문자를 기록해 보자
        String cyrillic = "안녕, 세상!";
        try {
            Files.write(Paths.get("badlatin1.txt"), cyrillic.getBytes(StandardCharsets.ISO_8859_1));
        } catch (Exception e) {
            System.out.println("오류: ISO-8859-1에 키릴 문자를 기록할 수 없습니다!");
        }
    }
}

ISO-8859-1(Latin-1) 인코딩은 프랑스어, 독일어, 스페인어처럼 발음 구별 기호가 있는 서유럽 문자를 지원합니다. 그래서 프랑스어 텍스트는 문제 없이 기록되고 읽힙니다.

하지만 이 인코딩에는 키릴 문자가 없기 때문에 이를 기록하려 하면 오류가 나거나 문자가 손실됩니다. 바로 이런 이유로 다국어 텍스트에는 훨씬 더 큰 문자 집합을 포괄하는 UTF-8 또는 UTF-16을 사용하는 것이 좋습니다.

ISO-8859-1의 특징

  • 언어 제한: 서유럽 언어만.
  • 문자당 1바이트: 컴팩트하지만 매우 제한적.
  • ‘외부’ 문자를 기록하면 왜곡: 키릴 문자나 한자 등을 저장하려 하면 물음표나 쓰레기 문자로 변합니다.

언제 사용할까요 ISO-8859-1?

  • 매우 오래된 시스템이나 데이터베이스와의 통합 등, 이 인코딩만을 요구하는 경우에만.
  • 현대적인 작업에는 — 권장하지 않습니다.

5. 유용한 팁

인코딩 비교 표

인코딩 문자당 바이트 지원 언어 ASCII 호환성 사용처
UTF-8
1-4 모든 언어 완전 Web, Java, JSON, XML, Linux
UTF-16
2 또는 4 모든 언어 없음 Java 내부, Windows, XML
ISO-8859-1
1 서유럽 완전 구형 프로그램, 데이터베이스, HTTP

인코딩을 어떻게 선택할까요?

  • UTF-8 — 전체 작업의 99%에 대한 선택. ASCII와 호환되고 모든 언어를 지원하며, 영어 텍스트에 가장 작고, 웹과 Java의 표준입니다.
  • UTF-16 — 명세가 요구하거나 Windows‑프로그램과의 통합이 필요한 경우에만 사용하세요.
  • ISO-8859-1 — 오래된 시스템과의 하위 호환용으로만. 키릴 문자, 그리스어, 아랍어 등 저장에는 절대 사용하지 마세요.

파일의 인코딩은 어떻게 확인하나요?

  • 인코딩을 표시하는 에디터(예: Notepad++, VS Code)에서 파일을 엽니다.
  • “깨진 문자”가 보이면 다른 인코딩으로 열어 보세요.
  • Linux 터미널에서는 file 파일명 명령을 사용할 수 있습니다 — 때때로 인코딩을 알려줍니다.

Java에서 인코딩을 지정하는 방법?

java.nio.charset 패키지의 클래스를 사용하세요:

  • StandardCharsets.UTF_8
  • StandardCharsets.UTF_16
  • StandardCharsets.ISO_8859_1

또는 인코딩 이름으로:

Charset windows1251 = Charset.forName("Windows-1251");
Charset utf8 = Charset.forName("UTF-8");

6. 실습: 인코딩을 혼동하면 어떻게 될까요?

UTF-8로 문자열을 기록한 뒤, 이를 ISO-8859-1로 읽어 보겠습니다:

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

public class EncodingMismatchDemo {
    public static void main(String[] args) throws Exception {
        String text = "안녕, 세상!"; // 키릴 문자

        // UTF-8로 기록
        Files.write(Paths.get("utf8demo.txt"), text.getBytes(StandardCharsets.UTF_8));

        // ISO-8859-1로 읽기(잘못됨!)
        String wrong = Files.readString(Paths.get("utf8demo.txt"), StandardCharsets.ISO_8859_1);
        System.out.println("잘못 읽음: " + wrong);

        // 이제 올바르게 읽기
        String correct = Files.readString(Paths.get("utf8demo.txt"), StandardCharsets.UTF_8);
        System.out.println("올바른 읽기: " + correct);
    }
}

결과:

  • 첫 번째 경우에는 “깨진 문자” — 이상한 기호들의 집합이 보입니다.
  • 두 번째 경우에는 정상적인 텍스트가 보입니다.

7. 인코딩 작업 시 흔한 실수

오류 №1: 파일을 잘못된 인코딩으로 읽기. 파일이 UTF-8로 저장되어 있는데 이를 ISO-8859-1이나 Windows-1251로 읽으면, 비‑ASCII 문자는 전부 쓰레기 문자로 바뀝니다. 고전적인 상황입니다: “내 컴퓨터에선 되는데 동료에겐 — 안 된다”.

오류 №2: 단일 바이트 인코딩에 ‘외부’ 문자를 기록하려는 시도. ISO-8859-1에 키릴 문자나 한자를 저장하려 하면, Java는 그것들을 물음표로 바꾸거나 오류를 던집니다. 데이터는 영구적으로 손실됩니다.

오류 №3: 시스템 인코딩 “기본값” 사용. Java의 일부 메서드(예: new FileReader("file.txt"))는 인코딩을 명시하지 않으면 시스템 값을 사용합니다. 어떤 컴퓨터는 UTF-8, 다른 컴퓨터는 Windows-1251, 또 다른 컴퓨터는 아주 특이한 값일 수도 있습니다. 그러므로 항상 인코딩을 명시할 수 있는 메서드를 사용하세요.

오류 №4: UTF-16 파일을 UTF-8로 열기. UTF-16으로 저장된 파일을 UTF-8로 열면 바이트가 잘못 해석되어 0 바이트와 이상한 기호들이 잔뜩 보이게 됩니다.

1
과제
JAVA 25 SELF, 레벨 37, 레슨 1
잠금
구식 통신 시스템으로 키릴 문자 전송 시도 📟
구식 통신 시스템으로 키릴 문자 전송 시도 📟
1
과제
JAVA 25 SELF, 레벨 37, 레슨 1
잠금
언어학 실험: 서로 다른 인코딩에서 단어의 무게 ⚖️
언어학 실험: 서로 다른 인코딩에서 단어의 무게 ⚖️
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION