java — String + 연산 (String Concat)

choi jeong heon
슬기로운 개발생활
4 min readJun 29, 2022

StringConcatFactory

String 을 + 연산자를 이용해서 이어붙일 때 내부적으로 이를 어떻게 처리할까?

val list = listOf("a", "b", "c", "d", "e", "f")
var s = ""
repeat(list.size) {
s = s + list[it]
}

JDK 5 이전

JDK 5 이전에는 String + String 연산을 할 때 새로운 String을 할당합니다. 따라서 매번 새로운 String 이 할당되기 때문에 성능이 좋지 않습니다.

JDK 5 이후

JDK 5 부터는 StringBuilder 를 내부적으로 이용합니다. 위 코드의 바이트 코드를 봅시다.

Intellij 를 사용하면 원하는 코드 라인의 바이트 코드를 따로 확인 할 수 있습니다. 위의 바이트 코드는 repeat 에 전달되는 람다 본문 부분의 바이트 코드 입니다.

s = s + list[it]

내부적으로 StringBuilder 를 사용하고 있으며 String#append 함수를 통해 문자열을 합치게 됩니다. StringBuilder 를 사용하면 왜 더 좋을까요?

StringBuilder 는 byte 배열을 가지고 있습니다. StringBuilder 를 생성할 때 이 크기를 지정할 수 있는데 생성자에 아무것도 넘겨주지 않으면 16byte 로 할당됩니다.

append 로 추가된 String 을 byte 배열에 이어 붙이는 방식입니다.

String 도 내부적으로 byte 배열을 가지고 있습니다. String + String 연산을 할 때 새로운 배열을 할당하고 이어붙일 String 의 배열들을 새로운 배열에 copy 하게 됩니다. 따라서 반복문이 많아질 수록 성능이 좋지 않게 됩니다.

JDK 9 이후

하지만 StringBuilder 에도 문제가 있습니다. 이전의 바이트 코드를 다시 봅시다.

이 바이트 코드는 반복문 안의 바이트 코드였습니다. 반복문을 돌 때 마다 새로운 StringBuilder 를 할당하고 있습니다. 또한 마지막에는 StringBuilder#toString 함수가 호출됩니다.

StringBuilder#toString 을 보면 StringLatin1.newString 또는 StringUTF16.newString 을 호출하고 있습니다.

이 함수들은 결국 StringBuilder 의 byte 배열을 새로운 String 에 copy 하게 됩니다.

이런 문제 뿐만 아니라, StringBuilder 의 byte 배열이 가득차면 새로운 배열을 할당하고 copy 하는 과정이 이루어지게 됩니다.

JDK 9 부터는 이 문제를 해결하기 위해 StringConcatFactory 를 도입하게 되었습니다.

StringConcatFactory 의 사용방법은 대강 위와 같은데, String Concat 이 여러번 이루어질 때 그때마다 바로바로 String 을 이어붙이는 게 아니라 MethodHandle 에 기억해두고 마지막에 한번에 이어붙이는 것이 핵심입니다.

그렇게 되면 이어붙일 String 의 전체 사이즈를 알 수 있게 되어 byte 배열을 한 번만 할당하고 이어붙인 String 을 구할 수 있게 됩니다.

JDK 9 부터는 String 을 이어붙일 때 + 연산을 사용하더라도 내부적으로 StringConcatFactory 를 이용하게 되므로 더 효율적으로 처리할 수 있습니다.

더 자세한 내용은 여기 를 참고하세요.

--

--