언어/C#

C# - 문자열 - 1

tsyang 2021. 2. 2. 02:05

책에는 문화권 및 암호화 관련된 내용이 있지만, 나중에 시간나면 정리하기로 함

 

 

System.String


C#의 String은 참조 타입이다. 따라서 String 타입과 그 내부의 배열은 항상 힙에 할당된다. 또한 변경이 불가능하다. C++ 처럼 특정 문자만 수정하는 등의 행동을 할 수 없다. (StringBuilder를 쓰면 되지만)

 


string s = "Hi" + " " + "there.";

 

또 위처럼 리터럴로 이뤄진 문자들은 컴파일 시점에 모든 문자열들을 연결하여 하나의 단일 문자열인 "Hi there."로 합한다. 따라서 Stringbuilder를 쓰지 않아도 임시 문자열이 생성되지 않는다.


줄 바꿈의 경우에는 System.Environment.NewLine을 써주는게 좋다.

string a = "Hi\r\nthere.";
string b = "Hi" + Environment.NewLine + "there.";

\r\n의 경우 윈도우 환경이냐 유닉스 환경이냐에 따라 다르지만, NewLine의 경우 환경에 맞춰 줄바꿈을 하는 문자를 반환해준다.

 

 

문자열 비교


CLR의 문자열 비교는 내부적으로 문자열의 길이가 동일한지 확인하고 동일하다면 문자를 한 글자씩 비교한다. 만약 문화권을 고려하는 비교를 수행한다면 항상 한 글짜씩 비교하는 과정을 수행한다. (길이가 달라도 같은 문자열로 취급될 수 있기 때문).

 

따라서 문화권을 고려한다면 문자열의 비교 속도가 느려질 수 있다. 그렇기에 프로그램 안에서만 사용하는 문자열을 비교하는 등, 단순히 문자열을 비교만 하면 되는 경우는 StringComparison.Ordinal을 옵션으로 넘겨주면 좋다.

 

string.Equals(strA, strB, StringComparison.Ordinal);

 

문자열 풀링


 

소스 코드를 컴파일 할 때, 컴파일러는 리터럴 문자를 처리하며 이러한 문자열을 메타데이터로 내보낸다. 만약 같은 리터럴 문자열이 여러 번 등장한다면 굳이 메타데이터에 이런 문자들을 여러 번 넣을 필요는 없을 것이다.

 

따라서 C#컴파일러를 비롯한 많은 컴파일러들은 동일 리터럴 문자열을 모듈의 메타데이터에 한 번만 기록한다. 

 

 

문자열 인터닝


 

CLR에는 문자열 인터닝 메커니즘이 존재한다. CLR은 초기화 시에 내부적으로 하나의 해시 테이블을 생성한다. 이 해시 테이블은 문자열을 키로 사용하고 값으로는 힙에 만들어진 string 객체의 참조를 저장한다. 

//해시테이블에 str이 있으면 참조 반환, 없으면 참조를 추가 후 반환
public static string Intern(string str);

//해시테이블에 str이 있으면 참조 반환, 없으면 null 반환
public static string IsInterned(string str);

위의 코드로 해시 테이블에 접근할 수 있다.

 

기본적으로 CLR이 어떤 어셈블리를 로드하면, 로드하는 어셈블리의 메타데이터에 있는 모든 리터럴 문자열들을 이런 메커니즘을 통해 보관한다. 그러나 이것은 추후 해시 테이블 검색시 성능에 나쁜 영향을 미치기 때문에 이러한 메커니즘을 사용하지 않는 플래그도 지정할 수 있다. 

 

그러나 CLR은 필요에 따라 플래그가 지정되어도 문자열을 인터닝할 수 있다. 관련해서 다음의 예를 보자

string s1 = "Hello";
string s2 = "Hello";

Console.WriteLine(object.ReferenceEquals(s1, s2)); //false? true?

ReferenceEquals 메서드는 두 인자가 가리키는 참조가 같다면 true를 반환한다.

 

그렇다면 위 코드에서의 출력은 무엇일까? 코드만 봐서는 s1 ,s2에서 둘 다 새로운 객체를 생성하므로 참조가 다를 것으로 예측해 볼 수 있다. 

 

그러나 CLR이 인터닝 매커니즘을 통해 문자열을 보관했다면 true가 반환될 수 있을 것이다. 위 코드는 CLR v4.5에서 실행하면 true로 나타난다. 이는 플래그로 초기화시 인터닝을 사용하지 않도록 지정하여도 true가 반환될 수 있다.

 

string s1 = string.Intern("Hello");
string s2 = string.Intern("Hello");

Console.WriteLine(object.ReferenceEquals(s1, s2)); //항상 true

따라서 위와 같은 코드처럼 string의 Intern 메서드를 명시적으로 호출하는 경우가 아니라면 인터닝이 되었는지의 여부를 고려하여 코드를 작성해서는 안 된다.


 

또한 이러한 인터닝을 잘 활용하면 문자열 비교를 빠르게 수행할 수 있는데,

//wordList는 interning 되어있다고 가정한다.
public static int NumTimesWordAppearsIntern(string word, string[] wordList)
{
    word = String.Intern(word);
    int count = 0;
    for (int i = 0; i < wordList.Length; ++i)
    {
        if (object.ReferenceEquals(word, wordList[i]))
            ++count;
    }
    return count;
}

이처럼 ReferenceEquals를 사용하면 참조 값을 비교하기때문에 훨씬 빠르다.

 

그러나 비교할 문자열인 wordList가 인터닝 되어 있어야 하고, wordList를 인터닝 하는데에도 오버헤드가 있으므로 wordList를 여러 번 사용해야 하는 응용프로그램에서 유용할 것이다.

'언어 > C#' 카테고리의 다른 글

C# - 델리게이트  (1) 2021.02.21
C# - 배열  (0) 2021.02.10
C# 인터페이스  (0) 2021.01.27
C# 제너릭 - 3  (0) 2021.01.20
C# - 제네릭 2  (0) 2021.01.18