글자 수 카운터
텍스트를 한 번에 다섯 가지로 셉니다. 보이는 그대로의 글자 수, 코드 포인트(Python의 len()이 돌려주는 값), UTF-16 단위(JavaScript의 .length), 단어 수, 줄 수.
"이 문자열은 얼마나 긴가"에 답이 하나가 아닌 이유
사소한 질문처럼 들리지만, 텍스트의 길이는 무엇을 세느냐에 따라 완전히 달라집니다. 사람은 눈에 보이는 글자를 셉니다. 데이터베이스는 값이 차지하는 저장 공간을 셉니다. JavaScript는 또 다른 것을 셉니다. 이 숫자들이 어긋날 때 — 이모지, 악센트 붙은 글자, 비라틴 문자에서는 자주 그렇습니다 — 하나의 "길이"는 오해를 부릅니다. 이 도구는 의미 있는 다섯 지표를 나란히 보여 주어, 자신의 상황에 맞는 것을 고를 수 있게 합니다.
다섯 지표
- 글자 수(자소): 사람이 한 글자로 지각하는 것입니다. 👨👩👧 같은 이모지 가족이나 결합 악센트가 붙은 글자도 한 글자로 셉니다 — "눈으로" 세는 것과 일치하는 값입니다.
- 코드 포인트: 유니코드 스칼라 값의 수입니다. 눈에 보이는 한 글자가 여러 코드 포인트를 결합해 만들어질 수 있어, 이 수는 자소 수보다 큰 경우가 많습니다.
- UTF-16 단위: JavaScript가 문자열의 .length 속성으로 보고하는 값입니다. 기본 다국어 평면 밖의 문자(대부분의 이모지)는 저마다 UTF-16 단위 두 개를 차지합니다.
- 단어 수: 단어 같은 토큰의 수로, 단순히 공백으로 나누지 않고 유니코드를 고려해 감지합니다.
- 줄 수: 줄바꿈마다 센 줄의 수입니다.
이모지와 악센트가 순진한 세기를 깨뜨리는 이유
현대의 텍스트는 유니코드로 이루어지며, 유니코드는 많은 눈에 보이는 글자를 단일 단위가 아니라 나열로 나타냅니다. 국기 이모지는 두 개의 지역 표시 기호입니다. 가족 이모지는 보이지 않는 "폭 없는 결합자"로 이어진 여러 사람입니다. 악센트 붙은 é는 단일 코드 포인트일 수도, 맨 e에 결합 악센트가 이어진 것일 수도 있습니다. 모두 한 글자로 보이지만 지표에 따라 둘, 셋, 또는 그 이상으로 셉니다. 손으로 세거나 하나의 숫자를 믿으면 버그로 이어지는 이유가 바로 이것입니다.
어느 수를 써야 할까요?
- 사람이 보는 글자 수 제한(소개글, 트윗 같은 입력란, SMS 미리보기): 글자 수(자소)를 쓰세요. 사용자가 세는 것과 일치합니다.
- JavaScript의 문자열 처리, 배열 인덱싱이나 슬라이싱: UTF-16 단위를 쓰세요. .length와 문자열 인덱스가 그것에 기반합니다.
- 데이터베이스 열 크기: 열이 글자 단위인지 바이트 단위인지 확인하세요 — 많은 것이 바이트 제한이고, UTF-8 바이트 수는 이 어느 값과도 다릅니다.
- 편집기용 단어 수·줄 수, 가독성 지표: 단어 수와 줄 수 지표를 쓰세요.
Python과 JavaScript가 각각 "길이"라고 부르는 것
위 다섯 가지 가운데 두 가지는 사람들이 이 페이지에 오는 바로 그 이유이고, 서로 다른 언어에 속합니다. Python의 len()과 JavaScript의 .length는 둘 다 문자열의 길이라고 설명되지만, 순수 ASCII를 벗어나는 순간 같은 텍스트에 대해 같은 시점에 다른 값을 돌려줍니다. 어느 쪽도 틀리지 않았습니다. 세는 단위가 다를 뿐이며, 두 칸을 위에 나란히 둔 것은 그 차이를 짐작이 아니라 읽을 수 있게 하기 위해서입니다.
- Python: len()은 유니코드 코드 포인트의 수를 돌려줍니다. Python 3의 문자열이 코드 포인트의 나열이기 때문입니다. "코드 포인트" 칸을 보세요. len()이 주는 값이 바로 그것입니다.
- JavaScript: .length는 UTF-16 코드 단위의 수를 돌려줍니다. JavaScript 문자열이 UTF-16으로 보관되기 때문입니다. "UTF-16 단위" 칸을 보세요. 반면 Array.from(text).length는 문자열을 코드 포인트 단위로 훑으므로 Python과 값이 같습니다.
- 둘 다 눈에 보이는 것을 세지 않습니다. "글자 수" 칸이 자소 수이고, 두 언어의 어떤 내장 길이도 이 값을 내놓지 않습니다. JavaScript에는 Intl.Segmenter가, Python에는 외부 패키지가 필요합니다. 적용하려는 제한을 사람이 읽을 것이라면 보아야 할 칸은 이것입니다.
- "단어 수"와 "줄 수"도 어느 언어의 것이 아닙니다. 둘 다 공백과 줄바꿈으로 나누어 비슷하게 흉내 낼 수 있지만, 둘 다 여기 나온 유니코드를 고려한 값과는 어긋납니다 — 공백 없이 쓰는 텍스트에서도, 문장 부호에서도, 텍스트를 끝내는 줄바꿈에서도.
예제 불러오기 버튼으로 들어오는 예시가 그 차이를 보는 가장 빠른 길입니다. 그 안의 가족 이모지는 읽는 사람에게는 한 글자, Python에는 코드 포인트 다섯 개, JavaScript에는 UTF-16 단위 여덟 개입니다. 다섯 코드 포인트 가운데 셋이 기본 다국어 평면 밖에 있어 두 단위씩 차지하고, 그 사이의 보이지 않는 결합자 둘이 한 단위씩 차지하기 때문입니다. 그래서 스무 글자를 쓸 수 있는 칸은 눈으로 정말 스무 글자를 담지만, 두 언어 어느 쪽으로 쓰든 순진한 검사는 들어가는 텍스트를 되돌려 보냅니다.
길이는 바이트 크기가 아닙니다
위 다섯 가지 값 가운데 바이트 수는 하나도 없고, 이 둘은 헷갈리기 쉽습니다. 세는 값은 텍스트 자체의 성질이지만, 바이트 크기는 텍스트를 어떤 인코딩으로 적어 낸 뒤의 성질입니다. 그래서 같은 문자열이라도 UTF-8에서 차지하는 크기와 UTF-16에서 차지하는 크기가 다릅니다. 바이트로 재는 것은 인코딩한 뒤에 재야 하고, 어떤 인코딩을 골랐는지가 답의 일부입니다.
- UTF-8은 거의 모든 곳이 쓰는 가변 길이 인코딩입니다. ASCII 글자와 숫자, 문장 부호는 한 바이트, 악센트가 붙은 글자와 라틴 문자가 아닌 글자는 대부분 두세 바이트, 이모지는 대부분 네 바이트입니다. 백 글자가 백 바이트가 되는 것은 그 백 글자가 모두 ASCII일 때뿐이고, 같은 백 글자가 그리스 문자나 히브리 문자, 아랍 문자, 한자라면 두세 배가 됩니다.
- Python: len()은 코드 포인트를 세므로 재기 전에 인코딩하세요. len(text.encode('utf-8'))이 UTF-8 바이트 수이고, 그 자리에 다른 코덱을 적으면 다른 값이 나옵니다. 바로 그 점이 핵심입니다.
- JavaScript: .length는 UTF-16 단위를 세므로 이것도 바이트 수가 아닙니다. new TextEncoder().encode(text).length가 브라우저에서도 Node에서도 UTF-8 바이트 수를 돌려줍니다. TextEncoder는 UTF-8만 만들어 내므로 적을 코덱이 없습니다.
- 무엇을 세기 전에 그 제한이 어떤 단위로 쓰였는지 확인하세요. 데이터베이스 엔진에 따라 열을 글자로 선언하기도 하고 바이트로 선언하기도 하며, HTTP 헤더 값과 파일 크기는 바이트이고, 사람이 읽을 규칙은 "글자 수" 칸이 보여 주는 값입니다. 글자로 재는 것은 이미 위에 나와 있고, 바이트로 재는 것은 인코딩한 뒤의 길이가 필요합니다.
바이트 수를 얻었다면 그다음은 데이터 용량 변환입니다. 날것의 바이트 수를 두 단위 체계로 한꺼번에 바꿔 주고 — 천의 거듭제곱에 기댄 쪽과 둘의 거듭제곱에 기댄 쪽 — 같은 크기를 내 컴퓨터의 도구들이 각각 어떻게 알려 주는지도 보여 줍니다. 이 페이지는 인코딩이 시작되는 곳에서 끝나고, 그 페이지는 바이트 수에서 시작합니다.
자주 묻는 질문
- JavaScript의 문자열 길이에 일치하는 것은 어느 것인가요?
- UTF-16 단위 값이 JavaScript의 원시 .length 속성과 일치합니다. 코드가 문자열을 슬라이싱하거나 인덱싱한다면 그 동작을 좌우하는 것은 이 수입니다.
- 이모지 하나가 여러 글자로 세어지는 이유는 무엇인가요?
- 많은 이모지가 여러 유니코드 코드 포인트를 결합해 만들어집니다. 자소(보이는 것)로는 한 글자지만 코드 포인트 수와 UTF-16 단위 수는 그보다 큽니다. 각 지표를 나누어 보여 주는 이유가 그것입니다.
- 끝의 공백과 줄바꿈도 세나요?
- 네. 붙여넣은 모든 문자를 셉니다. 앞뒤 공백과 줄바꿈도 포함합니다. 합계가 예상보다 크면 눈에 보이지 않는 끝의 공백이 없는지 확인하세요.
- 입력한 텍스트가 서버로 전송되나요?
- 아니요. 세기는 모두 브라우저 안에서 이루어집니다. 입력하거나 붙여넣은 텍스트가 기기 밖으로 나가지 않습니다.
- 자소란 무엇인가요?
- 자소("자소 묶음")는 사람이 지각하는 글자입니다. 여러 하위 코드 포인트로 이루어져 있어도 읽는 사람이 한 글자라고 부를 최소 단위입니다.
- 바이트 수는 이 값들과 어떻게 다른가요?
- 바이트 수는 인코딩에 따라 달라집니다. UTF-8에서 ASCII 글자는 1바이트, 대부분의 악센트·비라틴 문자는 2~3바이트, 많은 이모지는 4바이트입니다. 여기 다섯 지표는 어느 것도 바이트 수가 아니므로, 바이트 제한이 있는 입력란에는 먼저 UTF-8로 인코딩하세요.
관련 도구
- 데이터 용량 변환
여기 놓인 수 가운데 바이트 단위의 용량은 하나도 없습니다. 용량은 텍스트를 인코딩한 뒤에야 생기기 때문입니다. 그 수가 손에 들어왔다면, 저 페이지가 그것을 10진계와 2진계로 나란히 읽어 주고 각 운영체제가 그것을 뭐라고 부를지까지 알려 줍니다.
- 정규식 테스터
정규식을 실험 — 매치, 치환, 분할.
- 텍스트 비교
두 텍스트를 비교 — 바뀐 줄과 단어.
- 유니코드 문자 검사기
문자열이 어떤 문자로 되어 있는지 정확히 보기.