본문 바로가기

CS/JAVA

[JAVA] String 그리고 StringBuffer와 StringBuilder

Java로 코드를 작성하며 String 클래스를 많이 활용해 봤을 것이다. String 클래스 이외에도 StringBuffer, StringBuilder등 다양한 String 타입의 클래스들이 존재한다. 이러한 클래스들은 사용 의도가 모두 각각 다르다. 이들의 특성에 대해 알아보도록 하자.


계층 구조

Interface 기능 소개

  • CharSequence : String 타입의 문자열을 판독하는 기능을 가진 인터페이스. 문자열의 길이를 반환하거나, 특정 인덱스의 값을 반환하는 기능을 갖추고 있다.
  • Appendable : Appendable 인터페이스는 StringBuffer와 StrigBuilder가 구현하고 있는 대표적 인터페이스로, 문자열 뒤에 추가적인 문자열을 덧붙일 때  사용하는 기능을 가지고 있다.
  • Comparable : 객체를 정렬하는 데 사용하는 인터페이스로, 같은 타입의 인스턴스를 비교할 때 쓰인다.

 

String Constant Pool

String을 생성하는 방법에는 2가지가 있다.

1. new 연산자 사용

2. 큰 따옴표(" ") 사용

new 연산자를 사용할 경우 Heap 영역에 인스턴스가 생성되지만, 큰 따옴표를 사용할 경우 intern() 메서드를 통해 String 상수 풀에 문자열을 등록하게 된다.

※intern() : 2번째 방법인 큰 따옴표를 사용하여 생성된 문자열이 String Pool에 해당 문자열이 존재할 경우 해당 문자열을 반환하여 가리키게 하고, 없을 경우 문자열을 등록하는 메서드

 

String

Java에서 String은 Immutable(불변)하다는 특성을 가지고 있다. 즉, 상수와 같기 때문에 String 타입의 인스턴스가 생성되면 변경할 수 없다.

str 기존의 인스턴스에 대한 값을 수정하는 것이 아니다. 새로운 인스턴스를 생성해서 변경된 값을 새로 생성한다. 새로운 인스턴스의 생성 이후 이전에 생성된 필요 없어진 인스턴스는 GC(Garbage Collector)에 의해 제거될 것이다.

문자열이 생성되고 수정되지 않는다면 String 타입은 적절할 테지만, 빈번한 수정이 이루어진다면 절대 좋은 성능을 낼 수 없을 것이다. 그래서 이러한 단점을 해결하기 위해 StringBuffer와 String Builder가 만들어졌다.

  • StringBuffer/StringBuilder보다 인스턴스의 생성이 빠르다.
  • 수정이 발생할 때마다 매번 인스턴스를 새로 생성 → 성능 저하
  • String 값의 수정이 많이 필요하다면 String 클래스는 부적절
  • final 클래스이기 때문에 멀티스레드 환경에서 Safe 하다.

String과 StringBuffer/StringBuilder 이 둘의 가장 큰 차이점은 메모리 공간의 사용에 있어서 유연한가?(Flexible)이다. StringBuffer와 StringBuilder는 할당된 메모리 영역에서 새로 인스턴스를 생성하지 않고, 기존의 메모리 공간을 확장하여 값을 수정한다.

StringBuffer

StringBuffer의 경우 String에 비해 메모리의 크기가 유동적이다. 초기에 값을 설정하고 메모리 공간의 확장이 필요하며 그때마다 크기를 키운다. 즉, 문자열의 수정이 발생해도 새로운 인스턴스가 생성되는 것이 아니라 기존의 인스턴스에 메모리 공간을 확장하여 값을 수정한다.

    public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, Comparable<StringBuffer>, CharSequence
{

    ...
    
    // 초기 메모리 크기 16
    @IntrinsicCandidate
    public StringBuffer() {
        super(16);
    }
    // 버퍼의 크기를 명시적으로 설정하는 int형 매개변수를 넣어 초기값 세팅
    @IntrinsicCandidate
    public StringBuffer(int capacity) {
        super(capacity);
    }
    문자열의 크기를 받아 그대로 세팅
    @IntrinsicCandidate
    public StringBuffer(String str) {
        super(str);
    }
    
    ...
    
}

StringBuffer와 관련한 메서드 보기[참고]

  • 새로운 인스턴스 생성 없이 값의 변경 가능
  • 멀티 스레드 환경에서 Safe 하다.
  • String보다 느리다.
  • 단일 스레드 환경에서 StringBuffer는 StringBuilder보다 느리다(StringBuilder는 Thread-Safe하지 않음)

 

StringBuilder

StringBuilder의 경우 StringBuffer의 기능과 유사하다. 다만 Thread-Safe하지 않기 때문에 싱글 쓰레드 환경에서 사용하기 적합하다. 그러므로 StringBuffer보다 빠른 속도를 가지고 있다. 따라서 문자열 수정이 빈번히 발생하고, 동기화를 고려하지 않아도 되는 경우에 사용하자.

  • Thread-Safe하지 않다
  • StringBuffer보다 빠르다.

 

+연산과 append()

String 문자열 수정을 하며 + 연산을 많이 사용해 보았을 것이다. 결론부터 말하면 +연산은 append보다 느리다.

+ 연산이 진행되는 과정을 보자.

public static void main(String[] args) {

		String str1 = "Hello";
		String str2 = " World";
		str1 = str1 + str2;
		
		System.out.println(str1);
	}

확인해볼 코드이고 str1 = str1 + str2; 라인에 브레이크를 걸어 해당 라인이 동작하는 과정을 알아보았다.

 

//Step 1. StringBuilder 생성자 호출(str1)
public StringBuilder(String str) { //"Hello" 문자열에 대한 StringBuilder 인스턴스 생성
        super(str);
}

//Step 2.  상위 클래스 생성자 호출
AbstractStringBuilder(String str) {
        int length = str.length();
        int capacity = (length < Integer.MAX_VALUE - 16)
                ? length + 16 : Integer.MAX_VALUE;
        final byte initCoder = str.coder();
        coder = initCoder;
        value = (initCoder == LATIN1)
                ? new byte[capacity] : StringUTF16.newBytesFor(capacity);
        append(str);
}
//Step 3.  append 메서드 실행하여 새로운 문자열 생성 -> "Hello"
@Override
@IntrinsicCandidate
public StringBuilder append(String str) {
        super.append(str);
        return this;
}


//Step 4.  "World" 문자열에 대한 StringBuilder 생성 (str2)
@Override
@IntrinsicCandidate
public StringBuilder append(String str) {
        super.append(str);
        return this;
}
//Step 5. append 메서드를 활용하여 문자열 추가 -> "Hello World"
public AbstractStringBuilder append(String str) {
        if (str == null) {
            return appendNull();
        }
        int len = str.length();
        ensureCapacityInternal(count + len);
        putStringAt(count, str);
        count += len;
        return this;
}
//Step 6.  str1에 값을 할당하는 연산작업  -> str1 = "Hello World"
@Override
@IntrinsicCandidate
public String toString() {
        // Create a copy, don't share the array
        return isLatin1() ? StringLatin1.newString(value, 0, count)
                          : StringUTF16.newString(value, 0, count);
    }

String의 + 연산을 사용하면 StringBuilder 클래스를 활용하여 문자열을 생성한다. String 클래스가 아닌 StringBuilder를 활용함으로써 성능을 향상시킬 수 있다. + 연산을 사용함으로써 새로운 인스턴스를 생성하게 되고 append 메서드를 호출하여 문자열을 생성하게 된다. 즉, +연산 하나에 6단계 이상의 생성자와 메서드들이 사용되지만 개발자가 처음부터 +연산이 아닌 append를 사용하면 성능을 높일 수 있을 것이다. 따라서+연산을 지양하도록 하자

 

정리

  String StringBuffer StringBuilder
Storage String Constant Pool Heap Heap
Modifiable immutable mutable mutable
Thread-safe Yes Yes No
Synchronized No Yes No
Performance Fast Slow Fast

참고하면 좋은 사이트

 

String Constant Pool in Java - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

Java의 String.intern() 살펴보기 - Increment

며칠 전에 String 클래스 문서를 정리하면서 intern 메서드에 관해 짧게 다룬 적이 있었습니다. 그 후에 실제로 intern 메서드가 어떻게 동작하는 지 궁금해서 짧게나마 찾아본 내용을 정리해봅니다.

www.latera.kr

 

Java String + 연산에 대한 이해

1. 자바에서 +연산으로 문자열 연결 Java String 클래스는 + 연산으로 쉽게 두개의 스트링을 잇는 것이 가능하다. public static void main(String[] args) throws Exception { String a = "Hello, "; String b =..

blue-jay.tistory.com

 

'CS > JAVA' 카테고리의 다른 글

[JAVA] Record  (0) 2023.12.16
JAVA의 리플렉션 API  (0) 2023.01.08
Abstract Class와 Interface  (0) 2022.08.23
Java에서의 Hash  (0) 2022.08.16
정적 팩토리 메서드(Static Factory Method)  (0) 2022.07.31