~ / posts / developer

한글 한 글자 몇 바이트? UTF-8 3바이트·EUC-KR 2바이트 차이

한글 한 글자는 UTF-8에서 3바이트, EUC-KR에서 2바이트예요. 같은 문장이 왜 어디선 100바이트, 어디선 150바이트인지 바이트 구조로 보여주고, 글자가 깨지는 이유와 EUC-KR이 못 담는 한글, 바이트 수 계산 코드까지 정리했습니다.

"같은 문장인데 글자 수 사이트마다 바이트가 다르게 나와요." 자소서나 게시판 글자 수를 재다 보면 이런 경험을 해요. 결론부터 말하면 한글 한 글자는 UTF-8에서 3바이트, EUC-KR(또는 CP949)에서 2바이트예요. 영문·숫자는 두 인코딩 모두 1바이트고요. 그래서 한글이 많은 문장은 어떤 인코딩으로 세느냐에 따라 바이트 수가 1.5배까지 차이 나요.

이게 단순한 숫자 차이로 끝나면 좋은데, 인코딩을 잘못 맞추면 글자가 한글 이나 ??? 로 깨지는 진짜 문제로 이어져요. 왜 한글이 3바이트인지 비트 구조로 보여 주고, EUC-KR이 못 담는 한글이 있다는 사실, 그리고 바이트 수를 정확히 재는 코드까지 Python과 Node.js로 확인하며 정리했어요.

핵심 요약

한글 한 글자는 UTF-8에서 3바이트, EUC-KR·CP949에서 2바이트, 영문·숫자는 둘 다 1바이트예요.

UTF-8은 유니코드 번호를 1110·10·10 틀에 나눠 담아서, U+0800~U+FFFF 구간인 한글이 3바이트가 돼요.

EUC-KR은 완성형 한글 2,350자만 2바이트로 담고, 나머지는 CP949 확장이 필요해요. '똠' 같은 글자가 대표적이에요.

글자가 깨지면 저장과 읽기의 인코딩이 다른 거예요. 지금은 UTF-8로 통일하는 게 정답이에요.

바이트와 인코딩이란

컴퓨터는 글자를 숫자로 바꿔 저장해요. "어떤 글자를 어떤 숫자(바이트)로 바꿀지"의 약속이 문자 인코딩이에요. 영어권에서 출발한 ASCII는 글자 하나를 1바이트로 표현했는데, 1바이트로는 256가지밖에 못 담아서 한글·한자·이모지 같은 많은 문자를 담을 수 없었어요.

그래서 한글을 위한 인코딩이 따로 생겼어요. 대표가 EUC-KR(과 그 확장인 CP949)이고, 지금 전 세계 표준이 된 게 UTF-8이에요. 둘은 한글을 바이트로 바꾸는 방식이 완전히 달라요.

UTF-8에서 한글이 3바이트인 이유

UTF-8은 유니코드 번호(코드 포인트)를 바이트로 바꾸는데, 번호 크기에 따라 1~4바이트를 써요. 규칙은 이래요.

유니코드 범위바이트 수담기는 문자
U+0000 ~ U+007F1ASCII 영문·숫자·기본 기호
U+0080 ~ U+07FF2라틴 확장(é, ü), 그리스·키릴
U+0800 ~ U+FFFF3한글, 한자, 일본어
U+10000 ~ U+10FFFF4이모지, 희귀 한자

한글 완성형은 U+AC00(가)부터 U+D7A3(힣)까지라 3바이트 구간에 들어가요. '한'(U+D55C)이 실제로 어떻게 3바이트가 되는지 비트로 쪼개 봤어요.

한 글자의 유니코드 16비트를 UTF-8의 1110xxxx 10xxxxxx 10xxxxxx 틀에 나눠 담아 0xED 0x95 0x9C 세 바이트가 되는 구조도
유니코드 번호를 고정 비트 틀에 끼워 넣는다
list('한'.encode('utf-8'))   # [237, 149, 156]  = 0xED 0x95 0x9C
'한'.encode('utf-8').hex()   # 'ed959c'

3바이트 UTF-8은 1110xxxx 10xxxxxx 10xxxxxx 라는 틀이에요. 앞의 1110·10·10 은 "이건 3바이트짜리 글자"라고 알려 주는 고정 비트고, 나머지 x 자리 16개에 유니코드 번호를 채워요. '한'의 번호 D55C(16비트)가 그 16자리에 정확히 들어가서 세 바이트가 완성돼요. 이 고정 비트 덕분에 UTF-8은 바이트 하나만 봐도 글자의 시작인지 중간인지 알 수 있어, 깨진 데이터에서도 복구가 쉬워요.

EUC-KR에서 한글이 2바이트인 이유와 함정

EUC-KR은 완성형 한글마다 2바이트 코드를 직접 할당한 방식이에요. 구조가 단순해서 한글이 딱 2바이트예요.

len('한'.encode('euc-kr'))   # 2
len('가'.encode('euc-kr'))   # 2

그런데 여기 함정이 있어요. 순수 EUC-KR은 완성형 한글 11,172자 중 2,350자만 2바이트로 담아요. 자주 쓰는 글자는 대부분 들어가지만, '똠', '펲', '삡' 같은 글자는 빠져 있어요. 직접 세어 봤어요.

완성형 한글 11172자를 2바이트로 담는 범위 비교
02,0004,0006,0008,00010,00012,000EUC-KR 2바이트 · 담기는 한글 수 2,350자2,350EUC-KR 2바이트CP949(확장완성형) 2바이트 · 담기는 한글 수 11,172자11,172CP949(확장완성형) 2바이트

EUC-KR이 못 담는 8822자는 Windows 확장인 CP949에서야 2바이트로 담긴다

단위: 자 · 자료: Python 3.9 로 U+AC00~U+D7A3 각 글자를 인코딩해 2바이트로 담기는 개수를 직접 집계
표로 보기
구분담기는 한글 수
EUC-KR 2바이트2,350
CP949(확장완성형) 2바이트11,172

그래서 윈도우는 EUC-KR을 확장한 CP949(통합 완성형)를 썼고, 여기서 11,172자 전부가 2바이트로 담겨요. 흔히 "EUC-KR"이라고 부르는 한글 파일이 사실은 CP949인 경우가 많아요. 이름이 섞여 쓰이는 이유예요. 어느 쪽이든 이모지나 중국어 간체 같은 다른 문자는 담을 수 없어요. 한국어 외의 글자를 섞어야 한다면 UTF-8 말고는 답이 없어요.

같은 문장, 다른 바이트

안녕하세요 Hello 123! 를 각 인코딩으로 재 봤어요.

인코딩바이트 수한글 1자영문 1자
UTF-82631
EUC-KR / CP9492121
UTF-1634 (+BOM 2)22

한글 5자가 UTF-8에선 15바이트, EUC-KR에선 10바이트라 5바이트 차이가 나요. 한글이 많을수록 격차가 커지죠. UTF-16은 한글이 2바이트지만 영문도 2바이트라, 영문이 많으면 오히려 UTF-8보다 커요. 바이트 수를 직접 확인하려면 글자 수·바이트 세기에 문장을 넣으면 인코딩별로 보여 줘요.

s = '안녕하세요 Hello 123!'
len(s.encode('utf-8'))   # 26
len(s.encode('cp949'))   # 21
Buffer.byteLength('안녕하세요 Hello 123!', 'utf8');  // 26

글자가 깨지는 이유 — 저장과 읽기가 다를 때

인코딩을 잘못 맞추면 글자가 깨져요. 증상으로 원인을 구분할 수 있어요.

증상원인예
한글 (낯선 라틴 글자)UTF-8로 저장한 걸 EUC-KR 등으로 읽음한글 3바이트를 다른 1바이트 글자로 해석
??? 또는 □□대상 인코딩에 없는 글자 변환UTF-8 → EUC-KR로 저장 시 이모지
뷁逑 (깨진 한자·한글)EUC-KR을 UTF-8로 읽음2바이트를 3바이트 틀로 잘못 해석
맨 앞에 BOM을 글자로 해석UTF-8 BOM(EF BB BF)

핵심은 "어떻게 저장했는지"와 "어떻게 읽는지"가 달라서 생긴다는 거예요. CSV를 엑셀에서 열면 한글이 깨지는 고전적 문제도 이거예요. 엑셀이 기본으로 CP949로 읽는데 파일은 UTF-8이라서요. UTF-8로 저장하면서 BOM을 붙이면 엑셀이 알아보지만, BOM은 또 다른 곳에서 문제를 일으켜서(예: JSON 파싱) 양날의 검이에요. 관련 이야기는 JSON 파싱 에러 원인의 보이지 않는 문자 부분에서 다뤘어요.

어떤 인코딩을 써야 하나

지금은 고민할 필요가 거의 없어요. UTF-8로 통일이 정답이에요.

EUC-KR·CP949를 지금도 봐야 하는 경우는 정해져 있어요. 오래된 공공기관 시스템, 레거시 ERP, 과거에 만든 CSV·텍스트 파일, 일부 금융 전문(電文) 규격 등이에요. 이런 데이터를 받으면 받는 즉시 UTF-8로 변환하고, 내부에서는 UTF-8만 쓰는 게 좋아요.

# EUC-KR(CP949) 파일을 UTF-8로 변환
with open('legacy.txt', encoding='cp949') as f:
    text = f.read()
with open('converted.txt', 'w', encoding='utf-8') as f:
    f.write(text)
# 명령 한 줄로 변환 (iconv)
iconv -f CP949 -t UTF-8 legacy.csv > converted.csv

바이트 수가 중요한 실무 상황

글자 수가 아니라 바이트 수가 기준인 곳이 의외로 많아요.

이런 곳에서는 글자 수만 믿지 말고 바이트 수를 함께 확인해야 해요.

자주 묻는 질문

한글 한 글자는 몇 바이트인가요?

UTF-8에서는 3바이트, EUC-KR·CP949에서는 2바이트예요. UTF-16에서는 2바이트고요. 영문과 숫자는 UTF-8과 EUC-KR 모두 1바이트예요. 지금은 UTF-8이 표준이라 한글 3바이트로 계산하는 경우가 가장 많아요.

글자가 한글처럼 깨지는 이유는 무엇인가요?

UTF-8로 저장한 한글을 EUC-KR 같은 다른 인코딩으로 읽었기 때문이에요. 한글 3바이트가 각각 다른 라틴 문자로 해석되면서 생겨요. 파일을 올바른 인코딩(UTF-8)으로 다시 열거나, 읽는 쪽의 인코딩 설정을 맞추면 해결돼요.

엑셀에서 CSV 한글이 깨져요.

엑셀이 기본으로 CP949로 읽는데 파일이 UTF-8이라서 그래요. UTF-8 BOM을 붙여 저장하면 엑셀이 알아보고, 또는 엑셀의 데이터 가져오기에서 인코딩을 UTF-8로 지정하면 돼요. 반대로 BOM은 일부 프로그램에서 문제를 일으키니 상황에 맞게 선택하세요.

EUC-KR과 CP949는 같은 건가요?

CP949는 EUC-KR의 확장이에요. 순수 EUC-KR은 완성형 한글 2,350자만 담지만, CP949는 11,172자 전부를 담아요. '똠' 같은 글자는 EUC-KR엔 없고 CP949에만 있어요. 흔히 EUC-KR이라 부르는 한글 파일이 실제로는 CP949인 경우가 많아요.

지금 새 프로젝트는 어떤 인코딩을 써야 하나요?

UTF-8을 쓰세요. 웹 표준이고 모든 문자를 담을 수 있어요. 데이터베이스, API, 소스 파일까지 UTF-8로 통일하면 인코딩 변환으로 생기는 사고가 거의 없어져요. 레거시 EUC-KR 데이터는 받는 즉시 UTF-8로 변환하는 게 좋아요.

정리

한글은 UTF-8에서 3바이트, EUC-KR에서 2바이트예요. UTF-8은 유니코드 번호를 고정 비트 틀에 나눠 담는 방식이라 한글이 3바이트가 되고, EUC-KR은 완성형마다 2바이트를 직접 할당하지만 담을 수 있는 글자가 제한적이에요. 지금은 UTF-8로 통일하고, 레거시 데이터는 받는 즉시 변환하는 게 정답이에요.

바이트 수를 직접 확인하려면 글자 수·바이트 세기를 쓰고, 이모지처럼 더 복잡하게 세지는 문자 이야기는 이모지 글자 수와 grapheme에서 이어서 볼 수 있어요.

#한글 바이트#UTF-8 EUC-KR 차이#한글 몇 바이트#글자 깨짐#CP949#인코딩 변환#한글 3바이트
← 이전 글디자인 시스템 컬러 스케일 만들기 — 50~950 단계와 OKLCH 생성 코드다음 글 →JSON YAML XML 차이 — 용량·파싱 속도 실측과 고르는 기준