Название вопроса. Ниже моя попытка ответить на это через исследование. Но я не доверяю своим неинформированным исследованиям, поэтому я все еще задаю вопрос (Какой самый быстрый способ перебрать отдельные символы в строке в C #?).
Иногда я хочу поочередно циклически проходить символы строки, например, при разборе вложенных токенов - то, что нельзя сделать с помощью регулярных выражений . Мне интересно, какой самый быстрый способ состоит в том, чтобы перебирать отдельные символы в строке, особенно очень большие строки.
Я сам провел несколько испытаний, и мои результаты приведены ниже. Однако, есть много читателей с гораздо более глубокими знаниями компилятора .NET CLR и C #, поэтому я не знаю, упустил ли я что-то очевидное или допустил ошибку в своем тестовом коде. Поэтому я прошу ваш коллективный ответ. Если у кого-то есть понимание того, как на самом деле работает индексатор строк, это было бы очень полезно. (Это функция языка C #, скомпилированная во что-то другое за кулисами? Или что-то встроенное в CLR?).
Первый метод, использующий поток, был взят непосредственно из принятого ответа от потока: как генерировать поток из строки?
Тесты
longString
- строка длиной 99,1 миллиона символов, состоящая из 89 копий текстовой версии спецификации языка C # в виде простого текста. Результаты показаны за 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
Обновление: В соответствии с комментарием Эрика, здесь приведены результаты для 100 итераций по более обычной 1,1-миллиметровой строке символов (одна копия спецификации C #). Индексатор и массив символов по-прежнему самые быстрые, за ними следует 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 и Бена следующим образом:
fixed (char* pString = longString) {
char* pChar = pString;
for (int i = 0; i < strLength; i++) {
c = *pChar ;
pChar++;
}
}
Выполнение за 100 итераций по короткой строке составило 4,4 мс, с <0,1 мсек. Дев. </p>