UTF-8 바이트 뷰어

글자별 UTF-8 바이트와 유니코드 코드포인트 분석

0 0

UTF-8 바이트 뷰어 소개

이 도구는 입력을 코드 포인트 단위로 나눈 뒤, 글자마다 브라우저 내장 TextEncoder로 UTF-8 바이트를 만들어 보여 줍니다. 「글자 수」라는 말이 적어도 세 가지(코드 포인트, UTF-16 코드 유닛, 사람이 보는 글자)를 뜻할 수 있는데, 이 도구가 세는 것은 코드 포인트이고 바이트는 UTF-8 기준입니다.

DB 칼럼 길이, 문자 메시지·API의 바이트 제한, 파일 이름 길이 제한에 걸렸는데 글자 수로는 이유가 안 보일 때 쓰면 원인이 바로 드러납니다.

같은 글자, 세 가지 길이

유니코드는 글자마다 번호(코드 포인트)를 붙이고, 그 번호를 바이트로 적는 방법(인코딩)은 따로 있습니다. UTF-8은 1~4바이트, UTF-16은 2바이트 코드 유닛 1~2개를 씁니다. JavaScript의 str.length, Java의 String.length()는 UTF-16 코드 유닛 수를 돌려주므로 이 도구의 글자 수와 다를 수 있습니다.

  • A (U+0041): UTF-8 41, 1바이트 / UTF-16 1유닛
  • é (U+00E9): UTF-8 C3 A9, 2바이트 / UTF-16 1유닛
  • 한 (U+D55C): UTF-8 ED 95 9C, 3바이트 / UTF-16 1유닛
  • 😀 (U+1F600): UTF-8 F0 9F 98 80, 4바이트 / UTF-16 2유닛(D83D DE00)

바이트를 손으로 풀어 보기: 한 = ED 95 9C

RFC 3629가 정한 UTF-8 규칙은 코드 포인트 범위에 따라 틀이 정해져 있습니다. U+0000~007F는 0xxxxxxx(1바이트), U+0080~07FF는 110xxxxx 10xxxxxx, U+0800~FFFF는 1110xxxx 10xxxxxx 10xxxxxx, U+10000~10FFFF는 11110xxx에 이어지는 바이트 셋입니다.

한은 U+D55C, 2진수로 1101 0101 0101 1100입니다. 16비트를 4·6·6으로 나누면 1101 / 010101 / 011100이고, 틀에 넣으면 11101101 10010101 10011100, 곧 ED 95 9C입니다. 첫 바이트의 앞쪽 1의 개수(1110 → 3개)가 이 글자의 총 바이트 수를 알려 주고, 이어지는 바이트는 모두 10으로 시작합니다. 그래서 바이트열 중간에서 읽기 시작해도 다음 글자 경계를 찾을 수 있습니다.

한글 음절(U+AC00~D7A3, 11,172자)과 거의 모든 한자·가나는 이 3바이트 구간에 있습니다. EUC-KR이나 CP949에서 한글이 2바이트였던 것과 비교하면, 한글 위주 텍스트는 UTF-8로 바꾸면 약 1.5배 커집니다.

한 줄에 보이는 한 글자가 여러 행으로 나올 때

도구는 코드 포인트마다 한 행을 만듭니다. 그래서 화면에서는 한 글자인데 여러 행이 나오는 경우가 있고, 이것이 오히려 문제를 찾는 단서가 됩니다.

  • 가족 이모지 👨‍👩‍👧: 남자·ZWJ(U+200D)·여자·ZWJ·여자아이, 5개 코드 포인트, 18바이트. ZWJ는 E2 80 8D 3바이트입니다.
  • 피부색이 들어간 👍🏽: 엄지(U+1F44D) + 피부색 수정자(U+1F3FD), 8바이트.
  • macOS에서 만든 파일 이름의 「한」: ᄒ(U+1112) ᅡ(U+1161) ᆫ(U+11AB) 세 행, 9바이트(E1 84 92 E1 85 A1 E1 86 AB). 이것이 NFD(분해형)입니다.
  • é가 2행으로 나오면 e(U+0065) + 결합 악센트(U+0301, CC 81)로 적힌 것입니다. 겉보기는 같아도 C3 A9 한 글자와 문자열 비교에서 다르게 취급됩니다.

3바이트와 4바이트 경계가 문제가 되는 곳

MySQL의 utf8(정식 이름 utf8mb3) 문자 집합은 글자당 최대 3바이트만 저장하므로 4바이트 문자인 이모지와 일부 확장 한자를 넣으면 오류가 나거나 잘립니다. utf8mb4로 바꾸어야 합니다. VARCHAR(n)의 n은 글자 수지만 인덱스 키 길이 제한은 바이트로 계산되므로 utf8mb4에서는 같은 n이 더 많은 바이트를 차지합니다.

바이트 수로 문자열을 자르는 코드도 흔한 함정입니다. 한글은 3바이트 단위라 100바이트에서 자르면 34번째 글자의 첫 바이트 하나만 남아 깨진 문자(U+FFFD, 화면에 �)가 됩니다. 이 도구에서 자르려는 위치의 누적 바이트를 확인하면 어디서 끊어야 할지 알 수 있습니다.

눈에 안 보이는 문자 찾기

스페이스는 ␣, 탭·줄바꿈은 \t·\n·\r로, 그 밖의 제어 문자는 \x1B 같은 라벨로 표시됩니다. 빈 칸처럼 보이는 유니코드 공백도 라벨을 붙입니다. U+00A0(줄바꿈 없는 공백, C2 A0)은 [NBSP], U+200B(폭 없는 공백, E2 80 8B)는 [ZWSP], U+FEFF(BOM, EF BB BF)는 [BOM], U+3000(전각 공백, E3 80 80)은 [IDSP]로 나오고, ZWJ·ZWNJ·WJ·SHY도 같은 식입니다. 웹 페이지에서 복사한 문장이 검색이나 비교에서 안 맞는 경우 대부분 NBSP, ZWSP, BOM 중 하나입니다.

예시

이모지 하나가 DB 저장에서 실패하는 이유

입력
좋아요😀
결과
글자 4개, 13바이트 — EC A2 8B / EC 95 84 / EC 9A 94 / F0 9F 98 80

한글 3자는 3바이트씩 9바이트, 이모지는 4바이트입니다. utf8mb3 칼럼은 F0으로 시작하는 4바이트 문자를 받지 못합니다. JavaScript의 length는 5로 나옵니다.

같아 보이는데 비교가 실패하는 파일 이름

입력
한 (macOS에서 복사한 파일 이름)
결과
3행: U+1112 E1 84 92 / U+1161 E1 85 A1 / U+11AB E1 86 AB — 9바이트

Windows에서 입력한 「한」은 1행 ED 95 9C 3바이트입니다. 두 문자열은 NFC로 정규화하기 전까지 서로 다릅니다.

복사한 문장의 숨은 공백

입력
가격: 1 000원 (웹 페이지에서 복사)
결과
1과 0 사이 행이 C2 A0 — U+00A0

일반 스페이스(20)가 아니라 줄바꿈 없는 공백입니다. 정규식 \s는 잡지만 split(" ")은 나누지 못합니다.

사용 방법

  1. 텍스트를 입력하거나 붙여 넣습니다.
  2. 글자별 행에서 코드 포인트와 UTF-8 바이트를 확인합니다.
  3. 위쪽 합계로 전체 글자 수(코드 포인트)와 바이트 수를 봅니다.
  4. 필요하면 헥스 덤프를 복사해 다른 바이트와 대조합니다.

주요 용어

코드 포인트
유니코드가 글자에 붙인 번호. U+0000부터 U+10FFFF까지입니다.
코드 유닛
인코딩이 쓰는 최소 단위. UTF-8은 1바이트, UTF-16은 2바이트입니다.
서로게이트 페어
UTF-16에서 U+10000 이상을 나타내는 2개 코드 유닛(D800~DBFF + DC00~DFFF).
NFC / NFD
유니코드 정규화 형식. NFC는 가능한 한 합친 형태, NFD는 기본 글자와 결합 문자로 푼 형태입니다.

자주 묻는 질문

한글은 UTF-8에서 몇 바이트인가요?

완성형 한글 음절은 모두 3바이트입니다. 자모를 따로 적은 NFD 형태라면 음절 하나가 6~9바이트가 됩니다.

이모지는 항상 4바이트인가요?

코드 포인트 하나짜리 이모지는 대부분 4바이트지만 ☺(U+263A)처럼 오래된 기호는 3바이트입니다. 피부색, 성별, 가족 조합은 여러 코드 포인트를 이어 붙인 것이라 8~25바이트 이상이 됩니다.

글자 수가 문자 수 세기 도구와 다릅니다.

이 도구는 코드 포인트를 셉니다. 사람이 보는 글자(자소 클러스터) 단위로 세는 도구라면 가족 이모지를 1로, 이 도구는 5로 셉니다.

EUC-KR이나 UTF-16 바이트도 볼 수 있나요?

아니요. UTF-8만 보여 줍니다. UTF-16 코드 유닛은 유니코드 이스케이프 변환기에서 \uXXXX 형태로 확인할 수 있습니다.

BOM은 어떻게 나오나요?

U+FEFF 한 행, 바이트 EF BB BF로 나오고 글자 칸에는 [BOM]이라고 표시됩니다.

관련 가이드

출처

기준일:

개인정보와 저장 방식