将超过 300k 条记录导出到 Excel 会生成 System.OutOfMemoryException



我正在尝试导出超过 300k 条记录到 excel,但我真的不想为此使用任何 dll。

我创建了一个模拟该问题的示例应用程序。下面是具有生成虚拟数据表并将数据表导出到 excel 的方法的类。

public class ExcelCreator
    {
        /// <summary>
        /// Create one Excel-XML-Document with SpreadsheetML from a DataTable
        /// </summary>
        /// <param name="dataSource">Datasource which would be exported in Excel</param>
        /// <param name="fileName">Name of exported file</param>

        public static DataTable GiveDummyDataTable()
        {
            DataTable dt = new DataTable();
            dt.Columns.Add("abc");
            dt.Columns.Add("bcd");
            dt.Columns.Add("dfd");
            dt.Columns.Add("wer");
            dt.Columns.Add("werw");
            dt.Columns.Add("rete");
            dt.Columns.Add("lkj");
            dt.Columns.Add("ert");
            dt.Columns.Add("poi");
            dt.Columns.Add("wers");
            dt.Columns.Add("mnb");
            dt.Columns.Add("oiwu");
            dt.Columns.Add("qwe");
            dt.Columns.Add("uio");
            for (int i = 0; i < 500000; i++)
            {
                dt.Rows.Add(new object[] { "babo", 120, "poi", "123 3428749020", 35, "6.000", "$24,590", "$13,432",
            "$12,659", "12/13/21", "1/30/27", 55, "sonumonu", "wer"});
            }

            return dt;
        }
        public static bool sonaKaExcelBanao(DataTable dt, string filename)
        {
            try
            {
                string sTableStart = @"<HTML><BODY><TABLE Border=1>";
                string sTableEnd = @"</TABLE></BODY></HTML>";
                string sTHead = "<TR>";
                StringBuilder sTableData = new StringBuilder();
                foreach (DataColumn col in dt.Columns)
                {
                    sTHead += @"<TH>" + col.ColumnName + @"</TH>";
                }
                sTHead += @"</TR>";
                foreach (DataRow sonurow in dt.Rows)
                {
                    sTableData.Append(@"<TR>");
                    for (int i = 0; i < dt.Columns.Count; i++)
                    {
                        sTableData.Append(@"<TD>" + sonurow[i].ToString() + @"</TD>");
                    }
                    sTableData.Append(@"</TR>");
                }
                string sTable = sTableStart + sTHead + sTableData.ToString() + sTableEnd;
                System.IO.StreamWriter oExcelWriter = System.IO.File.CreateText(filename);
                oExcelWriter.WriteLine(sTable);
                oExcelWriter.Close();
                return true;
            }
            catch
            {
                return false;
            }
        }
    }

下面给出的是我如何调用这些方法。

DataTable dt = ExcelCreator.GiveDummyDataTable();          
            ExcelCreator.sonaKaExcelBanao(dt, @"c:chunchuntaiyar.xls");

这是我得到的错误。

抛出类型为"System.OutOfMemoryException"的异常。

它发生在下一行。

string sTable = sTableStart + sTHead + sTableData.ToString() + sTableEnd;

有时,它也可以工作。如果不模拟,您可以尝试将循环计数从 300k 增加到 500k。

我正在使用 excel 2007/2010。

你绝对是在浪费那段代码的内存。虽然你确实利用了StringBuilder,这可能会阻止你更快地耗尽内存,但你错过了几个更有效地使用StringBuilder的机会。例如,您可以将其用于完整的构建。

从您的代码中获取此示例:

sTHead += @"<TH>" + col.ColumnName + @"</TH>";

该行上的每一个 + 都会创建一个新字符串来保存结果。请改用 StringBuilder

这是您已经拥有 StringBuilder 的地方,但您的代码行仍然分配额外的字符串:

sTableData.Append(@"<TD>" + sonurow[i].ToString() + @"</TD>");

你可以在那里使用AppendFormat

sTableData.AppendFormat(@"<TD>{0}</TD>", sonurow[i]);

因此,字符串将被有效地复制到 StringBuilder 实例的内部缓冲区。

我选择了一个没有任何StringBuilders的解决方案。只需直接写出到流中:

public static bool ExcelExport(DataTable dt, string filename)
{
    try
    {
        // using makes sure the streamwriter gets closed and disposed
        using (StreamWriter oExcelWriter = File.CreateText(filename))
        {
            // leadin
            oExcelWriter.Write(@"<HTML><BODY><TABLE Border=1>");
            //header
            oExcelWriter.Write("<TR>");
            foreach (DataColumn col in dt.Columns)
            {
                oExcelWriter.Write(@"<TH>");
                oExcelWriter.Write(col.ColumnName);
                oExcelWriter.Write( @"</TH>");
            }
            oExcelWriter.Write("</TR>");
            // body 
            foreach (DataRow sonurow in dt.Rows)
            {
                oExcelWriter.Write(@"<TR>");
                for (int i = 0; i < dt.Columns.Count; i++)
                {
                    oExcelWriter.Write(@"<TD>");
                    oExcelWriter.Write(sonurow[i]); // calls ToString in the overload
                    oExcelWriter.Write(@"</TD>");
                }
                oExcelWriter.Write(@"</TR>");
            }
            // leadout
            oExcelWriter.WriteLine(@"</TABLE></BODY></HTML>");
        }
    }
    catch(Exception exp)
    {
            Trace.WriteLine(exp.Message);
            return false;
    }
    return true;
}

这不会执行更多的分配,甚至应该适用于较大的数据表。

最新更新