sitelink1 https://d2.naver.com/helloworld/19187 
sitelink2  
sitelink3  
sitelink4  
sitelink5  
sitelink6  

분명 제대로 보이는 한글 이름의 파일을 내려받았는데 읽을 수 없는 이상한 이름으로 저장된 파일을 받아본 경험이 있을 것입니다. 보통 '인코딩이 깨졌다.'라고 말하는 이런 상황은 왜 발생하는 것일까요? 그 이유는 컴퓨터에서 한글을 표현하는 다양한 방식이 있는데 해당 방식이 서로 맞지 않기 때문입니다.

최초로 컴퓨터가 발명되고 오랜 기간 동안 발전되어 온 지역이 미국이기에 해당 지역에서 사용하는 언어의 문자 집합인 영어 알파벳과 이와 비슷한 문자 체계를 지닌 유럽어 알파벳 처리에 대한 연구가 가장 먼저 시작되었습니다. 이 외의 다른 문자 집합(character set)은 기존에 수립된 인코딩(영어 및 유럽어 문자 집합용)으로 표현하기에는 한계가 있었기 때문에 이들을 처리하기 위한 연구가 추가로 진행되었습니다.

요즘은 어느 전자 기기에서나 한글을 제대로 입력할 수 있고 일부 소형 기기에서는 한글을 더 빠르게 입력할 수도 있어 컴퓨터에서 한글을 처리하는 작업이 너무나 쉽고 당연하게 받아들여지고 있습니다. 하지만 한글을 제대로 표현하기 위한 한글 인코딩 체계가 수립되기까지는 수십 년의 세월이 걸렸습니다.

현재 우리나라에서 주로 사용하고 있는 CP949 또는 EUC-KR(둘은 엄밀히 다릅니다) 인코딩과 유니코드를 제대로 이해하기 위해서는 한글을 표현하기 위한 그간의 역사를 알 필요가 있습니다. 2편 연작으로 기획된 이 기사의 1편에서는 한글 인코딩의 역사를 다루고, 2편에서는 'Java 언어를 기준으로 한글을 처리하는 방법'을 다루도록 하겠습니다.

문자 집합과 인코딩

컴퓨터는 수치 연산을 위해 설계되었다. 컴퓨터 발명 초기에는 문자를 표현해야 하는 요구가 없었다. 영어 단어 'compute'는 단순히 '계산하다'라는 뜻이고, 초창기의 컴퓨터와 '전자 계산기'는 동의어이기도 했다. 그러나 (너무나 당연하지만) 문자를 표현해야 하는 요구가 발생했다. 컴퓨터 간에 문자 데이터를 교환해야 할 일이 생기기도 했다. 이기종 컴퓨터끼리 문자 데이터를 교환하기 위해서는 표준이 필요하다. 이런 이유로 ASCII(American Standard Code for Information Interchange)와 같은 표준 문자 인코딩이 만들어졌다.

문자를 표현하기 위해서는 가장 먼저 '문자 집합'을 정의해야 한다. 문자 집합은 표현해야 할 문자를 정하고 순서를 지정한 것이다. 영어라면 'A', 'B', 'C'에서 'Z'까지(소문자 a에서 z), 한글이라면 '가', '각', '간'에서 '힣'까지다. 물론 숫자나 특수 문자뿐만 아니라 인쇄와 통신을 제어하기 위한 제어 문자도 문자 집합에 포함되어야 한다. 이러한 문자 집합을 코드 형태(일반적으로 행렬)로 표기한 것을 코드화된 문자 집합(CCS, coded character set)이라고 한다. 예를 들어 '가'에는 10001, '각'에는 10002와 같이 코드를 할당하는 방식 말이다. 그리고 문자 집합을 컴퓨터에 저장하기 위해서 옥텟(octet, 8비트 단위) 형태로 표현한 것을 인코딩 방식(CES, character encoding scheme)이라고 한다.

영어의 문자 집합과 인코딩

최초의 컴퓨터인 ENIAC(Electronic Numerical Integrator And Computer)이 만들어진 이후 약 15년이 지나서야 문자 집합이라는 개념이 생겼다. 그 시초는 ASCII이다. ASCII는 0x00부터 0x7F까지의 총 127개 문자(제어 문자, 특수 문자, 숫자, 알파벳 등)로 이루어져 있다. 이는 미국에서 제정된 표준이니 영어 알파벳을 표현하기에는 큰 문제가 없었겠지만, 'Ü'와 같은 문자를 표현할 수 없어 유럽어에는 사용할 수 없었다. 이를 해결하기 위해 확장 ASCII(Extended ASCII)를 제정하여 기존의 ASCII로 정의하지 못했던 128번부터 255번까지의 새로운 문자를 정의할 수 있게 되었다. 즉, 새로 추가된 128개의 코드(0x80 ~ 0xFF)로 프랑스어, 독일어 등의 유럽어를 표현할 수 있게 된 것이다. 이와 같이 다양한 유럽어를 표현할 수 있는 확장 ASCII는 ISO-8859 유럽 통일 표준안으로 제정되었다. (표준안의 ISO 버전은 언어에 따라 약간씩 다르다. 예를 들어 ISO 8859-1은 서유럽 언어를, ISO 8859-2는 동유럽 언어를 표현하기 위한 표준안이다.)

한글의 문자 집합과 인코딩

영어나 유럽어는 알파벳을 기초로 사용하므로 256개의 코드를 이용하여 문자 집합을 표현하는 것이 가능하다. 하지만 중국, 일본, 한국(CJK, Chinese-Japanese-Korean)에서 사용하는 문자 집합인 한글, 한자, 가나, 병음, 주음 부호 등은 그 개수가 많아 확장 ASCII 코드로도 이를 모두 처리할 수 없다. 따라서 CJK 문자를 처리하기 위한 별도의 방안이 필요하다.

그렇다면 컴퓨터에서 한글을 표현하는 방법에는 무엇이 있을까? 한글 표현 방법은 크게 조합형과 완성형으로 나눌 수 있으며, 이를 좀 더 세분화하면 N바이트 조합형, 3바이트 조합형, 7비트 완성형, 2바이트 조합형, 2바이트 완성형으로 나눌 수 있다. 조합형이란 한글의 제자 원리에 기반하여 초성, 중성, 종성에 각각 코드를 할당하는 방식이고, 완성형이란 '가', '각', '간'과 같은 완성된 문자에 코드를 할당하는 방식이다. 이 중 완성형이 한글 표준안으로 채택되었고, 따라서 유니코드의 한글 표현 방식에도 완성형이 먼저 채택되었다.

Microsoft Windows 95 이전, 본격적으로 한글 문자 집합과 한글 글꼴이 운영체제에 포함되기 전인 DOS(Disk Operating System) 시절에는, 결과를 출력하기 위해 BIOS(Basic Input/Output System)를 직접 제어하거나 '한글 카드'라 불리는 ISA(Industry Standard Architecture) 인터페이스와 같은 별도의 하드웨어를 사용해야 했다.

N바이트 조합형

유럽어만 표시할 수 있었던 컴퓨터에서 한글을 표시하기 위한 고육지책으로 고안된 최초의 한글 표현 방식이다. 이 방식은 대형 컴퓨터에서 단말기를 이용하여 한글을 표현하는 데 사용되었다. N바이트 조합형은 각각의 개별적인 한글 자모를 영문자 하나하나에 대응시키고, 시작과 끝에 SI(Shift In)와 SO(Shift Out)을 추가하여 한글과 영어를 구분하는 방식이다. 예를 들어, '한글'은 ^N^bDAzI^O로 표현할 수 있다(^N은 SI이고 ^O는 SO임).

다음 표는 N바이트 조합형의 한글/알파벳 매핑 규칙을 보여준다.

자음 모음
A b
B c
D d
G e
H f
I g
Q j
R k
S l
U r
V s
W w
X z
Y |
Z    
[    
\    
]    
^    

3바이트 조합형

초성, 중성, 종성에 1바이트씩 할당하여 사용하는 방식이다. 3바이트 조합형은 80년대 개인용 컴퓨터(PC)에서 사용되었다. 단지 중성과 종성이 없는 글자를 위해 채움 문자(fill code)를 정의하고 있다. N바이트 조합형이 한글을 2바이트에서부터 5바이트로 표현하는 데 반해, 3바이트 조합형은 3바이트로 표현하며 이는 KS X 1001 표준안의 한글 채움 문자 방식과 유사하다.

7비트 완성형

세운상가에서 만들었다고 하여 청계천 한글이라고도 한다. 소문자 뒤에 대문자가 오는 경우가 거의 없고, 특수 문자 뒤에 영문자가 오는 경우가 거의 없다는 점에 착안하여 고안한 방식이다. 즉, 처음 1바이트가 소문자이거나 특수 문자이고, 그 다음 1바이트가 대문자이면 한글로 표현한다. 글자 표현이 1,300여자 정도로 제한된다는 점과 일부 영어 단어가 한글로 표시(1990년대 초반 자주 사용되던 'dBASE'라는 프로그램 이름이 '늦ASE'로 표시)되는 문제가 있다.

표현 글자 수가 적었지만, 7비트 완성형은 당시 많이 사용했던 허큘리스 그래픽 카드를 확장하여 만들어진 기능이기 때문에 과거 80~90년대의 컴퓨터에서 작업 처리 속도를 지연하지 않는 유일한 방식이었다. 이런 이유로 당시에는 어느 정도의 수요가 있었다.

2바이트 조합형

초성, 중성, 종성에 각각 5비트씩 할당하고, 처음 1비트(MSB, most significant bit)는 1로 설정하여 한글임을 표시하는 방식이다. 대우, 삼보 등의 여러 회사에서 각각의 조합형 방식을 제안하여 사용하였으며, 나중에는 삼보 컴퓨터가 주도한 상용 조합형(KSSM)이 표준처럼 사용되었다. 예를 들어 '한글'은 1 10100(ㅎ) 00011(ㅏ) 00101(ㄴ) 1 00010(ㄱ) 11011(ㅡ) 01001(ㄹ) (0xD0, 0x65, 0x8B, 0x69)로 표현된다.

다음 표는 2바이트 조합형의 한글 매핑 규칙을 보여준다.

비트 조합 초성 중성 종성
00001 채움   채움
00010 채움
00011
00100
00101
00110
00111
01000  
01001  
01010
01011
01100
01101
01110
01111
10000  
10001  
10010  
10011
10100
10101  
10110  
10111  
11000    
11001    
11010  
11011  
11100  
11101  

2바이트 완성형

완성된 음절을 코드와 일대일 대응시키는 방식이다. 예를 들어, '가'는 0xB0A1, '각'은 0xB0A2로 코드화한다. ISO 2022 표준을 기준으로 하였으며, KS C 5601:1987 표준안으로 채택되었다. ISO 2022 표준은 ASCII 영역과 겹치지 않도록 첫 번째 비트 값을 1로 규정하였으므로, KS C 5601 표준안은 0xA1A1부터 0xFEFE까지의 영역(94x94, 8,836글자)만을 사용하였다. 더군다나 8,836개의 글자 중에서 부호 및 일본, 러시아 글자에 1,598자를, 한자에 4,888자를 할당하여 한글에는 2,350자밖에 사용할 수 없었다. 2바이트 완성형의 문제는 1990년에 방영된 MBC 드라마 제목 '똠방각하'를 표현하지 못하면서 불거졌는데, 완성형으로는 '똠'을 표현할 수 없어 한글의 제자 원리를 무시한 방식이라는 비난을 피할 수 없게 되었다.

이러한 문제를 해결하기 위해 1992년에 KS C 5601 표준안에 완성형/조합형을 모두 표준으로 지정하였으나, 표준 조합형은 기존에 사용되던 상용 조합형(KSSM)과 코드가 맞지 않아 거의 사용되지 않았다. 1990년대 초반까지 조합형과 완성형이 모두 사용되었지만, 국가 주도의 프로그램, 운영체제에서 완성형을 기본으로 지원하였고, Microsoft Windows 95에서 확장 완성형이 사용됨으로써 조합형은 사장되었다. 현재는 KS C 5601은 KS X 1001로, KS C 5636은 KS X 1003으로 변경되었다. KS X 1003 표준안은 ASCII와 동일하나, 역슬래시(\)가원화(\)로 표기되는 것만 다르다.

확장 완성형

Microsoft가 독자적으로 제정한 문자 집합으로 완성형 코드에서 표현할 수 없던 8,822자가 추가되었다. 통합형 한글 코드(UHC, Unified Hangul Code)라고도 하며, 현대 한글을 모두 표현할 수 있다. 그러나 완성형 영역의 2,350자는 자음, 모음 순서대로 배열되어 정렬에 문제가 없었으나, 확장 완성형의 문자 정렬에는 문제가 있었다.

한글의 인코딩 방식

EUC-KR은 KS X 1001과 KS X 1003 표준안의 인코딩 방식이며, CP949(MS949, x-windows-949)는 확장 완성형의 인코딩 방식이다. 그러므로 EUC-KR은 2,350자의 한글, CP949는 11,172자의 한글을 표현할 수 있다. 그러나 Java에서는 CP949와 MS949를 다르게 취급한다. CP949는 IBM에서 처음 지정한 코드 페이지(sun.nio.cs.ext.IBM949)가 기준이고 Microsoft가 제정한 확장 완성형은 MS949(sun.nio.cs.ext.MS949)를 기준이다. 그러므로 Java에서는 CP949와 EUC-KR이 사실상 같으며, 확장 완성형을 사용하기 위해서는 MS949로 지정해야 한다.

유니코드

한글뿐 아니라 일본어와 중국어에도 컴퓨터에서 해당 언어를 표현할 수 있는 독자적인 문자 집합이 있다(KPS-9566, EUC-CN, EUC-TW, EUC-JP, Shift JIS, Big5, GB, HZ 등). 문제는 '어떻게 동시에 한국어, 중국어, 일본어를 표현하느냐'이다. 하나의 문자 집합을 사용하는 문서에서는 이를 동시에 표현할 수 없다(escape sequence를 이용하여 여러 문자 집합을 표현할 수 있으나 이는 널리 쓰이지 않았다).

이런 문제는 유럽어의 문자 집합에도 있었다. 유로화를 나타내는 '€' 기호에는 ISO 8859-15(Latin 9)의 코드 값 중 0xA4이 할당되었으나 ISO 8859-1(Latin 1)의 0xA4 코드에 할당된 문자는 '¤'다. 이 문제를 해결하기 위해 전 세계적으로 사용되는 모든 문자 집합을 하나로 모아 탄생시킨 것이 유니코드이다. ISO 10646 표준에서 UCS(Universal Character Set)을 정의하고 있다. 유니코드 1.0.0은 1991년 8월 제정되었으며, 그 후 약 5년이 지나서야 유니코드 2.0.0에 한글 11,172자가 모두 포함되었다. 현재 버전은 2010년 10월 11일 제정된 6.0이다.

유니코드 값을 나타내기 위해서는 코드 포인트(code point)를 사용하는데, 보통 U+를 붙여 표시한다. 예를 들어, 'A'의 유니코드 값은 U+0041로 표현한다(\u0041로 표기하기도 함). 유니코드는 공식적으로 31비트 문자 집합이지만 현재까지는 21비트 이내로 모두 표현이 가능하다. 유니코드는 논리적으로 평면(plane)이라는 개념을 이용하여 구획을 나누며, 평면 개수는 0번 평면인 기본 다국어 평면(BMP; Basic Multilingual Plane)에서 16번 평면까지 모두 17개이다. 대부분의 문자는 U+0000~U+FFFF 범위에 있는 기본 다국어 평면에 속하며, 일부 한자는 보조 다국어 평면(SMP, Supplementary Multilingual Plane)인 U+10000~U+1FFFF 범위에 속한다. 이 중 한글은 U+1100~U+11FF 사이에 한글 자모 영역, U+AC00~U+D7AF 사이의 한글 소리 마디 영역에 포함된다(자세한 내용은 위키백과의 '유니코드 목록' 내용 참조).

유니코드 인코딩 방식

유니코드의 인코딩 방식으로는 코드 포인트를 코드화한 UCS-2와 UCS-4, 변환 인코딩 형식(UTF, UCS Transformation Format)인 UTF-7, UTF-8, UTF-16, UTF-32 인코딩 등이 있다. 이 중 ASCII와 호환이 가능하면서 유니코드를 표현할 수 있는 UTF-8 인코딩이 가장 많이 사용된다. UTF-8은 코드 포인트 범위에 따라 다음 표에서 보는 바와 같이 인코딩 방식이 다르다.

다음 표는 코드 포인트 범위에 따른 UTF-8 인코딩 방식을 보여준다.

<코드 포인트 범위 비트 수 인코딩
U+0000~U+007F 7 그대로 인코딩
U+0080~U+07FF 11 110xxxxx 10xxxxxx
U+0800~U+FFFF 16 1110xxxx 10xxxxxx 10xxxxxx
U+10000~U+1FFFFF 21 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

위의 표에서 xxxx로 표시된 부분에는 원래의 비트 값을 순서대로 적는다. 즉, U+0080을 비트 값으로 표현하면 000 1000 0000인데, 인코딩 방식에 의해 11000010 10000000으로 변환되어, 0xC2 0x80으로 저장된다. 예를 들어, '한글'을 코드 포인트로 표현하면 U+D55C U+AE00인데, 이를 UTF-8 인코딩하면, 0xED 0x95 0x9C 0xEA 0xB8 0x80이 된다.

U+D55C U+AE00

1101 0101 0101 1100 1010 1110 0000 0000            2진수 표현

1110 1101 1001 0101 1001 1100 1110 1010 1011 1000 1000 0000    인코딩 방식에 따라 인코딩

한글 완성형의 코드 포인트 범위는 U+AC00~U+D7AF이므로, UTF-8 인코딩에서 한글은 무조건 3바이트 인코딩이다. 그래서 URL에 파라미터 값이 %ED%95%9C%EA%B8%80과 같이 표시된다면 UTF-8 인코딩일 확률이 높다(ISO8859, EUC-KR, UTF-8 인코딩 중 하나라면 말이다).

이번 기사에서는 프로그램 언어에서 한글을 제대로 처리하기 위해 알아야 할 기본 지식을 알아 보았다. 다음 편 기사에서는 유니코드로 한글을 표현하는 방법과 Java에서 한글을 처리하는 방법에 대해서 다루고자 한다.

번호 제목 글쓴이 날짜 조회 수
291 자바 소수점 n번째 자리까지 반올림하기 황제낙엽 2019.07.29 163
290 java base64 encodeing,decoding 사용법 황제낙엽 2019.07.24 103
289 java.lang.StackTraceElement Class의 내용 출력 황제낙엽 2019.07.03 131
288 세션의 timeout 설정 >> HttpSession.setMaxInactiveInterval() 황제낙엽 2019.07.03 8311
287 jQuery JSON 데이터 통신의 특성 (HttpServletRequest) 황제낙엽 2019.06.23 103
286 [HttpURLConnection] 서버와의 통신 시도 시점 관련 황제낙엽 2019.06.23 116
285 역컴파일러 (decompiler, jad.exe) file 황제낙엽 2019.06.20 129
284 Microsoft SQL Server JDBC 드라이버 2.0 file 황제낙엽 2019.05.22 137
283 수치 데이터 처리 유틸리티 file 황제낙엽 2019.05.12 79
282 한글 초성 중성 종성 분리 유틸리티(자작) file 황제낙엽 2019.05.07 244
281 한글 초성 중성 종성 분리 (자모분리) 황제낙엽 2019.05.07 100
» 한글 인코딩의 이해 1편: 한글 인코딩의 역사와 유니코드 황제낙엽 2019.05.07 198
279 한글 인코딩의 이해 2편: 유니코드와 Java를 이용한 한글 처리 file 황제낙엽 2019.05.07 231
278 응답 헤더의 Content-disposition 속성 황제낙엽 2019.04.16 534
277 StringUtils - 문자열 처리 유틸리티 file 황제낙엽 2019.04.15 178
276 JSON과 GSON 황제낙엽 2019.03.24 134
275 File.length() 에 대하여 황제낙엽 2019.03.24 221
274 File.delete() 와 File.deleteOnExit() 황제낙엽 2019.03.24 1887
273 List to Array / Array to List 황제낙엽 2019.03.24 43
272 Oracle JAVA 유료화에 관련한 최신 기사 황제낙엽 2019.01.23 65