Il titolo è la domanda. Di seguito è il mio tentativo di rispondere attraverso la ricerca. Ma non mi fido delle mie ricerche disinformate, quindi pongo ancora la domanda (qual è il modo più veloce per scorrere i singoli caratteri in una stringa in C#?).Qual è il modo più veloce per scorrere i singoli caratteri in una stringa in C#?
Occasionalmente, desidero scorrere i caratteri di una stringa uno alla volta, ad esempio durante l'analisi di token annidati, cosa che è cannot be done with regular expressions. Mi chiedo quale sia il modo più veloce per scorrere i singoli caratteri di una stringa, in particolare stringhe molto grandi.
Ho fatto un po 'di test me stesso ei miei risultati sono sotto. Tuttavia ci sono molti lettori con una conoscenza molto più approfondita del compilatore .NET CLR e C# quindi non so se mi manca qualcosa di ovvio, o se ho fatto un errore nel mio codice di test. Quindi sollecito la tua risposta collettiva. Se qualcuno ha un'idea di come funziona realmente l'indicizzatore di stringhe, sarebbe molto utile. (È una caratteristica del linguaggio C# compilata in qualcos'altro dietro le quinte o qualcosa di integrato nel CLR?).
Il primo metodo che utilizza un flusso è stata presa direttamente dalla risposta accettata dal thread: how to generate a stream from a string?
Test
longString
è una stringa di 99,1 milioni di caratteri composto di 89 copie della versione di solo testo delle specifiche del linguaggio C#. I risultati mostrati sono per 20 iterazioni. Dove c'è un tempo di "avvio" (come per la prima iterazione dell'array implicitamente creato nel metodo # 3), l'ho testato separatamente, ad esempio interrompendo il ciclo dopo la prima iterazione.
Risultati
Dalle mie prove, la memorizzazione nella cache la stringa in un array di caratteri utilizzando il metodo ToCharArray() è il più veloce per iterare su l'intera stringa. Il metodo ToCharArray() è una spesa anticipata e il successivo accesso ai singoli caratteri è leggermente più rapido dell'accessorio incorporato nell'indice.
milliseconds
---------------------------------
Method Startup Iteration Total StdDev
------------------------------ ------- --------- ----- ------
1 index accessor 0 602 602 3
2 explicit convert ToCharArray 165 410 582 3
3 foreach (c in string.ToCharArray)168 455 623 3
4 StringReader 0 1150 1150 25
5 StreamWriter => Stream 405 1940 2345 20
6 GetBytes() => StreamReader 385 2065 2450 35
7 GetBytes() => BinaryReader 385 5465 5850 80
8 foreach (c in string) 0 960 960 4
Aggiornamento: Per @ commento di Eric, ecco i risultati per 100 iterazioni su una più normale 1.1 M stringa char (una copia del C# spec). Gli indicizzatori e gli array di char sono ancora più veloci, seguiti da foreach (char in string), seguiti dai metodi di streaming.
milliseconds
---------------------------------
Method Startup Iteration Total StdDev
------------------------------ ------- --------- ----- ------
1 index accessor 0 6.6 6.6 0.11
2 explicit convert ToCharArray 2.4 5.0 7.4 0.30
3 for(c in string.ToCharArray) 2.4 4.7 7.1 0.33
4 StringReader 0 14.0 14.0 1.21
5 StreamWriter => Stream 5.3 21.8 27.1 0.46
6 GetBytes() => StreamReader 4.4 23.6 28.0 0.65
7 GetBytes() => BinaryReader 5.0 61.8 66.8 0.79
8 foreach (c in string) 0 10.3 10.3 0.11
codice usato (testato separatamente; mostrato insieme per brevità)
//1 index accessor
int strLength = longString.Length;
for (int i = 0; i < strLength; i++) { c = longString[i]; }
//2 explicit convert ToCharArray
int strLength = longString.Length;
char[] charArray = longString.ToCharArray();
for (int i = 0; i < strLength; i++) { c = charArray[i]; }
//3 for(c in string.ToCharArray)
foreach (char c in longString.ToCharArray()) { }
//4 use StringReader
int strLength = longString.Length;
StringReader sr = new StringReader(longString);
for (int i = 0; i < strLength; i++) { c = Convert.ToChar(sr.Read()); }
//5 StreamWriter => StreamReader
int strLength = longString.Length;
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(longString);
writer.Flush();
stream.Position = 0;
StreamReader str = new StreamReader(stream);
while (stream.Position < strLength) { c = Convert.ToChar(str.Read()); }
//6 GetBytes() => StreamReader
int strLength = longString.Length;
MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(longString));
StreamReader str = new StreamReader(stream);
while (stream.Position < strLength) { c = Convert.ToChar(str.Read()); }
//7 GetBytes() => BinaryReader
int strLength = longString.Length;
MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(longString));
BinaryReader br = new BinaryReader(stream, Encoding.Unicode);
while (stream.Position < strLength) { c = br.ReadChar(); }
//8 foreach (c in string)
foreach (char c in longString) { }
risposta accettata:
ho interpretato @CodeInChaos e le note di Ben come segue:
fixed (char* pString = longString) {
char* pChar = pString;
for (int i = 0; i < strLength; i++) {
c = *pChar ;
pChar++;
}
}
Esecuzione per 100 iterazioni sul corto la stringa era di 4,4 ms, con < 0,1 ms st dev.
'ToCharArray()' crea una ** nuova copia ** di stringa "super lunga", quindi anche se le sue prestazioni sono notevoli, il consumo di memoria ha superato i vantaggi che porta. – Tigran
Basta chiedersi, misurando in LINQPad usando 'System.Diagnostics.Stopwatch'? – kamranicus
E riguardo 'foreach (char c in str)'? –