You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

控制台应用程序:如何快速对比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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 04:47:32