본문 바로가기
Old Posts/Java

[Java] 문자열 생성 클래스 - String, StringBuffer, StringBuilder 차이점

by A6K 2021. 6. 17.

자바에서 문자열은 독특한 특성을 가지고 있다. 바로 불변(Immutable)이라는 특성이다. String 타입의 객체는 한번 생성되면 할당된 메모리 공간이 변하지 않는다. '+' 연산이나 concat 등의 메소드를 이용해서 문자열을 수정할 때, 기존에 존재하는 String 객체의 데이터가 수정되는 것이 아닌 연산의 결과로 만들어지는 문자열을 이용해 새로운 String 객체를 생성하는 형태로 동작한다.

문제는 '+' 연산이나 concat() 등의 메소드를 이용해 문자열을 수정할 때마다 새로운 String 객체가 생성된다는 점이다. 예를 들어 다음과 같은 코드가 있다고 생각해보자.

String str = "String";
for (int i = 0; i < 100; i++) {
  str = str + i;
}

System.out.println(str);

이 코드는 "String"이라는 문자열 뒤에 0부터 99까지의 숫자를 붙여나가는 반복문이다. 이 코드의 반복문의 바디 부분이 동작하게 되면 매번 새로운 String 객체를 생성해서 str 이라는 변수에 할당한다. 최초 "String" 문자열은 리터럴로 String constant pool에 위치하겠지만 이 문자열을 기반으로 100개의 객체가 생성되어 JVM 힙 메모리에 존재하게 된다. 반복문의 결과로 결국 사용되는 String 객체는 최종적으로 만들어지는 하나의 객체이지만 중간에 99개의 객체들이 생성되는 비효율이 생기게되었다.

이런 비효율을 제거하기 위해 StringBuffer 클래스와 StringBuilder 클래스가 제공된다.

StringBuilder 

StringBuffer와 StringBuilder는 불변의 특성을 갖는 String 객체를 좀 더 효율적으로 생성할 수 있게 해준다. 위에서 봤던 코드는 StringBuilder를 이용해서 다시 작성할 수 있다.

StringBuilder builder = new StringBuilder("String");
for (int i = 0; i < 100; i++) {
  builder.append(i);
}

System.out.println(builder.toString());

StringBuilder 클래스를 만들고 append() 메소드를 이용해서 문자열을 뒤에 붙여나갈 수 있다. StringBuilder에 append() 할 때에는 실제 String 객체는 생성되지 않고, toString() 메소드가 호출 될 때 한꺼번에 String 객체화한다. 따라서 중간에 불필요하게 99개의 객체를 만들지 않는다.

이처럼 문자열을 생성할 때, 가변적 특성을 부여하고 싶을 때 StringBuilder를 사용하게 된다.

StringBuilder와 StringBuffer의 차이점

위에서 본 것처럼 StringBuilder 클래스와 StringBuffer 클래스는 둘 다 문자열을 생성할 때, 가변적 특성을 부여하기 위해 사용한다. 제공되는 메소드와 사용법은 동일하다. 다만 이 두 클래스 사이의 차이점은 바로 동기화 유무에 있다.

StringBuffer 클래스는 메소드 별로 synchronized 키워드가 붙어있다. 따라서 하나의 StringBuffer 객체에 여러 스레드가 동시에 작업을 할 수 있다. 반면 StringBuilder는 synchronized 키워드가 없으며 싱글 스레드에서의 사용을 가정하고 있다.

중간에 동기화 작업을 하기 때문에 StringBuffer 클래스가 좀 더 안전하게 멀티스레드 환경에서 사용할 수 있다. 하지만 싱글 스레드에서 사용할 경우 불필요한 동기화 동작이 추가되기 때문에 성능 저하가 발생할 수 있다.

Thanks to compiler

JDK 1.5 버전 이전에서는 문자열의 '+' 연산과 concat 등의 메소드 호출시 위에서 언급한 것처럼 매번 새로운 메모리가 할당되어 String 객체가 생성되는 문제가 있었다. 하지만 이후 버전에서 컴파일러가 판단하여 String 객체를 사용하더라도 StringBuilder로 컴파일 되도록 변경되었다. 덕분에 String 만을 이용해서 문자열 조작을 하더라도 자동으로 StringBuilder로 변환되어 성능 저하를 체감하기 힘들어졌다. (사실 가독성은 String을 이용하는게 더 좋은 경우가 많다)

하지만 컴파일러가 최적화하기 힘든 사용패턴의 경우, 예를 들어 반복문에서 문자열을 더해나간다던가 하는 경우에는 여전히 String 객체가 계속 생성된다. 따라서 간단한 문자열 조합의 경우가 아닌 반복문을 통해 긴 문자열을 만들어야하는 경우 StringBuilder를 사용하는 것을 추천하며 만약 멀티스레드 환경에서 동기화가 필요한 경우라면 StringBuffer를 사용하도록 하자.

댓글