添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
绅士的冰淇淋  ·  MYSQL ...·  4 天前    · 
呐喊的皮蛋  ·  第02篇 ...·  4 月前    · 
直爽的番茄  ·  AttributeError: ...·  12 月前    · 

背景和目的

本文介绍了几个常用的电子表格处理库,包括EPPlus、NPOI、Aspose.Cells和DocumentFormat.OpenXml,我们将对这些库进行性能测评,以便为开发人员提供实际的性能指标和数据。

下表将功能/特点、开源/许可证这两列分开,以满足需求:

功能 / 特点 EPPlus NPOI Aspose.Cells DocumentFormat.OpenXml
开源
许可证 MIT Apache 商业 MIT
支持的 Excel 版本 Excel 2007 及更高版本 Excel 97-2003 Excel 2003 及更高版本 Excel 2007 及更高版本

测评电脑配置

组件 规格
CPU 11th Gen Intel(R) Core(TM) i5-11320H @ 3.20GHz,2496 Mhz,4 个内核,8 个逻辑处理器
内存 40 GB DDR4 3200MHz
操作系统 Microsoft Windows 10 专业版
电源选项 已设置为高性能
软件 LINQPad 7.8.5 Beta
运行时 .NET 6.0.21

使用Bogus库生成6万条标准化的测试数据。

void Main()
    string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test-data.json");
    using var file = File.Create(path);
    using var writer = new Utf8JsonWriter(file, new JsonWriterOptions { Indented = true });
    var data = new Bogus.Faker<Data>()
        .RuleFor(x => x.Id, x => x.IndexFaker + 1)
        .RuleFor(x => x.Gender, x => x.Person.Gender)
        .RuleFor(x => x.FirstName, (x, u) => x.Name.FirstName(u.Gender))
        .RuleFor(x => x.LastName, (x, u) => x.Name.LastName(u.Gender))
        .RuleFor(x => x.Email, (x, u) => x.Internet.Email(u.FirstName, u.LastName))
        .RuleFor(x => x.BirthDate, x => x.Person.DateOfBirth)
        .RuleFor(x => x.Company, x => x.Person.Company.Name)
        .RuleFor(x => x.Phone, x => x.Person.Phone)
        .RuleFor(x => x.Website, x => x.Person.Website)
        .RuleFor(x => x.SSN, x => x.Person.Ssn())
        .GenerateForever().Take(6_0000)
        .Dump();
    JsonSerializer.Serialize(writer, data);
    Process.Start("explorer", @$"/select, ""{path}""".Dump());
 

Bogus输出结果

IdGenderFirstNameLastNameEmailBirthDateCompanyPhoneWebsiteSSN
1MaleAntonioPaucekAntonio.Paucek@gmail.com1987/10/31 5:46:50Moen, Willms and Maggio(898) 283-1583 x88626pamela.name850-06-4706
2MaleKurtGerholdKurt.Gerhold40@yahoo.com1985/11/1 18:41:01Wilkinson and Sons(698) 637-0181 x49124cordelia.net014-86-1757
3MaleHowardHegmannHoward2@hotmail.com1979/7/20 22:35:40Kassulke, Murphy and Volkman(544) 464-9818 x98381kari.com360-23-1669
4FemaleRosemariePowlowskiRosemarie.Powlowski48@hotmail.com1964/5/18 1:35:45Will Group1-740-705-6482laurence.net236-10-9925
5FemaleEuniceRogahnEunice84@gmail.com1979/11/25 11:53:14Rippin - Rowe(691) 491-2282 x3466yvette.net219-75-6886
……








创建公共类方便正式测评使用

void Main()
    string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\test-data.json";
    LoadUsers(path).Dump();
List<User> LoadUsers(string jsonfile)
    string path = jsonfile;
    byte[] bytes = File.ReadAllBytes(path);
    return JsonSerializer.Deserialize<List<User>>(bytes);
IObservable<object> Measure(Action action, int times = 5)
    return Enumerable.Range(1, times).Select(i =>
        var sw = Stopwatch.StartNew();
        long memory1 = GC.GetTotalMemory(true);
        long allocate1 = GC.GetTotalAllocatedBytes(true);
            action();
        long allocate2 = GC.GetTotalAllocatedBytes(true);
        long memory2 = GC.GetTotalMemory(true);
        sw.Stop();
        return new
            次数 = i, 
            分配内存 = (allocate2 - allocate1).ToString("N0"),
            内存提高 = (memory2 - memory1).ToString("N0"), 
            耗时 = sw.ElapsedMilliseconds,
    }).ToObservable();
class User
    public int Id { get; set; }
    public int Gender { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public DateTime BirthDate { get; set; }
    public string Company { get; set; }
    public string Phone { get; set; }
    public string Website { get; set; }
    public string SSN { get; set; }
 

1、上面的代码单位是字节 (bytes)

2 、其中IObservable(System.IObservable)是用于处理事件流的接口,它实现了观察者模式。它表示一个可观察的序列,可以产生一系列的事件,并允许其他对象(观察者)来订阅和接收这些事件。IObservable 适用于动态的、实时的事件流处理,允许观察者以异步方式接收事件,可以用于响应式编程、事件驱动的编程模型等。

3、GC.GetTotalAllocatedBytes(true) 获取分配内存大小
GC.GetTotalMemory(true) 获取占用内存大小

EPPlus

string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\test-data.json";
List<User> users = LoadUsers(path);
Measure(() =>
    Export(users, Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\export.epplus.xlsx");
}).Dump("EPPlus");
void Export<T>(List<T> data, string path)
    using var stream = File.Create(path);
    using var excel = new ExcelPackage(stream);
    ExcelWorksheet sheet = excel.Workbook.Worksheets.Add("Sheet1");
    PropertyInfo[] props = typeof(User).GetProperties();
    for (var i = 0; i < props.Length; ++i)
        sheet.Cells[1, i + 1].Value = props[i].Name;
    for (var i = 0; i < data.Count; ++i)
        for (var j = 0; j < props.Length; ++j)
            sheet.Cells[i + 2, j + 1].Value = props[j].GetValue(data[i]);
    excel.Save();
 

EPPlus (6.2.8) (2023/8/15)输出结果

次数ΞΞ分配内存ΞΞ内存提高ΞΞ耗时ΞΞ
1454,869,176970,1602447
2440,353,4881761776
3440,062,26401716
4440,283,58401750
5440,653,26401813

EPPlus (4.5.3.2)(2019/6/16)输出结果

次数ΞΞ分配内存ΞΞ内存提高ΞΞ耗时ΞΞ
1963,850,944192,0482765
2509,450,7926001897
3509,872,1604241920
4509,858,5764241989
5509,651,5124242076

由此看出 相比2019,到了2023年EPPlus的性能得到了略微的提升

示例代码一:XSSFWorkbook

List<User> users = LoadUsers(Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\test-data.json");
Measure(() =>
    Export(users, Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\export.npoi.xlsx");
}).Dump("NPOI");
void Export<T>(List<T> data, string path)
    IWorkbook workbook = new XSSFWorkbook();
    ISheet sheet = workbook.CreateSheet("Sheet1");
    var headRow = sheet.CreateRow(0);
    PropertyInfo[] props = typeof(User).GetProperties();
    for (var i = 0; i < props.Length; ++i)
        headRow.CreateCell(i).SetCellValue(props[i].Name);
    for (var i = 0; i < data.Count; ++i)
        var row = sheet.CreateRow(i + 1);
        for (var j = 0; j < props.Length; ++j)
            row.CreateCell(j).SetCellValue(props[j].GetValue(data[i]).ToString());
    using var file = File.Create(path);
    workbook.Write(file);
    workbook.Close();
 

NPOI (2.6.1)(2023/7/12)输出结果

次数ΞΞ分配内存内存提高耗时ΞΞ
11,589,285,792567,2725549
21,577,028,664967043
31,577,398,488488107
41,576,360,696-90,5129336
51,576,226,688-3,1208289

NPOI (2.4.1)(2018/12/18)输出结果

次数ΞΞ分配内存内存提高耗时ΞΞ
11,648,548,696526,8246947
21,633,685,1361207921
31,634,033,296248864
41,634,660,176-90,2008945
51,634,205,368-2,5848078

示例代码二:SXSSFWorkbook

List<User> users = LoadUsers(Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\test-data.json");
Measure(() =>
    Export(users, Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\export.npoi.xlsx");
}).Dump("NPOI");
void Export<T>(List<T> data, string path)
    IWorkbook workbook = new SXSSFWorkbook();
    ISheet sheet = workbook.CreateSheet("Sheet1");
    var headRow = sheet.CreateRow(0);
    PropertyInfo[] props = typeof(User).GetProperties();
    for (var i = 0; i < props.Length; ++i)
        headRow.CreateCell(i).SetCellValue(props[i].Name);
    for (var i = 0; i < data.Count; ++i)
        var row = sheet.CreateRow(i + 1);
        for (var j = 0; j < props.Length; ++j)
            row.CreateCell(j).SetCellValue(props[j].GetValue(data[i]).ToString());
    using var file = File.Create(path);
    workbook.Write(file);
    workbook.Close();
 

NPOI (2.6.1)(2023/7/12)输出结果

次数分配内存内存提高耗时
1571,769,14411,495,4882542
2482,573,584965106
3481,139,296241463
4481,524,384481510
5481,466,616481493

NPOI (2.4.1)(2018/12/18)输出结果

次数分配内存内存提高耗时
1660,709,472537,5127808
2650,060,3768,1288649
3649,006,9524,1367064
4649,267,920-89,7766973
5649,955,024486538

经过测试 发现SXSSFWorkbook 确实比XSSFWorkbook 性能好,有显著提升
由此看出 相比2018,到了2023年NPOI的性能得到了略微的提升

Aspose.Cells

Util.NewProcess = true;
List<User> users = LoadUsers(Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\test-data.json");
SetLicense();
Measure(() =>
    Export(users, Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\export.aspose2.xlsx");
}, 5).Dump("Aspose");
void Export<T>(List<T> data, string path)
    using var excel = new Workbook();
    excel.Settings.MemorySetting = MemorySetting.Normal;
    excel.Settings.CheckExcelRestriction = false;
    Worksheet sheet = excel.Worksheets["Sheet1"];
    sheet.Cells.ImportCustomObjects(data, 0, 0, new ImportTableOptions
        IsFieldNameShown = true, 
        DateFormat = "MM/DD/YYYY hh:mm:ss AM/PM", 
        ConvertNumericData = false, 
    excel.Save(path);
void SetLicense()
    Stream stream = new MemoryStream(Convert.FromBase64String(@"密钥"));
    stream.Seek(0, SeekOrigin.Begin);
    new Aspose.Cells.License().SetLicense(stream);
 

Aspose.Cells (23.8.0)(2023/8/9)输出结果

次数分配内存内存提高耗时
1443,025,1123,471,9842889
2392,090,30430,2081863
3391,419,072-81716
4392,041,144241797
5392,078,992241689

Aspose.Cells (19.8.0)(2019/8/20)输出结果

次数分配内存内存提高耗时
1552,862,0562,987,0002913
2508,337,87249,7761750
3507,922,728241933
4507,949,584241781
5508,368,208241773

由此看出 相比2019,到了2023年Aspose.Cells的性能还是一样差不多,只是内存占用减少了

DocumentFormat.OpenXml

List<User> users = LoadUsers(Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\test-data.json");
Measure(() =>
    Export(users, Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\export.openXml.xlsx");
}).Dump("OpenXML");
void Export<T>(List<T> data, string path)
    using SpreadsheetDocument excel = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Workbook);
    WorkbookPart workbookPart = excel.AddWorkbookPart();
    workbookPart.Workbook = new Workbook();
    WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
    worksheetPart.Worksheet = new Worksheet(new SheetData());
    Sheets sheets = excel.WorkbookPart.Workbook.AppendChild<Sheets>(new Sheets());
    Sheet sheet = new Sheet
        Id = excel.WorkbookPart.GetIdOfPart(worksheetPart),
        SheetId = 1,
        Name = "Sheet1"
    sheets.Append(sheet);
    SheetData sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();
    PropertyInfo[] props = typeof(User).GetProperties();
    {    // header
        var row = new Row() { RowIndex = 1 };
        sheetData.Append(row);
        row.Append(props.Select((prop, i) => new Cell
            CellReference = ('A' + i - 1) + row.RowIndex.Value.ToString(),
            CellValue = new CellValue(props[i].Name),
            DataType = new EnumValue<CellValues>(CellValues.String),
    sheetData.Append(data.Select((item, i) => 
        var row = new Row { RowIndex = (uint)(i + 2) };
        row.Append(props.Select((prop, j) => new Cell
            CellReference = ('A' + j - 1) + row.RowIndex.Value.ToString(),
            CellValue = new CellValue(props[j].GetValue(data[i]).ToString()),
            DataType = new EnumValue<CellValues>(CellValues.String),
        return row;
    excel.Save();
 

DocumentFormat.OpenXml (2.20.0)(2023/4/7)输出结果

次数ΞΞ分配内存内存提高耗时ΞΞ
1614,013,080421,5523909
2613,007,112963487
3613,831,6721043465
4613,058,344243650
5613,161,096243521

DocumentFormat.OpenXml (2.9.1)(2019/3/14)输出结果

次数ΞΞ分配内存内存提高耗时ΞΞ
1542,724,752139,0803504
2542,478,208962897
3543,030,904242826
4542,247,544242957
5542,763,312242941

由此看出 相比2019,到了2023年DocumentFormat.OpenXml的性能反而越差啦

结论和总结

结论一:如果你想找开源,(旧版本免费),(最新版收费)EPPlus 依旧是最佳选择

次数ΞΞ分配内存ΞΞ内存提高ΞΞ耗时ΞΞ
1454,869,176970,1602447
2440,353,4881761776
3440,062,26401716
4440,283,58401750
5440,653,26401813

结论二:如果你想找速度快,很稳定,但收费的,Aspose.Cells 依旧是最佳选择

次数分配内存内存提高耗时
1443,025,1123,471,9842889
2392,090,30430,2081863
3391,419,072-81716
4392,041,144241797
5392,078,992241689

总结:
1、EPPlus表现不错,内存和耗时在开源组中表现最佳
2、收费的Aspose.Cells表现最佳,内存占用最低,用时也最短

作者 => 百宝门瞿佑明

此文章是对此前《.NET骚操作》2019年写的文章的更新和扩展https://www.cnblogs.com/sdflysha/p/20190824-dotnet-excel-compare.html

引言背景和目的本文介绍了几个常用的电子表格处理库,包括EPPlus、NPOI、Aspose.Cells和DocumentFormat.OpenXml,我们将对这些库进行性能测评,以便为开发人员提供实际的性能指标和数据。下表将功能/特点、开源/许可证这两列分开,以满足需求:功能 / 特点EPPlusNPOIAspose.CellsDocumentFormat.OpenXml开源是是否是许可证MITA...  ----(来自百度百科) NPOI 是 POI 项目的 .NET 本。POI是一个开源的Java读写Excel、WORD等微软OLE2组件文档的项目。 (一)传统操作Excel遇到的问题: 1、如果是.NET,需要在服务器端装Office,且及时更新它,以防漏洞,还需要设定权限允许.NET访问COM+,如果在导出过程中出问题可能导致服务器宕机。 2、Excel会把
一 什么是数据仓 1.1 数据仓概念 数据仓,英文名称为Data Warehouse,可简写为DW或DWH。数据仓,是为企业所有级别的决策制定过程,提供所有类型数据支持的战略集合。它出于分析性报告和决策支持目的而创建。 1.2 数据仓特点 1.2.1面向主题 普通的操作型数据主要面向事务性处理,而数据仓中的所有数据一般按照主题进行划分。主题是对业务数据的一种抽象,是从较高层次上对信息系统中的数据进行归纳和整理。 面向主题的数据可以划分成两部分----根据原系统业务数据的特点进行主题的抽取和确定
OOP: Object Oriented Programming: 面向对象编程技术的关键性观念是它将数据及对数据的操作行为放在一起,作为一个相互依存、不可分割的整体——对象。对于相同类型的对象进行分类、抽象后,得出共同的特征而形成了类 AOP(面向切面编程) 在软件业,AOP为Aspect Oriented Prog
您需要使用.NET中的Interop来与Microsoft Excel交互并导出Excel文件。首先要确保您的程序中引用了Microsoft Excel,并使用如下代码: using Excel = Microsoft.Office.Interop.Excel; 然后可以使用下面的代码来创建一个Excel对象、工作薄和工作表,并填写数据,最终导出Excel文件: Excel.Application xlApp = new Excel.Application(); Excel.Workbook xlWorkbook = xlApp.Workbooks.Add(); Excel._Worksheet xlWorksheet = (Excel._Worksheet)xlWorkbook.Sheets[1]; //填写数据 xlWorksheet.Cells[1, 1] = "第一列,第一行"; xlWorksheet.Cells[1, 2] = "第二列,第一行"; xlWorksheet.Cells[2, 1] = "第一列,第二行"; xlWorksheet.Cells[2, 2] = "第二列,第二行"; //导出Excel文件 xlWorkbook.SaveAs("C:\\Users\\用户名\\Documents\\Excel文件名.xlsx"); xlWorkbook.Close(); xlApp.Quit(); 请注意,为了保证您可以成功导出Excel文件,请修改导出路径中的用户名和Excel文件名。