본문으로 바로가기

글자 수와 바이트 수는 왜 다른가

게시일: · 최종 수정일:

같은 글을 넣었는데 어떤 프로그램은 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바이트 기준 바이트 수를 함께 보여 줍니다.

참고 자료