유니코드

파이썬과 유니코드

적어도 파이썬3을 사용한다면 이 이슈에서는 좀 더 자유로울 수 있다. 그리고 또한 만약 고전적인 아스키문자 열 세트내의 문자, 숫자등을 포괄하는 127개의 글자만을 사용한다면 역시 이 부분에 대해서 자유로울 수 있다. 하지만 한글, 일본어, 한자와 같은 비 라틴 문자 계열의 언어를 사용해야 하고, 적어도 그것을 다른 시스템 (대표적으로 웹!)과 주고 받아야 하거나, 아니면 웹에서 다른 언어로 된 문자들을 주고 받아야 하는 종류의 작업과 관련이 있다면 유니코드에 대해서 어느 정도는 파악할 필요가 있다.

주목할만한 것은 사람들은 아스키코드에 대해서는 상당히 친숙하게 느끼면서 유니코드는 어려워한다는 것이다. 이는 단지 아스키코드를 “단순 문자열”로 생각하는대신 유니코드를 매우 특별한 것으로 생각하는데서 오는 착각이기도 하거니와, 사실 유니코드가 아니면서 유니코드인 척했던 수많은 문자열 인코딩 방식과 코드 페이지 이름들 때문에 오는 착각이 유니코드를 이해하는 것을 (그것도 매우 심하게) 방해하기 때문이다.

아스키코드

아스키코드는 컴퓨터 시스템의 리즈시절에 영문 대소문자와 숫자, 그리고 일부 특수 기호 및 출력 장치 제어문자들을 묶어서 정리한 문자 체계이다. 영문자는 26자, 대소문자를 합쳐도 52자에 불과하고 여기에 숫자라든지 그외 특수 기호들을 실어도 상당히 널널하게 사용할 수 있었기에 7비트(0~127) 크기에 테이블이 이를 정리하기란 그리 어렵지 않았을 것이다. 이는 컴퓨터에서 입출력의 기본 단위인 바이트 보다도 작았으므로 완벽하다고는 할 수 없지만 상당히 깔끔하고 견고한 체계로 자리 잡았다.

문자

컴퓨터에서 ‘A’라는 글자는 단지 숫자값으로 인식한다. 대문자 A는 아스키코드상에서 65번째에 위치한다. 따라서 글자 A는 값 65이다. 65에 1을 더한 값은 66인데, 이것을 한 글자(char)로 인식하면 ‘B’가 된다. 따라서 대문자로 변환하는 수식은 다음과 같이 표현할 수 있다. (C)

_upper = ( _lower >= 'a' && _lower <= 'z') ? (_lower - 'a' + 'A') : _lower;

문제

하지만 좀 더 많은 글자 수를 필요로 하는 언어 체계들이 있다. 한국어의 경우에도 자모 조합은 1만개 가량 되며 (11,172자 – 9위키백과 참고) 이를 다 표현하려면 1바이트로는 터무니 없이 부족하다. 그리고 한글만 있느냐? 아니다 한국어에서도 한자를 쓴다. 상용한자를 추가한다고 해도 몇 천자 정도를 추가해주어야 하는 것이다.

그래서 비영어권 국가에서는 2바이트 문자 체계를 각자 마련하였다. 그러니까, 아스키코드가 주욱 나열되는 문자표 뒤에 칸을 더 많이 만들어서 한글자, 한글자를 써 넣고 여기에 매겨진 코드 번호를 사용하여 글자를 표현하는 것이다. 여기서도 문제는 있는데 아스키 코드 값은 1바이트내에 그대로 들어가는데 반해, 한글이나 한자의 경우에는 2바이트를 모두 사용해야 한다. 따라서 컴퓨터가 2바이트의 자료를 “두 글자의 영문(아스키문자)으로 볼 것이냐, 한 글자의 2바이트 문자로 볼 것이냐”를 생각해주는 섬세함이 필요했다.

다행스럽게 아스키코드는 7비트만 사용하기 때문에 그 2바이트라면 한글을 넣을 공간은 충분해 보였다. 아니 무엇보다도 2바이트만큼의 갯수를 문자로 채워넣었을 때 그것을 구현하는 것이 당시 컴퓨터의 성능으로는 상당한 난제가 될 정도였다.

아무튼 이런 전차로 작으나마 추가적인 공간을 확보할 방법을 얻은 동아시아의 여러 나라들은 자신들의 문자를 사용한 2바이트 문자열 체계를 구축했다.

심지어 한국의 경우에도 국가에서 완성형 한글을 표준으로 제정하기 이전에는 완성형코드와 조합형 코드로 크게 양분되어 있었고, “컴퓨터 메이커들마다 모두 고유의 한글 문자 체계를 가지고 있었다. 예를 들면 ‘삼성한글’, ‘대우한글’ 뭐 이런 식이었다.

그것도 문제인 것이, 코드 페이지의 크기도 문제였지만, 글꼴 데이터의 양이 당시로서는 메모리에 모두 올려두기 벅찰만큼 많아서 (무려 몇십 킬로바이트) 한글도 3000자도 표현하지 못했다.

또 다른 문제

하지만 다시 문제가 되는 것이, 옛날에는 컴퓨터들끼리 데이터를 주고 받을 때, 예를 들어 바이너리 데이터의 경우에는 국가나 언어를 따지지 않아도 됐다. 하지만, 여러 언어의 텍스트가 만나는 인터넷에서는 이게 문제가 되는 것이다.

예를 들어 완성형 한글에서 ‘45218’ 번째 글자는 ‘각’인데, 이 위치의 일본어 코드는 ‘唖’이 있다. 덕분에 일본에서 보낸 메일을 한국에서 열어보면 (완성형 한글 코드페이지에는 가나 문자가 있지만, 그게 일본에서도 같은 코드 값이라는 보장이 없다.) 대신 ‘가갹떫샳뀳’ 뭐 이런 글자들이 화면을 메우게 되는 것이다. 이는 국가별로 그냥 자기 나라 언어에 대해서 독자적으로 코드 테이블을 작성하기는 했지만, 컴퓨터는 그게 뭐 어쨌든 지금 자기가 사용하고 있는 언의 코드표대로 화면에 뿌려줄 뿐인 것이다.

이런 문제때문에 전세계의 모든 언어를 전부 올려놓을 수 있는 거대한 문자표가 필요했고, 그렇게 제정된 것이 유니코드이다.

유니코드

유니코드는 6바이트. 그러니까 281,474,976,710,656 가지 글자를 담을 수 있는 엄청 무식하게 큰 문자열표이다. 아마 저정도 크기이면 전지구상의 언어가 아니라 전우주상의 언어에 필요한 문자들을 전부 모아놓을 수 있을 것이다. 하지만 문자열 표가 너무 컸던 탓에 대부분의 공간은 여전히 빈칸이다. 가장 앞쪽의 127자는 대장님인 아스키코드와 동일하고 그 이후부터는 동네마다 아주 널찍 널찍하게 구역을 나눠서 각 국의 언어를 표현하기 위한 문자세트들을 나열하여 번호를 하나씩 붙이고 있다. (그럼에도 불구하고 아직 많은 부분은 “예약”만 되어 있고, 심지어 가장 최근 버전인 유니코드6에서도 5바이트까지만 정의하고 있다.)

현재의 ‘통상적’ 유니코드의 범위는 4바이트이며 (이것도 벌써 4,294,967,296 가지 글자를 표현할 수 있다고!!!) 모든 문자를 4바이트로 표현한다.

다시 또 문제

그런데 그러다보니 엄청난 낭비가 발생하는 것이다. 컴퓨터가 처리하는 텍스트의 대부분은 한글이나 한자가 아니라 (물론 지금은 중국의 엄청난 인구가 엄청난 비율로 인터넷을 사용하고 있으니, 어쩌면 인터넷에서 가장 많은 문자는 한자일 수도 있겠다.) 아스키코드일 거란 이야기다. 뭐 물론 인터넷으로 주고 받는 문자는 그렇다치더라도 수많은 소스 코드들을 한 글자당 4바이트로 처리하면, 어떤 코드들은 그냥 용량이 4배로 뻥튀기 되는 비효율을 당하게 된다.

그래서 4바이트로 되어 있는 유니코드를 좀 더 적은 바이트를 사용해서 표현하려는 시도들이 있다. 용량이 큰 텍스트 파일을 압축하듯이 유니코드 문자표를 압축하는 것이다. 그래서 개발된 몇 가지 압축 방식이 있는데, 그 중 대표적인 것이 ‘UTF-7’, ‘UTF-8’, ‘UTF-16’과 같은 유니코드 인코딩이다. “유니코드 인코딩”이라는 표현이 혼동을 줄 수 있는 좋지 않은 표현이므로, 그냥 “인코딩”이라고 하겠다.

이 인코딩 방식들은 압축파일의 zip, rar, tar 등과 같은 포맷처럼 서로 다른 방식으로 유니코드 문자표를 압축한다. 그래서 4바이트나 그 이상의 바이트로 이루어진 문자들을 보다 적은 바이트를 사용하여 저장하고, 다시 문자를 표현해야 하는 시점에 압축을 풀어서 원래의 유니코드 문자표를 복원하는 것이다.

이것이 가능한 것은 앞서도 이야기했지만, 방대한 유니코드 문자표에는 광활한 우주마냥 빈공간이 매우 많기 때문이다.

UTF-8

인터넷에서 가장 많이 사용되는 문자 인코딩 방식이다. UTF-8은 특이하게 한 글자를 1바이트에서 3바이트를 사용하여 표현한다. 맨 앞쪽 아스키코드에 대응되는 문자는 한 글자가 1바이트로 되어 있으며, 그외의 문자는 두 번째 바이트의 값에 따라 2바이트 혹은 3바이트가 된다. 대부분의 라틴문자, 숫자, 기호가 1바이트로 구성되므로 파일 저장이나 인터넷을 통한 전송에 매우 유리한 장점이 있으나, 인코드된 데이터 그 자체로는 정렬이나 자르기 등의 조작이 매우 어렵다는 문제가 있다.

임의의 UTF8 데이터에 대해서 15글자까지만 딱 끊어서 읽으려면 몇 바이트를 읽어야 하느냐는 문제는 UTF8의 경우 상당한 난제가 된다.

하지만 저장 공간 활용의 효율성이 좋아서 인터넷에서 많이 쓰이며, 많은 OS에서 파일 저장용 인코딩으로도 널리 사용되고 있다.

UTF-16

UTF-16은 유니코드 문자열을 2바이트내에 모조리 맵핑하는 방식을 취하고 있고, 유니코드의 디자인에 가장 근접한 인코딩 방식이라는 평을 듣고 있다. 그러나 아스키문자 세트도 2바이트로 표현되어야 해서 호환이 되지 않는다는 단점도 있으나, 문자열을 프로그램이 처리하는데는 상당히 유리하다. OSX 및 iOS에서 사용되는 문자열 객체인 NSString은 내부적으로 UTF16으로 인코딩된 문자열을 사용하고 있고, 파일에 저장할 때는 다시 UTF8 문자열로 인코딩을 변경하여 저장한다.

그외의 인코딩들

하지만 인터넷이나 프로그래밍 관련 문서에서는 그보다도 더 다양한 엄청나게 많은 인코딩들이 존재한다. 우리에게 익숙한 인코딩은 한글 인코딩 중에서 EUC-KR이나 CP949 같은 것들이 있고, 가까운 일본에서 사용하는 EUC-JP 같은 것도 있다.

이들은 원래 코드페이지 (그러니까 유니코드가 생긴 전후에 사용되던 국가별 문자표)의 이름인데, 유니코드는 이러한 거의 대부분의 코드페이지에 들어있는 문자들을 모두 포함한다. 따라서 코드페이지의 문자 번호를 잘 다듬은 공식을 통해서 유니코드 상에서 같은 문자 번호가 되도록 변환하는 식을 만들 수 있을 것이다. 이렇게 만든 인코딩을 아예 코드페이지의 이름을 따서 이름을 지었다. 따라서 EUC-KR은 원래 EUC-KR에 있던 문자값으로 유니코드 문자를 변환하는 혹은 그 반대로 변환하는 코덱의 이름인 셈이다.

결론

다음과 같이 요약해서 이해하면 되겠다.

  • 유니코드는 그냥 유니코드이다.
  • UTF8, UTF16은 유니코드를 압축한 데이터이며, 그 자체로는 유니코드가 아니다.
  • EUC-KR, CP949는 더더욱 유니코드가 아니며, 그냥 한국어글자세트라는 의미정도로만 이해하면 된다.
  • 그리고 저런 코드페이지만 지원하는 시스템과 유니코드를 지원하는 시스템 사이의 호환을 위해서 각각의 인코딩을 쓴다.