초기의 아스키는 각 문자를 7비트로 표현하여 총 128개의 문자를 처리할 수 있었다. 그리고 후에 추가적인 문자를 지원해야 할 필요가 있어 여기에 1비트를 추가한 확장 아스키(extended ASCII)가 등장한다. 8비트를 사용하는 이 확장 아스키는 256개의 문자를 표현할 수 있다. 처리해야 할 문자의 종류가 지금처럼 다양하지 않았던 시절, 문자 표현은 이 아스키만으로 충분했다. 오늘날의 글로벌 커뮤니케이션이 예상되지 않았던 시기의 일이다. 이 때는 영어, 아랍어, 중국어 등 다양한 언어가 한 문서에 존재하는 일은 흔치 않았다.
세상에는 수많은 종류의 언어를 사용하는 사람들이 존재하고, 이들 중 라틴계열 문자를 사용하지 않는 사용자는 거의 절반 이상이 될 것이다. 이렇게 다양한 사용자에 대응하는 소프트웨어를 개발하는 데 있어 오직 자신이 사용하는 언어만을 취급한다면 이는 사려깊지 못한 일이다.
이러한 이유로 모든 언어를 수용할 수 있는 문자셋의 필요성이 대두되어 탄생한 것이 유니코드이다. 유니코드는 모든 문자에 대해 각각의 유니크한 번호를 부여하였는데, 이를 코드 포인트라 한다. 유니코드의 이점 중 하나는 처음 256개의 코드는 ISO-8859-1 과 동일하다는 것이다. 고로, 이는 아스키와 동일하다. 그리고 널리 쓰이는 문자들 중 절대 다수의, 엄청나게 많은 문자들이 단 2바이트만으로 표현될 수 있다(Basic Multilingual Plane, BMP). 이제 문자셋에 접근하기 위해 필요한 인코딩에 대해 이야기한다. 여기서는 UTF-8, UTF-16 에 대해 다룰 것이다.
메모리 관련
어떤 종류의 문자가 얼마만큼의 바이트를 필요로 하는가
UTF-8:
1 바이트: 표준 아스키
2 바이트: 아랍, 히브리, 대부분의 유럽계(조지안 문자를 제외한)
3 바이트: BMP
4 바이트: 모든 유니코드 문자
UTF-16:
2 바이트: BMP
4 바이트: 모든 유니코드 문자
여기서 BMP 에 속하지 않은 문자들에는 고대 문자, 수학/음악 기호, 중국/일본/한국 문자 중 몇몇 드물게 보이는 문자들이 포함된다.
만약 대부분의 작업에 아스키 문자를 사용한다면 UTF-8은 메모리에 있어 확실히 더 나은 효율성을 제공한다. 그러나 비 유렵계 문자를 사용한다면, UTF-16 이 UTF-8보다 최대 1.5배 나은 메모리 효율성을 보인다. 이런 사항은 웹페이지나 큰 사이즈의 워드 문서와 같은 거대한 양의 텍스트를 처리할 때 성능에 영향을 줄 수 있다.
인코딩에 있어
UTF-8: 표준 아스키(0-127)와 UTF-8 코드는 완전히 동일하다. 이는 기존 아스키 텍스트의 호환성을 고려해야 할 때 UTF-8이 이상적으로 꼽히는 이유가 된다. 이 밖에 다른 문자들은 2-4 바이트를 필요로 한다. 이 문자들에 대한 처리는 각 바이트의 일부 비트를 예약하는 방법으로 수행되는데, 이는 멀티 바이트 문자의 일부임을 나타내기 위함이다. 특히 아스키 문자와의 충돌을 피하기 위해 각 바이트의 첫 번째 비트는 1이다.
UTF-16: UTF-16의 BMP 문자 표현법은 단순하게도 코드 포인트로 이루어진다. 이에 비해 BMP 외 문자에 대한 표현은 좀 더 복잡한데, 여기에는 surrogate pairs 라 불리는 방법을 이용한다. 유니코드의 2바이트 기본 범위(BMP) 영역을 넘어선 문자들을 Supplementary Characters 라 하는데, surrogate pairs는 이 문자들을 표현하기 위해 도입된 방법이다. surrogate pairs는 두 쌍의 2바이트를 이용하여 2바이트를 넘어서는 문자를 표현한다. 이 2바이트 코드는 BMP 범위에 포함되지만, 유니코드 표준에 의해 BMP 문자가 아님을 보장받는다. 그리고 UTF-16은 기본 단위를 2바이트로 가지기 때문에 엔디안의 영향을 받는다. 이를 보완하기 위해 엔디안을 나타내는 데이터 스트림의 시작부에 예약된 바이트 순서 마크가 위치한다. 고로 엔디안이 지정되어 있지 않은 UTF-16 입력을 읽을 때는 이를 확인할 필요가 있다.
이처럼 UTF-8과 UTF-16은 서로 호환되지 않는다. 입출력을 처리할 때는 어떤 인코딩을 사용하고 있는지 정확히 알 필요가 있다.
프로그래밍에서의 고려사항
Character 와 String 데이터 타입: 사용하는 프로그래밍 언어에서 어떤 방식으로 인코딩을 하는지가 중요하다. raw 바이트를 비 아스키 문자로 출력하려고 한다면 문제가 발생할 수 있다. 또한 사용하는 문자 타입이 UTF 기반이라고 해도 이것이 알맞는 UTF 문자열을 보장해주지는 않는다. 문자열은 적합하지 않은 바이트 시퀀스를 허용할 수 있다. 일반적으로 이러한 처리에는 UTF를 제대로 지원하는 라이브러리가 사용된다. 어떤 경우든 입출력에 기본 인코딩이 아닌 다른 인코딩을 사용하려 한다면 먼저 컨버팅 작업을 수행하야 할 것이다.
권장되는 인코딩 방식: 어떤 UTF 방식을 사용할지 결정해야 할 경우에는 대부분 작업 환경의 표준을 따르는 것이 최선의 선택이 된다. 예를 들어, 웹 환경에서는 UTF-8이, 다른 자바 환경에서는 UTF-16이 권장된다.
라이브러리 지원: 사용중인 라이브러리가 어떤 인코딩을 지원하는가? 흔치 않는 경우에 발생하는 이슈에 대해서도 커버할 수 있는가? 1-3바이트 문자는 흔히 등장하기 때문에, UTF-8 라이브러리는 대부분 4바이트 문자를 정확하게 지원한다. 그러나 UTF-16을 지원한다고 알려진 모든 라이브러리가 surrogate pairs 를 제대로 지원하지는 않는다. (surrogate pairs가 등장하는 경우는 실제로 매우 드물기 때문이다)
문자 카운팅: 유니코드에는 결합 문자들이 존재한다. 예를 들어 코드 포인트 U+006E (n), U+0303(틸드 부호) 는 ñ을 표현한다. 그런데 코드 포인트 U+00F1 이 표현하는 것도 ñ 이다. 이들은 동일한 문자로 보이지만, 단순한 카운팅 알고리즘은 전자에 대해 2를 반환하고, 후자에 대해서는 1를 반환할 것이다. 이것은 잘못된 것이 아니다. 하지만 괜찮은 결과도 아닐 수 있다.
일치성 비교: A, А, Α. 이 A는 모두 같아 보인다. 그러나 앞에서부터 하나는 라틴, 하나는 키릴, 나머지 하나는 그리스 문자이다. 또 이런 경우도 있다. "C, Ⅽ". 하나는 문자이고 다른 하나는 로마 숫자이다. 이것들 외에도 위에서 언급한 결합 문자열 등 여러가지 고려사항이 있다.
Surrogate pairs: 위에서 언급했으므로 패스.