Самое простое, что вы можете сделать, это объявить StringBuilder с емкостью, отличной от значения по умолчанию, например
StringBuilder builder = new StringBuilder(100000);
Распределение по умолчанию составляет 16 байтов и удваивается каждый раз, когда его необходимо перераспределить. Это означает, что он будет перераспределяться много раз, если вы используете значение по умолчанию.
Если у вашей системы недостаточно памяти или она действительно очень велика, я сомневаюсь, что потоковая передача напрямую, как предлагалось ранее, будет иметь большое значение. Я подозреваю, что на самом деле это может немного ухудшить ситуацию, поскольку я сомневаюсь, что при записи потока файлов меньше накладных расходов, чем при добавлении данных в уже размещенный объект StreamBuilder (при условии, что его не нужно часто перераспределять!)
Оптимальным решением может быть периодическая отправка вывода stringbuilder в поток по мере его увеличения до некоторого размера (в зависимости от памяти вашей системы), если он может превышать, скажем, 10 или 20 мегабайт. Таким образом вы избежите проблем с памятью, а также избежите любых потенциальных издержек, связанных со многими небольшими записями в выходной поток.
Обновление - примечание по тестированию:
Я провел несколько тестов, создавая очень большие строки (> 50 мегабайт), и разница в распределении памяти заранее невелика.
Но, что более важно, количество времени, необходимое для создания такой строки в простейшей форме:
for (int i = 0; i < 10000000; i++)
{
builder.AppendLine("a whole bunch of text designed to see how long it takes to build huge strings ");
}
почти несущественно. Я могу заполнить всю память моего настольного компьютера за пару секунд.
Это означает, что издержки StringBuilder вовсе не являются вашей проблемой. Из этого также можно сделать вывод, что переключение на запись потока определенно вам тоже не поможет.
Вместо этого вам нужно взглянуть на некоторые операции, которые вы выполняете тысячи или десятки тысяч раз. Этот цикл ::
foreach (DataRow eachRow in table.Rows)
{
builder.AppendLine("<Row>");
foreach (DataColumn eachColumn in table.Columns)
{
if (eachColumn.ColumnName != "ID")
{
builder.AppendLine(FormatCell(eachColumn.ColumnName, eachRow[eachColumn]));
}
}
builder.AppendLine("</Row>");
}
- Устранить чек на
ColumnName! = "ID", удалив это
от вашего выбора
- FormatCell запускается один раз для каждого элемента данных. Незначительные изменения эффективности могут оказать огромное влияние
- Раньше об этом не думал, но если ваш DataTable исходит из источника данных SQL, используйте DataReader напрямую вместо DataTable в памяти
Предложение по улучшению FormatCell:
- Заранее создайте индекс типов данных каждого столбца, чтобы вам не приходилось каждый раз сравнивать дорогостоящие типы
- Установка строковых значений для Типа и Стиля и их изменение в зависимости от типа данных стоит дорого. Вместо этого используйте перечисления, а затем выводите значения с использованием жестко закодированных строк на основе значения перечисления.
- Переместите все переменные внутри FormatCell в основной класс, чтобы их не нужно было создавать / выделять при каждом вызове процедуры
Для построения индекса, я думаю, наиболее эффективным способом было бы сопоставить номера столбцов с массивом, который определяет тип для каждого столбца, как в приведенном ниже коде, а затем в FormatCell просто использовать предварительно составленную карту номеров столбцов для Типы данных.
enum DataTypes
{
DateTime = 1,
Float = 2,
Int = 3,
String = 4
}
DataTypes[] types = new DataTypes[tbl.Columns.Count];
for (int col=0;i<tbl.Columns.Count;col++) {
object value = tbl.Rows[0][col];
if (value is double || value is float || value is decimal) {
types[col]=DataTypes.Float;
} else if (value is DateTime) {
types[col]=DataTypes.DateTime;
} else if (value is int) {
types[col]=DataTypes.Int;
} else {
types[col]=DataTypes.String;
}
}
Затем передайте FormatCell номер столбца, и он сможет найти тип данных из массива и просто проверить с помощью переключателя:
switch(types[colNumber]) {
case DataTypes.DateTime:
...
break;
case DataTypes.Int:
...
/// and so on
}
Я думаю, это сильно сократило бы накладные расходы.