Название - вопрос. Ниже приведена моя попытка ответить на него посредством исследований. Но я не доверяю своим неосведомленным исследованиям, поэтому я все еще задаю вопрос (что является самым быстрым способом для итерации отдельных символов в строке на С#?).
Иногда я хочу циклически перемещать символы строки один за другим, например, при разборе для вложенных токенов - что-то, что не может быть выполнено с помощью регулярных выражений. Мне интересно, какой самый быстрый способ - перебирать отдельные символы в строке, особенно очень большие строки.
Я проверил себя, и мои результаты ниже. Однако есть много читателей с гораздо более глубокими знаниями о компиляторе .NET CLR и С#, поэтому я не знаю, не хватает ли я чего-то очевидного или если я допустил ошибку в своем тестовом коде. Поэтому я требую вашего коллективного ответа. Если у кого-то есть представление о том, как работает индексатор строк, это было бы очень полезно. (Является ли это языком языка С#, скомпилированным во что-то еще за сценой? Или что-то встроенное в CLR?).
Первый метод, использующий поток, был взят непосредственно из принятого ответа из потока: как создать поток из строки?
Испытания
longString
- это строка в 99,1 миллиона символов, состоящая из 89 экземпляров текстовой версии спецификации языка С#. Результаты показаны для 20 итераций. Там, где есть время запуска (например, для первой итерации неявно созданного массива в методе № 3), я тестировал это отдельно, например, разбивая его из цикла после первой итерации.
Результаты
Из моих тестов кеширование строки в массиве char с использованием метода ToCharArray() является самым быстрым для итерации по всей строке. Метод ToCharArray() является авансом, а последующий доступ к отдельным символам немного быстрее, чем встроенный индексный аксессуар.
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
Обновление: В комментарии @Eric приведены результаты для 100 итераций более обычной строки 1.1 M char (одна копия спецификации С#). Indexer и char все еще быстрее, за ними следует foreach (char в строке), а затем методы потока.
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
Используемый код (проверяется отдельно, показывается вместе для краткости)
//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) { }
Принятый ответ:
Я интерпретировал @CodeInChaos и Ben следующие:
fixed (char* pString = longString) {
char* pChar = pString;
for (int i = 0; i < strLength; i++) {
c = *pChar ;
pChar++;
}
}
Выполнение для 100 итераций по короткой строке составляло 4,4 мс, с < 0.1 ms st dev.