안녕하세요 마수리입니다.
오늘은 StringBuidler
에 대한 이야기 2번째 시간입니다.
오늘부터 차차 실제 구현에 대해서 알아볼 생각인데요.
이 시리즈를 천천히 읽어보시면 분명 StringBuilder
에 대해서 많은 것을 알게 되실 것이고 면접에서도 string
관련 질문이 나왔을 때 막힘없이 대답하실 수 있을 것이라고 생각합니다.
자 그럼 StringBuilder
의 내부 구현에 대해서 알아보도록 하겠습니다.
모든 코드를 하나하나 볼 순 없고 가장 많이 쓰는 것부터 몇 차례 나누어서 설명 드려볼까합니다.
기본 생성자를 포함한 몇 개의 생성자를 가져와 보겠습니다.
internal const int DefaultCapacity = 16;
// 기본 생성자 16개의 문자를 받을 수 있도록 초기화 한다.
public StringBuilder()
: this(DefaultCapacity) {
}
// 초기에 수용량을 초기화 할 수 있는 생성자 (이 생성자를 쓰도록 하자 🤞)
public StringBuilder(int capacity)
: this(String.Empty, capacity) {
}
public StringBuilder(String value)
: this(value, DefaultCapacity) {
}
// 최종적으로 불리는 생성자
// 주요 부분만 코드를 가져옴
public StringBuilder(String value, int startIndex, int length, int capacity) {
if (capacity == 0) {
capacity = DefaultCapacity;
}
// 이것이 StringBuilder가 가지고 있는 버퍼
// 버퍼의 크기를 생성자에서 받은 capacity로 초기화 하고 있다.
m_ChunkChars = new char[capacity];
m_ChunkLength = length;
}
기본적으로 가장 많이 사용할 것으로 예상 되는 기본 생성자 StringBuilder()
는 16개의 문자를 받을 수 있게 버퍼를 초기화 하고 있는 것을 확인할 수 있다. 추후에 보게 되겠지만 이 버퍼는 더 많은 양의 문자를 받아야 하는 상황이 되면 계속 새로운 버퍼를 만들게 되어있다 이 말은 코드를 보면 m_ChunkChars
에 new char[]
를 한다는 말인데 결국 heap에 연속된 메모리를 할당하고 최종적으론 GC
를 통해 지우는 부담을 늘릴 것이다.
웬만하면 기본 생성자를 사용하지말고
StringBuilder(int)
등을 사용해 메모리 할당을 최소화 하자
다음으로 알아볼 내용은 AppendLine()이다.
public StringBuilder AppendLine() {
return Append(Environment.NewLine);
}
public StringBuilder AppendLine(string value) {
Append(value);
return Append(Environment.NewLine);
}
보신 것과 같이 AppendLine()
은 Append()
함수를 사용해 구현되었다. Append()
를 살펴보자.
위에서 보았던 AppendLine()
은 Append()
를 감싸 만든 함수였을 뿐이였다. StringBuilder
의 문자열 추가 함수의 핵심 역할은 Append()
가 하고 있었다. Append()
의 코드를 살펴보자. 복잡해 보이는 최적화 등을 제외하고 변수명을 이해하기 쉽게 한글로 바꾼 후 핵심만 볼 수 있도록 수정 한 코드를 봐보도록 합시다.
public StringBuilder Append(String value)
{
Contract.Ensures(Contract.Result<StringBuilder>() != null);
if (value != null) {
int newCurrentIndex = 현재버퍼에_들어있는_문자열.Length + 새로운_문자열.Length
// 현재버퍼에 새로운문자를 추가해도 버퍼의 길이를 초과하지 않니?
if (newCurrentIndex < 현재버퍼.Length)
{
// 현재버퍼에 파라미터로 받은 value를 복사한다.
unsafe {
fixed (char* valuePtr = value)
fixed (char* destPtr = &chunkChars[chunkLength])
string.wstrcpy(destPtr, valuePtr, valueLen);
}
// 현재버퍼에 들어있는 문자열의 길이를 새로운 문자열이 들어간 길이로 업데이트한다.
m_ChunkLength = newCurrentIndex;
}
else
{
// 여기 들어왔다는 소리는 현재 버퍼에 새로운 문자열을 입력할 공간이 부족한단 소리이다.
// AppendHelper() 안에서는 아마 버퍼를 늘려주는 행동을 할 것이다.
AppendHelper(value);
}
}
return this;
}
모든 코드를 하나하나 이해할 필요는 없고 주석에 달린 글이라도 읽고 핵심만 파악해보아도 된다.
핵심은 아주 간단하다. 새로운 문자열이 들어갈 공간이 충분하면 현재 버퍼에 문자열을 복사하고 그렇지 않으면
AppendHelper()
를 호출한다.
AppendHelper()
를 살펴보면 버퍼가 부족할 때 StringBuilder
에서 어떤 동작을 하는지 알 수 있을 것이다.
다음 시간에는 AppendHelper()
함수를 이용해서 StringBuilder
가 버퍼를 늘리는 방법을 살펴보도록 하겠다.