Java에서 문자열을 다루는 클래스는 String, StringBuilder, StringBuffer 가 있습니다.
적은 양의 연산을 수행할 때는 보통 String 클래스를 사용합니다.
그러나 연산량이 많아질 경우에는 어떤 문제가 발생하는지를 알아보며 StringBuilder와 StringBuffer를 왜 사용하는지 이해해봅시다.
상수 풀 (Constant Pool)
Java에서 String은 불변성이라는 독특한 특성을 갖고 있습니다.
문자열 리터럴을 선언할 때 JVM은 Heap 공간 안의 String Pool에 오브젝트를 생성하고 그에 대한 참조를 스택에 저장합니다.
그리고 동일한 문자열에 대해서는 이미 String Pool에 존재하는 오브젝트를 참조하도록 하는 것이죠.
이렇게 되면 메모리 효율성을 극대화할 수 있으나 데이터의 연산이 있을 경우 새롭게 만들어서 다시 참조할 수 있도록 변경해야 합니다.
즉,
String greeting = "Hello";
위와 같이 변수 greeting에 "Hello"라는 문자열 리터럴을 선언, 할당했을 때엔 스택과 String Pool은 아래와 같이 됩니다.
Stack | |
greeting | 0x00000000 |
String Pool | |
Address | Data |
0x00000000 | Hello |
이 상태에서 다시 greeting에 다른 문자열 리터럴을 할당 또는 연산하면 아래와 같이 변경됩니다.
greeting = "Hello, World!";
// 또는 greeting += ", World!";
Stack | |
greeting | 0x00001000 |
String Pool | |
Address | Data |
0x00000000 | Hello |
0x00001000 | Hello, World! |
이처럼 String 클래스는 불변성이라는 특징 때문에 자주 변경될 문자열에 사용하는 것은 적합하지 않다고 봅니다.
StringBuffer / StringBuilder
위에서 변동성이 큰 상황에서 문자열 리터럴을 사용할 때 String Pool에 사용하지 않는 임시 가비지가 생성되는 것을 보았습니다.
성능에 영향을 최소화하기 위해서 대안으로 나온 것이 StringBuffer와 StringBuilder입니다.
StringBuffer와 StringBuilder는 가변성을 가지는 클래스입니다.
문자열 변경(추가, 수정, 삭제)에 있어서 새로운 객체가 생성되지 않기에 자주 변경되는 경우라면 이 클래스들을 활용하는 것이 좋습니다.
선언과 추가하는 예시 코드는 다음과 같습니다.
StringBuffer greeting = new StringBuffer("Hello");
greeting.append(", World!");
System.out.println(greeting.toString());
StringBuffer와 StringBuilder에 저장된 문자열을 String 객체로 빼내기 위해서 .toString() 메소드를 활용하면 되겠지요.
두 가지의 차이는 멀티 쓰레드 환경에서 동기화 여부 입니다.
단일 쓰레드에서 사용할 것이라면 StringBuilder를 사용하는 것이 속도 측면에서 더 좋지만 멀티 쓰레드 환경에서 문자열 연산이 빈번하다면 StringBuffer를 사용해야 합니다.
'개발 > Java' 카테고리의 다른 글
[SOLID] 객체 지향 설계의 5가지 원칙 (0) | 2023.07.04 |
---|---|
[Java] Functional Interface와 익명 클래스 (0) | 2022.10.19 |
[Java] Default Method (0) | 2022.10.19 |
[Java] JVM? JRE? JDK? (0) | 2022.10.18 |
객체지향 프로그래밍과 설계 (0) | 2022.10.18 |