.NET9 实现处理 Execl 数据操作的性能测试

.NET 平台操作 Execl 库对比

.NET 中处理 Excel 文件的常用库有以下几种,按功能和使用场景分类如下:

✅ 1. EPPlus(不推荐,商业用途需许可证)

  • 特点:曾广泛用于读写 Excel 文件(.xlsx),API 简洁易用。
  • 缺点:社区版从 5.x 开始收费,且官方宣布不再维护免费版本。
  • 安装包
Install-Package EPPlus

✅ 2. ClosedXML(推荐)

  • 特点:基于 DocumentFormat.OpenXml 封装,简化了 Excel 操作,适合中等复杂度的需求。
  • 优点
    • API 友好、文档丰富
    • 支持 LINQ 风格操作
    • 易于导出数据到 Excel
  • 安装包
Install-Package ClosedXML

3. DocumentFormat.OpenXml(微软官方 SDK)

  • 特点:微软提供的底层 SDK,可直接操作 Office Open XML 格式。
  • 优点
    • 性能高,适合大批量数据处理
    • 不依赖 Excel 应用程序
  • 缺点
    • 学习曲线陡峭
    • 需要熟悉 OpenXML 结构
  • 安装包
Install-Package DocumentFormat.OpenXml

✅ 4. NPOI(适用于旧版 .xls 和新版 .xlsx

  • 特点:兼容 .xlsExcel 2003)和 .xlsxExcel 2007+),适合需要兼容旧格式的项目。
  • 优点
    • 支持老格式 .xls
    • 不依赖 Excel 安装
  • 缺点
    • API 较为繁琐
  • 安装包
Install-Package NPOI

✅ 5. ExcelDataReader.DataSet(读取专用)

  • 特点:专注于读取 Excel 文件,速度快,内存占用低。
  • 适用场景:数据导入、批量读取。
  • 组合使用
    • ExcelDataReader + ExcelDataReader.DataSet 可将 Excel 转换为 DataSet
  • 安装包
Install-Package ExcelDataReader
Install-Package ExcelDataReader.DataSet
  • 支持格式.xls, .xlsx

6. Aspose.Cells(商业库,功能强大)

  • 特点
    • 功能全面:图表、公式、样式、打印、转换 PDF
    • 支持多种文件格式(Excel、CSV、PDF、HTML 等)
  • 优点
    • 性能强、稳定性高
    • 文档齐全
  • 缺点
    • 商业授权,费用较高(但提供免费试用)
  • 安装包
Install-Package Aspose.Cells

7. MiniExcel(轻量高性能开源库)

  • 特点:专为高效读写 Excel 设计,适合数据导入导出场景。
  • 优点
    • 轻量高效:零依赖、性能高、内存占用低,适合处理大数据量
    • 支持格式:支持 .xlsx(Office Open XML).xls(BIFF8) 格式
    • API 简洁:提供 LINQ 风格 API,易于上手,提供模板导出功能
    • 不依赖 Office:不依赖 Microsoft OfficeInterop 组件
    • 跨平台:支持 .NET Framework、.NET Core、.NET 5/6/7+
    • 开源免费 MIT 协议,可商用
  • 缺点
    • 不适合复杂样式或图表处理
  • 安装包
Install-Package MiniExcel

🚫 不推荐使用:

  • Microsoft.Office.Interop.Excel
    • 依赖本地安装 Microsoft Office
    • 不适用于服务器环境
    • 不支持 .NET Core/.NET 5+

📝 总结对比表

库名是否开源开源协议支持 .xls支持 .xlsx跨平台依赖 Office推荐用途
EPPlusGNU GPL v3.0(旧版) / 商业(新版)已不推荐
ClosedXMLMIT快速开发,简单报表
DocumentFormat.OpenXmlMIT高性能、复杂格式处理
NPOIApache-2.0兼容旧格式 .xls 文件
ExcelDataReaderMIT数据导入、只读场景
Aspose.Cells商业授权企业级高级功能需求
MiniExcelApache-2.0高性能数据导入导出

🔍 建议选择:

  • 快速开发 & 导出报表:👉 ClosedXML
  • 高性能 & 大数据量:👉 DocumentFormat.OpenXml 或 MiniExcel
  • 需兼容 .xls 文件:👉 NPOI、ExcelDataReader 或 MiniExcel
  • 企业级完整功能:👉 Aspose.Cells

如你有具体使用场景(如导入、导出、大数据、样式处理等),可以进一步说明,我可以给出更具体的代码示例或推荐。


导入导出 Execl 数据性能测试

通过上面的对比分析,这里我们挑选几个开源的 .net 库进行性能测试,使用 BenchmarkDotNet 作为基准测试框架,并模拟 数据导入和导出 操作。

.net 开源库如下:

  • 👉 ClosedXML
  • 👉 DocumentFormat.OpenXml
  • 👉 NPOI
  • 👉 ExcelDataReader
  • 👉 MiniExcel

✅ 测试目标

  • 对比不同 Excel 库在 相同数据结构和不同数据量下导入/导出性能
  • 支持多种行数级别:100 行、1000 行、5000 行
  • 使用统一模型类 Person 进行测试

🧪 测试环境要求

  • 添加基准测试框架的 NuGet 包:
dotnet add package Datadog.Trace.BenchmarkDotNet
  • 然后添加以下各库的 NuGet 包:
dotnet add package ClosedXML
dotnet add package DocumentFormat.OpenXml
dotnet add package NPOI
dotnet add package ExcelDataReader.DataSet
dotnet add package MiniExcel

项目实现

📦 统一模型类定义

public class Person
{
    public string Name { get; set; } = string.Empty;
    public int Age { get; set; }
}

完整的项目实现如下:

<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>net9.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<PublishAot>true</PublishAot>
		<InvariantGlobalization>true</InvariantGlobalization>
	</PropertyGroup>

	<ItemGroup>
		<PackageReference Include="ClosedXML" Version="0.105.0" />
		<PackageReference Include="Datadog.Trace.BenchmarkDotNet" Version="2.61.0" />
		<PackageReference Include="DocumentFormat.OpenXml" Version="3.3.0" />
		<PackageReference Include="ExcelDataReader.DataSet" Version="3.7.0" />
		<PackageReference Include="MiniExcel" Version="1.41.3" />
		<PackageReference Include="NPOI" Version="2.7.4" />
	</ItemGroup>
</Project>

⚙️ 基准测试配置类

  • ExcelLibraryBenchmark
//=================================================
// ExcelLibraryBenchmark 实现
//=================================================

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;

namespace BenchmarkTest.examples.Execl;

[MemoryDiagnoser]
[RankColumn]
public class ExcelLibraryBenchmark
{
    [Params(100, 1000, 5000)] // 不同数据量
    public int RowCount;

    private readonly List<Person> _data = [];

    [GlobalSetup]
    public void Setup()
    {
        for (int i = 0; i < RowCount; i++)
        {
            _data.Add(new Person { Name = $"Name_{i}", Age = i });
        }
    }

    [Benchmark(Baseline = true)]
    public void ClosedXML_Export() => ClosedXLHelper.Export(_data);

    [Benchmark]
    public void OpenXML_Export() => OpenXMLHelper.Export(_data);

    [Benchmark]
    public void Npoi_Export() => NpoiHelper.Export(_data);

    [Benchmark]
    public void MiniExcel_Export() => MiniExcelHelper.Export(_data);

    [Benchmark]
    public List<Person> ClosedXML_Import() => ClosedXLHelper.Import();

    [Benchmark]
    public List<Person> OpenXML_Import() => OpenXMLHelper.Import();

    [Benchmark]
    public List<Person> Npoi_Import() => NpoiHelper.Import();

    [Benchmark]
    public List<Person> ExcelDataReader_Import() => ExcelDataReaderHelper.Import();

    [Benchmark]
    public List<Person> MiniExcel_Import() => MiniExcelHelper.Import();

    public static void Run(IConfig config)
    {
        var summary = BenchmarkRunner.Run<ExcelLibraryBenchmark>(config);
        Console.WriteLine(summary);
    }
}

📚 各开源库实现类

✅ 1. ClosedXML 实现
//========================
// ClosedXML 实现
//========================

using ClosedXML.Excel;

namespace BenchmarkTest.examples.Execl;

public static class ClosedXLHelper
{
    public static void Export(List<Person> data)
    {
        using var workbook = new XLWorkbook();
        var ws = workbook.Worksheets.Add("People");
        ws.Cell(1, 1).Value = "Name";
        ws.Cell(1, 2).Value = "Age";

        for (int i = 0; i < data.Count; i++)
        {
            ws.Cell(i + 2, 1).Value = data[i].Name;
            ws.Cell(i + 2, 2).Value = data[i].Age;
        }

        workbook.SaveAs("ClosedXML.xlsx");
    }

    public static List<Person> Import()
    {
        using var workbook = new XLWorkbook("ClosedXML.xlsx");
        var ws = workbook.Worksheet(1);
        return ws.RowsUsed()
            .Skip(1)
            .Select(r => new Person
            {
                Name = r.Cell(1).GetString(),
                Age = r.Cell(2).GetValue<int>()
            }).ToList();
    }
}
✅ 2. DocumentFormat.OpenXml 实现
//====================
// ClosedXML 实现
//====================

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;

namespace BenchmarkTest.examples.Execl;

internal static class OpenXMLHelper
{
    public static void Export(List<Person> data)
    {
        using var spreadsheetDocument = SpreadsheetDocument.Create("OpenXML.xlsx", SpreadsheetDocumentType.Workbook);
        var workbookPart = spreadsheetDocument.AddWorkbookPart();
        workbookPart.Workbook = new Workbook();

        var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
        worksheetPart.Worksheet = new Worksheet(new SheetData());

        var sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();

        var headerRow = new Row();
        headerRow.Append(
            ConstructCell("Name", CellValues.String),
            ConstructCell("Age", CellValues.String));
        sheetData.AppendChild(headerRow);

        foreach (var item in data)
        {
            var row = new Row();
            row.Append(
                ConstructCell(item.Name, CellValues.String),
                ConstructCell(item.Age.ToString(), CellValues.String));
            sheetData.AppendChild(row);
        }

        workbookPart.Workbook.Append(new Sheets(new Sheet()
        {
            Id = workbookPart.GetIdOfPart(worksheetPart),
            SheetId = 1,
            Name = "People"
        }));

        workbookPart.Workbook.Save();
    }

    private static Cell ConstructCell(string value, CellValues dataType) => new()
    {
        CellValue = new CellValue(value),
        DataType = new EnumValue<CellValues>(dataType)
    };
    
    public static List<Person> Import()
    {
        using var document = SpreadsheetDocument.Open("OpenXML.xlsx", false);
        var workbookPart = document.WorkbookPart;
        var sheet = workbookPart.Workbook.Descendants<Sheet>().First();
        var worksheetPart = (WorksheetPart)workbookPart.GetPartById(sheet.Id);
        var worksheet = worksheetPart.Worksheet;
        var sheetData = worksheet.GetFirstChild<SheetData>();
        var rows = sheetData.Descendants<Row>().Skip(1);

        return rows.Select(row =>
        {
            var cells = row.Elements<Cell>().ToList();
            return new Person
            {
                Name = GetCellValue(workbookPart, cells[0]),
                Age = int.Parse(GetCellValue(workbookPart, cells[1]))
            };
        }).ToList();
    }

    private static string GetCellValue(WorkbookPart workbookPart, Cell cell)
    {
        if (cell.DataType.HasValue && cell.DataType == CellValues.SharedString)
        {
            var sstPart = workbookPart.SharedStringTablePart;
            if (sstPart != null)
            {
                var sharedStringTable = sstPart.SharedStringTable;

                return sharedStringTable.ElementAt(int.Parse(cell.InnerText)).InnerText;
            }
        }

        return cell.InnerText;
    }
}
✅ 3. NPOI 实现
//====================
// NpoiHelper 实现
//====================

using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;

namespace BenchmarkTest.examples.Execl;

internal static class NpoiHelper
{
    public static void Export(List<Person> data)
    {
        using var fs = new FileStream("Npoi.xlsx", FileMode.Create, FileAccess.Write);
        IWorkbook workbook = new XSSFWorkbook();
        ISheet sheet = workbook.CreateSheet("People");

        var header = sheet.CreateRow(0);
        header.CreateCell(0).SetCellValue("Name");
        header.CreateCell(1).SetCellValue("Age");

        for (int i = 0; i < data.Count; i++)
        {
            var row = sheet.CreateRow(i + 1);
            row.CreateCell(0).SetCellValue(data[i].Name);
            row.CreateCell(1).SetCellValue(data[i].Age);
        }

        workbook.Write(fs);
    }

    public static List<Person> Import()
    {
        using var fs = new FileStream("Npoi.xlsx", FileMode.Open, FileAccess.Read);
        IWorkbook workbook = WorkbookFactory.Create(fs);
        ISheet sheet = workbook.GetSheetAt(0);

        return sheet.Skip(1).Select(row =>
            new Person
            {
                Name = row.GetCell(0).StringCellValue,
                Age = (int)row.GetCell(1).NumericCellValue
            }).ToList();
    }
}
✅ 4. ExcelDataReader 实现
//================================
// ExcelDataReaderHelper 实现
//================================

using ExcelDataReader;
using System.Data;

namespace BenchmarkTest.examples.Execl;

internal static class ExcelDataReaderHelper
{
    public static List<Person> Import()
    {
        using var stream = File.Open("Npoi.xlsx", FileMode.Open, FileAccess.Read);
        using var reader = ExcelReaderFactory.CreateReader(stream);
        var result = reader.AsDataSet().Tables[0];

        return result.AsEnumerable().Skip(1).Select(row =>
            new Person
            {
                Name = row["Name"]?.ToString(),
                Age = int.Parse(row["Age"]?.ToString())
            }).ToList();
    }
}
✅ 5. MiniExcel 实现
//=========================
// MiniExcelHelper 实现
//=========================

using MiniExcelLibs;

namespace BenchmarkTest.examples.Execl;

internal static class MiniExcelHelper
{
    public static void Export(List<Person> data)
    {
        MiniExcel.SaveAs("MiniExcel.xlsx", data);
    }

    public static List<Person> Import()
    {
        return MiniExcel.Query<Person>("MiniExcel.xlsx").ToList();
    }
}

🏁 启动基准测试

  • Program.cs 中运行基准测试
using BenchmarkDotNet.Configs;
using BenchmarkTest.examples.Execl;
using Datadog.Trace.BenchmarkDotNet;

Console.WriteLine("Hello, BenchmarkDotNetTest!");

var config = DefaultConfig.Instance.WithDatadog();
ExcelLibraryBenchmark.Run(config);
  • 运行测试
dotnet run -c Release

输出信息:

ExcelLibraryBenchmark

以下是对 BenchmarkDotNet 测试结果的详细分析:


📊 输出内容说明

以下是对 BenchmarkDotNet 测试报告 的详细解释,涵盖:

  • 各库在不同数据量下的性能表现
  • 内存分配情况
  • 性能排名与对比
  • 异常项说明(如 NA、Outliers
  • 推荐选型建议

📊 一、测试目标回顾

我们使用了 BenchmarkDotNet 对以下 Excel 操作库进行了基准测试:

库名功能
ClosedXML数据导出/导入
DocumentFormat.OpenXml (简称 OpenXML)数据导出/导入
NPOI数据导出/导入
MiniExcel数据导出/导入
ExcelDataReader数据导入

测试数据量级别:

  • 100
  • 1000
  • 5000

测试指标包括:

  • 平均执行时间(Mean)
  • 标准差(StdDev)
  • 内存分配(Allocated)
  • GC 回收次数(Gen0, Gen1, Gen2)

📈 二、性能分析(按行数分类)

1. 小规模数据(100 行)
方法名Mean(us)内存分配(B)Rank
MiniExcel_Import577.3290,7961
OpenXML_Export572.3227,7721
Npoi_Import669.6826,1392
OpenXML_Import809.7363,1453
ClosedXML_Export1,755.0637,2835
ClosedXML_Import1,731.11,127,4495

📌 结论

  • MiniExcel.Import()OpenXML.Export() 在小数据量下表现最优。
  • ClosedXML 相对较慢,内存消耗也较高。

2. 中等规模数据(1000 行)
方法名Mean(us)内存分配(B)Rank
MiniExcel_Import570.7290,6371
OpenXML_Export1,974.41,423,4192
OpenXML_Import2,677.31,868,4853
Npoi_Import2,862.54,446,7864
ClosedXML_Export4,626.42,947,5156
ClosedXML_Import6,401.76,359,0647

📌 结论

  • MiniExcel.Import() 依然最快,且内存消耗极低。
  • OpenXML.Export() 性能稳定,适合中等规模导出。
  • NPOIClosedXML 随着数据量增加,性能下降明显,内存开销更大。

3. 大规模数据(5000 行)
方法名Mean(us)内存分配(B)Rank
MiniExcel_Import574.6290,7971
OpenXML_Export9,810.37,380,0152
OpenXML_Import12,492.48,559,2653
Npoi_Import16,804.920,818,2684
ClosedXML_Export20,664.312,961,7905
ClosedXML_Import31,425.429,650,4757

📌 结论

  • MiniExcel.Import() 在大数据量下依然保持超快性能,几乎不随数据量增长而显著变慢。
  • OpenXML.Export() 在导出时表现良好,适合需要大量导出的场景。
  • ClosedXML 在大数据下表现最差,尤其在导入操作中。

📦 三、内存与 GC 分析

库名内存分配趋势GC 压力
MiniExcel极低几乎无
OpenXML较低轻度
ExcelDataReader适中适中
NPOI
ClosedXML最高最严重

📌 总结

  • MiniExcelOpenXML 内存占用最小,GC 压力最低。
  • ClosedXMLNPOI 内存分配频繁,GC 回收多,影响整体性能。

⚠️ 四、异常项说明

1. NA 值问题(未完成的测试)
方法名问题描述
MiniExcel_Export所有 RowCount 下都为 NA
ExcelDataReader_Import所有 RowCount 下都为 NA

可能原因

  • MiniExcel.SaveAs(...) 方法调用失败或未实现。
  • ExcelDataReader 未正确绑定文件路径或未加载数据源。

🔧 建议修复

  • 确保导出路径可写。
  • 检查是否遗漏初始化逻辑或文件读取权限。
  • 使用 .AsStream().AsDataSet() 正确转换数据。

2. Outlier(异常值)

例如:

ExcelLibraryBenchmark.ClosedXML_Export: Default -> 3 outliers were removed (1.98 ms..1.99 ms)

📌 说明

  • BenchmarkDotNet 自动识别并移除了部分“偏离正常范围”的极端值(可能是 GC 干扰、系统调度等导致)。
  • 这是正常行为,确保最终结果更准确。

🧠 五、综合排名与推荐选型

排名方法名适用场景
1MiniExcel.Import()快速导入,无需复杂格式处理
2OpenXML.Export()高性能导出,支持样式和结构控制
3OpenXML.Import()导入 + 结构化操作
4ExcelDataReader.Import()只读导入,兼容性强
5NPOI.Export()兼容 .xls 格式
6ClosedXML.Export()快速开发、简单报表
7ClosedXML.Import()不推荐用于大规模数据

✅ 六、建议选择策略

场景推荐库
快速导入(只读)MiniExcel / ExcelDataReader
高性能导出(无复杂样式)OpenXML / MiniExcel
需要兼容 .xls 格式NPOI
快速原型开发 / 简单表格操作ClosedXML
企业级功能(图表、公式、打印等)Aspose.Cells(商业)

📌 七、后续优化建议

  1. 增加模板导出、样式保留、多Sheet等高级功能测试
  2. 加入异常处理机制,防止导出失败中断测试流程
  3. 尝试异步方式提升 I/O 性能
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ChaITSimpleLove

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值