2010-08-25 19 views
49

Lớp StringBuilder được triển khai như thế nào? Liệu nó có tạo nội bộ các đối tượng chuỗi mới mỗi lần chúng ta thêm vào không?Lớp StringBuilder được triển khai như thế nào? Liệu nó có tạo nội bộ các đối tượng chuỗi mới mỗi lần chúng ta thêm vào không?

+3

+1 Tôi cũng đã học được điều gì đó mới từ câu hỏi này :) –

+1

@Brian Rasmussen chờ câu trả lời của Jon Skeet. Tôi đặt cược nó sẽ là rất lớn và đầy đủ các công cụ mới để tìm hiểu;) – prostynick

+0

Reflector tiết lộ tất cả. –

Trả lời

51

Trong .NET 2.0 nó sử dụng lớp String nội bộ. String chỉ là không thay đổi bên ngoài không gian tên System, vì vậy StringBuilder có thể làm điều đó.

Trong .NET 4.0 String đã được thay đổi để sử dụng char[].

Trong 2,0 StringBuilder trông như thế này

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

Nhưng trong 4,0 nó trông như thế này:

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

Vì vậy, rõ ràng nó đã được thay đổi từ việc sử dụng một string để sử dụng một char[].

EDIT: Đã cập nhật câu trả lời để phản ánh các thay đổi trong .NET 4 (mà tôi chỉ mới phát hiện ra).

+0

Không có ý tưởng .. Hãy suy nghĩ Im sẽ làm một số ma thuật phản xạ để đáp ứng sự tò mò của tôi :) – cwap

+0

@Brian: theo như tôi biết nó giữ một mảng 'Char' nội bộ, không phải là một' String' (ít nhất là trong .NET 4, có lẽ điều này đã thay đổi?) –

+0

@ Fredrik - trong việc triển khai MS, nó thực sự là một chuỗi 'bị biến đổi –

7

Không thực sự - nó sử dụng bộ đệm ký tự bên trong. Chỉ khi dung lượng bộ đệm bị cạn kiệt, nó sẽ cấp phát bộ đệm mới. Nối thêm hoạt động sẽ đơn giản thêm vào bộ đệm này, đối tượng chuỗi sẽ được tạo ra khi phương thức ToString() được gọi trên nó - từ nay trở đi, nó được khuyến khích cho nhiều chuỗi nối vì mỗi chuỗi concat truyền thống sẽ tạo chuỗi mới. Bạn cũng có thể chỉ định dung lượng ban đầu cho trình tạo chuỗi nếu bạn có ý tưởng thô về nó để tránh phân bổ nhiều lần.

Chỉnh sửa: Mọi người đang chỉ ra rằng sự hiểu biết của tôi là sai. Vui lòng bỏ qua câu trả lời (Tôi không xóa nó - nó sẽ đứng như một bằng chứng về sự thiếu hiểu biết của tôi :-)

+1

Nó hoạt động * như thể * nó là một bộ đệm ký tự, nhưng nó thực sự là một thể hiện 'chuỗi' bị biến đổi. Thật thà. –

+0

Cảm ơn Marc - Tôi đã bị ấn tượng rằng nó sử dụng bộ đệm ký tự. Nó có nghĩa là nó sẽ có một số thực hiện bản địa để thay đổi đối tượng chuỗi. – VinayC

+0

chắc chắn, nhưng nó là một lớp khung cốt lõi. Nó có quyền truy cập vào cài đặt gốc. –

2

Nếu tôi nhìn vào NET Reflector tại NET 2 sau đó tôi sẽ tìm thấy điều này:

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

Vì vậy, nó là một ví dụ chuỗi biến đổi ...

EDIT Ngoại trừ trong .NET 4 nó là a char[]

+0

@Richard: cảm ơn EDIT. Không biết thực tế đó. –

2

Nếu bạn muốn xem một trong những triển khai có thể xảy ra (Điều này tương tự như việc triển khai thực hiện microsoft đến v3.5), bạn có thể thấy the source of the Mono one trên github.

2

Tôi đã thực hiện một mẫu nhỏ để chứng minh như thế nào StringBuilder làm việc trong .NET 4. Hợp đồng là

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

Và đây là một thực hiện rất cơ bản

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

Mã này được không thread- an toàn, không thực hiện bất kỳ xác thực đầu vào nào và không sử dụng ma thuật bên trong (không an toàn) của System.String. Tuy nhiên nó thể hiện ý tưởng đằng sau lớp StringBuilder.

Một số kiểm tra đơn vị và mã mẫu đầy đủ có thể được tìm thấy tại github.

22

Câu trả lời được chấp nhận đã bỏ lỡ dấu mốc một dặm.Thay đổi đáng kể đối với StringBuilder trong 4.0 không phải là thay đổi từ một không an toàn string thành char[] - thực tế là StringBuilderhiện tại là danh sách liên kết gồm StringBuilder trường hợp.


Lý do cho sự thay đổi này nên được rõ ràng: hiện nay có bao giờ là một nhu cầu để phân bổ lại bộ đệm (một hoạt động tốn kém, từ đó, cùng với việc phân bổ bộ nhớ hơn, bạn cũng phải sao chép tất cả các nội dung từ bộ đệm cũ sang bộ đệm mới).

Điều này có nghĩa là gọi ToString() hiện chậm hơn một chút, vì chuỗi cuối cùng cần được tính toán, nhưng thực hiện số lượng lớn các hoạt động Append() hiện tại là đáng kể nhanh hơn. Điều này phù hợp với trường hợp sử dụng thông thường cho StringBuilder: nhiều cuộc gọi đến Append(), theo sau là một cuộc gọi đến ToString().


Bạn có thể tìm thấy điểm chuẩn here. Kết luận? Danh sách liên kết mới StringBuilder sử dụng nhiều bộ nhớ hơn, nhưng nhanh hơn đáng kể đối với trường hợp sử dụng điển hình.

Các vấn đề liên quan