Kotlin과 Java의 Char 저장 방식
Kotlin과 Java는 UTF-16으로 인코딩 되어 저장된다.
아래는 Kotlin Char 클래스의 주석이다. 아래 주석에서 보면 Char은 16-bit 유니코드 문자를 표현한다고 되어 있다.
/**
* Represents a 16-bit Unicode character.
*
* On the JVM, non-nullable values of this type are represented as values of the primitive type `char`.
*/
public class Char private constructor() : Comparable<Char> {
Kotlin과 Java는 왜 UTF-16을 문자열 인코딩 방식으로 택했을까? 바로 JVM이 내부적으로 문자열을 UTF-16으로 다루기 때문이다. 이 때문에 Java는 초기부터 원시 타입인 char 데이터 타입을 16bit로 정의하고, String 클래스에서 내부적으로 UTF-16 인코딩으로 문자열을 저장하도록 설계하였다. 이와 관련해서는 아래 "Kotlin과 Java에서 String은 어떻게 저장되는가?" 섹션에서 다룬다.
Kotlin과 Java에서 String은 어떻게 저장되는가?
Kotlin은 Java의 String을 사용해 String을 만들어내며, Java String 클래스의 consructor은 아래와 같다.
public String(char value[]) {
this(value, 0, value.length, null);
}
이 constructor은 내부에서 인코딩 값을 UTF16으로 설정하고, char 값을 바이트로 변환해서 저장하도록 만든다.
String(char[] value, int off, int len, Void sig) {
...
this.coder = UTF16;
this.value = StringUTF16.toBytes(value, off, len);
}
즉, String은 내부에 value 파라미터로 ByteArray를 저장하며, 인코딩 방식(coder)을 UTF16으로 지정해 출력 시 디코딩할 수 있도록 한다.
private final byte coder;
private final byte[] value;
하지만 UTF-16인코딩은 문자당 2byte를 사용해 비효율적인 부분이 있기 때문에 Java9부터는 이와 관련된 개선이 이루어졌으며, String은 내부적으로 byte[] 배열 구조를 사용해 저장하는 Compact String이라는 새로운 기능을 도입하였다. JVM은 만약 String이 문자열 중 ISO-8859-1/Latin-1 문자만을 포함한다면 1byte만 사용하도록 만든다.
Java, Kotlin 에서 문자열 인코딩 방식 UTF-16 사용하는 것의 장점
Random Access 가능
UTF-16은 16비트일 수도 32비트 일 수도 있다. 하지만, 기본적으로 문자열을 저장할 때는 16비트를 기반으로 저장한다. 이는 32비트를 사용하는 이모지의 경우에서 확인할 수 있다. 이모지를 Char로 저장하려고 하면 "Too many characters in a character literal" 오류가 난다.
즉, JVM에서는 문자열에 대한 Random Access가 가능해 특정 위치에 있는 문자를 O(1) 시간으로 접근이 가능하다.
val str = "Hello, Dev World"
val ch = str[4] // 'o'
이를 통해 문자열의 검색, 조작과 같은 연산을 효율적으로 처리할 수 있다.
호환성
Koltlin, Java, JVM 모두 UTF-16을 기반으로 문자열을 처리하기 때문에 서로 간에 호환성이 높다.
JVM의 UTF-16은 Big-Endian 방식을 사용한다.
UTF-16은 두가지 형식을 가지고 있는데, Big-Endian은 높은 비트가 앞에 오는 형태로 데이터를 저장하고, Little-Endian은 낮은 비트가 앞에 오는 형태로 데이터를 저장한다. JVM은 Big-Endian 방식을 사용해 높은 비트가 앞에 오는 형식으로 저장한다. 따라서 ABC가 UTF-16으로 인코딩 된다면 아래와 같이 표현될 수 있다.
*UTF-16은 각 자릿수는 byte를 표현하는 16진수이다.
*A의 코드는 65(16x4+1), B의 코드는 66(16x4+2), C의 코드는 67(16x4+3)이다.
\u0041 \u0042 \u0043
이를 확인하기 위해 해당 유니코드로 CharArray를 만든 다음 String으로 변환해 보자.
fun main() {
val createdArray = charArrayOf('\u0041','\u0042','\u0043')
val createdString = String(createdArray)
println(createdString)
}