텍스트 파일을 열었더니 「锟斤拷烫烫烫」나 「â¥人」 같은 알 수 없는 문자가 가득했던 적 있나요? 혹은 API에서 받은 한국어가 전부 「??」로 표시됐던 경험은요? 이런 짜증스러운 문제들은 거의 모두 같은 원인에서 비롯됩니다: 문자 인코딩 불일치. 문자 인코딩을 이해하는 것은 기술적 문제를 해결하는 것을 넘어, 컴퓨터가 문자를 처리하는 방법의 기초를 이해하는 일입니다.
1. 문자와 숫자: 컴퓨터는 어떻게 글자를 저장할까?
컴퓨터 내부는 0과 1만 존재합니다. 글자를 저장하려면 「글자 → 숫자」 대응표가 필요합니다. 「65는 대문자 A」, 「44032는 한글 '가'」처럼요. 이 대응 규칙이 바로 문자 인코딩(Character Encoding)입니다.
문자 인코딩에는 두 가지 요소가 있습니다:
- 문자 집합(Character Set): 「어떤 문자들이 있고」 각 문자의 번호(코드 포인트, Code Point)를 정의
- 인코딩 방식: 「그 번호를 바이트 시퀀스로 어떻게 표현할지」를 정의
이 차이가 중요합니다: Unicode는 문자 집합(14만 개 이상 문자의 번호 체계), UTF-8은 그 인코딩 방식(번호를 바이트로 변환하는 규칙)입니다.
2. ASCII: 모든 것의 시작
1963년 미국 표준 협회(ANSI)가 ASCII(American Standard Code for Information Interchange)를 제정했습니다. 7비트로 128개 문자(0–127)를 정의하며, 영문자·숫자·기호·제어 문자를 포함합니다.
ASCII는 영어에는 완벽하지만, 128개 문자로는 유럽 언어의 악센트 문자도, 한국어도, 중국어도 담을 수 없습니다.
설계자는 대소문자 차이를 32(6번째 비트의 차이)로 정했습니다. 비트 하나만 뒤집으면 대소문자를 변환할 수 있는 영리한 설계입니다. Python에서
ord('A') == 65, ord('a') == 97인 것도 이 때문입니다.
3. 군웅할거의 시대: EUC-KR·CP949·Big5·GBK
ASCII로는 부족해서 각 지역과 조직이 독자적으로 확장을 시작했습니다. 이것이 글자 깨짐 문제의 근원입니다.
3.1 한국어: KS X 1001 / EUC-KR / CP949
KS X 1001은 1987년 한국에서 제정된 한국어 문자 집합입니다. EUC-KR은 KS X 1001을 기반으로 한 인코딩으로 Unix/Linux 환경에서 많이 쓰였습니다. CP949(또는 MS949)는 Microsoft가 EUC-KR을 확장한 인코딩으로, Windows 한국어 버전의 기본 인코딩입니다. 이들은 UTF-8이 등장하기 전까지 한국어 처리의 기반이었습니다.
3.2 중국어(번체): Big5
Big5는 1984년 대만에서 제정된 번체 중국어 인코딩으로, 약 13,060자를 수록합니다.
3.3 중국어(간체): GBK
중국 대륙의 GBK는 21,003자를 수록하며 Windows 간체 중국어 버전의 기본 인코딩입니다.
| 인코딩 | 대상 언어 | 문자 수 | 바이트/문자 | 주요 사용 지역 |
|---|---|---|---|---|
| ASCII | 영어 | 128 | 1 | 전 세계(영어) |
| EUC-KR/CP949 | 한국어 | 약 11,172 | 2 | 한국 |
| Big5 | 번체 중국어 | 13,060 | 2 | 대만·홍콩 |
| GBK | 간체 중국어 | 21,003 | 2 | 중국 대륙 |
| Shift-JIS | 일본어 | 약 6,879 | 1–2 | 일본 |
| UTF-8 | 모든 언어 | 140,000+ | 1–4 | 전 세계 |
4. Unicode: 분열을 끝낸 통합 표준
1991년 Unicode 1.0이 발표되어, 세계 모든 문자에 고유한 코드 포인트(Code Point)를 부여했습니다(U+ 16진수로 표기):
U+0041→ 대문자 AU+AC00→ 한글 「가」U+4E2D→ 한자 「中」U+1F600→ 😀(웃는 이모지)
현재 Unicode 14.0은 159개 문자 체계에 걸쳐 144,697개 이상의 문자를 정의합니다.
가장 흔한 혼동입니다. Unicode는 「문자에 번호를 부여하는 표준」이고, UTF-8은 「그 번호를 바이트로 저장하는 방식」입니다. UTF-8은 Unicode 구현 방식 중 하나이며, UTF-16과 UTF-32도 있습니다.
5. UTF-8: 왜 세계 표준이 됐을까?
UTF-8은 1992년 Ken Thompson과 Rob Pike가 설계한 가변 길이 인코딩으로, 코드 포인트 크기에 따라 1~4바이트를 사용합니다:
- 1바이트: ASCII 범위(U+0000–U+007F)——UTF-8은 ASCII와 완전히 호환
- 2바이트: 대부분의 유럽 언어 문자
- 3바이트: 한글·중국어·일본어(U+0800–U+FFFF)
- 4바이트: 이모지·고대 문자(U+10000 이상)
UTF-8이 전 세계 웹 페이지의 97% 이상에서 사용되는 이유:
- ASCII 하위 호환: 영문 콘텐츠는 변환 없이 기존 시스템과 공존 가능
- 자기 동기화: 스트림 어디서부터든 올바르게 디코딩 가능
- 바이트 순서 문제 없음: UTF-16/32와 달리 엔디언 문제가 없음
- 균형 잡힌 공간 효율: 영어 1바이트, 한국어 3바이트
| 인코딩 | 바이트/문자 | ASCII 호환 | 바이트 순서 문제 | 주요 용도 |
|---|---|---|---|---|
| UTF-8 | 1–4(가변) | 있음 | 없음 | 네트워크 통신·파일 저장 |
| UTF-16 | 2 또는 4 | 없음 | 있음(BOM 필요) | Windows 내부·Java 문자열 |
| UTF-32 | 4(고정) | 없음 | 있음 | 내부 처리·빠른 인덱싱 |
6. 글자 깨짐은 왜 생길까?
글자 깨짐이란 바이트 시퀀스를 잘못된 인코딩으로 해독하는 것입니다. 대표적인 시나리오:
6.1 EUC-KR 파일을 UTF-8로 열기
EUC-KR로 인코딩된 한국어 바이트 시퀀스는 UTF-8 규칙을 따르지 않아 「â–»」나 「?」로 표시됩니다.
6.2 데이터베이스의 「??」 문제
MySQL 연결 문자 집합이 latin1인데 UTF-8로 인코딩된 한국어를 저장하려 하면, 해독 불가 바이트가 ?로 변환됩니다. 이 과정은 되돌릴 수 없어 원본 데이터가 영구 손상됩니다.
6.3 HTML charset 선언 누락
HTML에 <meta charset="UTF-8">이 없으면 브라우저가 인코딩을 추측하는데, 잘못 추측하면 한국어가 전부 깨져 보입니다.
utf8 함정MySQL의
utf8 문자 집합은 최대 3바이트 UTF-8 문자만 지원합니다. 이모지 등 4바이트 문자를 저장하려면 반드시 utf8mb4를 사용해야 합니다. utf8을 쓰면 이모지 저장 시 오류가 발생하거나 데이터가 손상됩니다.
7. URL 인코딩과 문자 인코딩
URL에는 제한된 ASCII 문자만 사용할 수 있어, 한국어 등은 퍼센트 인코딩(Percent-Encoding)이 필요합니다: UTF-8 바이트 시퀀스의 각 바이트를 %XX(16진수)로 표현합니다.
예: 「한국어」 → %ED%95%9C%EA%B5%AD%EC%96%B4
현대 표준(RFC 3986)에서는 URL 인코딩의 기반을 UTF-8로 명확히 규정합니다.
8. 글자 깨짐을 근본적으로 방지하는 방법
근본적인 해결책은 시스템 전체에서 UTF-8을 통일하는 것입니다:
- 파일: 모든 텍스트 파일을 UTF-8(BOM 없음)로 저장
- HTML:
<head>최상단에<meta charset="UTF-8">추가 - 데이터베이스:
CHARACTER SET utf8mb4사용 - DB 연결:
SET NAMES utf8mb4또는 PDO에서charset=utf8mb4 - HTTP 헤더:
Content-Type: text/html; charset=utf-8
9. 정리
문자 인코딩의 역사는 분열에서 통합으로 가는 여정입니다:
- ASCII(1963): 128자로 영어의 디지털화 기반 마련
- 군웅할거 시대: EUC-KR, GBK, Big5, Shift-JIS 난립, 글자 깨짐 빈발
- Unicode(1991+): 모든 문자에 고유 코드 포인트 부여
- UTF-8(1992+): ASCII 호환·공간 효율·바이트 순서 문제 없음, 전 세계 웹의 97% 이상 채택
기술 스택 전체(파일·코드·DB·HTTP 헤더)에서 UTF-8을 통일하면 글자 깨짐은 거의 발생하지 않습니다. 깨짐이 생겼을 때는 어느 단계에서 인코딩 선언과 실제 저장이 불일치하는지 찾아내는 것이 빠른 해결책입니다.