← SEARCHABLE ARCHIVE
Computer Science · 2026-08-15

Unicode와 UTF-8은 무엇이 다를까?

코드 포인트, 인코딩, 글자 수와 정규화 이해하기

INTERACTIVE READING MODE →

본 블로그 작성 글들은, 강의를 목표로 하고 제작되었습니다. 또한 작성된 글의 정보도 틀렸을 수 있으니, 주의해주세요.

문자열은 화면에 보이는 글자들의 단순한 배열처럼 보이지만 컴퓨터 안에서는 여러 단계의 규칙을 거친다. Unicode는 문자의 번호와 속성을 정의하고 UTF-8은 그 번호를 바이트로 저장하는 방법 중 하나다.

문자 집합과 인코딩은 다르다

Unicode는 각 문자를 코드 포인트라는 번호로 구분한다.

A  → U+0041
가 → U+AC00
😀 → U+1F600

코드 포인트는 추상적인 번호이며 파일이나 네트워크에는 바이트로 바꿔 저장해야 한다. UTF-8, UTF-16, UTF-32는 Unicode 코드 포인트를 코드 유닛으로 표현하는 서로 다른 인코딩 방식이다.

같은 문자열도 인코딩에 따라 바이트 배열이 달라진다. 읽는 쪽이 다른 인코딩으로 해석하면 글자가 깨지는 이유가 여기에 있다.

UTF-8은 1바이트부터 4바이트를 사용한다

UTF-8은 코드 포인트 범위에 따라 하나의 Unicode 스칼라 값을 1~4바이트로 인코딩한다.

ASCII 범위       1바이트
일부 라틴 문자   2바이트
한글 음절        3바이트
일부 이모지      4바이트

ASCII의 0부터 127까지는 UTF-8에서도 같은 한 바이트 값을 사용한다. 기존 ASCII 텍스트와 호환하기 쉽고, 시작 바이트와 이어지는 바이트의 패턴이 구분되어 중간 위치에서도 경계를 찾을 수 있다.

text = "A가😀"
encoded = text.encode("utf-8")

print(len(text))     # Python에서는 코드 포인트 3개
print(len(encoded))  # UTF-8 바이트 8개

문자열 길이와 저장 바이트 수를 같다고 가정하면 버퍼 크기, 데이터베이스 제한, 네트워크 메시지 길이를 잘못 계산할 수 있다.

코드 포인트 하나가 화면 글자 하나는 아니다

화면에서 하나로 보이는 글자가 여러 코드 포인트로 구성될 수 있다.

é = U+00E9
e + ◌́ = U+0065 U+0301

두 문자열은 화면에서 같아 보일 수 있지만 코드 포인트 배열과 바이트가 다르다. 피부색이나 가족 모양이 결합된 이모지, 국기, 결합 문자도 여러 코드 포인트가 하나의 grapheme cluster를 만들 수 있다.

그래서 사용자에게 보이는 글자 수, 커서 이동 단위, 문자열 슬라이스는 단순한 바이트 수나 코드 포인트 수보다 복잡하다. 언어와 UI 프레임워크가 grapheme cluster 단위 처리를 제공하는지 확인해야 한다.

정규화가 필요한 이유

Unicode 정규화는 같은 텍스트를 서로 비교하기 쉬운 대표 형태로 바꾼다. NFC는 가능한 문자를 결합된 형태로 만들고 NFD는 분해된 형태로 만든다.

import unicodedata

first = "é"
second = "e\u0301"

print(first == second)  # False
print(
    unicodedata.normalize("NFC", first)
    == unicodedata.normalize("NFC", second)
)  # True

정규화는 대소문자 규칙, 언어별 정렬, 시각적으로 비슷한 다른 문자까지 모두 같게 만들지는 않는다. 사용자 이름이나 보안 식별자를 비교할 때는 제품이 허용할 문자와 정규화, 대소문자 정책을 별도로 정해야 한다.

텍스트를 안전하게 다루려면 문자, 코드 포인트, 코드 유닛, 바이트, 화면의 grapheme을 구분해야 한다. 파일과 네트워크 경계에서는 인코딩을 명시하고, 길이 제한과 비교 규칙은 실제로 세려는 단위에 맞게 설계해야 한다.