控制台应用程序:如何快速对比Excel中的数据值?
Excel数据匹配性能优化方案
问题背景
有一个Excel文件需按以下规则匹配数据并标记黄色:
- 从ARMADAS(B列)的D542295890,获取对应ARMADAW(A列)的B05KAYIP;
- 在ARMADAIWC(E列)查找B05KAYIP,获取对应SAPIWC(D列)的0010;
- 在SAPW(G列)查找0010,匹配ARMADAS(B列)与SAPS(H列)的值;
- 若B列和H列值匹配,标记对应单元格为黄色。
现有控制台程序处理40万行数据时耗时极长,需优化性能。
原代码性能瓶颈分析
原代码存在以下核心问题:
- 嵌套循环导致O(n²)时间复杂度:外层遍历每行时,内层两次全表扫描(找SAPIWC值、找SAPS匹配),40万行下会产生百亿级操作;
- 频繁读取Excel单元格:每次循环直接读取
worksheet.Cells,IO开销大; - 实时修改单元格样式:匹配到就立即设置单元格格式,频繁操作Excel对象拖慢速度。
优化方案
1. 预加载数据到内存字典
将需要查找的列数据提前加载到Dictionary,把O(n)的查找变为O(1):
- 构建
armadaiwcToSapiwc:键为ARMADAIWC(E列)值,值为对应SAPIWC(D列)值; - 构建
sapwToSapsMap:键为SAPW(G列)值,值为该值对应的所有SAPS(H列)值与行号的集合;
2. 批量读取单元格数据
一次性读取整列数据到数组,减少Excel对象的IO交互次数。
3. 批量标记单元格样式
先记录所有需要标记的单元格位置,最后统一设置样式,避免频繁操作Excel样式对象。
4. 减少重复判断
提前过滤空值,避免无效遍历。
优化后完整代码
using OfficeOpenXml; using OfficeOpenXml.Style; using System.Drawing; using System.Collections.Generic; string filePath = @"C:\Users\197199\Desktop\SAP-Armada\Test.xlsx"; string logFilePath = @"C:\Users\197199\Desktop\SAP-Armada\matches.txt"; if (!File.Exists(filePath)) { Console.WriteLine("文件未找到。"); return; } ExcelPackage.LicenseContext = LicenseContext.NonCommercial; using var package = new ExcelPackage(new FileInfo(filePath)); var worksheet = package.Workbook.Worksheets[0]; int rowCount = worksheet.Dimension.End.Row; // 定义列索引(1-based) int armadasCol = 2; // ARMADAS (B列) int armadawCol = 1; // ARMADAW (A列) int armadaiWCCol = 5; // ARMADAIWC (E列) int sapiWCCol = 4; // SAPIWC (D列) int gCol = 7; // SAPW (G列) int sapsCol = 8; // SAPS (H列) // -------------------------- // 步骤1:预加载数据到字典,实现O(1)查找 // -------------------------- // 构建 ARMADAIWC -> SAPIWC 的映射 Dictionary<string, string> armadaiwcToSapiwc = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // 构建 SAPW -> (SAPS值, 行号) 列表的映射 Dictionary<string, List<(string SapsValue, int Row)>> sapwToSapsMap = new Dictionary<string, List<(string, int)>>(StringComparer.OrdinalIgnoreCase); // 批量读取所有列数据到数组,减少Excel交互 var armadasValues = worksheet.Cells[2, armadasCol, rowCount, armadasCol].Select(c => c.Text).ToArray(); var armadawValues = worksheet.Cells[2, armadawCol, rowCount, armadawCol].Select(c => c.Text).ToArray(); var armadaiwcValues = worksheet.Cells[2, armadaiWCCol, rowCount, armadaiWCCol].Select(c => c.Text).ToArray(); var sapiwcValues = worksheet.Cells[2, sapiWCCol, rowCount, sapiWCCol].Select(c => c.Text).ToArray(); var gValues = worksheet.Cells[2, gCol, rowCount, gCol].Select(c => c.Text).ToArray(); var sapsValues = worksheet.Cells[2, sapsCol, rowCount, sapsCol].Select(c => c.Text).ToArray(); // 填充第一个字典:ARMADAIWC到SAPIWC for (int i = 0; i < rowCount - 1; i++) { string aiwc = armadaiwcValues[i]; string sapiwc = sapiwcValues[i]; if (!string.IsNullOrEmpty(aiwc) && !string.IsNullOrEmpty(sapiwc) && !armadaiwcToSapiwc.ContainsKey(aiwc)) { armadaiwcToSapiwc.Add(aiwc, sapiwc); } } // 填充第二个字典:SAPW到SAPS的行数据 for (int i = 0; i < rowCount - 1; i++) { string gVal = gValues[i]; string sapsVal = sapsValues[i]; if (!string.IsNullOrEmpty(gVal) && !string.IsNullOrEmpty(sapsVal)) { if (!sapwToSapsMap.ContainsKey(gVal)) { sapwToSapsMap[gVal] = new List<(string, int)>(); } // 行号是i+2(因为数组从0开始,原行从2开始) sapwToSapsMap[gVal].Add((sapsVal, i + 2)); } } // 记录需要标记黄色的单元格位置 List<(int Row, int Col)> yellowCells = new List<(int, int)>(); // -------------------------- // 步骤2:遍历主数据,用字典快速查找匹配 // -------------------------- using (StreamWriter writer = new StreamWriter(logFilePath)) { for (int i = 0; i < rowCount - 1; i++) { string armadas = armadasValues[i]; string armadaw = armadawValues[i]; if (string.IsNullOrEmpty(armadas) || string.IsNullOrEmpty(armadaw)) { continue; } // 快速查找SAPIWC值 if (!armadaiwcToSapiwc.TryGetValue(armadaw, out string sapiwc)) { continue; } // 快速查找该SAPIWC对应的SAPS列表 if (!sapwToSapsMap.TryGetValue(sapiwc, out var sapsList)) { continue; } // 遍历匹配SAPS值 foreach (var (sapsVal, sapsRow) in sapsList) { if (armadas.Equals(sapsVal, StringComparison.OrdinalIgnoreCase)) { // 记录需要标记的单元格 yellowCells.Add((i + 2, armadasCol)); yellowCells.Add((sapsRow, sapsCol)); string message = $"找到匹配行 {i + 2}: ARMADA仓库 = {armadaw}, ARMADA序列号 = {armadas}, SAP仓库 = {sapiwc}, SAP序列号 = {sapsVal}{Environment.NewLine}"; Console.WriteLine(message); writer.WriteLine(message); break; // 找到第一个匹配即可 } } } } // -------------------------- // 步骤3:批量设置单元格样式 // -------------------------- var yellowFill = worksheet.Cells.Style.Fill.Clone(); yellowFill.PatternType = ExcelFillStyle.Solid; yellowFill.BackgroundColor.SetColor(Color.Yellow); foreach (var (row, col) in yellowCells) { worksheet.Cells[row, col].Style.Fill = yellowFill; } // 保存修改 package.Save(); Console.WriteLine("匹配与标记完成。");
优化效果说明
- 时间复杂度从O(n²)降至O(n):预加载字典后,所有查找操作都是O(1),整体遍历仅需两次全表扫描(加载字典)+一次主遍历;
- 减少Excel IO交互:批量读取整列数据到内存数组,避免每次循环读取单元格;
- 批量样式设置:一次性应用样式,减少Excel对象的频繁修改开销。
内容的提问来源于stack exchange,提问作者raysefo
相关产品推荐
相关产品推荐

