[Java] - 자바 String byte[], Compact Strings

janjanee·2021년 6월 14일
1

Java

목록 보기
9/19
post-thumbnail

Java 8 기준으로 쓰여진 책이나 인터넷에 보면 String에 대해 아래와 같이 나와있다.

String은 인스턴스 생성 시 입력받은 문자열을 내부 인스턴스변수인 문자형 배열(char[]) 로 저장한다.

본인도 예전부터 그렇게 알고 사용하고 있었다.
최근에 책을 보며 공부하다가 문득 다른점을 발견하여 정리하려고 글을 쓴다.

친절한 책 또는 블로그는 String 클래스가 이렇게 생겼답니다. 하고 String 클래스 코드 일부를 적어놓는다.

public final class String implements ... {
    private final char[] value;
}

직접 눈으로 찾아보아야 더 잘 와닿기 때문에 나도 String 클래스를 열어보았다.

// String.class (Java 11)
public final class String implements ... {
    private final byte[] value;
    ...

이상하다? 아무리 찾아봐도 char[]는 안보이고 byte[]만 보인다.
테스트 환경은 Java 11이었고, 혹시 몰라서 Java 8 버전의 String 클래스도 열어보았다.

// String.class (Java 8)
public final class String implements ... {
    private final char[] value;
    ...

책에서 봤던 익숙한 char[] 배열이 보인다.
뭘까? 어느 버전부터 내부적으로 char 배열이 아닌 byte 배열에 String 데이터를 넣는걸까?
그리고 이유는 왜일까? 우선은 조금 더 살펴봤다.

// String.class (8)
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

// String.class (11)
public String(String original) {
    this.value = original.value;
    this.coder = original.coder;
    this.hash = original.hash;
}

String 생성자를 비교해보았다.
두 번째 줄에 변화가 있는데 coder라는 변수가 등장했다.
coder는 뭘하는 건지 봐보자.

/**
* The identifier of the encoding used to encode the bytes in
* {@code value}. The supported values in this implementation are
*
* LATIN1
* UTF16
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*/
private final byte coder;

coder는 byte 배열(value)을 인코딩하는데 사용하는 변수같다. 그리고 지원하는 인코딩 타입은 LATIN1, UTF16 이다.

메소드들도 몇 개 살펴보았다.

public int indexOf(int ch, int fromIndex) {
    return isLatin1() ? StringLatin1.indexOf(value, ch, fromIndex)
                        : StringUTF16.indexOf(value, ch, fromIndex);
}

private boolean isLatin1() {
    return COMPACT_STRINGS && coder == LATIN1;
}

대표로 indexOf를 보면 LATIN1의 여부에 따라서 구현을 다르게 하고있는 것을 볼 수 있는데
indexOf 메소드 뿐만 아니라 대부분의 코드에서 인코딩 타입에 대해 분기를 타고 있다.

확실하게 변화가 생긴건 맞다. 그럼 왜 이런 변화가 생겼는지, 그리고 언제부터 생겼는지에 대해 학습을 해보았다.


Compressed Strings

-XX:+UseCompressedStrings

JDK 6에 String을 char가 아닌 byte 배열로 저장할 수 있는 옵션을 제공하였다.
이로인해 메모리 절약과 관련된 성능상 이점이 있었다.

그러나 가장 큰 문제점인 String의 생성자가 char[]만 인수로 받는 문제점과
그 외에도 String 연산자들이 char[]만 의존하는 문제점이 있었다.
따라서 JDK 7에서 해당옵션은 사라졌다.

Compact Strings

JDK 9 부터 Compact Strings 라는 새로운 이름으로 앞의 개념을 다시 가져왔다.

String의 문자열이 Latin-1(1byte) 으로 표현될 수 있는지와 그렇지 않은 경우 UTF-16(2byte) 로 나누어진다.
이게 위에서 살펴봤던 coder가 등장한 이유이다.

자바의 char형은 UTF-16 기반으로 2byte를 참조한다.
자바의 문자열은 기본적으로 유니코드를 지원하기 때문에 영어를 쓰더라도 2byte씩 계산됐다.
즉 1바이트로 표현이 가능하지만, 2바이트로 표현돼야 하므로 바이트에 의미 없는 0이 채워진다는 말이다.

다음 예시를 통해서 어떻게 변했는지 확인해보자.

String english = "english";
String korean = "한글";

결과 1 (JDK 8)

결과 2 (JDK 11)

  • 영문의 경우 Latin-1로 표현될 수 있기 때문에 1byte로 저장되는 것이 가능해졌다.
  • coder = 0은 Latin-1

한글은 기존이랑 별 차이가 없더라도 애플리케이션 내에 사용되는 Latin1 형식의 String과 오픈소스에서 사용되는 String들이 Heap 메모리를 차지하므로 메모리 공간 절약에 도움이 될 수 있다.


정리

사실 String 클래스를 사용하는 개발자 입장에서 String 내부 구현이 바뀌었다고 해서
사용할 때 주의하거나 달라진점은 없다. 여지껏 모르고도 잘 써왔다.
그러나 우연한 기회로 어떠한 변화가 생겼고 왜 생겼는지에 대해 탐구하고 알아갈 때 재미가 있고
이런 상황은 기억하려고 노력하지 않아도 저절로 지식으로 남는다.


References

profile
얍얍 개발 펀치

0개의 댓글