멈추지 않고 끈질기게
[C#] StringBuilder 본문
※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.
※ 해당 포스팅은 .Net 5.0 버전을 기준으로 작성되었습니다.
이번 포스팅에서는 문자열을 다룰 수 있는 StringBuilder 클래스에 대해 알아보겠습니다.
1. StringBuilder의 선언 및 초기화
StringBuilder 클래스는 System.Text 네임스페이스에 정의되어 있으며, 초기화 방식은 다음과 같습니다.
//빈 StringBuilder 생성
StringBuilder sb1 = new StringBuilder();
//string 변수로 value 설정하며 초기화
StringBuilder sb2 = new StringBuilder("StringBuilder");
//int 값으로 capacity 값을 설정하며 초기화
StringBuilder sb3 = new StringBuilder(10);
//int 값으로 capacity와 MaxCapacity 값을 설정하며 초기화
StringBuilder sb4 = new StringBuilder(10, 100);
//value와 capacity 값을 설정하며 초기화
StringBuilder sb5 = new StringBuilder("테스트 중", 100);
//value를 시작 인덱스(3)부터 길이(6)만큼 뗀 substring으로 초기화
//capacity는 20으로 초기화
StringBuilder sb6 = new StringBuilder("Hello World!", 3, 6, 20);
//콘솔 출력
Console.WriteLine(sb6);
Console.WriteLine(sb6.ToString());
//string 변수와 + 연산
string txt = "Hel" + sb6 + "ld!";
Console.WriteLine(txt);
StringBuilder의 생성자는 6가지가 있습니다. 매개변수 없이 초기화할 수도 있고, string 값을 매개변수로 주어 인스턴스의 텍스트값을 설정할수도 있습니다. 또한 int 값을 매개변수로 주어 capacity 및 max capacity 값을 설정할 수도 있습니다(해당 속성에 대한 내용은 아래에서 설명하겠습니다). string 매개변수와 시작 인덱스, 길이 값을 넘겨주어 매개변수 텍스트의 substring으로 초기화 할 수도 있습니다.
이렇게 만들어진 StringBuilder 클래스의 인스턴스는 마치 string 변수처럼 사용할 수 있습니다. Console.WriteLine() 안에 그냥 클래스 인스턴스를 넘겨주어도 텍스트 값이 출력되며, 문자열과 + 연산으로 합친 값을 string 변수에 넣어줄 수도 있습니다(다만 좋은 방법은 아니며, 이는 밑에서 설명하겠습니다).
2. StringBuilder의 속성
StringBuilder 클래스의 속성값은 다음과 같습니다.
StringBuilder sb = new StringBuilder("abcd");
//현재 텍스트 길이
Console.WriteLine(sb.Length);
//인스턴스의 현재 capacity
Console.WriteLine(sb.Capacity);
//StrigBuilder의 최대 capacity
Console.WriteLine(sb.MaxCapacity);
Length 값은 string의 그것과 동일한 속성으로, 현재 StringBuilder 인스턴스의 텍스트의 길이를 반환합니다.
Capacity 값은 StringBuilder 인스턴스가 현재 저장할 수 있는 최대 텍스트 길이를 반환합니다. List와 마찬가지로 StringBuilder 클래스는 내부적으로 고정된 저장공간을 가지고 있으며, 텍스트 추가로 이를 넘어서는 순간 추가로 공간을 할당한 후 기존 영역에 연결하는 방식입니다. 생성 시에 따로 초기화하지 않았다면 디폴트 값은 16에서 시작합니다. 다뤄야 할 문자열의 크기가 클 것으로 예상된다면, 해당 값을 처음부터 큰 값으로 초기화하여 capacity를 늘리는 작업이 빈번하게 생기는 것을 막을 수 있습니다.
MaxCapacity 값은 StringBuilder 인스턴스의 capacity가 최대 얼마까지 늘어날 수 있는지를 나타냅니다. 생성 시 따로 초기화하지 않았다면 디폴트 값은 2,147,483,647(int형의 최대값)로, 웬만해서는 넘어갈 일이 없는 정도 크기입니다.
3. StringBuilder의 함수
StringBuilder 클래스의 주요 함수 몇가지에 대해 알아보겠습니다.
StringBuilder sb1 = new StringBuilder("Hello");
//텍스트 뒤에 문자열 추가
sb1.Append(" World!");
Console.WriteLine($"현재 텍스트: {sb1}");
//기존 텍스트에 문자열 삽입
sb1.Insert(0, "Hi ");
Console.WriteLine($"현재 텍스트: {sb1}");
//문자열 제거
sb1.Remove(3, 6);
Console.WriteLine($"현재 텍스트: {sb1}");
//문자열 교체
sb1.Replace("World", "Everyone");
Console.WriteLine($"현재 텍스트: {sb1}");
//인스턴스 초기화
sb1.Clear();
Console.WriteLine($"현재 텍스트: {sb1}");
Append() 함수를 통해 해당 StringBuilder 인스턴스의 텍스트 뒤에 문자열을 추가할 수 있습니다. string으로 치면 += 연산과 비슷하지만, 내부적으로 추가되는 방식이 string과는 다르며 이는 밑에서 설명하겠습니다.
Insert(), Remove(), Replace() 함수 등은 string의 그것과 비슷합니다. Insert() 함수를 통해 원하는 위치에 문자열을 삽입할 수 있으며, Remove() 함수를 통해 원하는 인덱스부터 원하는 길이만큼의 문자열을 제거할 수 있습니다. Replace() 함수를 통해 특정 문자(char)를 다른 문자로, 혹은 특정 문자열을 다른 문자열로 교체할 수 있습니다(첫번째 매개변수와 동일한 모든 값이 교체됩니다).
Clear() 함수로 텍스트를 빈 문자열로 초기화할 수도 있습니다. 다만 List와 마찬가지로 해당 함수로 내용을 초기화하여도 변화된 capacity값은 유지되므로, 큰 문자열을 저장했던 인스턴스를 초기화하고 작은 문자열을 저장하는데 사용하는 것은 부적절합니다.
4. string vs StringBuilder
그렇다면 굳이 StringBuilder 클래스를 사용해야 하는 이유는 무엇일까요?
class Program
{
static void Main(string[] args)
{
string txt = "Hello World!";
long stringTime = CheckTime(() => {
for (int i = 0; i < 200000; i++)
txt += "!";
});
StringBuilder sb = new StringBuilder("Hello World!");
long sbTime = CheckTime(() => {
for (int i = 0; i < 200000; i++)
sb.Append("!");
});
Console.WriteLine($"string 소요 시간: {stringTime}ms");
Console.WriteLine($"StringBuilder 소요 시간: {sbTime}ms");
}
static long CheckTime(Action action)
{
Stopwatch watch = new Stopwatch();
watch.Start();
action();
watch.Stop();
return watch.ElapsedMilliseconds;
}
}
string과 StringBuilder를 각각 "Hello Wrold" 문자열로 초기화한 뒤, 뒤에 "!"를 더하는 작업을 20만번씩 반복하는 과정의 시간을 측정하였습니다. 보다시피 string은 약 3초 정도 걸렸지만, StringBuilder쪽은 ms 단위로는 아예 측정도 되지 않을 정도로 빠르게 끝난 것을 알 수 있습니다.
이러한 현상이 나타나는 이유는 지난 가비지컬렉터 포스팅에서 다룬 내용대로 string 변수에 문자열을 붙이는 과정에서 매번 복사가 일어나기 때문입니다. 그저 느낌표 하나를 추가하는 작업처럼 보이지만, string 변수의 경우 원본과 느낌표를 붙인 복사본을 생성하여 변수에 할당하는 방식이기 때문에 20만번동안 매번 복사가 일어나게 됩니다. 따라서 속도가 느릴 뿐만 아니라 가비지도 많이 생성하게 됩니다. 반면 StringBuilder의 경우 Append() 함수를 통해 문자열을 추가할 경우, 우리가 생각하던 원본에 추가되는 방식이기 때문에 매번 복사가 일어나진 않습니다. capacity를 넘어설 때마다 늘리는 과정이 필요하긴 하지만, 매번 원본을 통째로 복사하는 string에 비해 문자열을 추가하는 작업은 비교가 안되게 빠릅니다.
다만 StringBuilder가 무조건 string의 상위호환이란 것은 아닙니다. 우선 StringBuilder를 사용하려면 따로 System.Text 네임스페이스를 참조해야 하며, 짧은 크기의 문자열을 다루는 경우에는 string에 비해 딱히 유의미한 성능 향상을 기대할 수 없습니다. 또한 StringBuilder 클래스는 IndexOf(), LastIndexOf()와 같은 특정 문자를 찾는 기능을 지원하지 않기 때문에, 문자열 내에서 검색하는 작업에는 부적절합니다. 따라서 StringBuilder는 길이가 긴 문자열(혹은 런타임에 크기가 정해지는 문자열)을 빈번하게 수정해야 하는 경우에 사용하는 것이 바람직하겠습니다.
'C#' 카테고리의 다른 글
[C#] 열거형(Enum) (0) | 2023.06.28 |
---|---|
[C#] 형변환 함수 / 연산자 (0) | 2023.06.01 |
[C#] 가비지 컬렉터(Garbage Collector) (0) | 2023.02.20 |
[C#] Linq 구문 (0) | 2023.02.18 |
[C#] 대리자(Delegate) (0) | 2023.02.09 |