UTF-8이 어떻게 가변 길이 인코딩으로 유니코드의 방대한 문자를 표현하면서도 ASCII와 하위 호환성을 유지하는지, 규칙과 표, 실제 파일 예제로 알기 쉽게 설명하고 UTF-8 Playground 도구도 소개합니다.
2025-09-12 처음 UTF-8 인코딩을 배웠을 때, 서로 다른 언어와 문자의 수백만 개 문자를 표현하도록 얼마나 치밀하고 영리하게 설계되었는지에 감탄했습니다. 게다가 ASCII와 하위 호환되도록 말이죠.
기본적으로 UTF-8은 32비트를 사용하고 예전 ASCII는 7비트를 사용하지만, UTF-8은 다음과 같이 설계되어 있습니다.
수백만 개 문자로 확장되면서도 128개 문자만 쓰는 옛 시스템과 호환되는 시스템을 설계했다는 점이 정말 뛰어난 설계입니다.
참고: 이미 UTF-8 인코딩을 알고 있다면, 제가 만든 UTF-8 Playground 유틸리티에서 UTF-8 인코딩을 시각화해 보세요.
UTF-8은 전 세계 대부분의 문자 체계를 아우르는 유니코드 문자 집합의 모든 문자를 표현하도록 설계된 가변 길이 문자 인코딩입니다.
문자를 1바이트에서 4바이트까지 사용해 인코딩합니다.
처음 128개 문자(U+0000부터 U+007F)는 단일 바이트로 인코딩되어 ASCII와의 하위 호환을 보장합니다. 그래서 ASCII 문자만 있는 파일은 유효한 UTF-8 파일이 됩니다.
그 외 문자는 두 바이트, 세 바이트, 또는 네 바이트가 필요합니다. 첫 바이트의 선행 비트가 현재 문자를 표현하는 데 사용되는 전체 바이트 수를 결정합니다. 이 비트들은 네 가지 특정 패턴 중 하나를 따르며, 그 뒤에 이어질 연속 바이트의 개수를 나타냅니다.
| 첫 바이트 패턴 | 사용되는 바이트 수 | 전체 바이트 시퀀스 패턴 |
|---|---|---|
| 0xxxxxxx | 1 | 0xxxxxxx (사실상 일반적인 ASCII 인코딩 바이트입니다) |
| 110xxxxx | 2 | 110xxxxx 10xxxxxx |
| 1110xxxx | 3 | 1110xxxx 10xxxxxx 10xxxxxx |
| 11110xxx | 4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
멀티바이트 시퀀스에서 두 번째, 세 번째, 네 번째 바이트는 항상 10으로 시작한다는 점에 주목하세요. 이는 해당 바이트들이 메인 바이트를 따르는 연속 바이트임을 나타냅니다.
메인 바이트의 나머지 비트와 연속 바이트의 비트를 합쳐서 문자의 코드 포인트를 형성합니다. 코드 포인트는 유니코드 문자 집합에서 문자를 고유하게 식별하는 값입니다. 코드 포인트는 보통 16진수 형식으로 "U+"를 접두로 붙여 표기합니다. 예를 들어 문자 "A"의 코드 포인트는 U+0041입니다.
소프트웨어가 UTF-8로 인코딩된 바이트에서 문자를 판별하는 방법은 다음과 같습니다.
0으로 시작하면 단일 바이트 문자(ASCII)입니다. 남은 7비트가 나타내는 문자를 화면에 표시하고 다음 바이트로 진행합니다.0으로 시작하지 않았다면:
* 110으로 시작하면 두 바이트 문자이므로 다음 바이트를 하나 더 읽습니다.
* 1110으로 시작하면 세 바이트 문자이므로 다음 두 바이트를 읽습니다.
* 11110으로 시작하면 네 바이트 문자이므로 다음 세 바이트를 읽습니다.힌디어 문자 "अ"(공식 명칭: Devanagari Letter A)는 UTF-8에서 다음과 같이 표현됩니다.
11100000 10100100 10000101
여기서:
첫 번째 바이트 11100000은 이 문자가 3바이트로 인코딩되었음을 나타냅니다.
세 바이트의 나머지 비트: xxxx0000 xx100100 xx000101을 합치면 이진수 00001001 00000105…가 아니라, 00001001 00000101이 됩니다. 이는 16진수로 0x0905이며, 코드 포인트 표기 U+0905입니다.
코드 포인트 U+0905(공식 차트 보기)는 유니코드 문자 집합에서 힌디어 문자 "अ"를 나타냅니다.
이제 UTF-8의 설계를 이해했으니, 다음 텍스트를 담은 파일을 살펴봅시다.
Hey👋 BuddyHey👋 Buddy에는 영어 문자와 이모지 문자가 함께 들어 있습니다. 이 내용을 디스크에 저장한 텍스트 파일에는 다음의 13바이트가 들어 있습니다.
01001000 01100101 01111001 11110000 10011111 10010001 10001011 00100000 01000010 01110101 01100100 01100100 01111001
UTF-8 디코딩 규칙에 따라 이 파일을 바이트 단위로 평가해 봅시다.
| 바이트 | 설명 |
|---|---|
| 01001000 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1001000은 문자 'H'를 나타냅니다. (플레이그라운드에서 열기) |
| 01100101 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1100101은 문자 'e'를 나타냅니다. (플레이그라운드에서 열기) |
| 01111001 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1111001은 문자 'y'를 나타냅니다. (플레이그라운드에서 열기) |
| 11110000 | 11110으로 시작하므로 네 바이트 문자 중 첫 바이트입니다. |
| 10011111 | 10으로 시작하므로 연속 바이트입니다. |
| 10010001 | 10으로 시작하므로 연속 바이트입니다. |
| 10001011 | 10으로 시작하므로 연속 바이트입니다. 이 네 바이트에서 선행 비트를 제외한 비트를 합치면 이진수 00001 11110100 01001011이 되고, 16진수로 1F44B입니다. 이는 코드 포인트 U+1F44B에 해당합니다. 이 코드 포인트는 유니코드 문자 집합에서 손을 흔드는 이모지 "👋"를 나타냅니다. (플레이그라운드에서 열기) |
| 00100000 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 0100000은 공백 문자를 나타냅니다. (플레이그라운드에서 열기) |
| 01000010 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1000010은 문자 'B'를 나타냅니다. (플레이그라운드에서 열기) |
| 01110101 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1110101은 문자 'u'를 나타냅니다. (플레이그라운드에서 열기) |
| 01100100 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1100100은 문자 'd'를 나타냅니다. (플레이그라운드에서 열기) |
| 01100100 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1100100은 문자 'd'를 나타냅니다. (플레이그라운드에서 열기) |
| 01111001 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1111001은 문자 'y'를 나타냅니다. (플레이그라운드에서 열기) |
이 파일은 유효한 UTF-8 파일이지만, 비ASCII 문자(이모지)를 포함하고 있으므로 ASCII와의 "하위 호환"일 필요는 없습니다. 다음으로, ASCII 문자만 포함하는 파일을 만들어 봅시다.
Hey Buddy이 텍스트 파일에는 비ASCII 문자가 전혀 없습니다. 디스크에 저장된 파일에는 다음의 9바이트가 들어 있습니다.
01001000 01100101 01111001 00100000 01000010 01110101 01100100 01100100 01111001
UTF-8 디코딩 규칙에 따라 이 파일을 바이트 단위로 평가해 봅시다.
| 바이트 | 설명 |
|---|---|
| 01001000 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1001000은 문자 'H'를 나타냅니다. (플레이그라운드에서 열기) |
| 01100101 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1100101은 문자 'e'를 나타냅니다. (플레이그라운드에서 열기) |
| 01111001 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1111001은 문자 'y'를 나타냅니다. (플레이그라운드에서 열기) |
| 00100000 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 0100000은 공백 문자를 나타냅니다. (플레이그라운드에서 열기) |
| 01000010 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1000010은 문자 'B'를 나타냅니다. (플레이그라운드에서 열기) |
| 01110101 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1110101은 문자 'u'를 나타냅니다. (플레이그라운드에서 열기) |
| 01100100 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1100100은 문자 'd'를 나타냅니다. (플레이그라운드에서 열기) |
| 01100100 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1100100은 문자 'd'를 나타냅니다. (플레이그라운드에서 열기) |
| 01111001 | 0으로 시작하므로 단일 바이트 ASCII 문자입니다. 나머지 비트 1111001은 문자 'y'를 나타냅니다. (플레이그라운드에서 열기) |
따라서 이 파일은 유효한 UTF-8 파일이면서 동시에 유효한 ASCII 파일입니다. 이 파일의 바이트들은 UTF-8과 ASCII 인코딩 규칙을 모두 만족합니다. 이것이 UTF-8이 ASCII와의 하위 호환성을 갖도록 설계된 방식입니다.
ASCII와 하위 호환인 다른 인코딩을 간단히 찾아보니 몇 가지가 있지만, UTF-8만큼 대중적이지는 않습니다. 예를 들어 GB 18030(중국 정부 표준)이 있습니다. 또 다른 예로 ISO/IEC 8859 계열은 ASCII를 확장하는 단일 바이트 인코딩이지만, 최대 256자라는 한계가 있습니다.
UTF-8의 형제격인 UTF-16과 UTF-32는 ASCII와 하위 호환이 아닙니다. 예를 들어 문자 'A'는 UTF-16에서는 00 41(두 바이트)로, UTF-32에서는 00 00 00 41(네 바이트)로 표현됩니다.
UTF-8 인코딩을 탐구하던 중, UTF-8이 어떻게 동작하는지 대화형으로 시각화할 수 있는 좋은 도구를 찾지 못했습니다. 그래서 UTF-8 Playground를 직접 만들어 UTF-8 인코딩을 시각화하고 실험해 볼 수 있게 했습니다. 직접 써 보세요!
이 글을 훨씬 확장하는 방대한 지식과 참고 자료를 Hacker News에서 읽어보세요.
OSnews, lobste.rs에서도 토론을 확인할 수 있습니다.
UTF-8에 관한 훌륭한 참고 자료: