Как реализован класс StringBuilder?Создает ли он внутри себя новые строковые объекты каждый раз, когда мы добавляем? - PullRequest
50 голосов
/ 25 августа 2010

Как реализован класс StringBuilder? Создает ли он внутри себя новые строковые объекты каждый раз, когда мы добавляем?

Ответы [ 6 ]

53 голосов
/ 25 августа 2010

В .NET 2.0 он использует класс String для внутреннего использования.String является неизменным только за пределами пространства имен System, поэтому StringBuilder может сделать это.

В .NET 4.0 String был изменен для использования char[].

В 2.0 StringBuilder выглядело так

public sealed class StringBuilder : ISerializable
{
    // Fields
    private const string CapacityField = "Capacity";
    internal const int DefaultCapacity = 0x10;
    internal IntPtr m_currentThread;
    internal int m_MaxCapacity;
    internal volatile string m_StringValue; // HERE ----------------------
    private const string MaxCapacityField = "m_MaxCapacity";
    private const string StringValueField = "m_StringValue";
    private const string ThreadIDField = "m_currentThread";

Но в 4.0 это выглядело так:

public sealed class StringBuilder : ISerializable
{
    // Fields
    private const string CapacityField = "Capacity";
    internal const int DefaultCapacity = 0x10;
    internal char[] m_ChunkChars; // HERE --------------------------------
    internal int m_ChunkLength;
    internal int m_ChunkOffset;
    internal StringBuilder m_ChunkPrevious;
    internal int m_MaxCapacity;
    private const string MaxCapacityField = "m_MaxCapacity";
    internal const int MaxChunkSize = 0x1f40;
    private const string StringValueField = "m_StringValue";
    private const string ThreadIDField = "m_currentThread";

Итак, очевидно, это было изменено с использованием string к использованию char[].

EDIT: обновленный ответ для отражения изменений в .NET 4 (которые я только что обнаружил).

28 голосов
/ 09 апреля 2012

Принятый ответ не попадает на милю. Значительное изменение StringBuilder в 4.0 - это не изменение с небезопасного string на char[] - это факт, что StringBuilder является , теперь фактически является связанным списком StringBuilder экземпляров.


Причина этого изменения должна быть очевидна: теперь нет необходимости перераспределять буфер (дорогостоящая операция, поскольку наряду с выделением большего объема памяти вам также необходимо скопировать все содержимое из старого буфера на новый) .

Это означает, что вызов ToString() теперь немного медленнее, поскольку необходимо вычислить конечную строку, но выполнение большого количества операций Append() теперь значительно быстрее. Это соответствует типичному сценарию использования для StringBuilder: много звонков на Append(), затем один вызов на ToString().


Вы можете найти тесты здесь . Вывод? Новый связанный список StringBuilder использует немного больше памяти, но значительно быстрее для типичного варианта использования.

7 голосов
/ 25 августа 2010

Не совсем - он использует внутренний символьный буфер.Только когда емкость буфера будет исчерпана, он выделит новый буфер.Операция Append просто добавит в этот буфер, строковый объект будет создан при вызове метода ToString () для него - впредь его рекомендуется использовать для многих конкатенаций строк, так как каждая традиционная операция конкатата строк создаст новую строку.Вы также можете указать начальную емкость для строителя строк, если у вас есть приблизительное представление об этом, чтобы избежать многократного выделения.

Редактировать : Люди указывают, что мое понимание неверно. Пожалуйста, проигнорируйте ответ (я скорее не удаляю его - это будет доказательством моего невежества: -)

3 голосов
/ 31 декабря 2011

Я сделал небольшой пример, чтобы продемонстрировать, как StringBuilder работает в .NET 4. Контракт

public interface ISimpleStringBuilder
{
    ISimpleStringBuilder Append(string value);
    ISimpleStringBuilder Clear();
    int Lenght { get; }
    int Capacity { get; }
}

И это очень базовая реализация

public class SimpleStringBuilder : ISimpleStringBuilder
{
    public const int DefaultCapacity = 32;

    private char[] _internalBuffer;

    public int Lenght { get; private set; }
    public int Capacity { get; private set; }

    public SimpleStringBuilder(int capacity)
    {
        Capacity = capacity;
        _internalBuffer = new char[capacity];
        Lenght = 0;
    }

    public SimpleStringBuilder() : this(DefaultCapacity) { }

    public ISimpleStringBuilder Append(string value)
    {
        char[] data = value.ToCharArray();

        //check if space is available for additional data
        InternalEnsureCapacity(data.Length);

        foreach (char t in data)
        {
            _internalBuffer[Lenght] = t;
            Lenght++;
        }

        return this;
    }

    public ISimpleStringBuilder Clear()
    {
        _internalBuffer = new char[Capacity];
        Lenght = 0;
        return this;
    }

    public override string ToString()
    {
        //use only non-null ('\0') characters
        var tmp = new char[Lenght];
        for (int i = 0; i < Lenght; i++)
        {
            tmp[i] = _internalBuffer[i];
        }
        return new string(tmp);
    }

    private void InternalExpandBuffer()
    {
        //double capacity by default
        Capacity *= 2;

        //copy to new array
        var tmpBuffer = new char[Capacity];
        for (int i = 0; i < _internalBuffer.Length; i++)
        {
            char c = _internalBuffer[i];
            tmpBuffer[i] = c;
        }
        _internalBuffer = tmpBuffer;
    }

    private void InternalEnsureCapacity(int additionalLenghtRequired)
    {
        while (Lenght + additionalLenghtRequired > Capacity)
        {
            //not enough space in the current buffer    
            //double capacity
            InternalExpandBuffer();
        }
    }
}

Этот кодне потокобезопасен, не выполняет никакой проверки ввода и не использует внутреннюю (небезопасную) магию System.String.Однако он демонстрирует идею, лежащую в основе класса StringBuilder.

Некоторые модульные тесты и полный пример кода можно найти по адресу github .

2 голосов
/ 25 августа 2010

Если вы хотите увидеть одну из возможных реализаций (которая похожа на ту, что поставляется с реализацией Microsoft до версии 3.5), вы можете увидеть источник Mono на github.

2 голосов
/ 25 августа 2010

Если я посмотрю на .NET Reflector на .NET 2, то найду следующее:

public StringBuilder Append(string value)
{
    if (value != null)
    {
        string stringValue = this.m_StringValue;
        IntPtr currentThread = Thread.InternalGetCurrentThread();
        if (this.m_currentThread != currentThread)
        {
            stringValue = string.GetStringForStringBuilder(stringValue, stringValue.Capacity);
        }
        int length = stringValue.Length;
        int requiredLength = length + value.Length;
        if (this.NeedsAllocation(stringValue, requiredLength))
        {
            string newString = this.GetNewString(stringValue, requiredLength);
            newString.AppendInPlace(value, length);
            this.ReplaceString(currentThread, newString);
        }
        else
        {
            stringValue.AppendInPlace(value, length);
            this.ReplaceString(currentThread, stringValue);
        }
    }
    return this;
}

Так что это измененный экземпляр строки ...

РЕДАКТИРОВАТЬ За исключением .NET 4 это char[]

...