2009-07-04 10 views
7

Po krótkim wyglądzie za pomocą Reflektora, wygląda na to, że String.Substring() przydziela pamięć dla każdego podłańcucha. Czy mam rację, że tak właśnie jest? Myślałem, że to nie będzie konieczne, ponieważ struny są niezmienne.Dlaczego .NET tworzy nowe podłańcuchy, zamiast wskazywać istniejące łańcuchy?

Moim podstawowym celem było stworzenie metody rozszerzenia IEnumerable<string> Split(this String, Char), która nie przydzieli dodatkowej pamięci.

+0

Nie myślałem o tym bardzo ciężko, lub spojrzałem na implementację StringBuildera z Reflectorem, ale czy działałaby metoda IEnumerable Split (ta metoda StringBuilder, Char)? – Domenic

+0

Jeśli ciąg.Podłańcuch() nie przydziela nowej pamięci, ciąg znaków nie będzie niezmienny –

Odpowiedz

22

Jedną z przyczyn, dla których większość języków z niezmiennymi łańcuchami tworzy nowe podłańcuchy zamiast odwoływania się do istniejących łańcuchów, jest to, ponieważ wpłynie to na odśmiecanie później tych łańcuchów.

Co się stanie, jeśli łańcuch zostanie użyty do jego podłańcuchu, ale wtedy większy ciąg stanie się nieosiągalny (za wyjątkiem podłańcucha). Większy ciąg będzie nieodkładalny, ponieważ spowoduje to unieważnienie podłańcucha. To, co wydawało się dobrym sposobem na oszczędzanie pamięci w krótkim czasie, w długim okresie staje się przeciekiem pamięci.

+1

Myślałem, że główny powód był w odniesieniu do algorytmów w ciągu ciągów. Jeśli możesz bezpiecznie założyć, że ciąg nigdy się nie zmieni, możesz przesłać referencje do niego bezpiecznie, a także będzie on z natury bezpieczny dla wątków. Sądzę, że to wiąże się również ze zbiorem śmieci. – Spence

+1

@Spence - to powód do niezmienności. Nie jest to powód do unikania współużytkowania buforów między łańcuchami. Gdy masz niezmienność i GC, możesz łatwo zaimplementować współużytkowane bufory za kulisami bez łamania bezpieczeństwa wątku lub istniejących algorytmów. –

2

Niemożliwe bez szturchania wewnątrz .net przy użyciu klas String. Musiałbyś przekazać odniesienia do tablicy, która była zmienna i upewnić się, że nikt się nie spieprzył.

. Net utworzy nowy ciąg za każdym razem, gdy go poprosisz. Jedynym wyjątkiem są internowane łańcuchy, które są tworzone przez kompilator (i mogą być wykonywane przez ciebie), które są umieszczane w pamięci raz, a następnie ustalane są wskaźniki dla ciągu znaków dla pamięci i wydajności.

0

Ponieważ ciągi są niezmienne w .NET, każda operacja łańcuchowa, która powoduje nowy obiekt łańcucha, przydzieli nowy blok pamięci dla zawartości ciągu.

Teoretycznie możliwe byłoby ponowne użycie pamięci podczas wyodrębniania podłańcucha, ale to bardzo utrudniałoby zbieranie pamięci: co zrobić, jeśli oryginalny ciąg jest zbierany na śmieci? Co stanie się z podciąganym fragmentem?

Oczywiście nic nie stoi na przeszkodzie zespołowi .NET BCL w zmianie tego zachowania w przyszłych wersjach .NET. Nie miałoby to żadnego wpływu na istniejący kod.

+6

Łańcuch Javy faktycznie robi to w ten sposób: Podciągi są jedynie wskaźnikami w oryginalnym łańcuchu. Jednakże oznacza to również, że gdy podejmiemy 200-znakowy łańcuch o długości 200 MiB, ciąg 200 MiB będzie zawsze znajdował się w pamięci, o ile mały podciąg nie jest zbędny. – Joey

+0

Myślę, że może to wpłynąć na istniejący kod, biorąc pod uwagę, że jest zaprojektowany wokół tego zachowania. Jeśli ludzie zakładają, że interakcja z ich ciągiem uniemożliwi jej duplikowanie i zachowanie to zostanie zatrzymane, może to spowodować, że działające aplikacje zatrzymają się z powodu wyjątków pamięci. – Spence

+0

Jak można zaprojektować wokół tego zachowania? Ze względu na niezmienność łańcuchów, naprawdę nie ma sposobu na stworzenie kodu, który mógłby się zepsuć, gdyby wewnętrzna implementacja klasy łańcuchowej uległa zmianie. –

1

Każdy ciąg musi mieć własne dane ciągu, w sposób zaimplementowany w klasie String.

Można stworzyć własną strukturę podciąg, który używa części łańcucha:

public struct SubString { 

    private string _str; 
    private int _offset, _len; 

    public SubString(string str, int offset, int len) { 
     _str = str; 
     _offset = offset; 
     _len = len; 
    } 

    public int Length { get { return _len; } } 

    public char this[int index] { 
     get { 
     if (index < 0 || index > len) throw new IndexOutOfRangeException(); 
     return _str[_offset + index]; 
     } 
    } 

    public void WriteToStringBuilder(StringBuilder s) { 
     s.Write(_str, _offset, _len); 
    } 

    public override string ToString() { 
     return _str.Substring(_offset, _len); 
    } 

} 

Można ciele go z innych metod, takich jak porównania, który jest również możliwe do zrobienia bez wyodrębniania ciąg.

+0

Co powiesz na podłańcuch na inny podłańcuch? –

+0

Tak, struktura SubString może łatwo utworzyć inną, która jest częścią samej siebie. – Guffa

0

Dodając do tego, że ciągi znaków są niezmienne, powinieneś być, że poniższy fragment wygeneruje wiele instancji String w pamięci.

String s1 = "Hello", s2 = ", ", s3 = "World!"; 
String res = s1 + s2 + s3; 

S1 + S2> New przykład łańcuch znaków (temp1)

temp1 + S3> New przykład łańcuch znaków (temp2)

Res jest odniesienie do temp2.

+0

To brzmi jak coś, co kompilator może zoptymalizować. –

+0

To nie jest problem z kompilatorem, to wybór dokonany w projektowaniu języka. Java ma te same reguły dla Ciągów. System.Text.StringBuilder to dobra klasa, która symuluje ciągi "zmienne". –

+1

Wrong - s1 + s2 + s3 zostaje przekształcone w jedno połączenie z String.Concat. Dlatego NIE lepiej jest używać String.Format lub StringBuilder (które są stosunkowo powolne) dla maksymalnie 4 ciągów. Sprawdź IL, aby zobaczyć, co robi kompilator, i użyj profilera, aby dowiedzieć się, co działa dobrze w twoim programie. W przeciwnym razie równie dobrze mógłbyś powiedzieć: "Patrz, to jest but!" Zdjął swój but i jest to znak, że inni, którzy pójdą za nim, powinni zrobić to samo! " Opublikuj prawdziwe odpowiedzi zamiast mitycznych. –