String.Replace() vs. StringBuilder.Replace()
У меня есть строка, в которой мне нужно заменить маркеры на значения из словаря. Он должен быть как можно более эффективным. Выполнение цикла с помощью string.replace просто будет потреблять память (строки неизменяемы, помните). Будет ли StringBuilder.Replace() лучше, так как это было разработано для работы со строковыми манипуляциями?
Я надеялся избежать расходов RegEx, но если это будет более эффективным, пусть так и будет.
Примечание. Меня не интересует сложность кода, только то, как быстро он работает, и память, которую он потребляет.
Средняя статистика: 255-1024 символов в длине, 15-30 ключей в словаре.
Ответы
Ответ 1
Использование RedGate Profiler с использованием следующего кода
class Program
{
static string data = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
static Dictionary<string, string> values;
static void Main(string[] args)
{
Console.WriteLine("Data length: " + data.Length);
values = new Dictionary<string, string>()
{
{ "ab", "aa" },
{ "jk", "jj" },
{ "lm", "ll" },
{ "yz", "zz" },
{ "ef", "ff" },
{ "st", "uu" },
{ "op", "pp" },
{ "x", "y" }
};
StringReplace(data);
StringBuilderReplace1(data);
StringBuilderReplace2(new StringBuilder(data, data.Length * 2));
Console.ReadKey();
}
private static void StringReplace(string data)
{
foreach(string k in values.Keys)
{
data = data.Replace(k, values[k]);
}
}
private static void StringBuilderReplace1(string data)
{
StringBuilder sb = new StringBuilder(data, data.Length * 2);
foreach (string k in values.Keys)
{
sb.Replace(k, values[k]);
}
}
private static void StringBuilderReplace2(StringBuilder data)
{
foreach (string k in values.Keys)
{
data.Replace(k, values[k]);
}
}
}
- String.Replace = 5.843ms
- StringBuilder.Replace # 1 = 4.059ms
- Stringbuilder.Replace # 2 = 0.461ms
Длина строки = 1456
stringbuilder # 1 создает строковый конструктор в методе, а # 2 - это не так, что разница в производительности окажется такой же, скорее всего, поскольку вы просто перемещаете эту работу из метода. Если вы начинаете с stringbuilder вместо строки, тогда вместо # 2 может быть вместо этого.
Что касается памяти, использующей профилировщик RedGateMemory, вам не о чем беспокоиться, пока вы не перейдете в МНОГИЕ операции замены, в которых построитель построит в целом.
Ответ 2
Это может быть полезно:
http://blogs.msdn.com/b/debuggingtoolbox/archive/2008/04/02/comparing-regex-replace-string-replace-and-stringbuilder-replace-which-has-better-performance.aspx
Короткий ответ кажется, что String.Replace работает быстрее, хотя это может иметь большее влияние на ваши служебные данные по объему памяти/мусора.
Ответ 3
Да, StringBuilder
даст вам как прирост скорости, так и память (в основном потому, что он не будет создавать экземпляр строки каждый раз, когда вы будете манипулировать им - StringBuilder
всегда работает с одним и тем же объектом), Вот ссылка
Ответ 4
Будет ли stringbuilder.replace лучше [чем String.Replace]
Да, намного лучше. И если вы можете оценить верхнюю границу для новой строки (она выглядит так, как вы можете), то она, вероятно, будет достаточно быстрой.
Когда вы создаете его, как:
var sb = new StringBuilder(inputString, pessimisticEstimate);
то StringBuilder не придется перераспределять свой буфер.
Ответ 5
Преобразование данных из String в StringBuilder и обратно займет некоторое время. Если вы выполняете только одну операцию замены, это время не может быть компенсировано улучшением эффективности, присущим StringBuilder. С другой стороны, если один преобразует строку в StringBuilder, то выполняет много операций Replace на ней и преобразует ее обратно в конец, подход StringBuilder может быть быстрее.
Ответ 6
Вместо того, чтобы выполнять 15-30 операций замены во всей строке, было бы более эффективно использовать что-то вроде структуры trie для хранения словаря. Затем вы можете прокручивать строку ввода один раз, чтобы выполнить весь поиск/замену.
Ответ 7
Это будет зависеть от того, сколько маркеров присутствует в данной строке в среднем.
Производительность поиска ключа, вероятно, будет одинаковой между StringBuilder и String, но StringBuilder победит, если вам придется заменить многие маркеры в одной строке.
Если вы ожидаете только одного или двух маркеров в строке в среднем, а ваш словарь невелик, я просто перейду к String.Replace.
Если существует много маркеров, вы можете определить собственный синтаксис для идентификации маркеров - например, заключая в фигурные скобки подходящее правило выхода для литеральной скобки. Затем вы можете реализовать алгоритм синтаксического анализа, который повторяется через символы строки один раз, распознавая и заменяя каждый найденный маркер. Или используйте регулярное выражение.
Ответ 8
Мои два цента здесь, я написал пару строк кода, чтобы проверить, как работает каждый метод, и, как и ожидалось, результат "зависит".
Для более длинных строк Regex
, похоже, работает лучше, для более коротких строк String.Replace
это так. Я вижу, что использование StringBuilder.Replace
не очень полезно, и если оно ошибочно используется, оно может быть летальным в перспективе GC (я попытался разделить один экземпляр StringBuilder
).
Проверьте StringReplaceTests GitHub repo.
Ответ 9
Проблема с ответом @DustinDavis заключается в том, что он рекурсивно работает с одной строкой. Если вы не планируете делать манипуляции типа "back-and-forth", у вас действительно должны быть отдельные объекты для каждого случая манипуляции в этом типе теста.
Я решил создать свой собственный тест, потому что нашел некоторые противоречивые ответы по всему Интернету, и я хотел быть абсолютно уверенным. Программа, над которой я работаю, обрабатывает множество текстов (файлы с десятками тысяч строк в некоторых случаях).
Итак, здесь быстрый метод, который вы можете скопировать и вставить и увидеть сами, что быстрее. Возможно, вам придется создать свой собственный текстовый файл для тестирования, но вы можете легко скопировать и вставить текст из любого места и сделать для вас достаточно большой файл:
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows;
void StringReplace_vs_StringBuilderReplace( string file, string word1, string word2 )
{
using( FileStream fileStream = new FileStream( file, FileMode.Open, FileAccess.Read ) )
using( StreamReader streamReader = new StreamReader( fileStream, Encoding.UTF8 ) )
{
string text = streamReader.ReadToEnd(),
@string = text;
StringBuilder @StringBuilder = new StringBuilder( text );
int iterations = 10000;
Stopwatch watch1 = new Stopwatch.StartNew();
for( int i = 0; i < iterations; i++ )
if( i % 2 == 0 ) @string = @string.Replace( word1, word2 );
else @string = @string.Replace( word2, word1 );
watch1.Stop();
double stringMilliseconds = watch1.ElapsedMilliseconds;
Stopwatch watch2 = new Stopwatch.StartNew();
for( int i = 0; i < iterations; i++ )
if( i % 2 == 0 ) @StringBuilder = @StringBuilder .Replace( word1, word2 );
else @StringBuilder = @StringBuilder .Replace( word2, word1 );
watch2.Stop();
double StringBuilderMilliseconds = watch1.ElapsedMilliseconds;
MessageBox.Show( string.Format( "string.Replace: {0}\nStringBuilder.Replace: {1}",
stringMilliseconds, StringBuilderMilliseconds ) );
}
}
Я получил эту строку. Замените() быстрее на 20% каждый раз, заменяя 8-10 буквенных слов. Попробуйте сами, если вы хотите, чтобы ваши собственные эмпирические данные.