原标题:2023版:深度比较几种.NET Excel导出库的性能差异
引言 背景和目的
本文介绍了几个常用的电子表格处理库,包括EPPlus、NPOI、Aspose.Cells和DocumentFormat.OpenXml,我们将对这些库进行性能测评,以便为开发人员提供实际的性能指标和数据。
下表将功能/特点、开源/许可证这两列分开,以满足需求:
功能 / 特点
EPPlus
Aspose.Cells
DocumentFormat.OpenXml
Apache
支持的 Excel 版本
Excel 2007 及更高版本
Excel 97-2003
Excel 2003 及更高版本
Excel 2007 及更高版本
测评电脑配置
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输出结果
Gender
FirstName
LastName
Email
BirthDate
Company
Phone
Website
Antonio
Paucek
Antonio.Paucek@gmail.com
1987/10/31 5:46:50
Moen, Willms and Maggio
(898) 283-1583 x88626
pamela.name
850-06-4706
Gerhold
Kurt.Gerhold40@yahoo.com
1985/11/1 18:41:01
Wilkinson and Sons
(698) 637-0181 x49124
cordelia.net
014-86-1757
Howard
Hegmann
Howard2@hotmail.com
1979/7/20 22:35:40
Kassulke, Murphy and Volkman
(544) 464-9818 x98381
kari.com
360-23-1669
Female
Rosemarie
Powlowski
Rosemarie.Powlowski48@hotmail.com
1964/5/18 1:35:45
Will Group
1-740-705-6482
laurence.net
236-10-9925
Female
Eunice
Rogahn
Eunice84@gmail.com
1979/11/25 11:53:14
Rippin - Rowe
(691) 491-2282 x3466
yvette.net
219-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)输出结果
分配内存ΞΞ
内存提高ΞΞ
454,869,176
970,160
440,353,488
440,062,264
440,283,584
440,653,264
EPPlus (4.5.3.2)(2019/6/16)输出结果
分配内存ΞΞ
内存提高ΞΞ
963,850,944
192,048
509,450,792
509,872,160
509,858,576
509,651,512
由此看出 相比2019,到了2023年EPPlus的性能得到了略微的提升
NPOI 示例代码一: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)输出结果
1,589,285,792
567,272
1,577,028,664
1,577,398,488
1,576,360,696
-90,512
1,576,226,688
-3,120
NPOI (2.4.1)(2018/12/18)输出结果
1,648,548,696
526,824
1,633,685,136
1,634,033,296
1,634,660,176
-90,200
1,634,205,368
-2,584
示例代码二: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)输出结果
571,769,144
11,495,488
482,573,584
481,139,296
481,524,384
481,466,616
NPOI (2.4.1)(2018/12/18)输出结果
660,709,472
537,512
650,060,376
8,128
649,006,952
4,136
649,267,920
-89,776
649,955,024
经过测试 发现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)输出结果
443,025,112
3,471,984
392,090,304
30,208
391,419,072
392,041,144
392,078,992
Aspose.Cells (19.8.0)(2019/8/20)输出结果
552,862,056
2,987,000
508,337,872
49,776
507,922,728
507,949,584
508,368,208
由此看出 相比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.<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)输出结果
614,013,080
421,552
613,007,112
613,831,672
613,058,344
613,161,096
DocumentFormat.OpenXml (2.9.1)(2019/3/14)输出结果
542,724,752
139,080
542,478,208
543,030,904
542,247,544
542,763,312
由此看出 相比2019,到了2023年DocumentFormat.OpenXml的性能反而越差啦
结论和总结
结论一:如果你想找开源,(旧版本免费),(最新版收费)
EPPlus
依旧是最佳选择
分配内存ΞΞ
内存提高ΞΞ
454,869,176
970,160
440,353,488
440,062,264
440,283,584
440,653,264
结论二:如果你想找速度快,很稳定,但收费的,
Aspose.Cells
依旧是最佳选择
443,025,112
3,471,984
392,090,304
30,208
391,419,072
392,041,144
392,078,992
1、EPPlus表现不错,内存和耗时在开源组中表现最佳
2、收费的Aspose.Cells表现最佳,内存占用最低,用时也最短
作者 => 百宝门瞿佑明
此文章是对此前《.NET骚操作》2019年写的文章的更新和扩展
https://www.cnblogs.com/sdflysha/p/20190824-dotnet-excel-compare.html
返回搜狐,查看更多
责任编辑:
平台声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
阅读 (
)