✅ String, StringBuffer, StringBuilder Classes
String, StringBuffer, StringBuilder는 자바에서 문자열을 다루는 클래스들입니다.
🔹String
String은 불변(immutable) 클래스로, 한번 생성된 문자열은 수정이 불가능합니다. 따라서 문자열 연산이 많은 경우, 매번 새로운 문자열 객체를 생성하기 때문에 메모리 사용량이 많아질 수 있습니다. 자바의 String은 변경할 수 없는(immutable) 유니코드 문자의 시퀀스를 포함합니다. 이는 C/C++와 달리 자바의 String은 단순한 char 배열이 아닌 java.lang.String 클래스의 객체입니다.
String str = "Hello";
str += " World";
위의 코드는 "Hello"와 " World" 문자열을 연결하여 "Hello World" 문자열을 생성합니다. 하지만 이 과정에서 "Hello"와 " World" 문자열 객체가 불필요하게 생성됩니다.
'+' 연산자는 두 개의 String 피연산자를 연결하는 데 사용됩니다. '+' 연산자는 Point나 Circle과 같은 다른 객체에는 사용할 수 없습니다. String 클래스만 특이하게 연산자 오버라이딩을 통해 여러 문자를 연결할 수 있습니다.
- String은 다음 두 가지 방법으로 생성할 수 있습니다
- 문자열 리터럴을 String 참조에 직접 할당하는 방법
- "new" 연산자와 생성자를 사용하여 생성하는 방법
다른 클래스와 유사하게 new 키워드를 사용하여 String 객체를 생성합니다. 그러나 이 방법은 흔히 사용되지 않으며 권장되지 않습니다. 예를 들어 첫 번째 문장에서, str1은 String 참조로 선언되고 문자열 리터럴 "Java is Hot"으로 초기화됩니다. 두 번째 문장에서는 str2가 String 참조로 선언되고 "I'm cool"을 포함하도록 new 연산자와 생성자를 통해 초기화됩니다.
String str1 = "Java is Hot"; // 문자열 리터럴을 통한 암시적 생성 String str2 = new String("I'm cool"); // new 연산자와 생성자를 통한 명시적 생성 문자열 리터럴은 공통 풀(common pool)에 저장됩니다.
Java는 문자열 리터럴을 "string common pool"이라고 불리는 특별한 메커니즘을 통해 유지합니다. 만약 두 개의 문자열 리터럴이 동일한 내용을 가지고 있다면, 그들은 common pool의 동일한 string literal을 공유합니다. 이 방식은 자주 사용되는 문자열의 저장 공간을 절약하기 위해 채택되었습니다. 반면에, new 연산자와 생성자를 통해 생성된 String 객체들은 힙(heap)에 유지됩니다. 힙 내의 각 String 객체는 다른 객체와 마찬가지로 자체 저장 공간을 갖습니다. 힙에서는 두 개의 String 객체가 동일한 내용을 가지더라도 저장 공간을 공유하지 않습니다.
🔹StringBuffer
StringBuffer는 가변(mutable) 클래스로, 문자열을 수정할 수 있습니다. 따라서 문자열 연산이 많은 경우에는 StringBuffer를 사용하는 것이 좋습니다. StirngBuffer 클래스는 Heap 메모리 영역에 저장되어 관리되기 때문에 String과 달리 값이 같더라도 공유되지 않으므로 다른 객체에 부작용을 일으키지 않고 수정할 수 있습니다.
StringBuffer buffer = new StringBuffer("Hello");
buffer.append(" World");
위의 코드는 StringBuffer 클래스를 사용하여 "Hello" 문자열 뒤에 " World" 문자열을 추가하여 "Hello World" 문자열을 생성합니다. 이 과정에서 문자열 객체를 새로 생성하지 않습니다.
🔹StringBuilder
StringBuilder는 StringBuffer와 거의 동일한 기능을 제공합니다. 다만, StringBuilder는 동기화(스레드)되지 않습니다. 즉, 여러 개의 스레드가 동시에 StringBuilder 인스턴스에 접근하는 경우, 일관성을 보장할 수 없습니다. 그러나 대부분의 경우 단일 스레드 프로그램에서는 동기화 오버헤드를 제거하여 StringBuilder를 더 빠르게 만들 수 있습니다. 스레드 환경에 따라 권장하는 클래스를 분류하자면 아래와 같습니다.
- 단일 스레드 환경 : StringBuilder
- 다중 스레드 환경 : StringBuffer
StringBuilder는 StringBuffer 클래스와 API 호환성을 가지며, 동일한 생성자와 메서드 세트를 갖추고 있지만 동기화 보장이 없습니다. 따라서 단일 스레드 환경에서는 StringBuffer에 대한 대체 수단으로 사용할 수 있습니다.
StringBuilder builder = new StringBuilder("Hello");
builder.append(" World");
위의 코드는 StringBuilder 클래스를 사용하여 "Hello" 문자열 뒤에 " World" 문자열을 추가하여 "Hello World" 문자열을 생성합니다. 이 과정에서 문자열 객체를 새로 생성하지 않습니다.
✅ String, StringBuffer, StringBuilder 성능 비교
String 클래스는 immutable으로 기존의 객체에 문자를 변경하는 것이 아닌, 새로운 객체를 매번 생성해야하기 때문에 성능이 떨어집니다. StringBuffer, StringBuilder 클래스와 비교하여 얼마나 성능 차이가 나는지 확인해보겠습니다.
package com.jhcode.comparestrs;
public class StringsBencMark {
public static void main(String[] args) {
long beginTime, elapsedTime;
// Build a long string
String str = "";
int size = 16536;
char ch = 'a';
beginTime = System.nanoTime(); // Reference time in nanoseconds, 시스템이 부팅된 이후부터 경과된 시간을 나노 초 단위로 알려줌.
for (int count = 0; count < size; ++count) {
str += ch;
int hashKey = System.identityHashCode(str);
++ch;
if (ch > 'z') {
ch = 'a';
}
}
elapsedTime = System.nanoTime() - beginTime;
System.out.println();
System.out.println("String 클래스를 가지고 문자열을 생성했을 경우");
System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Build String)");
//== Reverse a String by building another String character-by-character in the reverse order ==//
String strReverse = "";
beginTime = System.nanoTime();
for (int pos = str.length() - 1; pos >= 0 ; pos--) {
//String 클래스가 가지고 있는 char[] 배열의 인덱스의 끝에서부터 추가하겠다.
strReverse += str.charAt(pos); // Concatenate
int hashKey = System.identityHashCode(strReverse);
}
elapsedTime = System.nanoTime() - beginTime;
System.out.println();
System.out.println("String 클래스를 가지고 문자열을 뒤집기 했을 경우");
System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using String to reverse)");
//== Reverse a String via an empty StringBuffer by appending characters in the reverse order ==//
beginTime = System.nanoTime();
StringBuffer sBufferReverse = new StringBuffer(size);
for (int pos = str.length() - 1; pos >= 0 ; pos--) {
sBufferReverse.append(str.charAt(pos)); // append
int hashKey = System.identityHashCode(strReverse);
int x = 10;
}
elapsedTime = System.nanoTime() - beginTime;
System.out.println();
System.out.println("==StringBuffer 클래스를 가지고 문자열을 생성했을 경우==");
System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using StringBuffer to reverse)");
//== Reverse a String by creating a StringBuffer with the given String and invoke its reverse() ==//
beginTime = System.nanoTime();
StringBuffer sBufferReverseMethod = new StringBuffer(str);
sBufferReverseMethod.reverse(); // use reverse() method
elapsedTime = System.nanoTime() - beginTime;
System.out.println();
System.out.println("==StringBuffer 클래스를 가지고 문자열을 뒤집기 했을 경우==");
System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using StringBuffer's reverse() method)");
//== Reverse a String via an empty StringBuilder by appending characters in the reverse order ==//
beginTime = System.nanoTime();
StringBuilder sBuilderReverse = new StringBuilder(size);
for (int pos = str.length() - 1; pos >= 0 ; pos--) {
sBuilderReverse.append(str.charAt(pos));
}
elapsedTime = System.nanoTime() - beginTime;
System.out.println();
System.out.println("==StringBuilder 클래스를 가지고 문자열을 생성했을 경우==");
System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using StringBuilder to reverse)");
// Reverse a String by creating a StringBuilder with the given String and invoke its reverse()
beginTime = System.nanoTime();
StringBuffer sBuilderReverseMethod = new StringBuffer(str);
sBuilderReverseMethod.reverse();
elapsedTime = System.nanoTime() - beginTime;
System.out.println();
System.out.println("==StringBuilder 클래스를 가지고 문자열을 뒤집기 했을 경우==");
System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using StringBuidler's reverse() method)");
}
}
String 클래스는 아스키코드가 인코딩된 값이 저장된 char[] 배열을 가지고 있습니다. 이 값을 증감연산자(Syntactic suger)을 통하여 증가하거나 감소할 때, 해당 아스키코드에 맞는 문자가 출력되게 됩니다. 이를 통해서 a~z까지의 문자를 String, StringBuffer, StringBuilder 클래스에 a~z까지의 문자를 하나씩 추가하여 생성하는데 얼마나 걸리는지 minsecond(usec) 단위로 표시했습니다.
- 초 (Second): sec
- 밀리초 (Millisecond): ms
- 마이크로초 (Microsecond): μs (뮤에스), usec 도 사용됨.
- 나노초 (Nanosecond): ns
![](https://blog.kakaocdn.net/dn/bPDRDY/btskAWxv95L/nH4Buu884p5ngKa2hb5wNK/img.png)
![](https://blog.kakaocdn.net/dn/cbgmHc/btskCqdwZch/nPBoOSBt0o9LArxr3FL8lk/img.png)
![](https://blog.kakaocdn.net/dn/dHOOI7/btskEVqvKpe/Y776kZ2SmeWfv8EuCKtg2k/img.png)
![](https://blog.kakaocdn.net/dn/ZFe9z/btskDgPeFY5/WNEQNnXjCEMHKlwcH1eVhK/img.png)
위와 같이 String 클래스는 문자가 추가될 때마다 새로운 객체가 생성된다는 것을 해시코드가 다름으로써 알 수 있습니다.
🏷️이미지 출처 및 참고한 사이트
![](https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKfFBZ%2FbtqwyO3ElXL%2Fykm2DRsIq3Io956hU4ZR9k%2Fimg.png)
![](https://thejavabook.files.wordpress.com/2018/01/oop_stringlliteralvsobject.png)
Uploaded by N2T