언제 StringBuilder를 사용합니까?


82

StringBuilder의 이점을 이해합니다.

그러나 두 문자열을 연결하려면 StringBuilder없이 수행하는 것이 더 낫다고 가정합니다. 이 올바른지?

어느 시점 (문자열 수)에서 StringBuilder를 사용하는 것이 더 좋습니까?


1
나는 이것이 이전에 다루어 졌다고 믿는다.
Mark Schultheiss


답변:


79

Jeff Atwood가 쓴 The Sad Tragedy of Micro-Optimization Theatre 를 읽어 보시기 바랍니다 .

Simple Concatenation 대 ​​StringBuilder 대 다른 메서드를 처리합니다.

이제 숫자와 그래프를 보려면 링크를 따르십시오.)


예에 +1 ! 이것에 대해 걱정하는 시간은 실제로 중요한 일을하지 않는 시간입니다.
Greg D

8
그러나 당신의 독서는 잘못되었습니다 : 그것은 많은 경우에 중요하지 않습니다. 루핑이 관련되지 않은 경우, 다른 경우에는 중요 할 수 있습니다. A LOT
Peter

1
수락 된 답변의 잘못된 정보 였기 때문에 편집을 제거했습니다.
Peter

2
"대부분의 가비지 수집 된 언어에서 문자열은 변경할 수 없습니다. 두 문자열을 추가하면 두 문자열의 내용이 모두 복사됩니다. 계속 추가하면이 루프가 발생합니다. , 매번 더 많은 메모리가 할당됩니다. 이로 인해 2 차 n2 성능이 직접적으로 저하됩니다. "
Peter

1
왜 이것이 허용되는 대답입니까? 나는 단순히 링크를 드롭하고 "이것을 읽어라"라고 말하는 것이 좋은 대답이라고 생각하지 않습니다
Kolob Canyon

44

그러나 두 문자열을 연결하려면 StringBuilder없이 수행하는 것이 더 낫다고 가정합니다. 이 올바른지?

그것은 참으로 정확합니다, 당신은 왜 정확하게 설명되었는지 찾을 수 있습니다 :

http://www.yoda.arachsys.com/csharp/stringbuilder.html

요약 : 문자열을 한 번에 연결할 수 있다면

var result = a + " " + b  + " " + c + ..

복사본이 만들어 질 때만 StringBuilder를 사용하지 않는 것이 좋습니다 (결과 문자열의 길이는 미리 계산됩니다.);

같은 구조를 위해

var result = a;
result  += " ";
result  += b;
result  += " ";
result  += c;
..

새 객체는 매번 생성되므로 StringBuilder를 고려해야합니다.

마지막으로이 기사는 이러한 경험 규칙을 요약합니다.

경험의 규칙

그렇다면 언제 StringBuilder를 사용해야하고 언제 문자열 연결 연산자를 사용해야합니까?

  • 사소하지 않은 루프에서 연결할 때 반드시 StringBuilder를 사용하십시오. 특히 루프를 통해 반복 할 수있는 반복 횟수를 (컴파일시) 확실하지 않은 경우 특히 그렇습니다. 예를 들어, 한 번에 한 문자 씩 파일을 읽고 + = 연산자를 사용하여 문자열을 작성하는 것은 잠재적으로 성능 자살입니다.

  • 하나의 명령문에서 연결해야하는 모든 것을 (읽기 쉽게) 지정할 수있는 경우 연결 연산자를 반드시 사용하십시오. (연결할 배열이있는 경우 String.Concat을 명시 적으로 호출하거나 구분 기호가 필요한 경우 String.Join을 호출하는 것이 좋습니다.)

  • 리터럴을 여러 개의 연결된 비트로 나누는 것을 두려워하지 마십시오. 결과는 동일합니다. 예를 들어 성능에 영향을주지 않고 긴 리터럴을 여러 줄로 나누면 가독성을 높일 수 있습니다.

  • 다음 연결 반복을 제공하는 것 이외의 다른 연결에 대한 중간 결과가 필요한 경우 StringBuilder가 도움이되지 않습니다. 예를 들어, 이름과 성에서 전체 이름을 만든 다음 마지막에 세 번째 정보 (별명)를 추가하면 StringBuilder를 사용하지 않는 경우에만 혜택을받을 수 있습니다. 다른 목적을 위해 (이름 + 성) 문자열이 필요합니다 (Person 객체를 생성하는 예제에서와 같이).

  • 몇 개의 연결 만 수행 할 수 있고이를 별도의 문으로 수행하려는 경우 어느 방향으로 가는지는 중요하지 않습니다. 어떤 방법이 더 효율적인지는 관련된 문자열의 크기와 연결되는 순서에 따라 달라집니다. 만약 당신이 정말로 그 코드가 성능 병목 현상이라고 생각한다면 프로파일 링하거나 벤치마킹하십시오.


13

System.String은 변경 불가능한 개체입니다. 즉, 콘텐츠를 수정할 때마다 새 문자열이 할당되며 시간과 메모리가 필요합니다. StringBuilder를 사용하면 새 개체를 할당하지 않고 개체의 실제 콘텐츠를 수정할 수 있습니다.

따라서 문자열을 많이 수정해야 할 때 StringBuilder를 사용하십시오.


8

별로 ... 문자열 을 연결 하거나 루프와 같이 연결이 많은 경우 StringBuilder를 사용해야합니다 .


1
그건 틀렸어요. StringBuilder루프 또는 연결이 사양에 대한 성능 문제인 경우에만 사용해야 합니다.
Alex Bagnolini 2009

2
@Alex : 항상 그렇지 않나요? ;) 아니, 심각, 난 항상 루프 내부 연결을위한 StringBuilder에 사용했습니다 ...하지만, 내 루프는 모든이 더 1K 반복 ... @Binary 이상 :로 컴파일해야합니다 일반적으로 string s = "abcd"마지막 일이 그 적어도, 나는 들었지만 변수를 사용하면 Concat 일 가능성이 큽니다.
Bobby

1
사실은 거의 항상 사실이 아닙니다. 나는 항상 문자열 연산자를 사용 a + "hello" + "somethingelse"했고 그것에 대해 걱정할 필요가 없었습니다. 문제가된다면 StringBuilder를 사용하겠습니다. 하지만 처음에는 걱정하지 않았고 글을 쓰는 데 시간을 덜 들였습니다.
Alex Bagnolini 2009

3
- 큰 문자열과 성능에 이익을 절대적으로 없습니다 많은 회씩 연결와 함께.
Konrad Rudolph

1
@Konrad : 성능상의 이점 이 없다고 확신 하십니까? 큰 문자열을 연결할 때마다 많은 양의 데이터가 복사됩니다. 작은 문자열을 연결할 때마다 소량의 데이터 만 복사됩니다.
LukeH 09-12-01

6
  • 루프에서 문자열을 연결하는 경우 일반 문자열 대신 StringBuilder 사용을 고려해야합니다.
  • 단일 연결 인 경우 실행 시간의 차이가 전혀 보이지 않을 수 있습니다.

요점을 증명하는 간단한 테스트 앱은 다음과 같습니다.

class Program
{
    static void Main(string[] args)
    {
        const int testLength = 30000;
        var StartTime = DateTime.Now;

        //TEST 1 - String
        StartTime = DateTime.Now;
        String tString = "test string";
        for (int i = 0; i < testLength; i++)
        {
            tString += i.ToString();
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 2000 ms

        //TEST 2 - StringBuilder
        StartTime = DateTime.Now;
        StringBuilder tSB = new StringBuilder("test string");
        for (int i = 0; i < testLength; i++)
        {
            tSB.Append(i.ToString());
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 4 ms

        Console.ReadLine();
    }
}

결과 :

  • 30,000 회 반복

    • 문자열-2000ms
    • StringBuilder-4ms
  • 1000 회 반복

    • 문자열-2ms
    • StringBuilder-1ms
  • 500 회 반복

    • 문자열-0ms
    • StringBuilder-0ms

5

의역하다

그러면 너는 더도 더도 더도 더도 더도 덜도 안된다. 세 개는 세어야 할 숫자이고 세는 수는 세 개입니다. 네가 세지 말고 두 세도 세지 말라. 단 세 명으로 넘어가는 것 외에는 말이다. 세 번째 숫자 인 세 번째 숫자에 도달하면 안티오크의 성스러운 수류탄을 로브 베스트하십시오

나는 일반적으로 세 개 이상의 문자열을 연결하는 코드 블록에 문자열 작성기를 사용합니다.


Concetanation은 하나의 복사본 만 만듭니다. "Russell"+ ""+ Steen + "."은 문자열 길이를 미리 계산하기 때문에 하나의 복사본 만 만듭니다. 연결을 분할해야 할 때만 빌더에 대해 생각해야합니다
Peter

4

확실한 답은없고 경험의 규칙 만 있습니다. 내 개인 규칙은 다음과 같습니다.

  • 루프에서 연결하는 경우 항상 StringBuilder.
  • 문자열이 크면 항상 StringBuilder.
  • 연결 코드가 깔끔하고 화면에서 읽을 수 있다면 괜찮을 것입니다.
    그렇지 않은 경우 StringBuilder.

나는 이것이 오래된 주제라는 것을 알고 있지만 나는 배우는 것만 알고 있으며 "큰 문자열"이라고 생각하는 것을 알고 싶습니까?
MatthewD

4

그러나 2 개의 문자열을 연결하려면 StringBuilder없이 연결하는 것이 더 좋고 빠르다고 가정합니다. 이 올바른지?

예. 그러나 더 중요한 것은 그러한 상황에서 바닐라를 사용하는 것이 훨씬 더 읽기 쉽다 는 것입니다 String. 반면에 루프에서 사용하는 것은 의미가 있으며 연결만큼 읽을 수도 있습니다.

특정 수의 연결을 임계 값으로 인용하는 경험 규칙을주의해야합니다. 루프 (및 루프에만 해당)에서 사용하는 것은 아마도 유용하고 기억하기 쉽고 더 합리적 일 것입니다.


"나는 특정 수의 연결을 임계 값으로 인용하는 경험적 규칙을 경계 할 것입니다."<this. 또한 상식이 적용된 후 6 개월 후에 코드로 돌아 오는 사람을 생각해보십시오.
Phil Cooper

3

물리적으로 연결 수 (a + b + c ...)를 입력 할 수있는 한 큰 차이는 없습니다. N 제곱 (N = 10에서)은 100X 감속이며, 너무 나쁘지 않아야합니다.

큰 문제는 수백 개의 문자열을 연결할 때입니다. N = 100에서는 10000 배 속도가 느려집니다. 꽤 나쁘다.


3

의견에 영향을받지 않거나 자존심의 싸움이 뒤 따르지 않는 이에 대한 설명을 찾기가 어렵 기 때문에 직접 테스트하기 위해 LINQpad에 약간의 코드를 작성하려고 생각했습니다.

i.ToString ()을 사용하는 대신 작은 크기의 문자열을 사용하면 응답 시간이 변경된다는 것을 발견했습니다 (작은 루프에서 볼 수 있음).

이 테스트는 서로 다른 반복 순서를 사용하여 시간 측정을 현명하게 비교할 수있는 범위로 유지합니다.

마지막에 코드를 복사하여 직접 시도해 볼 수 있습니다 (results.Charts ... Dump ()는 LINQPad 외부에서 작동하지 않습니다).

출력 (X 축 : 테스트 된 반복 횟수, Y 축 : 소요 시간 (틱)) :

반복 순서 : 2, 3, 4, 5, 6, 7, 8, 9, 10 반복 순서 : 2, 3, 4, 5, 6, 7, 8, 9, 10

반복 순서 : 10, 20, 30, 40, 50, 60, 70, 80 반복 순서 : 10, 20, 30, 40, 50, 60, 70, 80

반복 순서 : 100, 200, 300, 400, 500 반복 순서 : 100, 200, 300, 400, 500

코드 (LINQPad 5를 사용하여 작성) :

void Main()
{
    Test(2, 3, 4, 5, 6, 7, 8, 9, 10);
    Test(10, 20, 30, 40, 50, 60, 70, 80);
    Test(100, 200, 300, 400, 500);
}

void Test(params int[] iterationsCounts)
{
    $"Iterations sequence: {string.Join(", ", iterationsCounts)}".Dump();

    int testStringLength = 10;
    RandomStringGenerator.Setup(testStringLength);
    var sw = new System.Diagnostics.Stopwatch();
    var results = new Dictionary<int, TimeSpan[]>();

    // This call before starting to measure time removes initial overhead from first measurement
    RandomStringGenerator.GetRandomString(); 

    foreach (var iterationsCount in iterationsCounts)
    {
        TimeSpan elapsedForString, elapsedForSb;

        // string
        sw.Restart();
        var str = string.Empty;

        for (int i = 0; i < iterationsCount; i++)
        {
            str += RandomStringGenerator.GetRandomString();
        }

        sw.Stop();
        elapsedForString = sw.Elapsed;


        // string builder
        sw.Restart();
        var sb = new StringBuilder(string.Empty);

        for (int i = 0; i < iterationsCount; i++)
        {
            sb.Append(RandomStringGenerator.GetRandomString());
        }

        sw.Stop();
        elapsedForSb = sw.Elapsed;

        results.Add(iterationsCount, new TimeSpan[] { elapsedForString, elapsedForSb });
    }


    // Results
    results.Chart(r => r.Key)
    .AddYSeries(r => r.Value[0].Ticks, LINQPad.Util.SeriesType.Line, "String")
    .AddYSeries(r => r.Value[1].Ticks, LINQPad.Util.SeriesType.Line, "String Builder")
    .DumpInline();
}

static class RandomStringGenerator
{
    static Random r;
    static string[] strings;

    public static void Setup(int testStringLength)
    {
        r = new Random(DateTime.Now.Millisecond);

        strings = new string[10];
        for (int i = 0; i < strings.Length; i++)
        {
            strings[i] = Guid.NewGuid().ToString().Substring(0, testStringLength);
        }
    }

    public static string GetRandomString()
    {
        var indx = r.Next(0, strings.Length);
        return strings[indx];
    }
}

2

사용할 때와 사용하지 않을 때 사이에 미세한 경계가 있다고 생각하지 않습니다. 물론 누군가가 황금 조건을 얻기 위해 광범위한 테스트를 수행하지 않는 한.

나를 위해 2 개의 거대한 문자열을 연결하는 경우 StringBuilder를 사용하지 않을 것입니다. 결정적이지 않은 개수의 루프가있는 경우 루프가 작은 개수 일 수도 있습니다.


실제로 StringBuilder를 사용하여 2 개의 문자열을 연결하는 것은 완전히 잘못된 것이지만 perf와는 아무 관련이 없습니다. 테스트-그것은 단순히 잘못된 것을 사용하는 것입니다.
Marc Gravell

1

단일 연결은 StringBuilder를 사용할 가치가 없습니다. 일반적으로 경험상 5 개의 연결을 사용했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.