Quando si ottiene la sottostringa in .Net, la nuova stringa fa riferimento agli stessi dati di stringa originali o i dati vengono copiati?

Supponendo che ho le seguenti stringhe:

string str1 = "Hello World!"; string str2 = str1.SubString(6, 5); // "World" 

Spero che nell’esempio precedente str2 non copi “World”, ma semplicemente finisca per essere una nuova stringa che punta allo stesso spazio di memoria solo che inizia con un offset di 6 e una lunghezza di 5.

In realtà ho a che fare con alcune stringhe potenzialmente molto lunghe e sono interessato a come funziona dietro le quinte per motivi di prestazioni. Non ho familiarità con IL per esaminare questo.

È una nuova stringa.

Le stringhe, in .NET, sono sempre immutabili. Ogni volta che generi una nuova stringa tramite un metodo, inclusa la sottostringa, costruirà la nuova stringa in memoria. L’unica volta che si condividono riferimenti agli stessi dati nelle stringhe in .NET è se si assegna esplicitamente una variabile stringa a un’altra stringa (in cui copia il riferimento) o se si lavora con costanti stringa, che sono in genere internate. Se sai che la tua stringa condividerà un valore con una stringa internata (costante / letterale dal tuo codice), puoi recuperare la copia “condivisa” tramite String.Intern .

Questa è una buona cosa, btw – Per fare ciò che stavi descrivendo, ogni stringa avrebbe richiesto un riferimento (ai dati stringa), oltre a una lunghezza + offset. Al momento, richiedono solo un riferimento ai dati di stringa.

Ciò aumenterebbe notevolmente la dimensione delle stringhe in generale, in tutto il framework.

Come altri hanno notato, il CLR esegue delle copie quando esegue un’operazione di sottostringa.

Come si nota, sarebbe certamente ansible rappresentare una stringa come un puntatore interno con una lunghezza. Ciò rende l’operazione di sottostringa estremamente economica.

Ci sono anche modi per rendere meno costose le altre operazioni. Ad esempio, la concatenazione di stringhe può essere resa economica rappresentando le stringhe come un albero di sottostringhe.

In entrambi i casi, ciò che sta accadendo qui è il risultato dell’operazione non è in realtà il “risultato” di per sé, ma piuttosto un object economico che rappresenta la capacità di ottenere i risultati quando necessario.

Il lettore attento si sarà appena reso conto che è così che funziona LINQ. Quando diciamo

 var results = from c in customers where c.City == "London" select c.Name; 

“risultati” non contiene i risultati della query. Questo codice ritorna quasi immediatamente; i risultati contengono un object che rappresenta la query . Solo quando la query viene iterata, il costoso meccanismo di ricerca della raccolta viene triggersto. Usiamo la potenza di una rappresentazione monadica della semantica della sequenza per rinviare i calcoli fino a dopo.

La domanda diventa quindi “è una buona idea fare la stessa cosa sulle stringhe?” e la risposta è un clamoroso “no”. Ho molti dolorosi esperimenti nel mondo reale su questo. Una volta ho passato un’estate a riscrivere le routine di gestione delle stringhe del compilatore VBScript per archiviare le concatenazioni di stringhe come un albero di operazioni di concatenazione di stringhe; solo quando il risultato viene effettivamente utilizzato come stringa, la concatenazione avviene effettivamente. Era disastroso; il tempo e la memoria aggiuntivi necessari per tenere traccia di tutti i puntatori di stringhe hanno reso il caso del 99% – qualcuno facendo alcune semplici operazioni con le stringhe per rendere una pagina web – circa due volte più lento, mentre accelerava enormemente la minuscola, minuscola minoranza di pagine che sono state scritte utilizzando concatenazioni di stringhe naive.

La stragrande maggioranza delle operazioni di stringa realistiche nei programmi .NET sono estremamente veloci; si accumulano in mosse di memoria che, in circostanze normali, si adattano bene ai blocchi di memoria che vengono memorizzati nella cache dal processore e sono quindi incredibilmente veloci.

Inoltre, l’utilizzo di un approccio “puntatore interno” per le stringhe complica notevolmente il garbage collector; andare con un tale approccio sembra rendere probabile che il GC rallenterebbe complessivamente, il che non giova a nessuno. Bisogna considerare il costo totale dell’impatto del cambiamento, non solo il suo impatto su alcuni scenari ristretti.

Se hai esigenze di prestazioni specifiche dovute a dati insolitamente grandi, dovresti considerare di scrivere la tua libreria di stringhe per scopi speciali che utilizza un approccio “monadico” come LINQ. È ansible rappresentare internamente le stringhe come array di char e quindi le operazioni di sottostringa si limitano a copiare un riferimento all’array e a modificare le posizioni di inizio e fine.

Crea una nuova stringa ma questa è una domanda molto intelligente e non sarebbe inconcepibile. Tuttavia, ritengo che le perdite di prestazioni nella maggior parte dei casi superino di gran lunga i risparmi di memoria in casi rari.

Recentemente ho sentito parlare di qualcosa chiamato “corde” che avrebbe funzionato come suggerito ma non conosco alcuna implementazione in .NET.

http://en.wikipedia.org/wiki/Rope_(computer_science)

Fa riferimento a una nuova stringa.

Sai cosa, non so niente di .NET.

Ma vorrei fare un’osservazione.

La maggior parte dei moderni pacchetti String ha comportamenti “copia su scrittura”.

In particolare, ciò significa che se si assegna una sottostringa, verrà utilizzata la memoria esistente della stringa padre, fino a quando la stringa avrà bisogno di cambiare, a quel punto copierà i dati sottostanti nel proprio nuovo spazio per l’uso.

Ora, se hai stringhe immutabili, in cui i dati sottostanti non possono cambiare, ci sono poche ragioni per NON farlo. Non c’è modo di “scrivere” su una stringa immutabile, quindi non ha nemmeno bisogno di copiare le funzioni di scrittura, solo la condivisione. Il C ++ ha stringhe mutabili, quindi copiano su scrittura.

Ad esempio, Java fa questo.

Normalmente questa è una buona cosa. C’è un piccolo impatto sulle prestazioni.

Dove NON vuoi che questo accada, però, è detto in questo esempio:

 String big1MBString = readLongHonkinStringFromTheInterTubes(); static String ittyBitty = big1MBString.substring(1, 5); 

Ora hai una stringa di “5 caratteri” che consuma 1MB di memoria, perché condivide il buffer di stringa 1MB sottostante della stringa grande, ma si manifesta solo come una stringa di 5 caratteri. Poiché mantieni il riferimento alla stringa più grande, internamente, non “libererai” mai lo spazio originale.

Guardando le fonti Mono, esse, di fatto, assegnano nuova memoria. Quindi, forse .NET è un’eccezione a quella che sembra essere una pratica comune oggi. Senza dubbio hanno le loro valide e informate ragioni (cioè non sto dicendo che .NET ha sbagliato), solo … diverso da quello che fanno gli altri.

SubString crea una nuova stringa. Quindi verrà assegnata una nuova memoria per il nuovo strin.

come ha detto Reed, le corde sono immutabili. se hai a che fare con stringhe lunghe, considera l’utilizzo di StringBuilder, potrebbe migliorare le prestazioni, a seconda ovviamente di ciò che stai cercando di realizzare. se puoi aggiungere alcuni dettagli alla tua domanda, riceverai sicuramente suggerimenti sulla migliore implementazione.

Le stringhe sono immutabili, quindi creerà una copia della stringa. Tuttavia, se la sottostringa corrisponde alla stringa esatta di un’altra stringa nota in fase di compilazione , utilizzerà effettivamente la stessa memoria di quella sottostringa. Questo è l’internazionalizzazione delle stringhe.

Da MSDN : “Il Common Language Runtime mantiene automaticamente una tabella, chiamata” pool interno “, che contiene una singola istanza di ogni costante di stringa letterale univoca dichiarata in un programma, nonché qualsiasi istanza unica di String aggiunta a livello di codice.

Il pool interno conserva l’archiviazione di stringhe. Se si assegna una costante di stringa letterale a più variabili, ciascuna variabile viene impostata per fare riferimento alla stessa costante nel pool interno anziché fare riferimento a diverse istanze di String con valori identici. ”

L’esempio del codice è informativo. È ansible impedire l’internamento automatico utilizzando l’attributo [assembly: CompilationRelaxations(CompilationRelaxations.NoStringInterning)] per impedire l’internamento automatico delle stringhe. Dovresti anche usare NGEN.exe per compilarlo in un’immagine nativa, per impedire l’interning.

Nota che se usi uno StringBuilder evita di internare. È solo per le stringhe che possono essere confrontate con altre stringhe note al momento della compilazione.

Questo è un esempio modificato dell’articolo MSDN, si noti che se passo una parte di “abcd” dalla Console, è ancora internato, anche se quella str3 è costruita in fase di runtime. Tuttavia, StringBuilder evita l’internamento.

 // Sample for String.IsInterned(String) using System; using System.Text; using System.Runtime.CompilerServices; using System.Diagnostics; // In the .NET Framework 2.0 the following attribute declaration allows you to // avoid the use of the interning when you use NGEN.exe to compile an assembly // to the native image cache. //[assembly: CompilationRelaxations(CompilationRelaxations.NoStringInterning)] class Sample { public static void Main() { // String str1 is known at compile time, and is automatically interned. String str1 = "abcd"; Console.WriteLine("Type cd and it will be ok, type anything else and Assert will fail."); string end = Console.ReadLine(); // Constructed, but still interned. string str3 = "ab" + end; // Constructed string, str2, is not explicitly or automatically interned. String str2 = new StringBuilder().Append("wx").Append("yz").ToString(); Console.WriteLine(); Test(1, str1); Test(2, str2); Test(3, str3); // Sanity checks. // Debug.Assert(Object.ReferenceEquals(str3, str1)); // Assertion fails, as expected. Debug.Assert(Object.ReferenceEquals(string.Intern(str3), string.Intern(str1))); // Passes Debug.Assert(Object.ReferenceEquals(string.Intern(str3), (str1))); // Passes Debug.Assert(Object.ReferenceEquals((str3), string.Intern(str1))); // Fails Console.ReadKey(); } public static void Test(int sequence, String str) { Console.Write("{0}) The string, '", sequence); String strInterned = String.IsInterned(str); if (strInterned == null) Console.WriteLine("{0}', is not interned.", str); else Console.WriteLine("{0}', is interned.", strInterned); } } 

Nelle stringhe CLR sono significati immutabili che non possono essere modificati. Quando si manipolano stringhe di grandi dimensioni, suggerirei di utilizzare la class del generatore di stringhe.