기본 패턴
var sb = new StringBuilder(256); // 초기 capacity 지정
sb.Append("이름: ").AppendLine("Mina");
sb.AppendFormat("점수: {0:N0}점", 1200);
sb.Insert(0, "=== 결과 ===\n");
sb.Replace("Mina", "민아");
string result = sb.ToString();설명
string 불변성과 += 연결의 비용
C#에서 string은 불변(immutable) 타입입니다. +=로 문자열을 연결할 때마다 기존 문자열을 수정하는 것이 아니라 새로운 string 객체를 힙에 할당하고 두 문자열을 복사합니다. 루프 안에서 이 작업을 반복하면 복사량이 이전 단계의 길이만큼 누적되어 O(n²) 비용이 발생합니다.
// ❌ 루프마다 새 string 객체 생성 → O(n²) 복사 비용
string result = "";
for (int i = 0; i < 1000; i++)
result += $"item{i},";
// ✅ StringBuilder — 내부 버퍼 재사용 → O(n)
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
sb.Append("item").Append(i).Append(',');
string result = sb.ToString();StringBuilder 내부 동작 — 가변 버퍼와 capacity
StringBuilder는 내부에 가변 char 배열(버퍼) 을 유지합니다. 기본 capacity는 16자이며, 버퍼가 가득 차면 두 배 크기로 자동 확장합니다. 예상 크기를 미리 알고 있다면 생성 시 capacity를 지정해 재할당 횟수를 최소화할 수 있습니다.
var sb1 = new StringBuilder(); // capacity = 16 (기본값)
var sb2 = new StringBuilder(512); // capacity = 512 (재할당 최소화)
var sb3 = new StringBuilder("초기값", 128); // 초기 문자열 + capacity
Console.WriteLine(sb2.Capacity); // 512
Console.WriteLine(sb2.Length); // 0 (현재 문자 수)주요 메서드
Append 와 AppendLine 은 가장 자주 쓰는 메서드로, 각각 문자열과 줄바꿈을 포함한 문자열을 버퍼 끝에 추가합니다. AppendFormat 은 string.Format과 동일한 서식 지정자를 지원합니다. Insert 는 지정 위치에, Remove 는 지정 범위를 삭제하며, Replace 는 버퍼 전체에서 특정 문자열을 치환합니다. Clear 는 버퍼를 비우면서 인스턴스를 재사용할 수 있게 합니다.
var sb = new StringBuilder();
sb.Append("Hello"); // "Hello"
sb.AppendLine(", World!"); // "Hello, World!\n"
sb.AppendFormat("값: {0:N0}", 1200); // 서식 지정 추가
sb.Insert(0, "▶ "); // 맨 앞에 삽입
sb.Remove(0, 2); // 인덱스 0부터 2자 삭제
sb.Replace("World", "C#"); // 전체 치환
sb.Clear(); // 버퍼 초기화 (인스턴스 재사용)메서드 대부분이 StringBuilder 자신을 반환하므로 체이닝이 가능합니다.
string result = new StringBuilder()
.Append("이름: ")
.AppendLine("민아")
.AppendFormat("점수: {0}점", 1200)
.ToString();ToString() — 최종 string 변환
StringBuilder의 버퍼를 실제 string으로 변환하는 것은 ToString() 한 번만 호출합니다. 중간에 반복 호출하면 매번 새 string이 생성되어 StringBuilder를 쓰는 이점이 줄어듭니다.
var sb = new StringBuilder();
// ... 여러 Append 작업 ...
string final = sb.ToString(); // ✅ 마지막에 한 번만 호출언제 StringBuilder를 써야 하나
루프 안에서 수십 개 이상의 문자열을 조립하거나, 크기가 예측하기 어려운 대용량 텍스트를 생성하거나, 동적으로 SQL·HTML 같은 쿼리/마크업을 빌드할 때 효과적입니다. 반대로 단순히 두세 개를 이어 붙이는 경우라면 **문자열 보간($"")**이 더 간결하고 컴파일러가 최적화해 줍니다.
빠른 정리
| 항목 | string | StringBuilder |
|---|---|---|
| 가변성 | 불변 (immutable) | 가변 (mutable) |
+= 연결 비용 | 매번 새 객체 할당 (O(n²)) | 내부 버퍼 확장 (O(n)) |
| 메모리 할당 | 연결마다 힙 할당 | 재할당 최소화 |
| 적합한 상황 | 소수 연결, 리터럴 값 | 루프 조립, 대용량 텍스트 |
| 최종 변환 | 이미 string | ToString() 한 번 호출 |
| 재사용 | 불가 (새 변수 필요) | Clear() 후 재사용 가능 |
주의할 점
단순히 두세 개를 이어 붙이는 경우에는 **문자열 보간($"")**이 더 읽기 쉽고 컴파일러가 최적화합니다. StringBuilder가 진짜 효과를 발휘하는 것은 루프 안에서 수십 개 이상을 조립하거나 크기가 클 때입니다.
Clear() 후 인스턴스를 재사용하면 새 StringBuilder를 매번 생성하는 비용을 아낄 수 있습니다. 예상 크기를 알고 있다면 new StringBuilder(capacity)로 초기 용량을 지정해 내부 버퍼 재할당 횟수를 줄이세요.
참고 링크
1 sources