글자 수와 바이트 수는 왜 다른가
게시일: · 최종 수정일:
같은 글을 넣었는데 어떤 프로그램은 5라고 하고 다른 프로그램은 15라고 합니다. 어느 쪽도 틀린 것이 아니라 세는 단위가 다를 뿐입니다. 이 글은 텍스트의 길이를 재는 네 가지 단위와, 지금 보고 있는 숫자가 어느 단위인지 알아내는 방법을 설명합니다.
세는 단위는 네 가지입니다
- 글자: 화면에 한 글자로 보이는 단위입니다. 유니코드 표준에서는 grapheme cluster라고 부릅니다. 사람이 “몇 글자”라고 할 때의 단위입니다.
- 코드 포인트: 유니코드가 문자마다 붙인 번호입니다.
A는 U+0041,€는 U+20AC입니다. 한 글자가 코드 포인트 여러 개로 이루어지기도 합니다. - UTF-16 코드 단위: 자바스크립트를 비롯한 여러 환경이 문자열을 내부에 담아 두는 16비트 단위입니다. 코드 포인트 대부분은 1개, U+10000 이상은 2개를 씁니다.
- 바이트: 파일로 저장하거나 네트워크로 보낼 때 실제로 차지하는 크기입니다. 어떤 인코딩을 쓰느냐에 따라 달라집니다.
| 문자 | 글자 | 코드 포인트 | UTF-16 코드 단위 | UTF-8 바이트 |
|---|---|---|---|---|
A |
1 | 1 | 1 | 1 |
é |
1 | 1 | 1 | 2 |
€ |
1 | 1 | 1 | 3 |
😀 |
1 | 1 | 2 | 4 |
UTF-8에서 문자 종류별 바이트 수
UTF-8은 코드 포인트의 번호 범위에 따라 1 ~ 4바이트를 씁니다.
| 코드 포인트 범위 | 바이트 | 해당하는 문자 | 예 |
|---|---|---|---|
| U+0000 ~ U+007F | 1 | 영문, 숫자, 기본 기호(ASCII) | A 7 ! |
| U+0080 ~ U+07FF | 2 | 강세 기호가 붙은 라틴 문자, 그리스 문자, 키릴 문자 등 | é ß α Ж |
| U+0800 ~ U+FFFF | 3 | 한글, 한자, 가나, 여러 기호 | 한 字 あ € |
| U+10000 ~ U+10FFFF | 4 | 이모지 대부분, 드물게 쓰는 한자 등 | 😀 |
그래서 글자 수가 같아도 문자 종류에 따라 바이트 수가 달라집니다.
| 문자열 | 글자 | UTF-8 바이트 | 계산 |
|---|---|---|---|
Hello |
5 | 5 | 5 × 1 |
café |
4 | 5 | 3 × 1 + 2 |
Größe |
5 | 7 | 3 × 1 + 2 × 2 |
Привет |
6 | 12 | 6 × 2 |
안녕하세요 |
5 | 15 | 5 × 3 |
こんにちは |
5 | 15 | 5 × 3 |
Hi 😀 |
4 | 7 | 3 × 1 + 4 |
한 글자로 보이는 이모지가 여러 바이트인 이유
이모지 가운데에는 코드 포인트 여러 개를 이어 붙여 하나의 그림으로 보여 주는 것이 있습니다.
| 이모지 | 글자 | 코드 포인트 | UTF-16 코드 단위 | UTF-8 바이트 | 구성 |
|---|---|---|---|---|---|
👍🏽 |
1 | 2 | 4 | 8 | 엄지 U+1F44D + 피부색 U+1F3FD |
🇺🇳 |
1 | 2 | 4 | 8 | 지역 표시 문자 U+1F1FA + U+1F1F3 |
👨👩👧 |
1 | 5 | 8 | 18 | 사람 3명 + 그 사이를 잇는 ZWJ 2개 |
가족 이모지는 👨 👩 👧 세 이모지 사이에 폭 없는 접합자(ZWJ, U+200D)를 넣어 만든 것입니다. 이모지 하나가 4바이트이고 ZWJ가 3바이트이므로 3 × 4 + 2 × 3 = 18바이트입니다. 글자 단위로 세는 프로그램은 1을, 코드 포인트로 세는 프로그램은 5를, UTF-16 코드 단위로 세는 프로그램은 8을 보여 줍니다.
이모지만 그런 것은 아닙니다. é는 코드 포인트 하나(U+00E9, 2바이트)로도, e 뒤에 결합 강세 기호(U+0301)를 붙인 코드 포인트 2개(3바이트)로도 쓸 수 있습니다. 화면에서는 똑같이 보여도 바이트 수가 다릅니다.
같은 글자인데 인코딩마다 바이트 수가 다른 이유
바이트 수는 글자가 아니라 인코딩이 정합니다. UTF-8이 널리 쓰이기 전에는 언어권마다 다른 인코딩을 썼고, 그 인코딩들은 자기 언어의 문자를 2바이트 이하에 담았습니다.
| 문자 | UTF-8 | UTF-16 | 옛 인코딩 |
|---|---|---|---|
A |
1 | 2 | 1 |
é |
2 | 2 | 1 (windows-1252) |
한 |
3 | 2 | 2 (EUC-KR) |
あ |
3 | 2 | 2 (Shift_JIS) |
字 |
3 | 2 | 2 (EUC-KR, Shift_JIS) |
😀 |
4 | 4 | 표현할 수 없음 |
같은 문자를 두고 “2바이트”라는 말과 “3바이트”라는 말이 둘 다 나오는 까닭이 여기에 있습니다. 앞의 것은 EUC-KR이나 Shift_JIS 같은 옛 인코딩, 뒤의 것은 UTF-8을 기준으로 한 말입니다. 한글 abc는 UTF-8로 10바이트, EUC-KR로 8바이트이고, Tokyo 東京은 UTF-8로 12바이트, Shift_JIS로 10바이트입니다. 요즘 만든 시스템은 대부분 UTF-8을 쓰지만, 오래된 시스템이나 그 규칙을 이어받은 양식은 지금도 2바이트 기준으로 셉니다.
실무에서는 무엇을 세나요
제한이 있는 곳마다 세는 단위가 다릅니다. 숫자를 맞추기 전에 무엇을 세는지부터 확인해야 합니다.
- 데이터베이스 컬럼: 길이가 문자 수인 제품도 있고 바이트 수인 제품도 있습니다. PostgreSQL의
varchar(n)은 문서에 n이 바이트가 아니라 문자 수라고 적혀 있습니다. 쓰는 제품과 설정의 문서를 확인하세요. - 웹 입력칸: HTML의
maxlength속성과 자바스크립트의length는 UTF-16 코드 단위로 셉니다.maxlength가 10인 입력칸에😀는 5개까지만 들어갑니다. - URL과 Base64: 둘 다 UTF-8 바이트를 변환하므로 결과 길이가 글자 수가 아니라 바이트 수를 따릅니다.
€한 글자는 URL 인코딩에서%E2%82%AC(9자), Base64에서4oKs(4자)가 됩니다. - 문자 메시지와 바이트 제한 양식: 내부에서 쓰는 인코딩에 따라 한 건에 담기는 양이 달라집니다. 안내문에 적힌 단위가 글자인지 바이트인지 확인하세요.
단위가 적혀 있지 않으면 글자 수 표시가 있는 입력칸에 문자를 하나씩 넣어 보고 숫자가 얼마나 늘어나는지 보면 됩니다.
A / 字 / 😀를 넣을 때 늘어나는 수 |
세는 단위 |
|---|---|
| 1 / 1 / 1 | 글자 또는 코드 포인트 |
| 1 / 1 / 2 | UTF-16 코드 단위 |
| 1 / 3 / 4 | UTF-8 바이트 |
| 1 / 2 / 그 밖의 값 | 2바이트 기준의 옛 방식 |
글자와 코드 포인트는 👍🏽로 구분합니다. 1이 늘면 글자, 2가 늘면 코드 포인트입니다.
직접 세어 보는 방법
브라우저 개발자 도구의 콘솔에 아래 코드를 붙여넣으면 네 가지 값을 직접 볼 수 있습니다. 첫 줄의 문자열만 바꾸면 됩니다.
const s = '👨👩👧';
[...new Intl.Segmenter().segment(s)].length; // 1: 글자
[...s].length; // 5: 코드 포인트
s.length; // 8: UTF-16 코드 단위
new TextEncoder().encode(s).length; // 18: UTF-8 바이트
코드를 쓰지 않고 확인하려면 아래 관련 도구에 글을 붙여넣으세요. 글자 수와 UTF-8 바이트 수, 2바이트 기준 바이트 수를 함께 보여 줍니다.
참고 자료
- RFC 3629: UTF-8, a transformation format of ISO 10646 — (영어)
- Unicode Standard Annex #29: Unicode Text Segmentation — (영어)
- Unicode Technical Standard #51: Unicode Emoji — (영어)
- String: length – MDN Web Docs — (영어)
- Intl.Segmenter – MDN Web Docs — (영어)
- HTML attribute maxlength – MDN Web Docs — (영어)
- Encoding Standard – WHATWG — (영어)
- PostgreSQL Documentation: Character Types — (영어)