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 | |
|
| Ya | |
|
| € | |
|
| 😀 | |
|
왜 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 | |
|
2바이트 |
| Ya | |
|
2바이트 |
| € | |
|
2바이트 |
| 😀 | |
|
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 | |
|
ASCII와 동일 |
| é | |
|
프랑스어 |
| ü | |
|
독일어 |
| Ya | |
— | 해당 문자가 없음! |
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 호환성 | 사용처 |
|---|---|---|---|---|
|
1-4 | 모든 언어 | 완전 | Web, Java, JSON, XML, Linux |
|
2 또는 4 | 모든 언어 | 없음 | Java 내부, Windows, XML |
|
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 바이트와 이상한 기호들이 잔뜩 보이게 됩니다.
GO TO FULL VERSION