Java는 utf16으로 데이터를 저장한다는 이야기는 많이 들어보았을 듯 싶습니다. 이모지는 2byte로 커버가 되지 않을 텐데 어떻게 저장할까요? 2개의 쌍을 가진 서러게이트로 나누어서 저장합니다. Java 시간에도 잠깐 언급을 했었는데, 그리고 sql을 할 때도 언급을 한 거 같은데, 또 언급하는 이유는 그만큼 중요하기 때문입니다. 몇 메소드에 대해서 더 알아가실 겸. 겸사 겸사.

 

 

 예를 들어, 강아지 이모티콘은 16진수로, 0x1F415입니다. 이는 2byte의 한계인 0xFFFF보다는 큰 수입니다.

 

 


 Character의 toChars는 codepoint 값을 받아서, 이것을 char형 배열로 리턴해 주는 함수입니다. 이는 Unicode의 코드값을 의미합니다. 예를 들어, '가'는 0xAC00이니까, '가'를 넣으려면, toChar에 0xAC00을 넣어주면 됩니다.

 

 

 그러면, 문제의 0x0001F415를 넣었을 때, 어떠한 일이 벌어지는지 보도록 하겠습니다.

 

 

 사실 BMP가 무엇인지, supplementary Code point가 무엇인지는 여기에서 무엇인지 몰라도 됩니다. 이 정도만 유추하실 수 있으시면 됩니다. codepoint 값을 UTF-16으로 인코딩을 했을 때, 2byte 내에서 커버가 되면, char array 안에 코드 포인트가 그대로 들어갑니다.

 

 그렇지 않으면, 서러게이트 pair가 들어간 char array를 리턴합니다. BMP에 속하는 경우를 예로 들어보겠습니다. '가'는 BMP에 속합니다. U+AC00에 mapping이 되어 있기 때문입니다.

 

 

 0xAC00을 넣어보겠습니다. 그리고, t.length()가 돌 때 까지, char형 배열에 있는 값을 빼 오겠습니다. int형 값을 얻어오기 위해서, 9번째 줄에서 일부러 형변환을 하였습니다.

 

 

 44032를 16진수로 변환하면 AC00입니다. char Array가 가지고 있는 값이, codePoint의 값인 0xAC00과 일치합니다. 그런데, 제가 언급한 개 이모지의 경우에는, 2byte로 커버가 될 사이즈가 되지 않습니다. 그러면 어떻게 처리될까요?

 

 


 toChar 메서드의 내부를 보겠습니다.

 

 

 BMP가 아니라면, 5167번째 줄을 타고 갑니다. 2개의 서러게이트가 필요하므로, 2개의 char형 배열을 할당합니다. 그러면, 이것은 어떻게 판단을 할까요?

 

 

 그것은 간단합니다. codePoint를 2^16으로 나누었을 때, 몫이 0이면 BMP입니다. 그렇지 않으면 BMP가 아니라는 것이죠. 이모지는 0xFFFF보다는 크기 때문에 BMP는 아니고, 서러게이트 2개로 나뉘어서 저장이 됩니다.

 

 

 toSurrogates에는 dst[0]에 high를, dst[1]에 low를 저장합니다. 이들이 어떻게 계산되는지 보겠습니다.

 

 

 먼저 high입니다. 뭔가 복잡해 보이는데요. A + (B - C)꼴입니다. 코드가 복잡해 보이지만, A와 C는 2^10을 나눈 몫을 취한다는 것은 공통적인 거 같으니, (A - C) + B꼴로 빼 보겠습니다.

 

 

 먼저, MIN_HIGH_SURROGATE 값은 D800입니다. 이를 2진수로 표현하면 1101 1000 0000 0000으로 표현할 수 있습니다. 다음에, MIN_SUPPLEMENTRY_CODE_POINT가 문제인데요. 이 값은 0x010000입니다.

 

 

 code_point를 10칸만큼 shift 한 것에, 0x010000을 10칸만큼 shift 한 것을 뺀 값이라. 사실, 0xFFFF 내에 들어오는 문자 집합들은 필요가 없을 겁니다. 왜냐하면, 이들은 사실 UTF-16에서, 2byte 내에서도 커버가 가능한 문자들이기 때문입니다. 그렇기 때문에, 0x10000을 빼고 시작하는 것입니다.

 

 예를 들어, 개 이모지의 경우 1F415입니다.

 

 

 이를 2진수로 표현하면 위와 같이 표현할 수 있는데요. 보라색 부분은, 사실 2byte 내에서도 표현이 가능합니다. 여기에서, 0x10000을 빼면 아래와 같습니다.

 

 

 여기서 상위 10bit만 끊어버린 값이 (A-C), 그리고 1101 1000 0000 0000의 값은 B입니다.

 

 

 그러므로, high surrogate는 위와 같이 채워집니다. 이를 계산해 보면 55357이 나옵니다.

 

 


 이제 low surrogate를 계산해 보겠습니다.

 

 

 이것은 크게 어렵지 않습니다. 0x3ff랑 mask를 취한다는 의미는 11 1111 1111이랑 mask를 취한다는 의미인데요. 이는 codePoint의 하위 10bit를 가져온다는 의미입니다. 다음에, MIN_LOW_SURROGATE 값을 더하는데요.

 

 

 이 값은 DC00입니다. 즉, 1101 1100 0000 0000에, codePoint의 하위 10bit 값을 취한 값을 더한다고 생각하시면 됩니다. 이모지는 BMP에 속해 있지는 않기 때문에, 2개의 서러게이트 pair로 표현이 된다는 것 정도만 아셔도 무난할 듯 싶습니다.

 

 

 다시 예제로 돌아와 보겠습니다. 위 프로그램의 실행 결과는 어떻게 나올까요?

 

 

 true가 나옵니다. t가 내부적으로 어떻게 변환되었는지 찍어보겠습니다.

 

 

 

 이제 개 이모지만 들어간 String이 어떤 데이터를 저장하고 있는지 찍어보겠습니다.

 

 

 55357, 56341. 같습니다.

 

 

 String의 equals는, char 단위로 돈다는 것을 알 수 있어요. 그것 단위로 돌았을 때, 멍멍이 이모지만 갔다 놓은 String하고, 0x1F415를 toChars 메소드로 char형 배열로 바꾼 다음에, 그것을 String 생성자로 넘긴 String과 내용이 같았습니다. 따라서, equals를 돌리면 true가 나옵니다.

 

 아래 프로그램은 어떨까요?

 

 

 "\u1F415"와 개 이모지랑 비교하면, false가 뜹니다. "\u1F415"는 내부적으로 8001, 53으로 저장이 되는데, 이는 55357, 56341로 저장되는 개 이모지와는 다른 패턴을 가지기 때문입니다. String에 있는 문자 수와, length의 길이 값이 같다고 섣불리 판단하면 안 되는데요. 이는 이모지와 같이 2개의 surrogate를 쓰는 경우가 있기 때문입니다.

 

 이에 대한 처리를 하는 방법은 다음에 배워보도록 하겠습니다.