Computer >> 컴퓨터 >  >> 프로그래밍 >> C#

C# StringBuilder 클래스란? 문자열 불변성 문제를 해결하는 방법

C#에서 문자열은 불변(Immutable)입니다

C#에서 string 타입은 불변(immutable)입니다. 즉, 문자열이 한 번 생성되면 내용을 직접 수정할 수 없습니다. 문자열에 어떤 변경을 가하면 항상 변경된 내용을 담은 새로운 문자열 객체가 반환되며, 원본 문자열은 그대로 유지됩니다.

string word = "aaabbbccc";
string newWord = word.Replace('b', 'd');
Console.WriteLine(word);     // aaabbbccc 출력
Console.WriteLine(newWord);  // aaadddccc 출력

위 예제에서 Replace 메서드는 원본인 word를 전혀 건드리지 않고, 'b'를 'd'로 바꾼 새 문자열만 반환합니다.

StringBuilder는 가변(Mutable) 문자열입니다

StringBuilder 클래스는 내용을 자유롭게 수정할 수 있는, 즉 가변(mutable) 문자열을 표현합니다. 불변 문자열을 다루는 string 타입과 달리 내부적으로 완전히 다른 방식으로 구현되어 있습니다.

문자열 객체를 수정할 때마다 새 복사본이 만들어지기 때문에, 반복문 안에서 문자열을 계속 수정하면 성능 저하가 발생할 수 있습니다. 몇 번 정도의 반복이라면 차이가 미미하지만, 대규모 루프에서는 상당한 부담이 됩니다. StringBuilder는 문자를 추가(Append), 삭제(Remove), 교체(Replace), 삽입(Insert)하는 작업을 효율적으로 처리할 수 있는 대안을 제공합니다.

내부 버퍼 동작 원리

StringBuilder는 문자를 담아두기 위한 내부 버퍼(internal buffer)를 유지합니다. 버퍼에 여유 공간이 있으면 새 데이터를 그대로 추가하고, 공간이 부족하면 더 큰 새 버퍼를 할당한 뒤 기존 데이터를 복사하고 새 데이터를 덧붙입니다. 이런 방식 덕분에 매번 새 객체를 생성하는 string보다 훨씬 효율적입니다.

var sb = new StringBuilder();
for (int i = 0; i < 10; i++)
{
    sb.Append("a");
}
Console.WriteLine(sb.ToString()); // aaaaaaaaaa 출력

StringBuilder 객체 생성 방법

StringBuilder는 다양한 생성자 오버로드를 제공합니다.

// 기본 생성
var sb1 = new StringBuilder();

// 초기 용량(capacity)을 지정하여 생성
var sb2 = new StringBuilder(capacity: 10);

// 초기 문자열을 지정하여 생성
var sb3 = new StringBuilder(value: "Hello World");

// 초기 용량과 최대 용량(maxCapacity)을 지정하여 생성
var sb4 = new StringBuilder(capacity: 20, maxCapacity: 50);

// 초기 문자열과 용량을 함께 지정하여 생성
var sb5 = new StringBuilder(value: "Hello", capacity: 20);

// 부분 문자열(substring)과 용량을 지정하여 생성
var sb6 = new StringBuilder(value: "Hello World", startIndex: 0, length: 5, capacity: 20);

Length, Capacity, MaxCapacity의 이해

StringBuilder에는 현재 저장된 문자 수를 나타내는 Length 속성이 있습니다. 문자를 계속 추가하면 Length가 늘어나다가, 현재 담을 수 있는 최대 문자 수를 의미하는 Capacity(용량)에 도달하게 됩니다.

추가된 문자로 인해 길이가 현재 용량을 초과하면, 클래스는 새 메모리를 할당해 용량을 두 배로 늘립니다. 이후 새 문자가 객체에 추가되고 Length 속성도 갱신됩니다.

StringBuilder는 MaxCapacity 속성 값에 도달할 때까지 메모리를 동적으로 계속 확장합니다. MaxCapacity에 도달한 후에는 더 이상 메모리를 할당할 수 없으며, 그 이상 데이터를 추가하려 하면 ArgumentOutOfRangeException 또는 OutOfMemoryException 예외가 발생합니다.

주요 메서드 정리

StringBuilder는 문자열 수정 작업을 손쉽게 해주는 다음과 같은 메서드들을 제공합니다.

  • Append — 지정된 객체의 문자열 표현을 현재 인스턴스 끝에 추가합니다.
  • AppendFormat — 복합 형식 문자열(composite format string)을 처리한 결과를 현재 인스턴스에 추가합니다. 각 형식 항목은 해당 개체 인수의 문자열 표현으로 대체됩니다.
  • AppendJoin — 제공된 객체 배열 요소들의 문자열 표현을 지정된 구분자로 연결한 뒤, 그 결과를 현재 인스턴스에 추가합니다.
  • AppendLine — 현재 StringBuilder 객체 끝에 기본 줄 종결자(line terminator)를 추가합니다.
  • Clear — 현재 StringBuilder 인스턴스에서 모든 문자를 제거합니다.
  • CopyTo — 문자들을 대상 char 범위(span) 또는 char 배열로 복사합니다.
  • EnsureCapacity — 이 인스턴스의 용량이 최소한 지정된 값 이상이 되도록 보장합니다.
  • Equals — 이 인스턴스와 제공된 인스턴스의 문자열 값, Capacity, MaxCapacity가 모두 같으면 true를 반환합니다.
  • GetChunks — 문자 청크(chunk)들을 순회(iterate)할 수 있는 객체를 반환합니다.
  • Insert — 지정된 객체의 문자열 표현을 이 인스턴스의 지정된 문자 위치에 삽입합니다.
  • Remove — 이 인스턴스에서 지정된 범위의 문자를 제거합니다.
  • Replace — 이 인스턴스에서 지정된 문자 또는 문자열이 나타나는 모든 부분을 다른 문자나 문자열로 교체합니다.
  • ToString — 현재 인스턴스를 string으로 변환합니다.

종합 예제

using System;
using System.Text;

class Program
{
    static void Main(string[] args)
    {
        // string은 불변임을 확인
        string word = "aaabbbccc";
        string newWord = word.Replace('b', 'd');
        Console.WriteLine(word);     // aaabbbccc
        Console.WriteLine(newWord);  // aaadddccc

        // StringBuilder로 문자 반복 추가
        var sb = new StringBuilder();
        for (int i = 0; i < 10; i++)
        {
            sb.Append("a");
        }
        Console.WriteLine(sb.ToString()); // aaaaaaaaaa

        // 다양한 생성자 활용
        var sb1 = new StringBuilder();
        var sb2 = new StringBuilder(capacity: 10);
        var sb3 = new StringBuilder(value: "Hello World");
        var sb4 = new StringBuilder(capacity: 20, maxCapacity: 50);
        var sb5 = new StringBuilder(value: "Hello", capacity: 20);
        var sb6 = new StringBuilder(value: "Hello World", startIndex: 0, length: 5, capacity: 20);
    }
}

실행 결과

aaabbbccc
aaadddccc
aaaaaaaaaa

마무리

정리하자면, C#의 string은 불변이므로 반복적인 문자열 수정 작업에서는 매번 새 객체가 생성되어 성능이 떨어질 수 있습니다. 반면 StringBuilder는 내부 버퍼를 활용해 문자열을 효율적으로 추가·수정할 수 있는 가변 문자열 클래스입니다. 특히 루프 안에서 문자열을 조립하거나 대량의 텍스트를 처리할 때 StringBuilder를 사용하면 눈에 띄는 성능 향상을 기대할 수 있습니다.