2023版:深度比較幾種.NET Excel匯出庫的效能差異
引言
背景和目的
本文介紹了幾個常用的電子表格處理庫,包括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輸出結果
Id | Gender | FirstName | LastName | BirthDate | Company | Phone | Website | SSN | |
---|---|---|---|---|---|---|---|---|---|
1 | Male | 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 |
2 | Male | Kurt | Gerhold | Kurt.Gerhold40@yahoo.com | 1985/11/1 18:41:01 | Wilkinson and Sons | (698) 637-0181 x49124 | cordelia.net | 014-86-1757 |
3 | Male | Howard | Hegmann | Howard2@hotmail.com | 1979/7/20 22:35:40 | Kassulke, Murphy and Volkman | (544) 464-9818 x98381 | kari.com | 360-23-1669 |
4 | Female | Rosemarie | Powlowski | Rosemarie.Powlowski48@hotmail.com | 1964/5/18 1:35:45 | Will Group | 1-740-705-6482 | laurence.net | 236-10-9925 |
5 | 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)輸出結果
次數ΞΞ | 分配記憶體ΞΞ | 記憶體提高ΞΞ | 耗時ΞΞ |
---|---|---|---|
1 | 454,869,176 | 970,160 | 2447 |
2 | 440,353,488 | 176 | 1776 |
3 | 440,062,264 | 0 | 1716 |
4 | 440,283,584 | 0 | 1750 |
5 | 440,653,264 | 0 | 1813 |
EPPlus (4.5.3.2)(2019/6/16)輸出結果
次數ΞΞ | 分配記憶體ΞΞ | 記憶體提高ΞΞ | 耗時ΞΞ |
---|---|---|---|
1 | 963,850,944 | 192,048 | 2765 |
2 | 509,450,792 | 600 | 1897 |
3 | 509,872,160 | 424 | 1920 |
4 | 509,858,576 | 424 | 1989 |
5 | 509,651,512 | 424 | 2076 |
由此看出 相比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 | 1,589,285,792 | 567,272 | 5549 |
2 | 1,577,028,664 | 96 | 7043 |
3 | 1,577,398,488 | 48 | 8107 |
4 | 1,576,360,696 | -90,512 | 9336 |
5 | 1,576,226,688 | -3,120 | 8289 |
NPOI (2.4.1)(2018/12/18)輸出結果
次數ΞΞ | 分配記憶體 | 記憶體提高 | 耗時ΞΞ |
---|---|---|---|
1 | 1,648,548,696 | 526,824 | 6947 |
2 | 1,633,685,136 | 120 | 7921 |
3 | 1,634,033,296 | 24 | 8864 |
4 | 1,634,660,176 | -90,200 | 8945 |
5 | 1,634,205,368 | -2,584 | 8078 |
示例程式碼二: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)輸出結果
次數 | 分配記憶體 | 記憶體提高 | 耗時 |
---|---|---|---|
1 | 571,769,144 | 11,495,488 | 2542 |
2 | 482,573,584 | 96 | 5106 |
3 | 481,139,296 | 24 | 1463 |
4 | 481,524,384 | 48 | 1510 |
5 | 481,466,616 | 48 | 1493 |
NPOI (2.4.1)(2018/12/18)輸出結果
次數 | 分配記憶體 | 記憶體提高 | 耗時 |
---|---|---|---|
1 | 660,709,472 | 537,512 | 7808 |
2 | 650,060,376 | 8,128 | 8649 |
3 | 649,006,952 | 4,136 | 7064 |
4 | 649,267,920 | -89,776 | 6973 |
5 | 649,955,024 | 48 | 6538 |
經過測試 發現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)輸出結果
次數 | 分配記憶體 | 記憶體提高 | 耗時 |
---|---|---|---|
1 | 443,025,112 | 3,471,984 | 2889 |
2 | 392,090,304 | 30,208 | 1863 |
3 | 391,419,072 | -8 | 1716 |
4 | 392,041,144 | 24 | 1797 |
5 | 392,078,992 | 24 | 1689 |
Aspose.Cells (19.8.0)(2019/8/20)輸出結果
次數 | 分配記憶體 | 記憶體提高 | 耗時 |
---|---|---|---|
1 | 552,862,056 | 2,987,000 | 2913 |
2 | 508,337,872 | 49,776 | 1750 |
3 | 507,922,728 | 24 | 1933 |
4 | 507,949,584 | 24 | 1781 |
5 | 508,368,208 | 24 | 1773 |
由此看出 相比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)輸出結果
次數ΞΞ | 分配記憶體 | 記憶體提高 | 耗時ΞΞ |
---|---|---|---|
1 | 614,013,080 | 421,552 | 3909 |
2 | 613,007,112 | 96 | 3487 |
3 | 613,831,672 | 104 | 3465 |
4 | 613,058,344 | 24 | 3650 |
5 | 613,161,096 | 24 | 3521 |
DocumentFormat.OpenXml (2.9.1)(2019/3/14)輸出結果
次數ΞΞ | 分配記憶體 | 記憶體提高 | 耗時ΞΞ |
---|---|---|---|
1 | 542,724,752 | 139,080 | 3504 |
2 | 542,478,208 | 96 | 2897 |
3 | 543,030,904 | 24 | 2826 |
4 | 542,247,544 | 24 | 2957 |
5 | 542,763,312 | 24 | 2941 |
由此看出 相比2019,到了2023年DocumentFormat.OpenXml的效能反而越差啦
結論和總結
結論一:如果你想找開源,(舊版本免費),(最新版收費)EPPlus 依舊是最佳選擇
次數ΞΞ | 分配記憶體ΞΞ | 記憶體提高ΞΞ | 耗時ΞΞ |
---|---|---|---|
1 | 454,869,176 | 970,160 | 2447 |
2 | 440,353,488 | 176 | 1776 |
3 | 440,062,264 | 0 | 1716 |
4 | 440,283,584 | 0 | 1750 |
5 | 440,653,264 | 0 | 1813 |
結論二:如果你想找速度快,很穩定,但收費的,Aspose.Cells 依舊是最佳選擇
次數 | 分配記憶體 | 記憶體提高 | 耗時 |
---|---|---|---|
1 | 443,025,112 | 3,471,984 | 2889 |
2 | 392,090,304 | 30,208 | 1863 |
3 | 391,419,072 | -8 | 1716 |
4 | 392,041,144 | 24 | 1797 |
5 | 392,078,992 | 24 | 1689 |
總結:
1、EPPlus表現不錯,記憶體和耗時在開源組中表現最佳
2、收費的Aspose.Cells表現最佳,記憶體佔用最低,用時也最短
作者 => 百寶門瞿佑明
此文章是對此前《.NET騷操作》2019年寫的文章的更新和擴充套件
https://www.cnblogs.com/sdflysha/p/20190824-dotnet-excel-compare.html
原文地址:https://blog.baibaomen.com/2023版:深度比較幾種-net-excel匯出庫的效能差異/