char, String, Unicode, UTF-X

2023. 5. 31. 20:53High Level Programming Language/Learning the Java Language

다음과 같이 char 타입의 변수를 선언하고 's' 를 대입하였다면,

char alpabet = 's';

alpabet에는 실제 아스키코드 0x73(0b0111 0011) 값이 저장됩니다.

 

유니코드

유니코드(Unicode)는 컴퓨터에서 문자를 표현하는 국제 표준입니다. 유니코드는 전 세계의 모든 문자를 정확하게 표현하고 조작할 수 있도록 설계되었습니다. 이는 각 문자마다 고유한 번호를 부여하는 방식으로 동작하며, 이를 '코드 포인트(세상의 모든 문자에 대한 유일한 식별자, ID)'라고 부릅니다.

유니코드는 다양한 문자 집합, 인코딩, 스크립트, 기호 등을 지원하며, 이는 각각의 글자를 숫자로 매핑함으로써 이루어집니다. 이로 인해 유니코드는 세계 각국의 언어, 수학적 기호, 이모지 등 다양한 형태의 문자를 포함할 수 있습니다.

유니코드에는 UTF-8, UTF-16, UTF-32 등 여러 가지 인코딩 방식이 있습니다. 이들 인코딩 방식은 유니코드 코드 포인트를 실제 바이트로 변환하는 방법을 정의합니다. UTF-8은 가장 널리 사용되는 유니코드 인코딩 방식으로, 아스키 코드와의 호환성 및 가변 길이 문자 인코딩의 이점 때문에 인기가 있습니다.
유니코드의 도입으로 인해 글로벌 컴퓨팅 환경에서 다양한 언어의 처리가 간편해졌습니다. 이전(웹브라우저 등장과 DB 데이터 저장 등등)에는 문자를 표현하는 방식이 언어와 지역에 따라 다르게 적용되었기 때문에, 다양한 언어 간의 데이터 교환에 많은 문제가 있었습니다. 유니코드의 표준화를 통해 이러한 문제가 크게 줄었습니다.

유니코드 코드 포인트는 유니코드 표준에서 문자나 기호를 나타내는 고유한 숫자 값입니다. 

유니코드 코드 포인트는 보통 "U+" 다음에 4~6개의 16진수 숫자로 표현됩니다. 이 숫자는 해당 문자의 고유한 ID와 같은 역할을 합니다.

다음은 몇 가지 유니코드 코드 포인트의 예입니다:
- 'A'의 유니코드 코드 포인트: U+0041
- 'a'의 유니코드 코드 포인트: U+0061
- '가'의 유니코드 코드 포인트: U+AC00
- '中'의 유니코드 코드 포인트: U+4E2D
- '😀'의 유니코드 코드 포인트: U+1F600

유니코드 코드 포인트를 실제 바이트로 변환하는 방법은 사용하는 유니코드 인코딩 방식에 따라 달라집니다. 

유니코드 코드 포인트를 실제 바이트로 변환하는 이유는 컴퓨터 시스템이 데이터를 저장하고 전송하기 위한 실질적인 방법이기 때문입니다. 컴퓨터의 기본적인 작동 단위는 바이트이며, 컴퓨터는 메모리와 디스크에 바이트 단위로 데이터를 저장합니다. 네트워크를 통해 데이터를 전송할 때도 바이트 단위로 이루어집니다.
유니코드 코드 포인트는 이론적인 개념으로, 각 문자나 기호에 대해 고유한 번호를 할당한 것입니다. 하지만 이 코드 포인트 자체는 바이트 형태가 아니라, 문자를 실제로 저장하거나 전송하기 위해서는 이를 바이트 형태로 변환해야 합니다. 이 변환 작업이 바로 인코딩입니다. 다양한 유니코드 인코딩 방식들(예: UTF-8, UTF-16 등)이 바로 이런 변환 작업을 수행합니다. 이들 인코딩 방식은 유니코드 코드 포인트를 실제 바이트로 변환하고, 그 반대 작업도 수행하는 규칙을 정의합니다. 이런 방식으로, 유니코드는 다양한 문자와 기호를 컴퓨터 시스템에서 표현하고 처리하는 표준 방법을 제공합니다.

 

UTF-8

UTF-8은 문자 인코딩 방식 중 하나로, 유니코드(Unicode)를 8비트 단위로 나눠서 인코딩합니다. 유니코드는 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다루기 위한 국제 표준입니다. UTF-8은 웹에서 가장 널리 사용되는 인코딩 방식 중 하나로, ASCII 문자들은 1바이트로, 다른 문자들은 최대 4바이트까지 사용할 수 있습니다.

 

UTF-8의 주요 특징은 다음과 같습니다:
1. 변동길이 인코딩: UTF-8은 문자에 따라 1바이트에서 4바이트까지 변동하는 길이의 인코딩을 사용합니다. 기본 ASCII 문자는 1바이트를 사용하며, 다른 다양한 문자들은 2바이트 이상을 사용합니다.
2. 하위 호환성: UTF-8은 ASCII와 완전히 호환됩니다. 즉, 기존의 ASCII 텍스트는 UTF-8 인코딩된 텍스트로 취급할 수 있으며, 반대도 마찬가지입니다.
3. 자체 복구 기능: UTF-8 인코딩은 바이트 순서에 의해 문자의 시작과 끝을 결정하므로, 데이터가 손실되거나 변경된 경우에도 복구가 가능합니다.
4. 전 세계 문자 지원: UTF-8은 전 세계의 거의 모든 문자를 지원하므로, 다양한 언어와 기호를 하나의 문서나 메시지에서 사용할 수 있습니다.

UTF-8은 가변 길이 문자 인코딩 방식으로, 1바이트에서 4바이트까지 다양한 길이의 바이트를 사용할 수 있습니다. 

  • 코드 포인트 U+0000부터 U+007F까지(ASCII 범위)의 경우, UTF-8은 단일 바이트를 사용합니다. 예를 들어, 'A'의 코드 포인트인 U+0041은 UTF-8에서 41이 됩니다(마커비트는 bit position7인데 값은 0).
  • 코드 포인트 U+0080부터 U+07FF까지는 2바이트를 사용합니다. 첫 번째 바이트는 110[마커비트]으로 시작하고 두 번째 바이트는 10으로 시작합니다. 나머지 비트는 코드 포인트를 표현하는 데 사용됩니다.
  • 코드 포인트 U+0800부터 U+FFFF까지는 3바이트를 사용합니다. 첫 번째 바이트는 1110[마커비트]으로 시작하고, 두 번째와 세 번째 바이트는 각각 10으로 시작합니다. 나머지 비트는 코드 포인트를 표현하는 데 사용됩니다.
  • 코드 포인트 U+10000부터 U+10FFFF까지는 4바이트를 사용합니다. 첫 번째 바이트는 11110[마커비트]으로 시작하고, 두 번째, 세 번째, 네 번째 바이트는 각각 10으로 시작합니다. 나머지 비트는 코드 포인트를 표현하는 데 사용됩니다.

예를 들어, '가'의 코드 포인트는 U+AC00입니다. 이를 16진수로 표현하면 AC00이고, 이를 2진수로 변환하면 10101100 00000000이 됩니다. 이 값을 UTF-8 방식으로 인코딩하면, 첫 번째 바이트는 1110으로 시작하고, 두 번째와 세 번째 바이트는 각각 10으로 시작하므로 1110 1010, 10 110000, 10 000000의 세 바이트로 표현됩니다. 이것이 바로 '가'의 UTF-8 인코딩입니다.

 

UTF-16

Java에서 사용하는 UTF-16은 Unicode 표준 인코딩의 한 부분으로, 각 문자를 16비트 단위로 인코딩하는 방식입니다. 여기서 '문자'란 유니코드 코드 포인트를 의미하는데, 이는 각각의 문자에 부여된 고유의 숫자 값입니다. 

UTF-16 인코딩은 각 코드 포인트를 하나 또는 두 개의 16비트 단위로 인코딩합니다.

(단, U+D800~U+DFFF 영역은 사용되지 않음. Supplementary Planes을 위해 예약되어 있음)

이 때 한 개의 16비트 단위로 인코딩 가능한 코드 포인트의 범위를 '기본 다국어 평면'(BMP:Ba sic Multilingual Plane)라고 부르며, 이 범위에는 대부분의 일반적으로 사용되는 문자가 포함됩니다.

 


그러나 유니코드 표준은 BMP를 넘어서는 문자도 정의하고 있습니다. 이런 문자들(이 범위의 코드 포인트는 일반적으로 특수한 사용 용도를 위한 문자, 고대 언어의 문자, 특정 기호, 이모지 등을 포함하고 있습니다.)은 16비트 단위로는 인코딩할 수 없으므로, UTF-16은 이들을 '서로게이트 페어'(surrogate pair)라는 두 개의 16비트 단위로 인코딩합니다.

첫 번째 16비트는 '고위 서로게이트'(high surrogate)로, 두 번째 16비트는 '저위 서로게이트'(low surrogate)로 불립니다.
※Surrogate의 사전적인 의미는 '대리의, 대용의'

 

그러나 이 방식의 단점은 모든 코드 포인트를 같은 크기로 인코딩하지 않는다는 것입니다. 즉, 한 문자가 몇 개의 16비트 단위로 이루어져 있는지는 문자에 따라 다릅니다. 이로 인해 문자열의 길이를 계산하거나 문자열을 조작하는 데 추가적인 로직이 필요할 수 있습니다. 

 

Surrogate Pairs

UTF-16 인코딩 방식은 마커 비트를 사용하지 않습니다. UTF-8과는 달리, UTF-16은 서로게이트 쌍(surrogate pairs)라는 메커니즘을 사용하여 16비트 범위를 넘는 코드 포인트(U+10000~U+10FFFF : Supplementary Planes)를 인코딩합니다.
UTF-16에서 서로게이트 쌍은 U+10000부터 U+10FFFF 범위의 코드 포인트를 인코딩하기 위해 사용됩니다. 이 범위의 코드 포인트는 두 개의 16비트 단위로 인코딩되는데, 첫 번째 단위는 U+D800부터 U+DBFF 범위에서, 두 번째 단위는 U+DC00부터 U+DFFF 범위에서 선택됩니다.

  1. U' = Code Point - 0x10000 = ( 0x0 ~ 0xFFFFF) : 20bit 영역만 남김
  2. W1 = 상위 10-bit(0x0 ~ 0x03FF) + 0xD800 : High Surrogate(0xD800 ~ 0xDBFF)
  3. W2 = 하위 10-bit(0x0 ~ 0x03FF) + 0xDC00 : Low Surrogate(0xDC00~0xDFFF)
U' = yyyyyyyyyyxxxxxxxxxx // U - 0x10000
W1 = 110110yyyyyyyyyy      // 0xD800 + yyyyyyyyyy
W2 = 110111xxxxxxxxxx      // 0xDC00 + xxxxxxxxxx

 

U+10437()을 UTF-16으로 인코딩하려면:

  • 코드 포인트에서 0x10000을 빼면 0x0437이 남습니다.
  • 상위 서로게이트의 경우 오른쪽으로 10만큼 이동(0x400으로 나누기)한 다음 0xD800을 추가하여 0x0001 + 0xD800 = 0xD801이 됩니다.
  • 하위 서로게이트의 경우 하위 10비트(0x400으로 나눈 나머지)를 취한 다음 0xDC00을 추가하여 0x0037 + 0xDC00 = 0xDC37이 됩니다.

UTF-16에서 U+10437()을 디코딩하려면:

  • 상위 대리(0xD801)에서 0xD800을 뺀 다음 0x400을 곱하면 0x0001 × 0x400 = 0x0400이 됩니다.
  • 로우 대리(0xDC37)에서 0xDC00을 빼면 0x37이 됩니다.
  • 이 두 결과를 함께 더하고(0x0437), 마지막으로 0x10000을 더하여 최종 코드 포인트인 0x10437을 얻습니다.

따라서 UTF-16에서는 "마커 비트" 대신 이러한 특별한 코드 포인트 범위를 사용하여 다중 단위 문자를 인코딩하고 식별합니다. 이는 UTF-16의 디코딩을 복잡하게 만드는 한편, 문자 단위와 코드 포인트가 일치하지 않는 경우를 생성함으로써 다양한 처리 문제를 유발할 수 있습니다.

 

바이트 순서 표시(Byte Order Mark, BOM)

바이트 순서 표시(Byte Order Mark, BOM)는 인코딩된 데이터의 바이트 순서를 나타내는 데 사용되는 특별한 유니코드 문자입니다. BOM의 유니코드 코드 포인트는 U+FEFF이며, 이는 바이트 순서가 빅 엔디언인지 리틀 엔디언인지를 판별하는 데 사용될 수 있습니다.

  1. 빅 엔디언(Big Endian) 방식에서는 높은 바이트가 먼저 나오므로, '가'는 AC00으로 표현됩니다.
  2. 리틀 엔디언(Little Endian) 방식에서는 낮은 바이트가 먼저 나오므로, '가'는 00AC로 표현됩니다.

이러한 바이트 순서는 시스템 아키텍처에 따라 다르므로, 인코딩된 데이터를 해석할 때는 해당 시스템의 바이트 순서를 알아야 합니다. UTF-16 인코딩된 데이터 스트림에서는 바이트 순서 표시(Byte Order Mark, BOM)를 사용하여 이를 나타낼 수 있습니다.


예를 들어, '가' 문자 (유니코드 코드 포인트 U+AC00)를 UTF-16으로 인코딩하고 바이트 순서를 표시하려면 다음과 같이 표현할 수 있습니다:

1. 빅 엔디언(Big Endian) 방식:
   - BOM: FE FF
   - '가': AC 00
   따라서, BOM을 사용하여 빅 엔디언 방식으로 인코딩된 '가'는 FE FF AC 00으로 표현됩니다.

2. 리틀 엔디언(Little Endian) 방식:
   - BOM: FF FE
   - '가': 00 AC
   따라서, BOM을 사용하여 리틀 엔디언 방식으로 인코딩된 '가'는 FF FE 00 AC으로 표현됩니다.

이와 같이 BOM을 사용하면 데이터를 해석하는 측이 바이트 순서를 쉽게 파악할 수 있으며, 따라서 다양한 플랫폼과 시스템에서 유니코드 텍스트를 원활하게 처리할 수 있습니다.