Optimization 使用XLS操作提高性能
我有一些简单的方法可以使用字符串将DataTable导出到XLS。列数为5-30,行数可能在1到1000之间。有时会出现性能问题,我想知道我可以在代码中修改什么。我正在使用.NET4.0Optimization 使用XLS操作提高性能,optimization,c#-4.0,performance,xls,Optimization,C# 4.0,Performance,Xls,我有一些简单的方法可以使用字符串将DataTable导出到XLS。列数为5-30,行数可能在1到1000之间。有时会出现性能问题,我想知道我可以在代码中修改什么。我正在使用.NET4.0 public string FormatCell(string columnName, object value) { StringBuilder builder = new StringBuilder(); string formattedValue = str
public string FormatCell(string columnName, object value)
{
StringBuilder builder = new StringBuilder();
string formattedValue = string.Empty;
string type = "String";
string style = "s21";
if (!(value is DBNull) && columnName.Contains("GIS"))
formattedValue = Convert.ToDouble(value).ToString("##.00000000°");
else if (value is DateTime)
{
style = "s22";
type = "DateTime";
DateTime date = (DateTime)value;
formattedValue = date.ToString("yyyy-MM-ddTHH:mm:ss.fff");
}
else if (value is double || value is float || value is decimal)
{
formattedValue = Convert.ToDecimal(value).ToString("#.00").Replace(',', '.');
type = "Number";
}
else if (value is int)
{
formattedValue = value.ToString();
type = "Number";
}
else
formattedValue = value.ToString();
builder.Append(string.Format("<Cell ss:StyleID=\"{0}\"><Data ss:Type=\"{1}\">", style, type));
builder.Append(formattedValue);
builder.AppendLine("</Data></Cell>");
return builder.ToString();
}
public string ConvertToXls(DataTable table)
{
StringBuilder builder = new StringBuilder();
int rows = table.Rows.Count + 1;
int cols = table.Columns.Count;
builder.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
builder.AppendLine("<?mso-application progid=\"Excel.Sheet\"?>");
builder.AppendLine("<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"");
builder.AppendLine(" xmlns:o=\"urn:schemas-microsoft-com:office:office\"");
builder.AppendLine(" xmlns:x=\"urn:schemas-microsoft-com:office:excel\"");
builder.AppendLine(" xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\"");
builder.AppendLine(" xmlns:html=\"http://www.w3.org/TR/REC-html40/\">");
builder.AppendLine(" <DocumentProperties xmlns=\"urn:schemas-microsoft-com:office:office\">;");
builder.AppendLine(" <Author>Author</Author>");
builder.AppendLine(string.Format(" <Created>{0}T{1}Z</Created>", DateTime.Now.ToString("yyyy-mm-dd"), DateTime.Now.ToString("HH:MM:SS")));
builder.AppendLine(" <Company>Company</Company>");
builder.AppendLine(" <Version>1.0</Version>");
builder.AppendLine(" </DocumentProperties>");
builder.AppendLine(" <ExcelWorkbook xmlns=\"urn:schemas-microsoft-com:office:excel\">");
builder.AppendLine(" <WindowHeight>8955</WindowHeight>");
builder.AppendLine(" <WindowWidth>11355</WindowWidth>");
builder.AppendLine(" <WindowTopX>480</WindowTopX>");
builder.AppendLine(" <WindowTopY>15</WindowTopY>");
builder.AppendLine(" <ProtectStructure>False</ProtectStructure>");
builder.AppendLine(" <ProtectWindows>False</ProtectWindows>");
builder.AppendLine(" </ExcelWorkbook>");
builder.AppendLine(" <Styles>");
builder.AppendLine(" <Style ss:ID=\"Default\" ss:Name=\"Normal\">");
builder.AppendLine(" <Alignment ss:Vertical=\"Bottom\"/>");
builder.AppendLine(" <Borders/>");
builder.AppendLine(" <Font/>");
builder.AppendLine(" <Interior/>");
builder.AppendLine(" <Protection/>");
builder.AppendLine(" </Style>");
builder.AppendLine(" <Style ss:ID=\"s21\">");
builder.AppendLine(" <Alignment ss:Vertical=\"Bottom\" ss:WrapText=\"1\"/>");
builder.AppendLine(" </Style>");
builder.AppendLine(" <Style ss:ID=\"s22\">");
builder.AppendLine(" <NumberFormat ss:Format=\"Short Date\"/>");
builder.AppendLine(" </Style>");
builder.AppendLine(" </Styles>");
builder.AppendLine(" <Worksheet ss:Name=\"Export\">");
builder.AppendLine(string.Format(" <Table ss:ExpandedColumnCount=\"{0}\" ss:ExpandedRowCount=\"{1}\" x:FullColumns=\"1\"", cols.ToString(), rows.ToString()));
builder.AppendLine(" x:FullRows=\"1\">");
//generate title
builder.AppendLine("<Row>");
foreach (DataColumn eachColumn in table.Columns) // you can write a half columns of table and put the remaining columns in sheet2
{
if (eachColumn.ColumnName != "ID")
{
builder.Append("<Cell ss:StyleID=\"s21\"><Data ss:Type=\"String\">");
builder.Append(eachColumn.ColumnName.ToString());
builder.AppendLine("</Data></Cell>");
}
}
builder.AppendLine("</Row>");
//generate data
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>");
}
builder.AppendLine(" </Table>");
builder.AppendLine(" <WorksheetOptions xmlns=\"urn:schemas-microsoft-com:office:excel\">");
builder.AppendLine(" <Selected/>");
builder.AppendLine(" <Panes>");
builder.AppendLine(" <Pane>");
builder.AppendLine(" <Number>3</Number>");
builder.AppendLine(" <ActiveRow>1</ActiveRow>");
builder.AppendLine(" </Pane>");
builder.AppendLine(" </Panes>");
builder.AppendLine(" <ProtectObjects>False</ProtectObjects>");
builder.AppendLine(" <ProtectScenarios>False</ProtectScenarios>");
builder.AppendLine(" </WorksheetOptions>");
builder.AppendLine(" </Worksheet>");
builder.AppendLine(" <Worksheet ss:Name=\"Sheet2\">");
builder.AppendLine(" <WorksheetOptions xmlns=\"urn:schemas-microsoft-com:office:excel\">");
builder.AppendLine(" <ProtectObjects>False</ProtectObjects>");
builder.AppendLine(" <ProtectScenarios>False</ProtectScenarios>");
builder.AppendLine(" </WorksheetOptions>");
builder.AppendLine(" </Worksheet>");
builder.AppendLine(" <Worksheet ss:Name=\"Sheet3\">");
builder.AppendLine(" <WorksheetOptions xmlns=\"urn:schemas-microsoft-com:office:excel\">");
builder.AppendLine(" <ProtectObjects>False</ProtectObjects>");
builder.AppendLine(" <ProtectScenarios>False</ProtectScenarios>");
builder.AppendLine(" </WorksheetOptions>");
builder.AppendLine(" </Worksheet>");
builder.AppendLine("</Workbook>");
return builder.ToString();
}
嗯,你只是在内存中创建一个越来越大的字符串…所以随着大小的增加,情况会越来越糟 有什么理由不在运行时将其流式输出到文件中,而不是构建一个巨大的字符串,然后将其序列化到文件中 添加详细信息后编辑: 不要让ConvertToXL返回字符串,而是将该streamwriter传递给ConvertToXL方法
public void ConvertToXLS( DataTable table, StreamWriter stream )
{
...
}
在ConverToXLS内部,去掉StringBuilder,并替换builder.AppendLine(x)
to的所有调用
stream.WriteLine(x);
这样,您就可以向流中写入数据,而不是创建一个巨大的字符串。您应该使用类似dotTrace的东西来分析代码,以查看时间的走向。至少要放上计时器,看看每个部分需要多长时间。在不知道瓶颈在哪里的情况下进行优化很可能是浪费时间。例如:
DateTime startTime = DateTime.Now;
Debug.WriteLine("Start : " + startTime);
//some code
Debug.WriteLine("End: " + DateTime.Now);
Debug.WriteLine("Elapsed : " + (DateTime.Now - startTime));
不过我认为上面的约翰是对的。使用流。例如
StreamWriter streamWriter = System.IO.File.CreateText("c:\\mynewfile.xls");
streamWriter.AutoFlush = false;
//lots of writes
streamWriter.Flush();
streamWriter.Close();
您应该使用autoflush false和true进行测试。您可能还想尝试一个内存流
StreamWriter streamWriter = new StreamWriter(new MemoryStream());
您可以做的最简单的事情是使用默认值以外的容量声明StringBuilder,例如
StringBuilder builder = new StringBuilder(100000);
默认分配为16个字节,每次需要重新分配时会加倍。这意味着,如果使用默认值,它将被重新分配多次
除非你的系统内存很紧,或者这是非常非常巨大的,否则我怀疑像以前建议的那样直接流式传输会有很大的不同。我怀疑这实际上可能会让事情变得更糟,因为我怀疑,与向已分配的StreamBuilder对象添加数据相比,文件流写入的开销更小(假设不需要频繁重新分配!)
最佳的解决方案可能是在stringbuilder输出增长到一定大小(基于系统内存)时定期将其发送到流,如果它可能超过10或20兆字节。这样可以避免内存问题,也可以避免与对输出流的许多小写入相关的任何潜在开销
更新-测试说明:
我运行了一些创建非常大的字符串(>50MB)的测试,在预先分配内存方面没有明显的差别
但更重要的是,使用最简单的形式创建这样一个字符串所需的时间:
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 ");
}
我认为这会减少很多开销。与其将行写两次,一次在内存中,然后再写到磁盘,不如尝试将其简化为一次写操作。直接到磁盘 我不知道.net和stringbuilder中的xml对象之间的性能比较是什么样的,但如果我知道我在编写xml,我会倾向于使用xml对象解决方案、xmlwriter xlinq等。知道您每次都按时生成符合xml的数据是非常令人放心的 SS上的其他帖子表示,他们认为使用XmlTextWriter比使用StringBuilder更快
关于更改缓冲区大小和延迟写入的答案是可行的,但可能会非常偶然,是的,如果您在内存中完成所有操作,您的操作会更快,但随后您的内存占用可能会非常大,因此操作系统可能会进行一些影响整个机器的磁盘交换(取决于您在机器上运行的内容)。找到令人满意的折衷方案,然后以您的生产系统满意的写入速度流式传输数据。流式传输?你是什么意思?你能给出一些例子或链接吗?我添加了如何使用此方法的代码。@user278618:流式传输是指不在一个大片段中发送数据,而是在小片段中连续发送。就像有一股水流通过管道流入你的房子,而不是每周都用一个大水箱。我在下面更新了我的答案。StringBuilder不是你的问题,我提出了一些建议来简化FormatCell,这几乎可以肯定是所有处理时间的方向。
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 ");
}
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>");
}
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;
}
}
switch(types[colNumber]) {
case DataTypes.DateTime:
...
break;
case DataTypes.Int:
...
/// and so on
}