JAVA/JAVA2

[java2] #29 - StringBuilder, StringBuffer

yoonddo 2022. 11. 2. 17:44

Java에서 String 클래스는 불변성을 갖는다. 그래서 변하지 않는 문자열을 자주 사용하는 경우엔

좋은 성능을 기대할 수 있다. 하지만 문자열에 대한 변경이 자주 일어나는 프로그램에서 String

사용하게 된다면 효율적인 성능을 기대하기 어렵다. 그래서 우리는 StringBuilder , StringBuffer

사용하여 효율적인 성능을 보일 수 있다.

Java에서 문자열을 다루는 대표적인 클래스는 String, StringBuilder, StringBuffer가 있다.


1. String VS StringBuilder, StringBuffer

Java에서 String 객체는 한번 값이 할당되면 그 공간은 변하지 않는다.

하지만 Stringbuilder StringBuffer 객체는 한번 값이 할당되더라도 한번 더 다른 값이 할당되면

할당된 공간이 변하는 특성을 갖고 있다.

여기서 할당된 공간이 변하지 않는 특성을 불변(Immutable)성이라고 하고,

할당된 공간이 변하는 특성을 가변(mutable)성이라고 합니다.

 

String

  • 불변성을 갖는다. -> immutable 하다.

StringBuilder, StringBuffer

  • 가변성을 갖는다. → mutable 하다.

 

가변성과 불변성 예시 코드

String str = "strA";
StringBuilder sbd = new StringBuilder();
StringBuffer sbf = new StringBuffer();

sbd.append("sbdA");
sbf.append("sbfA");

System.out.println("String 객체의 주소 : "+str.hashCode());
System.out.println("StringBuilder 객체의 주소 : "+sbd.hashCode());
System.out.println("StringBuffer 객체의 주소 : "+sbf.hashCode());

str += "strB";
sbd.append("sbdB");
sbf.append("sbfB");
System.out.println("=============================");

System.out.println("String 객체의 주소 : "+str.hashCode());
System.out.println("StringBuilder 객체의 주소 : "+sbd.hashCode());
System.out.println("StringBuffer 객체의 주소 : "+sbf.hashCode());

결과

String 객체의 주소 : 3541040
StringBuilder 객체의 주소 : 1468177767
StringBuffer 객체의 주소 : 434091818
=============================
String 객체의 주소 : 1758230625
StringBuilder 객체의 주소 : 1468177767
StringBuffer 객체의 주소 : 434091818

String, StringBuilder, StringBuffer 타입의 변수를 선언하고 문자열을 수정하기 전에 객체의 주소를 해싱하여

값을 반환해주는 hashCode()의 반환 값을 출력하고, 문자열을 수정한 뒤 hashCode()의 반환값을 출력해보면

다음과 같이 String의 객체의 주소만 바뀐 것을 확인할 수 있습니다.


1-1 String Constant Pool

String 변수에 값을 할당하는 방법

  • 리터럴 변수를 대입
  • new 키워드를 사용

 

위 두가지 방식을 사용한 예시

String strA = "abc";
String strB = new String("abc");
String strC = "abc";
String strD = new String("abc");

System.out.println(strA==strB); //false
System.out.println(strA==strC); //true
System.out.println(strB==strD); //false

3개의 변수 모두 "abc"라는 문자열을 갖는데 3개의 주소비교(==)의 결과가 상이한 이유는

String 타입 값 할당 방식에 따른 저장 방식이 다르기 때문이다.


# 리터럴 변수 대입

 

- 변수(variable)은 하나의 값을 저장하기 위한 공간 (변경 가능)

- 상수(constant)는 값을 한번만 저장할 수 있는 공간 (변경 불가능)

- 리터럴(literal)은 그 자체로 값을 의미하는 것

int year = 2014;
final int Max_value=100;

int = 변수 타입
year = 변수
2014 = 리터럴
Max_value = 상수
100 = 리터럴

 

String 을 리터럴 값으로 할당하는 경우엔 Heap 메모리 영역안의 특별한 메모리 공간인 String constant pool 

저장된다. 만약 String constant pool에 존재하는 리터럴 값을 사용하게 된다면 새롭게 리터럴 값을 만들어

String constant pool에 저장하는 것이 아닌, 현재 존재하는 값을 사용하게 된다.

위와 같은 이유로 System.out.println(strA==strC);의 실행 결과가 true가 나오게 되는 것이다.


# new 키워드 사용

 

new 키워드를 통해 String 변수에 값을 할당하게 되면 일반적인 객체와 동일하게

Heap 영역에 동적으로 메모리 공간이 할당되게 된다. 마찬가지로 같은 문자열이더라도

new 키워드를 한번 더 사용하게 되면 같은 값이지만 다른 메모리 공간(Heap 영역 안)을 참조하게 된다.

위와 같은 이유로 System.out.println(strB==strD); 에서 false 를 출력하게 된다.

또한 System.out.println(strA==strB); 에서도 false 를 출력하는 것도 서로 상이한 메모리 공간을 참조하기 때문이다.

  • strA → Heap → String Constant Pool
  • strB → Heap

2. StringBuilder VS StringBuffer

String 과 달리 StringBuilder StringBuffer는 둘 다 크기가 유연하게 변하는 가변성을 갖는다.

이는 두 클래스 모두 AbstractStringBuilder 라는 추상클래스를 상속받아 구현되어있기 때문이다.

 

AbstractStringBuilder 추상클래스의 멤버변수엔 다음 2가지 변수가 존재한다.

  • value : 문자열의 값을 저장하는 byte형 배열
    The value is used for character storage.
  • count : 현재 문자열 크기의 값을 가지는 int 형 변수
    The count is tthe number of characters used.

StringBuilder와 StringBuffer 클래스의 문자열을 수

정하고 싶으면 append() 메서드를 사용하게 되는데

append() 메서드는 AbstractStringBuilder에 다음과 같이 구현되어 있다.

public AbstractStringBuilder append(String str) {
        if (str == null) {
            return appendNull();
        }
        int len = str.length();
        ensureCapacityInternal(count + len);
        putStringAt(count, str);
        count += len;
        return this;
    }

다음과 같이 StringBuilder , StringBuffer에 문자열을 추가하게 되면 추가할 문자열의 크기(길이)만큼

현재의 문자열을 저장하는 배열의 공간을 늘려주고, 늘려준 공간에 추가할 문자열을 넣어주는 방식으로 되어있다.

위에서 살펴본 내부동작을 통해 값이 변경되더라도 같은 주소공간을 참조하게 되는 것이며, 값이 변경되는 가변성

띄게 된다.

 

하지만 두 클래스의 기능은 동일한 한 가지 차이점이 있다. 바로 동기화(Synchronization)에서의 차이점인데

StringBuilder는 동기화를 지원하지 않는 반면, StringBuffer는 동기화를 지원해 멀티스레드 환경에서도

안전하게 동작할 수 있다.

그 이유는 StringBuffer는 메서드에서 synchronized 키워드를 사용하기 때문인데, java에서 synchronized

키워드는 여러개의 스레드가 한 개의 자원에 접근하려고 할 때, 현재 데이터를 사용하고 있는 스레드를

제외하고 나머지 스레드들이 데이터에 접근할 수 없도록 막는 역할을 한다.

예를 들어 멀티스레드 환경에서 A 스레드와 B 스레드 모두 같은 StringBuffer 클래스 객체 sb의

append() 메서드를 사용하려고 하면 다음과 같은 절차를 수행하게 된다.

  • A 스레드 : sb의 append() 동기화 블록에 접근 및 실행
  • B 스레드 : A 스레드 sb 의 append() 동기화 블록에 들어가지 못하고 block 상태가 됨.
  • A 스레드 : sb의 append() 동기화 블록에서 탈출
  • B 스레드 : block 에서 running 상태가 되며 sb 의 append() 동기화 블록에 접근 및 실행.

StringBuilder 클래스 주석에서 동기화가 필요할 경우 StringBuffer을 추천한다는 문구를 확인할 수 있다.


정리

 - String불변성을 갖기때문에 변하지 않는 문자열을 자주 사용할 경우 String 타입을 사용하는 것이

   성능면에서 유리 할 것이다.

 - StringBuilder동기화를 지원하지 않는 반면, 속도면에선 StringBuffer보다 성능이 좋다.

   그렇기 때문에 StringBuilder를 사용하는 것이 성능면에서 유리 할 것이다.

 - StringBuffer동기화를 지원멀티 스레드 환경에서도 안전하게 동작할 수 있다. 그렇기 때문에

   우리는 멀티스레드 환경과 문자열의 추가, 수정, 삭제 등이 빈번히 발생하는 경우 StringBuffer를 사용

   하는 것이 성능면에서 유리 할 것이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

출처 : https://velog.io/@heoseungyeon/StringBuilder%EC%99%80-StringBuffer%EB%8A%94-%EB%AC%B4%EC%8A%A8-%EC%B0%A8%EC%9D%B4%EA%B0%80-%EC%9E%88%EB%8A%94%EA%B0%80