본문 바로가기

Theory/Algorithms

유니코드와 UTF-8

반응형

유니코드 (Unicode)

유니코드는 유니코드 consortium 에서 규율하는, 전 세계의 모든 문자를 다루도록 처리된 표준 문자 전산 처리 방식으로 전 세계의 모든 문자를 담은 ISO/IEC 10646 Universal Character Set을 사용함으로써 각 언어의 문자체계에 따른 충돌을 해결했습니다. 따라서, 한글, 간체자, 아랍 문자 등을 통일된 환경에서 깨뜨리지 않고 사용할 수 있습니다.

아스키코드

초창기 문자를 표현하던 방식은 미국 국립 표준 협회 (ANSI)에서 표준화한 ASCII (American Standard Code for Information Interchange) 코드로 1바이트에 문자를 표현한 방식입니다. Checksum 1비트를 제외한 7비트의 128글자로  (영문 키보드로 입력할 수 있는 모든 기호) 문자를 표현했습니다.

참고로 1바이트를 구성하는 8비트 중 7비트만 사용한 이유는 1비트는 parity bit로 7비트의 1의 개수가 짝수면 0, 홀수면 1로 설정하여 전송 도중 오류를 검출하기 위한 것인데 현재는 거의 사용하지 않고 밑에서 다룰 UTF-8은 1바이트 인코딩의 경우 맨 앞에 0을 붙이고 7비트를 이어서 붙입니다.

아스키코드는 간단하지만 1바이트안의 남는 공간이 없어 한글이나 한자 같은 문자를 표현할 경우 2개 이상의 특수 문자를 합쳐서 표현해야 했는데, 이 방식은 문자가 깨지거나 제대로 표현되지 않는 경우가 많았습니다. 즉, 1바이트를 넘어가는 2바이트 이상의 문자를 표현할 수 없었기 때문에 사장되고 2~4 바이트의 공간에 여유 있게 문자를 할당하고자 하는 유니코드로 국제 표준이 넘어가게 됩니다.

역사 및 표기

유니코드는 1991년 최초 1.0.0 버젼이 발표되었으며 2020년 기준 최신 버젼은 13.0입니다. 지구상에 통용되는 거의 모든 문자를 담고 있고 악보 기호, 이모티콘 등도 포함되어 있습니다.

유니코드는 해당 문제를 표현할 때, U+(16진수, hexadecimal)로 표현합니다. 한글 '가'는 유니코드에서 16진수로 U+AC00에 할당되어 있습니다. 참고로 16진수는 컴퓨터의 수를 표현할 때 주로 사용하며, 0000(0x0) - 1111(0xF)의 범위를 가져 2개의 16진수 숫자로 1바이트를 표현할 수 있습니다.

한국어는 1996년 발표된 유니코드 2.0에서 특정 유니코드 영역 (U+AC00 - U+D7A3)에 가나다 순으로 11172자를 배정받았으며, 이는 아직까지도 컴퓨터 상의 한국어 처리에 사용되고 있습니다.

인코딩

문자열 표현에서의 인코딩은 코드가 컴퓨터 상에서 어떻게 저장되어야 하는 문제입니다. 아스키코드나 유니코드는 사람들이 정한 국제 표준 문자표이고 이를 컴퓨터가 이해할 수 있는 형식으로 바꿔주어야 합니다.

물론 아스키코드처럼 코드표 그대로 인코딩해도 되지만 유니코드 자체는 1바이트로 표현 가능한 영문자도 2바이트 이상의 공간을 사용하기 때문에 이를 그대로 사용하기에는 메모리 낭비가 심합니다. 여기서 등장한 인코딩 방식이 가변 길이 문자 인코딩 방식으로 효율적으로 인코딩하는 UTF-8 입니다.

 

UTF-8

UTF-8 은 Universal Transformation Format - 8bit 의 약자로 고(Go)를 개발한 롭 파이크와 켄 톰슨이 만든 유니코드의 인코딩 방식입니다. 다른 인코딩과의 호환을 위해 4바이트까지 사용하며 따라서 한 글자가 1바이트 ~ 4바이트 중 하나로 인코딩 될 수 있습니다. 대량 100만자 정도 표현할 수 있습니다.

특히, 1바이트 영역은 아스키코드 (특히 영문자)와 하위 호환 관계를 지닙니다. 즉, 아스키코드의 0-127까지는 UTF-8 인코딩으로도 동일하게 기록됩니다. 이러한 장점 때문에 인터넷 사이트에서 가장 많이 쓰이는 인코딩이며 옛날 국가 별 인코딩 (한국의 경우 EUC-KR)을 사용하던 사이트들도 UTF-8로 많이 옮겨가는 추세입니다. 그렇다면 어떻게 인코딩할까요?

인코딩 방식

바이트 수 바이트 1 바이트 2 바이트 3 바이트 4
1 0xxxxxxx      
2 110xxxxx 10xxxxxx    
3 1110xxxx 10xxxxxx 10xxxxxx  
4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

 

위 표의 이진 포맷으로 바이트 수와 인코딩 결과를 결정합니다. 먼저 시작 비트를 살펴보면 문자의 전체 바이트를 결정할 수 있습니다. 시작 비트가 0인 경우 1바이트 문자, 10인 경우 특정 문자의 중간 바이트, 110인 경우 2바이트, 1110인 경우 3바이트, 11110은 4바이트 인 것을 알 수 있습니다. 특히, 아스키 문자의 유니코드 값은 동일하므로 모두 그대로 1바이트에 표현이 가능해 불필요한 메모리 공간 낭비를 줄일 수 있는 것을 알 수 있습니다.

Example

 

위 그림은 각 문자의 유니코드 값을 UTF-8로 인코딩한 결과를 나타냅니다.

  • 문자 'A'는 아스키 문자로 유니코드 값은 65입니다. UTF-8 인코딩 결과도 코드표와 같습니다.

  • 문자 '$\pi$'의 경우 1바이트를 벗어나지만 11비트 이내에 표현이 가능합니다. 따라서 인코딩 결과 2바이트로 표현됩니다.

  • 문자 '한'의 경우 유니코드 상에서 16비트를 모두 사용합니다. 그림과 같이 3바이트로 표현됩니다. 참고로 유니코드에는 한글의 자음, 모음 뿐만 아니라 완성형 한글 11172자가 모두 포함되어 있고 유니코드 상에서 44032~55203의 영역이므로 모든 한글의 인코딩 결과는 3바이트로 표현됩니다. 

    • 유니코드에는 한글의 조합형과 완성형 (11172자)가 모두 코드표에 등록되어 있습니다. 하지만 유니코드에서는 조합형을 사용해서 한글을 구성하지는 않습니다. 조합형의 경우 (U+1100 - U+11FF, 십진수로 4352 - 4607)의 영역을 점유하고 있는데, 이 또한 3바이트 영역입니다. 따라서 조합형으로 한글을 표현할 경우 완성형으로 표현할 경우에 비해 코드 용량이 2~3배로 늘어나게 됩니다.

파이썬에서

파이썬 2 이전에는 한글을 비롯한 특수 문자들이 모두 별도로 인코딩되었으나 파이썬 3 이상부터는 문자열은 모두 유니코드 기반으로 전환되었습니다. (Cpython에서의 문자열 처리가 unicodeobject.c로 변경) 하지만 내부적으로는 UTF-8 인코딩을 사용하지는 않는데 이는 인덱스를 통해 개별 문자에 접근하기 어렵기 때문입니다.

UTF-8로 인코딩할 경우 각 문자마다 길이가 달라지게 되므로 문자열 슬라이싱의 경우 모든 문자열을 탐색해야하므로 원하는 인덱스에 빠르게 접근할 수 없습니다. 따라서 파이썬은 효율적인 인덱싱을 위한 고정 길이 인코딩 방식을 사용합니다.

만약 모든 문자열이 아스키 범위 내의 있다면 고정 1바이트 인코딩인 Latin-1 인코딩을 사용하고 이외의 대부분 문자열은 고정 2바이트 인코딩 (UCS-2)를 사용합니다. 특수 기호, 이모티콘 등이 포함된 문자열이라면 고정 4바이트 인코딩 (UCS-4)를 사용합니다. (UCS는 Universal Characteristic Set의 약자로 UCS 인코딩은 유니코드 문자표를 그대로 사용합니다.)

 

참조

반응형