Java에서 문자열을 다루를 대표적인 클래스로 String, StringBuffer, StringBuilder가 있다. 연산이 많지 않을때는 위에 나열된 어떤 클래스를 사용하더라도 이슈가 발생할 가능성은 거의 없지만 연산횟수가 많아지거나 멀티쓰레드, Race condition등의 상황이 자주 발생 한다면 각 클래스의 특징을 이해하고 상황에 맞는 적절한 클래스를 사용해줘야 한다.
String 클래스는 불변(immutable) 이며, 한 번 생성된 문자열은 변경할 수 없다. 따라서 문자열을 수정할 때마다 새로운 객체를 생성하기 때문에 메모리 사용량이 증가하게 된다.
문자열의 길이가 변하지 않는 경우나, 문자열 변경이 빈번하지 않은 경우에 적합하다. 예를 들어, 데이터베이스 연결 문자열이나 파일 경로 등을 저장할 때 사용된다.
Java에서 String 객체는 new 연산자 로 생성하거나, 리터럴(literal) 를 이용해 생성할 수 있다.
new 연산자를 이용해 String 객체를 생성하면, 새로운 객체가 생성된다. 새로운 객체는 문자열 리터럴 풀에 저장되지 않는다.
String str1 = new String("Hello, World!"); // new 연산자를 이용해 객체를 생성
String str2 = new String("Hello, World!"); // 새로운 객체가 생성됨
System.out.println(str1 == str2); // false
위 코드에서 str1과 str2는 서로 다른 객체를 참조한다. 이는 new 연산자를 이용해 새로운 객체가 생성되기 때문이다.
리터럴은 코드 상에 고정된 문자열 값이다. 리터럴로 생성된 String 객체는 JVM 내부의 문자열 리터럴 풀(string literal pool)에 저장된다. 문자열 리터럴 풀에서는 중복된 문자열이 존재하지 않도록 관리된다.
String str3 = "Hello, World!"; // 리터럴로 생성
String str4 = "Hello, World!"; // 이미 문자열 리터럴 풀에 존재하는 문자열을 참조
System.out.println(str3 == str4); // true
위 코드에서 str3과 str4는 같은 문자열을 참조한다. 이는 문자열 리터럴 풀에서 중복된 문자열을 관리하기 때문이다.
리터럴 생성과 new 연산자를 이용한 생성은 성능 차이가 존재한다. 리터럴을 이용해 문자열을 생성하면 문자열 리터럴 풀에서 문자열 중복을 제거하기 때문에 메모리를 효율적으로 사용할 수 있다. 따라서 문자열을 생성할 때는 리터럴을 이용하는 것이 성능상 이점이 있다.
StringBuilder 클래스는 가변(mutable) 이며, 문자열을 변경할 수 있다. 문자열을 변경할 때마다 새로운 객체를 생성하지 않고 기존 객체 내부에서 문자열을 변경한다.
문자열의 길이가 자주 변경되는 경우에 적합하다. 예를 들어, 반복문을 통해 문자열을 동적으로 생성하거나, 긴 문자열을 빠르게 생성해야 하는 경우에 사용된다.
StringBuilder는 동기화를 지원하지 않기때문에 멀티쓰레드 환경에서 사용하는 것은 적합하지 않지만 동기화를 고려하지 않는 만큼 단일쓰레드에서의 성능은 StringBuffer 보다 뛰어나다.
StringBuffer 클래스는 StringBuilder와 기능적으로 동일하지만, 동기화(synchronization) 가 보장되어 멀티스레드 환경에서 안전(thread-safe)하게 문자열을 다룰 수 있다. 참고로 String도 불변성을 가지기때문에 마찬가지로 멀티쓰레드 환경에서의 안정성(thread-safe)을 가지고 있다.
문자열 조작에 대한 안정성이 중요한 경우나, 여러 스레드가 동시에 문자열을 조작해야 하는 경우에 사용된다.
위와 같이 String은 불변성을 가지기 때문에 변하지 않는 문자열을 자주 읽어들이는 경우 String을 사용해 주시면 좋은 성능을 기대할 수 있다. 그러나 문자열 추가, 수정, 삭제 등의 연산이 빈번하게 발생하는 알고리즘에 String 클래스를 사용하면 힙 메모리(Heap)에 많은 임시 가비지(Garbage)가 생성되어 힙 메모리 부족으로 어플리케이션 성능에 치명적인 영향을 끼치게 된다.
이를 해결하기 위해 Java에서는 가변(mutable)성을 가지는 StringBuilder와 StringBuffer 클래스를 도입하였다. String 과는 반대로 StringBuilder, StringBuffer는 가변성 가지기 때문에 .append() .delete() 등의 API를 이용하여 동일 객체내에서 문자열을 변경하는 것이 가능하다. 따라서 문자열의 추가, 수정, 삭제가 빈번하게 발생할 경우라면 String 클래스가 아닌 StringBuilder, StringBuffer를 사용하는게 적합하다.