C++ String에 대해서는 이전 글 참고

 

이번에는 Java다.

 

Java String은 익히 알려진것처럼 UniCode 사용을 예상하고 작업되어 각 문자 1개가 2바이트씩 차지하고, 문자열을 수정시에는 문자열 자체가 수정되는것이 아니라 수정된 문자열이 새로 생성되는 등의 특징이 있다.

하지만 '체감상' Java String이 C++ String보다 다루기 편했던것으로 기억하는데, 오랫만에 한번 뜯어보도록 하자.

 

이번 레퍼런스 자료는 Open JDK의 소스코드입니다.

검색하면 금방 찾으실 수 있으니 링크는 딱히 달지 않겠습니다.

 

2. Java String의 경우

 

    public String(char value[]) {
        this(value, 0, value.length, null);
    }

 

Java String 또한 Char형 배열로 이루어져 있으며 (* C++과는 다르게 포인터 개념을 사용할 수 없기에 일반 배열이다), Java에서는 Char형의 바이트 값이 2바이트 이므로, String의 내부 구현또한 2바이트 문자열을 저장하는것으로 이루어진다.

어찌보면 당연한 소리인데... 배열의 각 값이 2바이트면 전체가 2바이트 데이터의 배열 구조인것은 당연한 소리 아니겠는가..

 

여기서 바로 차이점이 나타난다.

Java String의 경우 처음 값이 정해져버리기 때문에, 사이즈 변환에 좀 심한 에로사항이 생기게 된다. C++의 경우에는 (아무튼) 배열 포인터로 구현이 되어있으니, 값을 늘리는것 만큼은 (메모리 침범이 일어나지 않는다는 전제하에) 쉽게 늘릴 수 있으나...

 

그리고 또한, Java String은 기본적으로 'UTF-16' 인코딩을 사용하고 있다.

 

참고중인 Open JDK의 String.java 파일에 보면...

 

A {@code String} represents a string in the UTF-16 format in which supplementary characters are represented by surrogate pairs. 

(see the section Character.html#unicode">Unicode haracter Representations in the {@code Character} class for
more information).

 

(Surrogate pairs 에 대해서는 나중에 따로 다루도록 하겠습니다. 저도 조금 이해할 필요가 있어보이네요)

 

아무튼 UTF-16 포멧으로 표현된다고 나타나있다.

UTF-8과 다른점은, 뭐 당연히 표현 비트 수가 8비트 / 16비트라는 차이점이라는것... 정도겠다.

 

 

아래부터는 String str = "Hello World";  라는 String을 선언해둔것으로 가정하고 시작한다.

* 또한, 기본적으로 세부 자료형은 StringUTF16임을 명시해둔다.

1) 접근법

(1) charAt(N);

C++ String의 at과 기능상 동일하다. N의 위치에 존재하는 값을 리턴해주는 메소드로, 내부적으로는 String Data Array(= byte 배열) 과 int N 값을 인자로 받고, 이를 getChar() 메소드에서 탐색하고 리턴해주는 방식이다.

 

그래서 getChar()는 어떻게 되어있느냐...

 

    static char getChar(byte[] val, int index) {
        assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
        index <<= 1;
        return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
                      ((val[index]   & 0xff) << LO_BYTE_SHIFT));
    }

이런식으로, byte배열에서 index에 맞는 위치의 값을 bit shift를 통해 리턴해주는 모습을 보여준다.

Java에서는 Char형도 2바이트로 표현하니, 바이트 값이 2개가 올라가는것을 볼 수 있다.

 

 

(2)str.indexof(temp);

temp 라는 문자가 어디에 존재하는지 탐색해주는 메소드이다.

내부 구현은, indexof가 호출시, 호출된 String의 구조가 latin1 (잠깐 찾아보니 그냥 아스키코드값하고 동일하다.) 인가, 아닌가로 비교를 실시한다.

만일 latin1 형식이 아닌경우, StringUTF16라는 클래스의 indexof 메소드로 넘어가게되는데, 일반 구조의 경우에는 'getChar()' 를 호출하여, 문자열의 위치를 탐색하고 값을 리턴해주는 방식으로 이루어진다.

그런데 재밌는것이...

 

해당 메소드의 구현 밑바닥까지 가면

    @HotSpotIntrinsicCandidate
    private static int indexOfChar(byte[] value, int ch, int fromIndex, int max) {
        checkBoundsBeginEnd(fromIndex, max, value);
        return indexOfCharUnsafe(value, ch, fromIndex, max);
    }

    private static int indexOfCharUnsafe(byte[] value, int ch, int fromIndex, int max) {
        for (int i = fromIndex; i < max; i++) {
            if (getChar(value, i) == ch) {
                return i;
            }
        }
        return -1;
    }

'Unsafe' 라는 문구가 붙어있는것을 확인 할 수 있다.

범위 침범을 할 수 있어서 그런건지, 아니면 다른 이유가 있는건지는 모르겠지만 구현시에 '사실 좀 위험하긴한데...' 라는 생각을 했던건 아닌가 싶다.

 

2) 길이 파악

length(); 라는 메소드가 존재한다.

 

근데 구현이... 내가 잘못본게 아니라면

    public static int length(byte[] value) {
        return value.length >> 1;
    }

이게 끝이다.

배열값을 input으로 받고, 배열의 길이를 1 shift (뒤의 '\0' 문자 제거용으로 추정)하여 리턴해준다.

 

더 찾아봐도 저거 외에는 제대로 된 메소드를 찾을수가 없었다...

 

 

3) 비교

Java String에서는 '==' 를 쓰면 안된다는 사실을 알것이다.

==를 사용하게 되면, 값 자체를 비교하는것이 아니라 값의 위치, "객체"가 동일한지 아닌지를 비교하기 때문에 무조건 false가 날 수 밖에 없다.

그래서 사용하는것이 equals인데...

    public static boolean equals(byte[] value, byte[] other) {
        if (value.length == other.length) {
            int len = value.length >> 1;
            for (int i = 0; i < len; i++) {
                if (getChar(value, i) != getChar(other, i)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

구조는 다음과 같다.

1)) 길이를 비교한다.

2)) 길이가 같다면 -> 동일한 위치의 문자를 1개씩 비교한다

3)) 이 중 하나라도 틀린다면 -> false

4)) 모든 비교를 통과했다면 true 

 

 

이런 식으로 구현이 되어있다.

 

아직 주제인 'C++ String 과 Java String의 차이' 에 대해서는 언급을 하지 않았는데, 이는 다음장에서 언급하도록 하겠다.

+ Recent posts