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>
Template
<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
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);
}
}