为啥在 C# .NET 中写入 Excel 范围所需的时间比预期的要长得多?

Posted

技术标签:

【中文标题】为啥在 C# .NET 中写入 Excel 范围所需的时间比预期的要长得多?【英文标题】:Why does writing to an Excel range take much longer than expected in C# .NET?为什么在 C# .NET 中写入 Excel 范围所需的时间比预期的要长得多? 【发布时间】:2014-11-27 01:02:08 【问题描述】:

我在 C# 中构建了一个脚本,该脚本采用 CSV 格式的大型数据文件,并拆分为 Excel 2007+ 格式的两个输出文件。我有满足所有要求的完整工作代码,但在相对较小的源文件上运行我的 save_files() 方法需要大约 15 秒。我想知道是否有更快的方法来做我正在做的事情。

第一个输出最终将多达 180 列(每列 125,000 个点)写入 excel 文件。 (代码的 15 秒运行只使用了 20 列)。 output1_temp_array 是一个 List<string[,]>,每个列表项都包含一个包含 125k 数据点的字符串数组。 (它被定义为二维数组,因为 Excel 的 Range.Value2 需要一个二维数组,但实际上它是 125k x 1 项宽。)

第二个输出最终以 1 行写入 195 列。填充data_temp_array 的方式(它也是List<string[,]),我必须将其转置为temp_array,然后将该temp_array 写入Excel 范围。

以下是部分代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;
using MyExcel = Microsoft.Office.Interop.Excel;

namespace TransposeAThing

    public partial class Form1 : Form
    
        private void save_files()
        
            MyExcel.Application excelApp = null;
            MyExcel.Workbook excelWorkbook = null;
            MyExcel.Worksheet worksheet = null;
            MyExcel.Range range = null;
            excelApp = new MyExcel.Application();
            excelApp.DefaultSaveFormat = XlFileFormat.xlOpenXMLWorkbook;
            excelApp.Visible = false;

            //Write data into first output file
            excelWorkbook = excelApp.Workbooks.Open(xlsOutput1_Filename, 1, false, 5, "", "", false, MyExcel.XlPlatform.xlWindows, "", true, false, 0, true, false, false);
            worksheet = excelWorkbook.Worksheets.get_Item("Output Data");

            for (int i = 0; i < output1_temp_array.Count(); i++)
            
                range = worksheet.get_Range((MyExcel.Range)worksheet.Cells[1, i + 2], (MyExcel.Range)worksheet.Cells[output1_temp_array[i].Length, i + 2]);
                range.set_Value(Type.Missing,output1_temp_array[i]);
            
            excelWorkbook.Save();
            excelWorkbook.Close();

            //Write data into second output file
            string[,] temp_array;
            temp_array = new string[1,data_temp_array.Count()];

            for (int i = 0; i < data_temp_array.Count(); i++)
            
                temp_array[0,i] = data_temp_array[i][0,0];
            

            excelWorkbook = excelApp.Workbooks.Open(xlsData_Filename, 1, false, 5, "", "", false, MyExcel.XlPlatform.xlWindows, "", true, false, 0, true, false, false);
            worksheet = excelWorkbook.Worksheets.get_Item("Aggregate Data");

            int start_row = worksheet.UsedRange.Rows.Count + 1;
            range = worksheet.get_Range((MyExcel.Range)worksheet.Cells[start_row, 1], (MyExcel.Range)worksheet.Cells[start_row, worksheet.UsedRange.Columns.Count]);

            range.Value2 = temp_array;
            excelWorkbook.Save();

            GC.Collect();
            GC.WaitForPendingFinalizers();

            if (worksheet != null)
            
                Marshal.FinalReleaseComObject(worksheet);
            
            if (excelWorkbook != null)
            
                excelWorkbook.Close(true, Type.Missing, Type.Missing);
                Marshal.FinalReleaseComObject(excelWorkbook);
            
            if (excelApp != null)
            
                excelApp.Quit();
                Marshal.FinalReleaseComObject(excelApp);
            
        
    

您知道为什么需要这么长时间才能完成此过程吗?有关如何加快速度的任何提示?

作为参考,我编写的用于执行相同操作的 Python 脚本在相同数据上大约需要 2.3 秒,所以我知道这可能比现在更快。

【问题讨论】:

您是否考虑过使用 OpenXML SDK 而不是 COM?它可能会更快,因为它不必打开 excel。显然,这将意味着重大的重构,但如果您遇到性能问题,则值得考虑。 我同意 Greg 的观点,使用 Openxml 是一种更好的方法。如果它不需要太多重构,你可能想要采用 openxml 方式。 你知道你代码的哪一部分是瓶颈吗?是在构建数据吗?写入文件?一些分析可以帮助您缩小范围。 我将研究 OpenXML。我发布的代码几乎是瓶颈;构建数组的所有代码都需要不到半秒的时间(包括将所有 15MB 以上的数据从 CSV 读取到源数组中)。即使我注释掉 output1 写入,也需要 5 秒。 【参考方案1】:

所以在大量深入研究 OpenXML 之后,我采用了一种混合解决方案,它并没有真正解决问题,但至少让它更快。

对于第一个输出文件,我坚持原来的方法。我试了SpreadsheetLight,发现没有writerow或者range.setValue()方法,所以写了很多单元格,最后都是嵌套的for循环。这被证明是非常慢。

private void save_files()

    MyExcel.Application excelApp = null;
    MyExcel.Workbook excelWorkbook = null;
    MyExcel.Worksheet worksheet = null;
    MyExcel.Range range = null;
    excelApp = new MyExcel.Application();
    excelApp.DefaultSaveFormat = XlFileFormat.xlOpenXMLWorkbook;
    excelApp.Visible = false;

    excelWorkbook = excelApp.Workbooks.Open(xlsOutput1_Filename, 1, false, 5, "", "", false, MyExcel.XlPlatform.xlWindows, "", true, false, 0, true, false, false);
    worksheet = excelWorkbook.Worksheets.get_Item("Output Data");

    for (int i = 0; i < output1_temp_array.Count(); i++)
    
        range = worksheet.get_Range((MyExcel.Range)worksheet.Cells[1, i + 2], (MyExcel.Range)worksheet.Cells[output1_temp_array[i].Length, i + 2]);
        range.set_Value(Type.Missing, output1_temp_array[i]);
    
    excelWorkbook.Save();
    excelWorkbook.Close();

然而,对于第二个数据输出,SpreadsheetLight 肯定会缩短时间:

    SLDocument data_file = new SLDocument(xlsData_Filename, "Aggregate Data");
    SLWorksheetStatistics data_file_info = new SLWorksheetStatistics();
    data_file_info = data_file.GetWorksheetStatistics();
    int start_row = data_file_info.NumberOfRows + 1;

    for (int i = 0; i < data_temp_array.Count(); i++)
    
        data_file.SetCellValue(start_row, i + 1, data_temp_array[i]);
    

使用此解决方案,现在生成相同的文件需要 8.5 秒而不是 15 秒。

【讨论】:

以上是关于为啥在 C# .NET 中写入 Excel 范围所需的时间比预期的要长得多?的主要内容,如果未能解决你的问题,请参考以下文章

C# .NET 工具类 一Excel 读取与写入

c#如何将html标签写入指定的excel中

.NET Core(C#) EPPlus写入保存Excel(.xlsx)文件的方法及示例代码

.NET Core(C#) EPPlus创建Excel(.xlsx)写入数据的方法及示例代码

.Net下C#针对Excel开发控件汇总(ClosedXML,EPPlus,NPOI)

C#将一个excel工作表根据指定范围拆分为多个excel文件