본문 바로가기
Study/C#

[C#] 문자열 String, StringBuilder 차이점(feat 얕은 복사, 깊은 복사)

by 스테디코디스트 2023. 11. 17.
반응형

✍️먼저 아래의 간단한 C# 코드의 출력을 예상해보자.

 string s1 = "AA";
 string s2 = s1;
 string s3 = string.Copy(s1);
 
 Console.WriteLine(s1);
 Console.WriteLine(s2);
 Console.WriteLine(s3);
 Console.WriteLine("-----------------------------------------------");
 
 s1 = "ZZ";
 
 Console.WriteLine(s1);
 Console.WriteLine(s2);
 Console.WriteLine(s3);
 Console.WriteLine("-----------------------------------------------");

 

 

 string은 참조타입이므로, s1을 "ZZ"로 수정한 후 s2는 값을 대입하는 얕은복사이므로 s1과 같은 "ZZ"가 되고, s3는 깊은복사이므로 "AA"가 출력될 것으로 예상했다. 하지만 결과는 아니었다. 결론부터 말하자면, s2,s3 모두 변하지 않고 "AA"로 출력된다. 아래의 출력 결과를 참고하자.

좌) 예상 출력 결과, 우) 실제 출력 결과

 

Why❓ 왜 이런일이 발생할까?

 

 찾아보니 string이 참조타입인 것은 맞지만 다른 참조타입과 다른 특성이 있었다. 그건 바로 불변성(Immutable)이었다. string 타입은 읽기전용의 값이고 따라서 한 번 값을 결정하면 그 값이 변경되지 않는다. 그렇기에 대입시 내용이 바뀌는 것이 아닌 새로운 문자열이 생성되어 변수에 할당되고, 기존의 문자열은 GC에 의해 소멸된다.

 이렇게 string은 수정시에 매번 새로운 객체의 생성과 소멸이 일어나기 때문에 속도면에서 불리할 수 밖에 없다. 따라서 문자열이 자주 변하는 반복문 등에서는 string 대신 System.Text.StringBuilder를 이용할 것이 권장된다.

 

 아래 그림을 보고 위의 과정을 이해해보자.

1) 수정 전, 2) 예상 동작 과정, 3) 실제 동작 과정

 

 처음 그림은 s1을 "ZZ"로 수정하기 전, 값을 복사만한 상태이다. 여기까지는 단순 복사이기 때문에 어려울 것이 없다. 다음그림은 내가 예상했던 동작과정을 보여준다. string은 참조타입이므로 s1을 그대로 복사한 s2는 s1과 같은 주소를 공유하고 있다. 그리고 이후 과정에서 s1에 "ZZ"를 대입했으므로 기존 주소의 "AA"가 "ZZ"로 변경되고, 같은 주소를 공유하는 s2도 "ZZ"를 출력하는 것을 예상했다. 하지만 결과는 예상과는 달랐고, 3번째 그림과 같은 동작과정이 일어났다. s1에 "ZZ"를 대입할 때 string은 불변성을 가지므로 해당 주소의 "AA"가 "ZZ"로 바뀌는 것이 아니고 "ZZ"라는 새로운 객체가 만들어져 해당 주소로 s1이 할당되게 되는 과정을 거친다. 따라서 s2는 그대로 기존 "AA"를 가진 주소를 가지고 있으므로 s2의 출력은 "AA"가 된다.

 

System.Text.StringBuilder

 

 string과 달리 StringBuilder는 변경이 가능한 동적 문자열 클래스이다. 즉, 가변성(mutable)을 가진다. 위의 예제를 StringBuilder를 이용해 다시 알아보자.

StringBuilder sb1 = new StringBuilder("BB");
StringBuilder sb2 = sb1;				// 얕은 복사
StringBuilder sb3 = new StringBuilder().Append(sb1);	// 깊은 복사

Console.WriteLine(sb1);
Console.WriteLine(sb2);		
Console.WriteLine(sb3);
Console.WriteLine("-----------------------------------------------");

// 기존의 값을 지우고 새로운 값 추가
sb1.Clear();
sb1.Append("YY");

Console.WriteLine(sb1);
Console.WriteLine(sb2);
Console.WriteLine(sb3);
Console.WriteLine("-----------------------------------------------");

 

 위와 거의 동일한 코드이다. 하지만 StringBuilder는 string과 달리 가변성을 가지므로 내가 처음에 예상했던 결과가 나오게 된다.

출력 결과

 

⚠️StringBuilder 사용시 주의점

 

1) 검색 메서드가 없다. 검색이 필요하다면 string으로 다시 변환해서 사용해야 한다.

2) 용량을 동적으로 할당하는 과정에서 성능상 좋지 않은 문제가 발생할 수 있다.

3) 최대 용량을 초과하면 예외가 발생한다.