본문 바로가기
안드로이드/자바

(java) StringBuilder vs StringBuffer

by 나이아카 2021. 8. 1.

 자바를 이용해 코딩테스트를 하다보면 문자열을 다루는 경우가 종종 있습니다. 가끔 자주 변경되는 문자열을 String에 담다가 시간초과나 효율성 문제 때문에 StringBuilder로 변경하는 경우가 있는데요. 그래서 2개의 차이는 어느정도 파악하고 있습니다. 그러나 제가 지원했던 다른 회사의 면접 내용 중 하나였던 StringBuilder와 StringBuffer의 차이를 설명하지 못했었는데요. 사실 StringBuffer를 써본 적이 없어서 String과 StringBuilder를 설명하고 난 후, StringBuffer는 써본 적이 없어 모른다고 대답했었네요.

 먼저 StringBuilder와 StringBuffer의 차이점을 찾기 전에 String에 대해서 짧게 설명하고 넘어가겠습니다.

 String은 불변 객체로 String 변수 안에 값을 초기화한 이후, String 값을 변경하기 위해 아래와 같이 사용할 수 있습니다.

String data = "data is";
data += " none data";

 이 경우, data의 초기 값음 "data is"이고 이후에 "none data"라는 값이 추가되었습니다. 처음에 생성했던 "data is"라는 객체는 연결된 변수를 해제하고 메모리 내에 남아있게 됩니다. data라는 변수는 이전 객체와의 연결을 해제하고 새로운 객체인 "data is none data"를 연결하게 되죠. 이후 GC가 "data is"라는 객체를 메모리에서 삭제시킵니다. 이는 한 두번 정도의 변경은 괜찮지만, 코딩테스트의 특성처럼 계속해서 바꿔주는 경우가 생긴다면 극명하게 속도가 느려질 겁니다.

 StringBuilder와 StringBuffer의 가장 큰 특징은 가변적이라는 것인데요. 실제로 AbstractStringBuilder라는 클래스를 상속받고 있는 두 클래스는 append와 같은 메소드를 지원하고 있습니다.

 이 두 클래스는 append나 delete를 통해 현재 데이터의 메모리 주소 바로 뒤에 다시 데이터를 추가하는 형태로 작성되어 GC의 작동을 줄여주곤 합니다.

StringBuilder sb = new StringBuilder("data of");
sb.append(" StringBuilder");

 위와 같은 코드는 원래 존재하던 객체 "data of"의 주소 바로 뒤에 " StringBuilder"라는 문자열을 추가해주는 형태로 작성되어 있어 GC의 작동을 줄여줍니다. 실제로 AbstractStringBuilder에는 나중에 후술할 코드에서 확인할 수 있지만, 캐릭터 배열인 value와 count라는 변수를 가지고 있습니다.

 String과 다른 두 클래스의 차이점은 위와 같습니다. 그렇다면 StringBuilder와 StringBuffer로 나뉜 이유가 무엇일까요. 일단 두 클래스 모두 AbstractStringBuilder를 상속받고 있는 것을 동일합니다. 그렇다는 것은 기본적인 토대는 동일하다는 의미네요! 하지만 둘 다 override하는 방식이 조금 다른데요. 두 코드 모두 매우 긴 코드기 때문에 둘 다 append중 하나씩만 가져와 보겠습니다.

//여기는 StringBuffer의 append

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

@Override
public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, 0, count);
}


//여기는 StringBuilder의 append

@Override
public StringBuilder append(String str) {
    super.append(str);
    return this;
}

 

 StringBuffer는 먼저 Override된 코드에 synchronized라는 동기화 키워드를 지원합니다. 또한 toStringCache를 통해 toString 메소드를 발생시킬 때 마다 캐싱 변수에 현재 value 데이터를 저장하고 있습니다.

 이 두 코드를 통해 StringBuffer는 멀티 쓰레드에서 동기화 키워드를 이용해 변수를 안전하게 보호해준다는 점을 알 수 있고, StringBuilder의 경우, 멀티 쓰레드에서 변수의 제대로 된 변화를 보장하지는 않지만 동기화를 고려하지 않아 속도가 더 빠르다는 것을 알 수 있습니다.

 결국 둘의 가장 큰 차이는 멀티 쓰레드 동기화입니다. 혹시 다른 차이점은 없을까해서 직접 코드를 열어봤지만, 둘 다 상속받는 코드가 동일했고 오버라이딩하는 과정에서 StringBuffer 쪽이 동기화 키워드 + toStringCache(이쪽은 미묘한 수준이지만)를 통해 StringBuilder보다 조금 더 느린 것 외에는 차이점을 발견할 수 없었습니다.(결국 toString 코드가 많이 다르겠네요!)

 

댓글