2010-08-25 12 views

risposta

51

In .NET 2.0 utilizza internamente la classe String. String è immutabile solo all'esterno dello spazio dei nomi System, quindi StringBuilder può farlo.

In .NET 4.0 String è stato modificato per utilizzare char[].

In 2.0 StringBuilder si presentava così

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"; 

Ma in 4.0 Sembra che questo:

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"; 

Così evidentemente è stata cambiata da utilizzando un string di utilizzare un char[].

MODIFICA: risposta aggiornata per riflettere i cambiamenti in .NET 4 (che ho appena scoperto).

+0

Non ne avevo idea .. Pensa che farò un po 'di magia riflettente per soddisfare la mia curiosità :) – cwap

+0

@Brian: per quanto ne so detiene internamente una matrice 'Char', non una' String' (almeno in .NET 4, forse questo è cambiato?) –

+0

@Fredrik - nell'implementazione MS, è davvero una 'stringa' che viene mutata –

7

Non proprio: utilizza il buffer dei caratteri interno. Solo quando la capacità del buffer si esaurisce, allocherà un nuovo buffer. L'operazione di aggiunta verrà semplicemente aggiunta a questo buffer, l'oggetto stringa verrà creato quando il metodo ToString() viene chiamato su di esso; d'ora in poi, è consigliabile per molte concatenazioni di stringhe poiché ogni concatop tradizionale stringa creerebbe una nuova stringa. È anche possibile specificare la capacità iniziale del generatore di stringhe se si ha una vaga idea su di esso per evitare allocazioni multiple.

Modifica: Le persone sottolineano che la mia comprensione è sbagliata. Si prega di ignorare la risposta (Io invece non eliminarlo - si presenterà come una prova della mia ignoranza :-)

+1

Agisce * come se fosse un buffer di caratteri, ma in realtà è un'istanza 'stringa' mutata. Onesto. –

+0

Grazie Marc - Avevo l'impressione che usasse il buffer dei caratteri. Significa che avrebbe un'implementazione nativa per mutare l'oggetto stringa. – VinayC

+0

sicuro, ma è una classe di base del framework. Ha accesso all'implementazione nativa. –

2

Se guardo .NET Reflector a .NET 2 poi mi trovo:

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; 
} 

Quindi è un esempio di stringa mutato ...

EDIT Tranne che in .NET 4 è a char[]

+0

@Richard: grazie per l'EDIT. Non lo sapevo. –

2

Se si desidera visualizzare una delle possibili implementazioni (che è simile a quella fornita con l'implementazione di microsoft fino alla v3.5), è possibile vedere su github the source of the Mono one.

2

ho fatto un piccolo campione per dimostrare come funziona StringBuilder in .NET 4. Il contratto è

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

e questa è un'implementazione molto semplice

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(); 
     } 
    } 
} 

Questo codice non viene filettatura sicuro, non esegue alcuna convalida dell'input e non utilizza la magia interna (non sicura) di System.String. Tuttavia dimostra l'idea alla base della classe StringBuilder.

Alcuni test di unità e codice di esempio completo possono essere trovati a github.

22

La risposta accettata manca il marchio di un miglio.La modifica significativa a 4.in 4.0 non è il passaggio da un valore non sicuro a char[] - è il fatto che StringBuilder è ora in realtà un elenco collegato di istanze StringBuilder.


La ragione di questo cambiamento dovrebbe essere ovvio: oggi non c'è mai la necessità di riallocare il buffer (un'operazione costosa, dal momento che, insieme con l'allocazione di più memoria, si hanno anche per copiare tutti i contenuti da il vecchio buffer a quello nuovo).

Questo significa chiamare ToString() è ora leggermente più lento, dal momento che la stringa finale deve essere calcolata, ma facendo un gran numero di operazioni di Append() è ora significativamente più veloce. Ciò si adatta al tipico caso d'uso per StringBuilder: molte chiamate a Append(), seguite da una singola chiamata a ToString().


È possibile trovare parametri di riferimento here. La conclusione? La nuova lista collegata StringBuilder utilizza marginalmente più memoria, ma è significativamente più veloce per il tipico caso d'uso.

Problemi correlati