본문 바로가기

신입 개발자 면접 기초

JAVA String, StringBuffer, StringBuilder 차이점

반응형

String, StringBuffer, StringBuilder의 장단점 및 차이점

자바에서 String과 StringBuffer, StringBuilder의 차이점을 알아본다.

앞서 이 클래스들의 공통점은 모두다 String(문자열)을 저장하고 관리하는 클래스들이 라는 것이다.(간단히 참고)

String vs StringBuffer, StringBuilder

String은 immutable(불변)하고 StringBuffer, StringBuilder는 mutable(가변)하다.

다시 말해서, String 클래스는 StringBuffer 클래스나 StringBuilder 클래스와 다르게 리터럴을 통해 생성되면 그 인스턴스의 메모리 공간은 절대 변하지 않는다.

String literalString = "literal"; //리터럴로 생성하는 방식
String newString = new String("literal"); //new로 생성하는 방식

//위에서 "literal" 이라는 문자열을 String Pool에서 생성했기 때문에 이후에 추가한 str1, str2, str3는 추가적으로 생성하지않고 똑같은 문자열을 가리킨다.
String str1 = "literal";
String str2 = "literal";
String str3 = "literal";

위 코드의 두 가지 String class 생성 방식에 따라 나뉘는데, 두 방식 모두 JVM 메모리 중 힙(heap) 영역에 생성된다는 점은 같다.

하지만 리터럴로 생성하면 특수하게 "String Pool"이라는 공간에 생성된다. 이 메모리 공간에 생성된 문자열 값은 절대 변하지 않는다는 얘기다.

그래서 '+' 연산이나 .concat() 메소드를 이용해서 문자열 값에 변화를 줘도 메모리 공간 내의 값이 변하는 것이 아니라, "String Pool"이라는 공간 안에 메모리를 할당받아 새로운 String 클래스 객체를 만들어서 문자열을 나타내는 것이다.

이렇게 새로운 문자열이 만들어지면 '기존의 문자열'은 가비지 콜렉터에 의해 제거되야 하는 단점(언제 제거될지 모름)이 있다.

또한 이러한 문자열 연산이 많아진다면, 연산 한 번 할 때마다 계속해서 문자열 객체를 만드는 오버헤드가 발생하므로 성능이 떨어질 수 밖에 없다. (+ 연산에 내부적으로 char배열을 사용함)

대신 String 클래스의 객체는 불변하기 때문에 단순하게 읽어가는 조회연산에서는 타 클래스보다 빠르게 읽을 수 있는 장점이 있다. (추가 장점으로 불변하기 때문에 멀티쓰레드환경에서 동기화를 신경쓸 필요가 없다.)

String 클래스가 적절한 경우

결론적으로, String 클래스는 문자열 연산이 적고, 자주 참조(조회)하는 경우에 사용하면 좋다. (특히 멀티 쓰레드 환경에서 신경쓸 게 없어서 좋다.)


StringBuffer vs StringBuilder ?

StringBuffer와 StringBuilder 클래스는 String과 다르게 mutable(변경가능)하다고 그랬다.

즉 문자열 연산을 할 때, 클래스는 한 번만 만들고(new), 메모리의 값을 변경시켜서 문자열을 변경한다.

그러므로 쓸데없이 중간에 문자열데이터들을 안 만들게 되니까 문자열 연산이 자주 있을 때 사용하면 성능이 좋다!

심지어 StringBuffer와 StringBuilder 클래스의 메서드들이 같으므로 호환(?)이 가능하다.

그렇다면 StringBuffer와 StringBuilder의 차이는 무엇일까?

차이점은 StringBuffer는 멀티쓰레드환경에서 synchronized키워드가 가능하므로 동기화가 가능하다. 즉, thread-safe하다. 반대로 StringBuilder는 thread-safe하지 않다.

StringBuilder는 동기화를 지원하지 않기 때문에 멀티 쓰레드 환경에서는 적합하지 않다.

대신 StringBuilder가 동기화를 고려하지 않기 때문에 싱글쓰레드 환경에서 StringBuffer에 비해 연산처리가 빠른 장점이 있다.

StringBuffer, StringBuilder가 적절한 경우

결론적으로, 문자열 연산이 많을 때 두 클래스를 사용하지만 멀티 쓰레드 환경에서는 StringBuffer를 사용하면 좋고, 싱글 쓰레드 환경이거나 멀티 쓰레드여도 굳이 동기화가 필요없는 경우에는 StringBuilder를 사용하는 것이 좋다.


정리

String 클래스불변 객체이기 때문에 문자열 연산이 많은 프로그래밍이 필요할 때 계속해서 인스턴스를 생성하므로 성능이 떨어지지만 조회가 많은 환경, 멀티쓰레드 환경에서 성능적으로 유리합니다.

StringBuffer 클래스와 StringBuilder 클래스는 문자열 연산이 자주 발생할 때 문자열이 변경가능한 객체기 때문에 성능적으로 유리합니다.

StringBuffer와 StringBuilder의 차이점은 동기화 지원의 유무이고 동기화를 고려하지 않는 환경에서 StringBuilder가 성능이 더 좋고, 동기화가 필요한 멀티쓰레드 환경에서는 StringBuffer를 사용하는 것이 유리합니다.


추가적인 내용

  • StringBuffer와 StringBuilder는 성능으로 따졌을 때 2배의 속도 차이가 있다고 합니다. 하지만 참고사이트의 속도 차이 실험 결과, append() 연산이 약 1억6천만 번 일어날 때 약 2.6초 정도의 속도차이를 보인다고 합니다. (해당 수치가 유의미할지 무의미할지는 만드시는 프로그램의 스펙에 따라 나뉩니다. 참고하시기 바랍니다.)
    (String은 +연산이 16만 번이상 넘어가게 되면 10초이상 걸리면서 못 쓸정도의 성능을 보입니다.)
    → 따라서 문자열 연산이 많지만 엄청나게 일어나지 않는 환경이라면 StringBuffer를 사용해서 thread-safe한 것(안정적인 것)이 좋다는 게 제 생각입니다.
  • JDK1.5이상부터 String에서 +연산으로 작성하더라도 StringBuilder로 컴파일하게 만들어 놨다지만 여전히 String클래스의 객체 생성하는 부분을 동일하므로 StringBuffer, StringBuilder 사용이 필요합니다. (* 반복문 안에서는 StringBuilder로 컴파일 시켜주지 않는다는 얘기를 들었습니다. (사실확인필요하지만...))
  • StringBuffer, StringBuilder의 경우 buffer size를 초기에 설정해야하는데 이런 생성, 확장 오버로드가 걸려 버퍼사이즈를 잘못 초기화할 경우 성능이 좋지 않을 수 있는 문제가 있습니다.
  • String클래스가 컴파일러 분석 단계에서 최적화될 가능성이 있기때문에 간혹 성능이 잘나오는 경우도 있음. 문자열 연산이 많지 않은 경우는 그냥 사용해도 무방.
  • 런타임에서 문자열 조합이 많아질 경우, String 클래스는 여전히 성능이 아주 안좋기 때문에!
  • , .concat()을 사용하는 사고(?)를 치면 안된다. 특히 현업에서...
반응형
  • Favicon of https://haruhiism.tistory.com BlogIcon 하루히즘 2018.11.23 01:40 신고

    감사합니다!

  • 허허 2019.01.07 11:30

    이해가 쏙쏙 잘 되었어요
    감사합니다 !!

  • Favicon of https://syeon02.tistory.com BlogIcon 셔니 2019.03.01 18:56 신고

    오 ~ 정말 정리를 잘해주셨네요 :) 감사합니다!

  • 준비자 2019.04.18 11:21

    감사합니다 아주 큰 도움이 되고 있습니다.

  • Favicon of https://tosikgun.tistory.com BlogIcon 토식군 2019.05.09 17:43 신고

    그동안 궁금했던게 확 풀리네요!!

  • 앵낄낄 2019.12.12 10:34

    글로 이해를 잘 못하는 편인데
    이 글은 한번 읽고 이해를 했습니다. 정리를 아주 잘하시는 듯요 ~! 굿굿~!

  • 레알이 2019.12.14 14:24

    잘보고 갑니다... 감사합니다.!

  • Favicon of https://knooow.tistory.com BlogIcon 눈부셔요 2020.01.30 21:01 신고

    대단히 명료한 포스트입니다 감사합니다.

  • 헝그리 2020.02.15 11:42

    정리가 엄청 깔끔하네요. 좋은 글 감사드립니다.

  • tena 2021.04.15 23:28

    정프로님 포스팅 유익하게 잘 봤습니다.

    코드에서 StringBuilder를 사용하던 중,
    몇가지 궁금증이 들어 질문을 드리는데 아시는 선까지 알려주시면 감사하겠습니다.
    검색을 해봤지만,
    거의 교과서적인 포스팅이 전부이고 명확하게 이해할 수가 없어서 이해에 도움을 받고자 합니다.


    ```````````
    protected String 테스트(String msg) {

      StringBuilder sb = new StringBuilder();
    // -> Q1) 초기 버퍼 크기(capacity)를 어떤 기준으로 줘야할지 모르겠습니다. (StringBuilder 생성자 기본값은 16), buffer size를 잘못 초기화할 경우 성능이 좋지 않다고 많은 분들께서 포스팅은 해놨지만, 다들 Ctrl C + Ctrl V 식으로 양산된 포스팅 들이라, 정작 이를 테크니컬하게 초기화하는 기준에 대해 명확히 설명하는 글을 찾기 힘들었습니다.

      sb.append("<script>");

      sb.append("alert('" + msg + "');");
    // -> Q2) 이렇게 String 타입의 메서드 매개변수인 msg를 직접 넣어 문자열 조합 시, String concatenation as argument to 'StringBuilder.append()' call 이라는 경고가 발생합니다. 어떤 문제가 있는 것이고, 코드를 변경해야 한다면 어떻게 해결해야 하는지 알고 싶습니다.

      sb.append("history.back();");
      sb.append("</script>");

      return sb.toString();
    }

    ```````````

    시간내어 질문 읽어주셔서 감사합니다.

    • BlogIcon anet 2021.04.27 15:45

      글쓴이는 아니지만 우연히 보게되어 제가 생각하는 답변 올려봅니다.

      Q1) 초기 capacity 크기는 StringBuilder 내부 문자열이 담기게 될 char배열의 초기 값입니다. 알고 계신 것 처럼 생성자 기본 값은 16이며 StringBuilder 내부에 길이 16의 char배열이 생성됩니다.

      만약 문자열을 append 하다가 이 길이보다 더 길어지게 된다면 새로운 capacity를 생성하고 기존의 char 배열을 새롭게 생성된 capacity 길이만큼의 char 배열에 복사합니다.

      capacity를 새로 생성하는 알고리즘은 아래와 같습니다.
      ```
      private int newCapacity(int minCapacity) {
      // overflow-conscious code
      int newCapacity = (value.length << 1) + 2;
      if (newCapacity - minCapacity < 0) {
      newCapacity = minCapacity;
      }
      return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
      ? hugeCapacity(minCapacity)
      : newCapacity;
      }
      ```

      Q2) 먼저 위의 경우를 해결하려면 아래와 같이 코드를 작성하는게 좋습니다.
      `sb.append("alert('").append(msg).append("');");`

      경고가 발생하는 이유는 + 연산이 수행되면 포스트 내용과 같이 성능상 문제가 발생하기 때문입니다.

    • Favicon of https://jeong-pro.tistory.com BlogIcon JEONG_AMATEUR 2021.04.28 08:07 신고

      엇 답변이 늦었군요...
      anet 님께서 적절하게 답변드린 것 같습니다.
      StringBuilder의 초기 버퍼의 크기를 지정하는 기준의 최적에 대해서는 정확하게 저도 모릅니다.
      다만 코드상으로 추측해보면, 너무 작게 잡아서 불필요하게 새로운 capacity로 확장해나가는 게 아주아주 미미하지만 오버헤드가 있긴 하겠습니다.
      굳이 최적화하면, StringBuilder에 의해 만들어지는 문자열의 길이를 예측가능한 경우에 이보다 살짝 큰 capacity를 초기화하면 좋겠으나, 통상적으로 예측가능한 경우가 거의 없으므로 기본으로 쓰지 않을까 싶네요.