아래 설명은 MSDN의 열받는 번역체를 조금 더 다듬은 내용입니다.
String, StringBuilder 둘 모두 문자열을 다룰 수 있게 도와주지만 세부 구현은 다릅니다. String 은 기본적으로 변경할 수 없는 형식(불변객체)입니다. 즉, String 객체를 수정하는 것처럼 보이는 각 작업들은 사실 새 문자열을 만드는 것과 같습니다.
광범위한 문자열 조작(예: 루프에서 문자열을 여러 번 수정하는 앱)을 수행하는 루틴의 경우 문자열을 반복적으로 수정하면 상당한 성능 저하가 발생할 수 있습니다.
대안은 변경 가능한 문자열 클래스인 사용 StringBuilder입니다.
가변성은 클래스의 인스턴스가 만들어지면 문자를 추가, 제거, 바꾸기 또는 삽입하여 수정할 수 있음을 의미합니다.
StringBuilder 개체는 문자열에 대한 확장을 수용하기 위해 버퍼를 유지 관리합니다.
버퍼에 공간이 남은 경우 : 새 데이터가 버퍼에 추가됩니다.
버퍼가 가득 찬 경우 : 더 큰 크기의 새 버퍼가 할당됩니다. -> 기존 버퍼의 데이터(문자열)을 새 버퍼에 복사합니다.
String은 불변객체라고 볼 수 있다.
const char* str = "hello world";
이와 같은 상수 문자 포인터가
string str = "hello world";
string 구현의 기본 토대가 된다.
그러니까, str의 "hello world"라는 문자열은 바꿀 수가 없는 것이다.
엥? str = "bye bye";
이러면 되잖아? 라는 질문을 할 수 있지만, 사실 이건 문자열 값을 수정하는 것이 아니다. 이런 수정은 아래처럼 동작한다.
const char*
는 포인터 주소는 변경할 수 있지만, 포인터가 가리키는 실제 값은 수정할 수 없다.- 그럼 새로운
const char* temp = "bye bye";
를 할당한다.str = temp;
를 통해 str의 포인터 주소를 변경해준다.
그러니까 위와 같은 과정에서 새로운 메모리를 할당하고, 기존 string에 새로 할당하는 등의 오버헤드가 포함되어 있는 것이다.
참고로 str += "append"
같은 동작을 해도 위와 비슷하게 동작한다.
결국 위에서 설명한 문제를 해결하기 위해 StringBuilder가 취하는 방식은 간단하다.
애초에 문자열을 담아놓을 공간을 크게 잡아놓으면 문제가 없잖아?
이 '공간'이 MSDN에서 말하는 버퍼다. 그리고 이 버퍼는 가변객체인 것 같다.(정확하지 않음)
기본적으로 StringBuilder sb = new StringBuilder();
를 통해 할당해주면 버퍼의 크기(Capacity)는 16이다.
Capacity와 문자열의 길이는 다르다.
예를 들어 Capacity는 사과 상자의 크기이고, sb.Length는 담은 사과의 갯수와 같다.
버퍼가 가득 찰 때까지는 문자열을 조작해도 메모리 재할당이 일어나지 않는다.(string보다 오버헤드가 적다는 뜻)
문자열의 길이가 16을 '초과'하는 순간 Capacity는 약 2배로 늘어난다.
문자열의 길이가 32를 또 초과하면 다시 2배 늘어난다.(반복)
하지만 이 Capacity가 커지는 순간 더 큰 블록의 메모리를 찾아서 재할당하므로 이 때는 오버헤드가 발생한다.
class Program
{
static void Main(string[] args)
{
StringBuilder sb = new StringBuilder();
Console.WriteLine("sb: {0}", sb);
Console.WriteLine("Length: {0}, Capacity: {1}\n", sb.Length, sb.Capacity);
for (int i = 0; i <= 16; i++)
sb.Append("a");
Console.WriteLine("sb: {0}", sb);
Console.WriteLine("Length: {0}, Capacity: {1}\n", sb.Length, sb.Capacity);
for (int i = 0; i <= 15; i++)
sb.Append("b");
Console.WriteLine("sb: {0}", sb);
Console.WriteLine("Length: {0}, Capacity: {1}\n", sb.Length, sb.Capacity);
}
}
sb:
Length: 0, Capacity: 16
sb: aaaaaaaaaaaaaaaaa
Length: 17, Capacity: 32
sb: aaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb
Length: 33, Capacity: 64