Эффективность памяти и производительность String.Replace .NET Framework
string str1 = "12345ABC...\\...ABC100000";
// Hypothetically huge string of 100000 + Unicode Chars
str1 = str1.Replace("1", string.Empty);
str1 = str1.Replace("22", string.Empty);
str1 = str1.Replace("656", string.Empty);
str1 = str1.Replace("77ABC", string.Empty);
// ... this replace anti-pattern might happen with upto 50 consecutive lines of code.
str1 = str1.Replace("ABCDEFGHIJD", string.Empty);
Я унаследовал некоторый код, который делает то же самое, что и фрагмент выше. Он принимает огромную строку и заменяет (удаляет) постоянные меньшие строки из большой строки.
Я считаю, что это очень интенсивный процесс памяти, учитывая, что новые большие неизменяемые строки выделяются в памяти для каждой замены, ожидая смерти через GC.
1. Какой самый быстрый способ заменить эти значения, игнорируя проблемы с памятью?
2. Как наиболее эффективно использовать память для достижения того же результата?
Я надеюсь, что это один и тот же ответ!
Практические решения, которые подходят где-то между этими целями, также приветствуются.
Предположения:
- Все замены постоянны и известны заранее
- Базовые символы содержат некоторые символы Unicode [non-ascii]
9 ответов
Все символы в строке.NET являются "символами Юникода". Вы имеете в виду, что они не Ascii? Это не должно иметь шансов - если только вы не столкнетесь с проблемами композиции, например, "e + острый акцент" не будет заменен, когда вы попытаетесь заменить "e острый".
Вы можете попробовать использовать регулярное выражение с Regex.Replace
, или же StringBuilder.Replace
, Вот пример кода, который делает то же самое с обоими:
using System;
using System.Text;
using System.Text.RegularExpressions;
class Test
{
static void Main(string[] args)
{
string original = "abcdefghijkl";
Regex regex = new Regex("a|c|e|g|i|k", RegexOptions.Compiled);
string removedByRegex = regex.Replace(original, "");
string removedByStringBuilder = new StringBuilder(original)
.Replace("a", "")
.Replace("c", "")
.Replace("e", "")
.Replace("g", "")
.Replace("i", "")
.Replace("k", "")
.ToString();
Console.WriteLine(removedByRegex);
Console.WriteLine(removedByStringBuilder);
}
}
Я не хотел бы догадываться, что является более эффективным - вам придется сравнивать с вашим конкретным приложением. Путь регулярного выражения может быть в состоянии сделать все это за один проход, но этот проход будет относительно загружен процессором по сравнению с каждой из множества замен в StringBuilder.
Если вы хотите быть очень быстрым, а я имею в виду очень быстрым, вам придется выйти за рамки StringBuilder и просто написать хорошо оптимизированный код.
Одна вещь, которую ваш компьютер не любит делать, это ветвление, если вы можете написать метод замены, который работает с фиксированным массивом (char *) и не ветвится, у вас высокая производительность.
Что вы будете делать, так это то, что операция замены будет искать последовательность символов, и если она найдет любую такую подстроку, она заменит ее. По сути, вы скопируете строку, и при этом предварительно выполните поиск и замену.
Вы будете полагаться на эти функции для выбора индекса некоторого буфера для чтения / записи. Цель состоит в том, чтобы подготовить метод замены так, чтобы, когда ничего не нужно было менять, вы пишете ненужные файлы вместо ветвления.
Вы должны быть в состоянии выполнить это без единого оператора if и не забывать использовать небезопасный код. В противном случае вы будете платить за проверку индекса для каждого элемента доступа.
unsafe
{
fixed( char * p = myStringBuffer )
{
// Do fancy string manipulation here
}
}
Я написал такой код на C# для удовольствия и увидел значительное улучшение производительности, почти на 300% ускоренное на поиск и замену. Несмотря на то, что.NET BCL (библиотека базовых классов) работает довольно хорошо, он изобилует ветвящимися конструкциями, и обработка исключений замедлит ваш код, если вы используете встроенный материал. Кроме того, JIT-компилятор не обеспечивает идеальную оптимизацию, и вам придется запускать код как сборку релиза без подключенного отладчика, чтобы можно было наблюдать значительное увеличение производительности.
Я мог бы предоставить вам более полный код, но это значительный объем работы. Тем не менее, я могу гарантировать вам, что это будет быстрее, чем что-либо еще предложенное до сих пор.
Я уже несколько раз попадал в эту ветку, и я не был полностью убежден после прочтения предыдущих ответов, так как некоторые тесты выполнялись с помощью StopWatch, который может давать какие-то указания, но не очень хорош.
В моем случае у меня есть строка, которая может быть довольно большой, то есть вывод HTML с веб-сайта. Мне нужно заменить несколько заполнителей внутри этой строки (около 10, максимум 20) на значения.
Я создал Benchmark.NET-тест, чтобы получить надежные данные по этому поводу, и вот что я нашел:
TL; DR:
- Не используйте String.Replace, если производительность / память вызывает беспокойство.
- Regex.Replace - самый быстрый, но использует немного больше памяти, чем StringBuilder.Replace. Скомпилированное регулярное выражение является самым быстрым, если вы намереваетесь повторно использовать один и тот же шаблон, для небольшого количества использований некомпилированный экземпляр Regex дешевле создать.
- Если вас интересует только потребление памяти и вас устраивает более медленное выполнение, используйте StringBuilder.Replace
Результаты теста:
| Method | ItemsToReplace | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---------------------- |--------------- |-----------:|----------:|----------:|--------:|-------:|------:|----------:|
| StringReplace | 3 | 21.493 us | 0.1182 us | 0.1105 us | 3.6926 | 0.0305 | - | 18.96 KB |
| StringBuilderReplace | 3 | 35.383 us | 0.1341 us | 0.1119 us | 2.5024 | - | - | 13.03 KB |
| RegExReplace | 3 | 19.620 us | 0.1252 us | 0.1045 us | 3.4485 | 0.0305 | - | 17.75 KB |
| RegExReplace_Compiled | 3 | 4.573 us | 0.0318 us | 0.0282 us | 2.7084 | 0.0610 | - | 13.91 KB |
| StringReplace | 10 | 74.273 us | 0.7900 us | 0.7390 us | 12.2070 | 0.1221 | - | 62.75 KB |
| StringBuilderReplace | 10 | 115.322 us | 0.5820 us | 0.5444 us | 2.6855 | - | - | 13.84 KB |
| RegExReplace | 10 | 24.121 us | 0.1130 us | 0.1002 us | 4.4250 | 0.0916 | - | 22.75 KB |
| RegExReplace_Compiled | 10 | 8.601 us | 0.0298 us | 0.0279 us | 3.6774 | 0.1221 | - | 18.92 KB |
| StringReplace | 20 | 150.193 us | 1.4508 us | 1.3571 us | 24.6582 | 0.2441 | - | 126.89 KB |
| StringBuilderReplace | 20 | 233.984 us | 1.1707 us | 1.0951 us | 2.9297 | - | - | 15.3 KB |
| RegExReplace | 20 | 28.699 us | 0.1179 us | 0.1045 us | 4.8218 | 0.0916 | - | 24.79 KB |
| RegExReplace_Compiled | 20 | 12.672 us | 0.0599 us | 0.0560 us | 4.0894 | 0.1221 | - | 20.95 KB |
Итак, мои выводы таковы:
- Regex.Replace - это способ быстрого выполнения и разумного использования памяти. Используйте скомпилированный общий экземпляр для ускорения.
- StringBuilder имеет наименьший объем памяти, но намного медленнее, чем Regex.Replace. Я бы использовал его, только если память - это единственное, что имеет значение.
Код теста выглядит так:
[MemoryDiagnoser]
[HtmlExporter]
[PlainExporter]
[RPlotExporter]
public class String_Replace
{
private Dictionary<string, string> _valuesToReplace = new Dictionary<string, string>()
{
{"foo","fooRep" },
{"bar","barRep" },
{"lorem","loremRep" },
{"ipsum","ipsumRep" },
{"x","xRep" },
{"y","yRep" },
{"z","zRep" },
{"yada","yadaRep" },
{"old","oldRep" },
{"new","newRep" },
{"enim","enimRep" },
{"amet","ametRep" },
{"sit","sitRep" },
{"convallis","convallisRep" },
{"vehicula","vehiculaRep" },
{"suspendisse","suspendisseRep" },
{"accumsan","accumsanRep" },
{"suscipit","suscipitRep" },
{"ligula","ligulaRep" },
{"posuere","posuereRep" }
};
private Regex _regexCompiled;
private string GetText_With_3_Tags()
{
return @"<html>
<body>
Lorem ipsum dolor sit [foo], consectetur [bar] elit. Proin nulla quam, faucibus a ligula quis, posuere commodo elit. Nunc at tincidunt elit. Sed ipsum ex, accumsan sed viverra sit amet, tincidunt id nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nam interdum ex eget blandit lacinia. Nullam a tortor id sapien fringilla pellentesque vel ac purus. Fusce placerat dapibus tortor id luctus. Aenean in lacinia neque. Fusce quis ultrices odio. Nam id leo neque.
Etiam erat lorem, tincidunt volutpat odio at, finibus pharetra felis. Sed magna enim, accumsan at convallis a, aliquet eu quam. Vestibulum faucibus tincidunt ipsum et lacinia. Sed cursus ut arcu a commodo. Integer euismod eros at efficitur sollicitudin. In quis magna non orci sollicitudin condimentum. Fusce sed lacinia lorem, nec varius erat. In quis odio viverra, pharetra ex ac, hendrerit ante. Mauris congue enim et tellus sollicitudin pulvinar non sit amet tortor. Suspendisse at ex pharetra, semper diam ut, molestie velit. Cras lacinia urna neque, sit amet laoreet ex venenatis nec. Mauris at leo massa.
Aliquam mollis ultrices mi, sit amet venenatis enim rhoncus nec. Integer sit amet lectus tempor, finibus nisl quis, sodales ante. Curabitur suscipit dolor a dignissim consequat. Nulla eget vestibulum massa. Nam fermentum congue velit a placerat. Vivamus bibendum ex velit, id auctor ipsum bibendum eu. Praesent id gravida dui. Curabitur sollicitudin lobortis purus ac tempor. Sed felis enim, ornare et est egestas, blandit tincidunt lacus. Ut commodo dignissim augue, eget bibendum augue facilisis non.
Ut tortor neque, dignissim sit amet [lorem] ut, facilisis sit amet quam. Nullam et leo ut est congue vehicula et accumsan dolor. Aliquam erat dolor, eleifend a ipsum et, maximus suscipit ipsum. Nunc nec diam ex. Praesent suscipit aliquet condimentum. Nulla sodales lobortis fermentum. Maecenas ut laoreet sem. Ut id pulvinar urna, vel gravida lacus. Integer nunc urna, euismod eget vulputate sit amet, pharetra nec velit. Donec vel elit ac dolor varius faucibus tempus sed tortor. Donec metus diam, condimentum sit amet odio at, cursus cursus risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam maximus tellus id quam consequat vestibulum. Curabitur rutrum eros tellus, eget commodo mauris sollicitudin a. In dignissim non est at pretium. Nunc bibendum pharetra dui ac ullamcorper.
Sed rutrum vehicula pretium. Morbi eu felis ante. Aliquam vel mauris at felis tempus dictum ac a justo. Suspendisse ultricies nisi turpis, non sagittis magna porttitor venenatis. Aliquam ac risus quis leo semper viverra non ac nunc. Phasellus lacinia augue sed libero elementum, at interdum nunc posuere. Duis lacinia rhoncus urna eget scelerisque. Morbi ullamcorper tempus bibendum. Proin at est eget nibh dignissim bibendum. Fusce imperdiet ut urna nec mattis. Aliquam massa mauris, consequat tristique magna non, sodales tempus massa. Ut lobortis risus rhoncus, molestie mi vitae, accumsan enim. Quisque dapibus libero elementum lectus dignissim, non finibus lacus lacinia.
</p><p>
</body>
</html>";
}
private string GetText_With_10_Tags()
{
return @"<html>
<body>
Lorem ipsum dolor sit [foo], consectetur [bar] elit. Proin nulla quam, faucibus a ligula quis, posuere commodo elit. Nunc at tincidunt elit. Sed ipsum ex, accumsan sed viverra sit amet, tincidunt id nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nam interdum ex eget blandit lacinia. Nullam a tortor id sapien fringilla pellentesque vel ac purus. Fusce placerat dapibus tortor id luctus. Aenean in lacinia neque. Fusce quis ultrices odio. Nam id leo neque.
Etiam erat lorem, tincidunt volutpat odio at, finibus pharetra felis. Sed magna enim, [z] at convallis a, aliquet eu quam. Vestibulum faucibus tincidunt ipsum et lacinia. Sed cursus ut arcu a commodo. Integer euismod eros at efficitur sollicitudin. In quis magna non orci sollicitudin condimentum. Fusce sed lacinia lorem, nec varius erat. In quis odio viverra, pharetra ex ac, hendrerit ante. Mauris congue enim et tellus sollicitudin pulvinar non sit amet tortor. Suspendisse at ex pharetra, semper diam ut, molestie velit. Cras lacinia urna neque, sit amet laoreet ex venenatis nec. Mauris at leo massa.
Aliquam mollis ultrices mi, sit amet venenatis enim rhoncus nec. Integer sit amet [y] tempor, finibus nisl quis, sodales ante. Curabitur suscipit dolor a dignissim consequat. Nulla eget vestibulum massa. Nam fermentum congue velit a placerat. Vivamus bibendum ex velit, id auctor ipsum bibendum eu. Praesent id gravida dui. Curabitur sollicitudin lobortis purus ac tempor. Sed felis enim, ornare et est egestas, blandit tincidunt lacus. Ut commodo dignissim augue, eget bibendum augue facilisis non.
Ut tortor neque, dignissim sit amet [lorem] ut, [ipsum] sit amet quam. [x] et leo ut est congue [new] et accumsan dolor. Aliquam erat dolor, eleifend a ipsum et, maximus suscipit ipsum. Nunc nec diam ex. Praesent suscipit aliquet condimentum. Nulla sodales lobortis fermentum. Maecenas ut laoreet sem. Ut id pulvinar urna, vel gravida lacus. Integer nunc urna, euismod eget vulputate sit amet, pharetra nec velit. Donec vel elit ac dolor varius faucibus tempus sed tortor. Donec metus diam, condimentum sit amet odio at, cursus cursus risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam maximus tellus id quam consequat vestibulum. Curabitur rutrum eros tellus, eget commodo mauris sollicitudin a. In dignissim non est at pretium. Nunc bibendum pharetra dui ac ullamcorper.
Sed rutrum vehicula pretium. Morbi eu felis ante. Aliquam vel [old] at felis [yada] dictum ac a justo. Suspendisse ultricies nisi turpis, non sagittis magna porttitor venenatis. Aliquam ac risus quis leo semper viverra non ac nunc. Phasellus lacinia augue sed libero elementum, at interdum nunc posuere. Duis lacinia rhoncus urna eget scelerisque. Morbi ullamcorper tempus bibendum. Proin at est eget nibh dignissim bibendum. Fusce imperdiet ut urna nec mattis. Aliquam massa mauris, consequat tristique magna non, sodales tempus massa. Ut lobortis risus rhoncus, molestie mi vitae, accumsan enim. Quisque dapibus libero elementum lectus dignissim, non finibus lacus lacinia.
</p><p>
</body>
</html>";
}
private string GetText_With_20_Tags()
{
return @"<html>
<body>
Lorem ipsum dolor sit [foo], consectetur [bar] elit. Proin nulla [convallis], faucibus a [vehicula] quis, posuere commodo elit. Nunc at tincidunt elit. Sed ipsum ex, accumsan sed viverra sit amet, tincidunt id nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nam interdum ex eget blandit lacinia. Nullam a tortor id sapien fringilla pellentesque vel ac purus. Fusce placerat dapibus tortor id luctus. Aenean in lacinia neque. Fusce quis ultrices odio. Nam id leo neque.
Etiam erat lorem, tincidunt [posuere] odio at, finibus pharetra felis. Sed magna enim, [z] at convallis a, [enim] eu quam. Vestibulum faucibus tincidunt ipsum et lacinia. Sed cursus ut arcu a commodo. Integer euismod eros at efficitur sollicitudin. In quis magna non orci sollicitudin condimentum. Fusce sed lacinia lorem, nec varius erat. In quis odio viverra, pharetra ex ac, hendrerit ante. Mauris congue enim et tellus sollicitudin pulvinar non sit amet tortor. Suspendisse at ex pharetra, semper diam ut, molestie velit. Cras lacinia urna neque, sit amet laoreet ex venenatis nec. Mauris at leo massa.
[suspendisse] mollis [amet] mi, sit amet venenatis enim rhoncus nec. Integer sit amet [y] tempor, finibus nisl quis, sodales ante. Curabitur suscipit dolor a dignissim consequat. Nulla eget vestibulum massa. Nam fermentum congue velit a placerat. Vivamus bibendum ex velit, id auctor ipsum bibendum eu. Praesent id gravida dui. Curabitur sollicitudin lobortis purus ac tempor. Sed felis enim, ornare et est egestas, blandit tincidunt lacus. Ut commodo dignissim augue, eget bibendum augue facilisis non.
Ut tortor neque, dignissim sit amet [lorem] ut, [ipsum] sit amet quam. [x] et leo ut est congue [new] et accumsan [ligula]. Aliquam erat dolor, eleifend a ipsum et, maximus suscipit ipsum. Nunc nec diam ex. Praesent suscipit aliquet condimentum. Nulla sodales lobortis fermentum. Maecenas ut laoreet sem. Ut id pulvinar urna, vel gravida lacus. Integer nunc urna, euismod eget vulputate sit amet, pharetra nec velit. Donec vel elit ac dolor varius faucibus tempus sed tortor. Donec metus diam, condimentum sit amet odio at, cursus cursus risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam maximus tellus id quam consequat vestibulum. Curabitur rutrum eros tellus, eget commodo mauris sollicitudin a. In dignissim non est at pretium. Nunc bibendum pharetra dui ac ullamcorper.
Sed rutrum vehicula [accumsan]. Morbi eu [suscipit] [sit]. Aliquam vel [old] at felis [yada] dictum ac a justo. Suspendisse ultricies nisi turpis, non sagittis magna porttitor venenatis. Aliquam ac risus quis leo semper viverra non ac nunc. Phasellus lacinia augue sed libero elementum, at interdum nunc posuere. Duis lacinia rhoncus urna eget scelerisque. Morbi ullamcorper tempus bibendum. Proin at est eget nibh dignissim bibendum. Fusce imperdiet ut urna nec mattis. Aliquam massa mauris, consequat tristique magna non, sodales tempus massa. Ut lobortis risus rhoncus, molestie mi vitae, accumsan enim. Quisque dapibus libero elementum lectus dignissim, non finibus lacus lacinia.
</p><p>
</body>
</html>";
}
private string GetText(int numberOfReplace)
{
if (numberOfReplace == 3)
return GetText_With_3_Tags();
if (numberOfReplace == 10)
return GetText_With_10_Tags();
if (numberOfReplace == 20)
return GetText_With_20_Tags();
return "";
}
public String_Replace()
{
_regexCompiled = new Regex(@"\[([^]]*)\]",RegexOptions.Compiled);
}
[Params(3,10,20)]
public int ItemsToReplace { get; set; }
[Benchmark]
public void StringReplace()
{
var str = GetText(ItemsToReplace);
foreach (var rep in _valuesToReplace.Take(ItemsToReplace))
{
str = str.Replace("[" + rep.Key + "]", rep.Value);
}
}
[Benchmark]
public void StringBuilderReplace()
{
var sb = new StringBuilder(GetText(ItemsToReplace));
foreach (var rep in _valuesToReplace.Take(ItemsToReplace))
{
sb.Replace("[" + rep.Key + "]", rep.Value);
}
var res = sb.ToString();
}
[Benchmark]
public void RegExReplace()
{
var str = GetText(ItemsToReplace);
Regex regex = new Regex(@"\[([^]]*)\]");
str = regex.Replace(str, Replace);
var res = str;
}
[Benchmark]
public void RegExReplace_Compiled()
{
var str = GetText(ItemsToReplace);
str = _regexCompiled.Replace(str, Replace);
var res = str;
}
private string Replace(Match match)
{
if(match.Groups.Count > 0)
{
string collectionKey = match.Groups[1].Value;
return _valuesToReplace[collectionKey];
}
return string.Empty;
}
}
1. Какой самый быстрый способ заменить эти значения, игнорируя проблемы с памятью?
Самый быстрый способ - создать пользовательский компонент, соответствующий вашему варианту использования. Начиная с.NET 4.6, в BCL нет класса, предназначенного для замены нескольких строк.
Если вам нужно что-то быстрое из BCL, StringBuilder - это самый быстрый компонент BCL для простой замены строки. Исходный код можно найти здесь: он достаточно эффективен для замены одной строки. Используйте Regex только в том случае, если вам действительно нужна сила сопоставления с образцом регулярных выражений. Это медленнее и немного громоздче, даже когда компилируется.
2. Как наиболее эффективно использовать память для достижения того же результата?
Наиболее эффективным способом памяти является выполнение отфильтрованной потоковой копии из источника в место назначения (объяснено ниже). Потребление памяти будет ограничено вашим буфером, однако это будет более ресурсоемким; как правило, вы собираетесь обменять производительность процессора на потребление памяти.
Технические детали
Заменить строку сложно. Даже при замене строки в изменяемой области памяти (например, в StringBuilder) это дорого. Если строка замены отличается от исходной строки, вы будете перемещать каждый символ, следующий за строкой замены, чтобы вся строка была смежной. Это приводит к большому количеству операций записи в память, и даже в случае StringBuilder заставляет переписывать большую часть строки в памяти при каждом вызове Replace.
Итак, каков самый быстрый способ замены строк? Запишите новую строку, используя один проход: не позволяйте вашему коду вернуться назад, и вам придется что-то переписывать. Пишет дороже, чем читает. Вам придется написать это самостоятельно для достижения наилучших результатов.
Решение с высокой памятью
Класс, который я написал, генерирует строки на основе шаблонов. Я помещаю токены ($ReplaceMe$) в шаблон, который отмечает места, где я хочу вставить строку позже. Я использую его в тех случаях, когда XmlWriter слишком обременителен для XML, который в значительной степени статичен и повторяется, и мне нужно создавать большие потоки данных XML (или JSON).
Класс работает, разрезая шаблон на части и помещая каждую часть в пронумерованный словарь. Параметры также перечислены. Порядок, в котором детали и параметры вставляются в новую строку, помещается в целочисленный массив. Когда генерируется новая строка, части и параметры выбираются из словаря и используются для создания новой строки.
Он не является ни полностью оптимизированным, ни пуленепробиваемым, но отлично работает для генерации очень больших потоков данных из шаблонов.
Решение с низким объемом памяти
Вам нужно будет прочитать небольшие фрагменты из исходной строки в буфер, выполнить поиск в буфере с использованием оптимизированного алгоритма поиска, а затем записать новую строку в целевой поток / строку. Здесь есть много потенциальных предостережений, но это будет эффективным для использования памятью и лучшим решением для динамических данных, которые невозможно кэшировать, таких как перевод на целую страницу или исходные данные, которые слишком велики для разумного кэширования. У меня нет примерного решения для этого.
Образец кода
Желаемые результаты
<DataTable source='Users'>
<Rows>
<Row id='25' name='Administrator' />
<Row id='29' name='Robert' />
<Row id='55' name='Amanda' />
</Rows>
</DataTable>
шаблон
<DataTable source='$TableName$'>
<Rows>
<Row id='$0$' name='$1$'/>
</Rows>
</DataTable>
Прецедент
class Program
{
static string[,] _users =
{
{ "25", "Administrator" },
{ "29", "Robert" },
{ "55", "Amanda" },
};
static StringTemplate _documentTemplate = new StringTemplate(@"<DataTable source='$TableName$'><Rows>$Rows$</Rows></DataTable>");
static StringTemplate _rowTemplate = new StringTemplate(@"<Row id='$0$' name='$1$' />");
static void Main(string[] args)
{
_documentTemplate.SetParameter("TableName", "Users");
_documentTemplate.SetParameter("Rows", GenerateRows);
Console.WriteLine(_documentTemplate.GenerateString(4096));
Console.ReadLine();
}
private static void GenerateRows(StreamWriter writer)
{
for (int i = 0; i <= _users.GetUpperBound(0); i++)
_rowTemplate.GenerateString(writer, _users[i, 0], _users[i, 1]);
}
}
StringTemplate Source
public class StringTemplate
{
private string _template;
private string[] _parts;
private int[] _tokens;
private string[] _parameters;
private Dictionary<string, int> _parameterIndices;
private string[] _replaceGraph;
private Action<StreamWriter>[] _callbackGraph;
private bool[] _graphTypeIsReplace;
public string[] Parameters
{
get { return _parameters; }
}
public StringTemplate(string template)
{
_template = template;
Prepare();
}
public void SetParameter(string name, string replacement)
{
int index = _parameterIndices[name] + _parts.Length;
_replaceGraph[index] = replacement;
_graphTypeIsReplace[index] = true;
}
public void SetParameter(string name, Action<StreamWriter> callback)
{
int index = _parameterIndices[name] + _parts.Length;
_callbackGraph[index] = callback;
_graphTypeIsReplace[index] = false;
}
private static Regex _parser = new Regex(@"\$(\w{1,64})\$", RegexOptions.Compiled);
private void Prepare()
{
_parameterIndices = new Dictionary<string, int>(64);
List<string> parts = new List<string>(64);
List<object> tokens = new List<object>(64);
int param_index = 0;
int part_start = 0;
foreach (Match match in _parser.Matches(_template))
{
if (match.Index > part_start)
{
//Add Part
tokens.Add(parts.Count);
parts.Add(_template.Substring(part_start, match.Index - part_start));
}
//Add Parameter
var param = _template.Substring(match.Index + 1, match.Length - 2);
if (!_parameterIndices.TryGetValue(param, out param_index))
_parameterIndices[param] = param_index = _parameterIndices.Count;
tokens.Add(param);
part_start = match.Index + match.Length;
}
//Add last part, if it exists.
if (part_start < _template.Length)
{
tokens.Add(parts.Count);
parts.Add(_template.Substring(part_start, _template.Length - part_start));
}
//Set State
_parts = parts.ToArray();
_tokens = new int[tokens.Count];
int index = 0;
foreach (var token in tokens)
{
var parameter = token as string;
if (parameter == null)
_tokens[index++] = (int)token;
else
_tokens[index++] = _parameterIndices[parameter] + _parts.Length;
}
_parameters = _parameterIndices.Keys.ToArray();
int graphlen = _parts.Length + _parameters.Length;
_callbackGraph = new Action<StreamWriter>[graphlen];
_replaceGraph = new string[graphlen];
_graphTypeIsReplace = new bool[graphlen];
for (int i = 0; i < _parts.Length; i++)
{
_graphTypeIsReplace[i] = true;
_replaceGraph[i] = _parts[i];
}
}
public void GenerateString(Stream output)
{
var writer = new StreamWriter(output);
GenerateString(writer);
writer.Flush();
}
public void GenerateString(StreamWriter writer)
{
//Resolve graph
foreach(var token in _tokens)
{
if (_graphTypeIsReplace[token])
writer.Write(_replaceGraph[token]);
else
_callbackGraph[token](writer);
}
}
public void SetReplacements(params string[] parameters)
{
int index;
for (int i = 0; i < _parameters.Length; i++)
{
if (!Int32.TryParse(_parameters[i], out index))
continue;
else
SetParameter(index.ToString(), parameters[i]);
}
}
public string GenerateString(int bufferSize = 1024)
{
using (var ms = new MemoryStream(bufferSize))
{
GenerateString(ms);
ms.Position = 0;
using (var reader = new StreamReader(ms))
return reader.ReadToEnd();
}
}
public string GenerateString(params string[] parameters)
{
SetReplacements(parameters);
return GenerateString();
}
public void GenerateString(StreamWriter writer, params string[] parameters)
{
SetReplacements(parameters);
GenerateString(writer);
}
}
Вот быстрый тест...
Stopwatch s = new Stopwatch();
s.Start();
string replace = source;
replace = replace.Replace("$TS$", tsValue);
replace = replace.Replace("$DOC$", docValue);
s.Stop();
Console.WriteLine("String.Replace:\t\t" + s.ElapsedMilliseconds);
s.Reset();
s.Start();
StringBuilder sb = new StringBuilder(source);
sb = sb.Replace("$TS$", tsValue);
sb = sb.Replace("$DOC$", docValue);
string output = sb.ToString();
s.Stop();
Console.WriteLine("StringBuilder.Replace:\t\t" + s.ElapsedMilliseconds);
Я не видел большой разницы на моей машине (string.replace был 85ms и stringbuilder.replace был 80), и это было против около 8 МБ текста в "source"...
Вот мой тест:
using System;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
internal static class MeasureTime
{
internal static TimeSpan Run(Action func, uint count = 1)
{
if (count <= 0)
{
throw new ArgumentOutOfRangeException("count", "Must be greater than zero");
}
long[] arr_time = new long[count];
Stopwatch sw = new Stopwatch();
for (uint i = 0; i < count; i++)
{
sw.Start();
func();
sw.Stop();
arr_time[i] = sw.ElapsedTicks;
sw.Reset();
}
return new TimeSpan(count == 1 ? arr_time.Sum() : Convert.ToInt64(Math.Round(arr_time.Sum() / (double)count)));
}
}
public class Program
{
public static string RandomString(int length)
{
Random random = new Random();
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new String(Enumerable.Range(1, length).Select(_ => chars[random.Next(chars.Length)]).ToArray());
}
public static void Main()
{
string rnd_str = RandomString(500000);
Regex regex = new Regex("a|c|e|g|i|k", RegexOptions.Compiled);
TimeSpan ts1 = MeasureTime.Run(() => regex.Replace(rnd_str, "!!!"), 10);
Console.WriteLine("Regex time: {0:hh\\:mm\\:ss\\:fff}", ts1);
StringBuilder sb_str = new StringBuilder(rnd_str);
TimeSpan ts2 = MeasureTime.Run(() => sb_str.Replace("a", "").Replace("c", "").Replace("e", "").Replace("g", "").Replace("i", "").Replace("k", ""), 10);
Console.WriteLine("StringBuilder time: {0:hh\\:mm\\:ss\\:fff}", ts2);
TimeSpan ts3 = MeasureTime.Run(() => rnd_str.Replace("a", "").Replace("c", "").Replace("e", "").Replace("g", "").Replace("i", "").Replace("k", ""), 10);
Console.WriteLine("String time: {0:hh\\:mm\\:ss\\:fff}", ts3);
char[] ch_arr = {'a', 'c', 'e', 'g', 'i', 'k'};
TimeSpan ts4 = MeasureTime.Run(() => new String((from c in rnd_str where !ch_arr.Contains(c) select c).ToArray()), 10);
Console.WriteLine("LINQ time: {0:hh\\:mm\\:ss\\:fff}", ts4);
}
}
Время регулярного выражения: 00:00:00:008
Время StringBuilder: 00:00:00:015
Время строки: 00: 00: 00: 005
LINQ не может обработать rnd_str (фатальная ошибка: превышен лимит использования памяти)
String.Replace является самым быстрым
StringBuilder: http://msdn.microsoft.com/en-us/library/2839d5h5.aspx
Производительность самой операции Replace должна быть примерно такой же, как и у string.Replace, и, по мнению Microsoft, мусора не должно быть.
StringBuilder sb = new StringBuilder("Hello string");
sb.Replace("string", String.Empty);
Console.WriteLine(sb);
StringBuilder, изменяемая строка.
Поскольку у вас есть несколько замен в одной строке, я рекомендую вам использовать RegEx вместо StringBuilder.
Если вы хотите встроенный класс в dotnet, я думаю, что StringBuilder является лучшим. чтобы сделать это вручную, вы можете использовать небезопасный код с символом char*, перебирать вашу строку и заменять в зависимости от ваших критериев