It's Ward

JAVA String, StringBuilder, StringBuffer 본문

Java/Java 기본

JAVA String, StringBuilder, StringBuffer

I'm ward 2022. 5. 26. 05:00

프로그래밍을 하다보면 문자열들을 더하고 없에거나 수정하는 작업은 빈번하게 일어난다.

그중 문자열을 더하는 과정에서 어떻게 처리했냐에 따라 속도차이가 발생하는데, 이 포스트는 String을 더하는 방법과 속도 차이에 대해 설명한다.

 

우선적으로  JVM에서 String이 어떻게 관리되는 지를 이해하여야 하는데

출처: https://www.javatpoint.com/string-pool-in-java

다음과 같이  String 문자열들을 String Pool에 저장하지만, new를 사용하게 되면 다른 주소값으로 Heap 영역에 저장됩니다.

String pool은 HashMap 형태로 되어있어 new하지않은 str1과 str3은 동일한 데이터를 불러오는 형식으로

String 객체를 생성하면 String Pool 내에서 기존의 같은 값을 가지는 객체가 있는지 검사하고

있다면(hasIs) 그 객체의 참조값을 return 하고, 아니라면 새로 String 객체 생성 후 그 참조값을 return 합니다. 이러한 과정을 intern이라고 합니다. (str7은 new를 했지만 intern()으로 인해 String pool에 저장된 값을 찾습니다. str2와 동일한 모습)

왜 이렇게 사용하냐 했더니, JVM에서 Heap 영역에 String 객체가 계속해서 생성되면, 메모리측면에서 비효율적이게 되고, 이를 방지하기위해 Pool을 사용한 캐싱을 통해 해결하도록 구현되어 있습니다. 

String str1 = "Hi";
String str2 = "Hi";
String str3 = new String("Hi");

System.out.println( str1 == str2 ); //true
System.out.println( str1.equals(str2)); //true
System.out.println( str1 == str3 ); //false
System.out.println( str1.equals(str3)); //true

 

이와 같이 데이터는 같지만, new를 통해 주소값이 다른경우 ==(주소값 비교) , equals(실제 데이터 비교) 의 결과가 나옵니다.

String은 불변(immutable)의 속성을 가지고있어, 문자열을 수정해야하는 경우 저장한 메모리 영역이 버려지는 상황이 생기는데요, 이는 문자열을 합칠 때 문제가 생깁니다. 

head와 tail의 문자열을 합칠 수 있는 메소드는 4가지가 있습니다. 

String head = "안녕";
String tail = "하세요";

1. + 연산자

+를 할 때, StringBuilder 객체를 생성하고 append 합니다. 

String result_1 = head + tail;

2. concat

합치는 두 문자열의 길이만큼 배열을 새롭게 할당해 후, 배열에 두값을 복사하여 String으로 변환합니다. 

String result_2 = head.concat(tail);

3. StringBuilder

가변 속성을 가져 메모리의 크기를 변경할 수 있어, 동일한 객체의 문자열을 변경할 수 있습니다. (비동기화 - 단일 가장 빠름)

StringBuilder sBuilder = "";
sBuilder.append(head);
sBuilder.append(tail);

4. StringBuffer

가변 속성을 가져 메모리의 크기를 변경할 수 있어, 동일한 객체의 문자열을 변경할 수 있습니다. (동기화 - 멀티스레드 환경 안전함)

StringBuffer sBuffer = "";
sBuffer.append(head);
sBuffer.append(tail);

모두 동일한 결과를 가지지만, 단일 속도적인 측면에서는

StringBuilder >+ 연산자 > StringBuffer > concat 가 되겠네요.

하지만 반복문을 작동시키면 이야기가 달라집니다.

 + 연산자는 StringBuilder 와 동일해보이지만, 반복될 때 매 연산마다 StringBuilder 를 생성하기 때문에 속도가 점점 느려지는겁니다.

Stack Overflow에서는 다음과 같이 설명합니다.

반복문을 사용하지않을때만 stringBuilder처럼 사용되어 성능 최적화가 이루어집니다.

 

결론 

단순 문자열을 한번만 사용할 경우 +를 사용해도 괜찮다.

싱글 스레드 환경에서 반복문을 두번 이상 사용할 경우 StringBuilder 를 사용하는 것이 좋다.

멀티 스레드 환경에서는 StringBuffer 를 사용하는 것이 좋다.

 

Comments