Ordinary
About

StringBuilder StringBuffer

profileordilov / 2022. 3. 21

String부터 알아보기

StringBuilderStringBuffer는 모두 String 조작을 도와주는 클래스입니다. 간단한 +, - 같은 연산자로도 String 조작이 가능하지만 효율성이 좋지 않습니다.

그 이유는 String은 불변 객체이기 때문입니다. 왜 효율적인지 알기 위해 String이 어떻게 불변 객체로 관리 되는지부터 살펴보겠습니다.

String 관리

String도 primitive 타입이 아닌 클래스이기 때문에 당연히 primitive 타입으로 구성됩니다.

private final byte[] value;

기능이 더 많지만 String을 단순하게 보면 byte 배열입니다. final로 선언되어 생성 이후에 수정할 수 없는 것을 알 수 있습니다. 그렇다면 final로 수정이 불가능한데 어떻게 이런 코드가 가능할까요?

String apple = "appl"; apple += "e";

이 코드는 Java compiler에 의해 밑의 코드와 비슷하게 변경됩니다.

String apple = "appl"; apple = new StringBuilder(apple).append("e").toString();

다시 말하면 String 연산은 내부적으로 StringBuidler(StringBuffer) 와 같은 구현을 사용합니다. 같은 구현을 사용하는데 왜 연산을 할 때 StringBuilder가 더 효율적이라고 할까요?

String 메모리 관리

StringBuilder가 더 효율적인 부분은 시간이 아닌 메모리에서 차이가 납니다. 그 이유를 알기 위해선 String이 어떻게 메모리에서 관리되는지 알아야 합니다. String은 내부적으로 힙 메모리의 Constant Pool에서 관리됩니다. "apple" 같은 리터럴 문자를 힙 메모리에서 공유해서 사용하게 됩니다.

String a = "apple"; String b = "apple"; assertEquals(a == b);

서로 다른 객체를 생성했더라도 같은 리터럴 문자열을 가리키면 주소값은 같게 됩니다.

여기서 "appl" 에서 "e"를 더했을 때 a의 주소값은 "apple"을 가리키는 b와 같아지게 됩니다. 계산해서 나온 "apple" 이라는 문자열이 Constant Pool에 올라가고 그 주소를 가리키게 되서입니다. 문제는 String은 불변 객체이기에 "appl"은 남아있다는 점입니다. "apple"은 StringBuilder에 의해서 새로 만들어진 문자열입니다. 따라서 Constant Pool 에는 "appl" 과 "apple" 둘 다 존재하게 됩니다.

String a = "appl"; String b = "apple"; a += "e"; assertEquals(a == b);

즉 String 자체로 연산을 하다보면 Constant Pool에 사용하지 않는 문자열들이 쌓이게 됩니다. 이렇게 참조되지 않는 문자열들은 이후에 Garbage Collector에 의해 비워집니다. 그렇다면 StringBuilder는 어떻게 관리할까요?

StringBuilder 메모리 관리

StringBuilder도 String 처럼 byte 배열로 이루어져있습니다. 하지만 차이점이라면 final이 없다는 점입니다. 즉 생성 이후에도 값이 변경될 수 있다는 걸 의미합니다.

byte[] value;

StringBuilder에서 가장 핵심적인 기능은 insertappend입니다. 둘 다 String을 추가하는 기능을 맡고 있습니다. 간단하게 생각하면 함수 인자로 들어온 문자열들을 받아와 원하는 배열 위치에 넣어줍니다. 하지만 배열이라 크기가 고정되어있기 때문에 크기가 더 커지는 경우 자동으로 배열을 새로 할당 받아 큰 배열에 문자열들을 복사해오는 기능을 맡습니다.

여기까지 정리해보면 StringBuilder는 연산마다 Constant Pool에 사용되지 않는 문자열이 남지 않습니다. 사용하는 문자열만 객체의 byte 배열에서 관리하고 있기 때문에 메모리 영역에서 효율적입니다.

StringBuffer와의 차이

StringBufferStringBuilder의 차이점은 Thread Safe입니다.

StringBufferStringBuilder에서 핵심 기능인 append로 비교해보겠습니다. 차이점은 toStringCache 라는 부분과 synchronized 입니다.

// StringBuilder @Override @HotSpotIntrinsicCandidate public StringBuilder append(String str) { super.append(str); return this; } // StringBuffer @Override @HotSpotIntrinsicCandidate public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }

멀티 쓰레드 환경에서 안전하기 위해서 같은 기능을 하는 메서드에 synchronzied 처리를 한 것 외에 크게 다른 게 없습니다. 실제로도 두 클래스는 모두 AbstractStringBuilder 클래스를 상속받고 있으며 super.append() 처리시 그 클래스에서 실제 append가 실행됩니다. 하지만 synchronzied 때문에 동기화 처리를 위한 시간이 더 소모되고, 싱글 스레드 환경에서도 기본 메서드보다 속도가 저하됩니다.

결론

문자열 연산이 많을 때
  • 싱글스레드 환경

    • StringBuilder 사용
  • 멀티스레드 환경

    • 지역 변수인 경우 : StringBuilder 사용
    • 인스턴스 변수, 클래스 변수인 경우 : StringBuffer 사용