UTF-32
고친 사람 github-actions[bot]
UTF-32 는 글자에 매긴 번호를 네 바이트에 그대로 옮겨 적는 방법입니다. 영어 글자든 한글이든 이모지든 똑같이 네 바이트를 씁니다. 길이가 고르니 n 번째 글자를 앞에서부터 세지 않고 바로 찾아갑니다. 그 대신 공간을 많이 써서 파일로 주고받는 일은 드뭅니다.
쉽고 빠른 이해
UTF-32 는 글자마다 정해진 번호를 네 바이트짜리 칸 하나에 담는 약속입니다. A 도 네 바이트, 한 도 네 바이트, 웃는 얼굴 이모지도 네 바이트입니다.
글자마다 바이트 수가 다르면 n 번째 글자를 찾으려고 앞에서부터 세어야 합니다. 모든 글자가 네 바이트면 위치를 곱셈 한 번으로 구합니다. 다른 방식처럼 큰 번호를 두 칸에 나눠 적을 일도 없습니다.
어떻게 도나:
- 글자의 번호를 읽습니다
- 그 번호를 네 바이트 칸에 적고 남는 앞쪽은 0 으로 채웁니다
- 네 바이트를 어느 순서로 늘어놓을지는 따로 정합니다
대가는 크기입니다. 영어로만 된 글은 한 바이트로 적는 방식보다 네 배 커집니다. 바이트 대부분이 0 으로 채워집니다. 그래서 저장이나 전송보다는 프로그램이 글자를 하나씩 다루는 동안 잠깐 씁니다.
상세
UTF-32 가 하는 일은 번호를 받아 네 바이트에 옮겨 적는 것이 전부입니다. 문자 인코딩 가운데 가장 단순한 방식입니다.
아래에서는 A · 한 · 웃는 얼굴 이모지 세 글자를 네 바이트로 적어 봅니다. 그 과정에서 이 단순함이 무엇을 얻고 무엇을 내주는지 따라갑니다.
번호와 네 바이트 칸
UTF-32 는 Unicode Transformation Format 32-bit 의 줄임말입니다. 우리말로 옮기면 유니코드 변환 형식 32비트입니다. 이름의 32 는 32비트, 곧 네 바이트를 한 덩이로 삼는다는 뜻입니다.
Unicode(유니코드)는 세상의 글자마다 번호를 하나씩 매겨 둔 표입니다. 이 번호를 코드 포인트라고 부릅니다. 코드 포인트는 U+ 뒤에 16진수로 적습니다. A 는 U+0041, 한 은 U+D55C, 웃는 얼굴 이모지는 U+1F600 입니다.
번호가 정해져도 그 수를 바이트로 어떻게 적을지는 따로 약속해야 합니다. 이 약속을 문자 인코딩이라고 부릅니다. 같은 번호를 적는 방법이 여럿인 까닭이 여기 있습니다.
UTF-32 는 번호를 수 그대로 네 바이트에 담습니다. 이 네 바이트 덩이를 코드 단위라고 부릅니다. 아래에서 「칸」이라고 적는 것이 이 코드 단위입니다.
한 글자가 바이트가 되기까지 거치는 약속은 셋입니다.
flowchart TD
A["글자 한"] --> B["번호 U+D55C · 유니코드가 정한다"]
B --> C["칸 0000D55C · UTF-32 가 정한다"]
C --> D["바이트 00 00 D5 5C 또는 5C D5 00 00 · 늘어놓는 순서가 정한다"]
가운데 단계에서 UTF-32 가 하는 일은 앞을 0 으로 채우는 것뿐입니다. 번호 D55C 가 칸 0000D55C 가 됩니다. 마지막 단계의 두 갈래는 아래 「바이트 순서」 소절에서 다룹니다.
모든 글자가 한 칸
번호가 작든 크든 글자 하나는 칸 하나입니다. 세 글자를 적어 보면 이렇습니다. 바이트는 16진수 수를 적을 때처럼 자릿값이 큰 바이트부터 늘어놓았습니다. D55C 를 D5 다음에 5C 로 적는 식입니다.
| 글자 | 번호 | UTF-32 바이트 |
|---|---|---|
A |
U+0041 |
00 00 00 41 |
한 |
U+D55C |
00 00 D5 5C |
| 웃는 얼굴 이모지 | U+1F600 |
00 01 F6 00 |
세 줄 모두 네 바이트입니다. 번호가 작을수록 앞쪽에 00 이 많이 붙을 뿐 칸 수는 바뀌지 않습니다.
같은 번호를 적는 다른 방식은 글자마다 길이가 다릅니다. UTF-8(Unicode Transformation Format 8-bit)은 A 를 한 바이트, 한 을 세 바이트, 이모지를 네 바이트로 적습니다.
UTF-16(Unicode Transformation Format 16-bit)은 칸 하나가 두 바이트입니다. 이모지처럼 번호가 큰 글자는 한 칸에 안 들어가서 두 칸에 나눠 적습니다. 이렇게 한 글자를 두 칸에 나눠 적는 짝을 대리 쌍이라고 부릅니다.
UTF-32 에는 이런 나눔이 없습니다. 네 바이트면 가장 큰 번호 U+10FFFF 까지 한 칸에 들어가기 때문입니다.
칸에 들어갈 수 있는 값에도 선이 있습니다. U+10FFFF 를 넘는 값은 글자가 아닙니다. UTF-16 이 대리 쌍을 만들려고 비워 둔 U+D800 부터 U+DFFF 까지도 글자가 아닙니다. 칸에 이런 값이 들어 있으면 잘못된 UTF-32 입니다.
곱셈 한 번으로 찾는 위치
UTF-32 가 칸 크기를 고정한 까닭은 위치 계산에 있습니다. 글자마다 길이가 같으면 n 번째 글자가 어디 있는지 계산으로 바로 나옵니다.
UTF-8 로 적힌 글에서 100 번째 글자를 찾으려면 앞의 99 글자를 하나씩 읽어야 합니다. 글자마다 몇 바이트인지를 첫 바이트를 봐야 알기 때문입니다. UTF-32 에서는 100 번째 칸이 시작 위치에서 4 × 99, 곧 396 바이트 뒤에 있습니다. 앞의 글자를 하나도 안 읽고 거기로 건너뜁니다.
Python 에서 글자를 UTF-32 바이트로 바꿔 길이를 재 보면 고정 길이가 눈에 보입니다. 이름 끝의 -be 는 빅 엔디언(big-endian), 곧 자릿값이 큰 바이트를 먼저 놓는 순서를 고르는 표시입니다.
len("A".encode("utf-32-be")) # 4
len("한".encode("utf-32-be")) # 4
len("😀".encode("utf-32-be")) # 4
세 줄이 모두 4 입니다. 같은 세 글자를 "utf-8" 로 바꾸면 1, 3, 4 로 갈립니다.
번호 하나와 보이는 글자 하나
UTF-32 가 고르게 만드는 것은 번호의 길이입니다. 화면에 보이는 글자의 길이까지 고르게 해 주지는 않습니다.
사람이 한 글자로 보는 것이 번호 여러 개로 이루어지는 일이 흔합니다. é 는 e 뒤에 악센트 기호 U+0301 을 붙여 적을 수 있습니다. 이렇게 앞 글자에 덧붙는 번호를 결합 문자라고 부릅니다.
이모지에도 같은 일이 있습니다. 엄지 이모지 U+1F44D 뒤에 피부색 번호 U+1F3FD 를 붙이면 화면에는 한 글자로 보입니다. UTF-32 로 적으면 칸 두 개, 여덟 바이트입니다.
사람이 한 글자로 보는 이런 덩이를 그래핌 클러스터라고 부릅니다. UTF-32 로 바로 찾아가는 것은 n 번째 번호이지 n 번째 보이는 글자가 아닙니다. 화면에 보이는 대로 자르거나 세려면 UTF-32 에서도 앞에서부터 덩이를 가려 읽어야 합니다.
비어 있는 비트
가장 큰 번호 U+10FFFF 는 2진수로 21비트면 적힙니다. 칸은 32비트이니 앞쪽 11비트는 어떤 글자를 적어도 늘 0 입니다.
packet-beta 0-10: "늘 0 · 11비트" 11-31: "번호 · 21비트"
그래서 칸의 맨 앞 바이트는 언제나 00 입니다. 둘째 바이트도 거의 모든 글자에서 00 입니다. 한글과 자주 쓰는 한자, 영어가 전부 U+FFFF 아래에 있기 때문입니다.
이 빈 비트가 크기로 드러납니다. 한 글자에 드는 바이트를 세 방식으로 견주면 이렇습니다.
| 글자 종류 | UTF-8 | UTF-16 | UTF-32 |
|---|---|---|---|
| 영어 글자 | 1 | 2 | 4 |
| 한글 음절 | 3 | 2 | 4 |
| 이모지 | 4 | 4 | 4 |
UTF-32 는 어느 줄에서도 가장 작지 않습니다. 영어 위주의 글은 UTF-8 로 적을 때보다 네 배 커집니다.
빈 바이트는 크기 말고 다른 문제도 부릅니다. 바이트 00 을 문자열의 끝 표시로 쓰는 도구가 있기 때문입니다.
C 언어의 문자열이 그렇습니다. 이 방식을 널 종단 문자열이라고 부릅니다. 빅 엔디언으로 적은 A 는 첫 바이트부터 00 이라서 이런 도구는 빈 문자열로 읽습니다.
바이트 순서
칸 하나가 네 바이트라서 어느 바이트를 먼저 놓을지 정해야 합니다. 이 문제를 엔디언이라고 부릅니다.
자릿값이 큰 바이트를 먼저 놓는 방식은 빅 엔디언, 줄여서 BE(big-endian)입니다. 이 순서로 적는 UTF-32 의 이름이 UTF-32BE 입니다. 자릿값이 작은 바이트를 먼저 놓는 방식은 리틀 엔디언, 줄여서 LE(little-endian)입니다. 이 순서의 이름은 UTF-32LE 입니다.
어느 쪽인지는 인코딩 이름으로 정하거나 파일 맨 앞에 표시를 붙여 알립니다. 맨 앞에 붙이는 표시는 번호 U+FEFF 입니다. 이 표시를 바이트 순서 표시, 줄여서 BOM(Byte Order Mark)이라고 부릅니다. 읽는 쪽은 첫 네 바이트를 보고 순서를 알아냅니다.
| 늘어놓는 방식 | 한 의 바이트 |
파일 앞에 붙는 표시 |
|---|---|---|
| 큰 자릿값 먼저 (BE) | 00 00 D5 5C |
00 00 FE FF |
| 작은 자릿값 먼저 (LE) | 5C D5 00 00 |
FF FE 00 00 |
리틀 엔디언의 표시에는 함정이 하나 있습니다. 앞 두 바이트 FF FE 가 UTF-16 리틀 엔디언의 표시와 똑같습니다.
그래서 첫 두 바이트만 보고 판정하는 도구는 UTF-32 파일을 UTF-16 으로 잘못 읽습니다. 뒤따르는 00 00 은 번호가 0 인 글자로 읽힙니다. 번호 0 은 화면에 안 보이는 널 문자라서 본문 앞에 엉뚱한 글자가 하나 끼어듭니다. 두 방식을 가리려면 네 바이트를 끝까지 봐야 합니다.
쓰이는 곳
UTF-32 로 파일을 저장하거나 네트워크로 보내는 일은 드뭅니다. 크기가 크기 때문입니다. 옛 도구와도 어긋납니다. 주고받는 텍스트는 대개 UTF-8 입니다.
UTF-32 가 쓰이는 곳은 주로 프로그램의 메모리 안쪽입니다. UTF-8 로 받은 글을 먼저 번호의 배열로 풀어 놓습니다. 그 배열에서 글자 단위로 자르거나 비교합니다. 일이 끝나면 다시 UTF-8 로 적어 내보냅니다. 풀어 놓은 배열이 곧 UTF-32 입니다.
오래 들고 있거나 밖으로 내보낼 글은 작게 적습니다. 글자 단위로 다루는 동안만 네 바이트로 펼칩니다. 크기의 손해를 잠깐만 치르고 위치 계산의 이점을 얻는 방법입니다.
관련 항목
UTF-32 가 적는 번호를 정하는 표준
Unicode · 코드 포인트 · 기본 다국어 평면 · 보충 평면 · ISO/IEC 10646
UTF-32 가 속하는 상위 분류
같은 번호를 다르게 적는 인코딩
UTF-8 · UTF-16 · UCS-4 · UCS-2 · ASCII · CP949
네 바이트 칸 하나로 적으면서 생기는 문제
엔디언 · 바이트 순서 표시 · 널 종단 문자열 · 대체 문자 · 모지바케
번호 하나와 보이는 글자 하나를 가르는 개념
그래핌 클러스터 · 결합 문자 · 유니코드 정규화 · 제로 폭 결합자 · 이모지
UTF-32 를 읽고 쓸 때 거치는 처리 단계
코드 단위 · 디코딩 · 인코딩 · 문자열 길이 · 대리 쌍
다른 이름: utf-32 · utf32 · Unicode Transformation Format 32-bit · 유니코드 변환 형식 32비트