数据去重:通过 C# 删除 Excel 中的重复行

数据去重:通过 C# 删除 Excel 中的重复行

在日常的数据处理工作中,Excel 表格中经常会出现重复行。这些重复数据可能是由于多次导入、手工录入错误或系统合并导致的。重复数据不仅占用存储空间,还会影响数据分析的准确性。本文将介绍如何使用 C# 高效地删除 Excel 中的重复行,并提供完整的代码示例。### 为什么选择 C# 处理 Excel 去重?虽然 Excel 自带去重功能(数据 > 删除重复项),但在以下场景中,使用 C# 编程更为合适:- 批量处理 :需要处理大量 Excel 文件时,自动化脚本比手工操作更高效- 复杂规则 :需要根据多个列的组合来判断重复,或需要保留特定行(如最新记录)- 集成需求 :将去重功能嵌入到现有的 .NET 应用程序中- 可追溯性 :程序可以记录去重前后的数据量,便于审计### 准备工作:安装必要的库在 C# 中操作 Excel,最常用的库是 EPPlusClosedXML 。这里我推荐使用 ClosedXML ,因为它 API 简洁,且支持 .NET Core 和 .NET 5+。首先,在项目中安装 ClosedXML:bashdotnet add package ClosedXML或者通过 NuGet 包管理器安装。---### 基础方案:简单去重(基于整行完全重复)最简单的去重逻辑是:如果两行的所有单元格值完全相同,则视为重复行,只保留第一行。csharpusing ClosedXML.Excel;using System;using System.Collections.Generic;class ExcelDeduplicator{ /// <summary> /// 删除 Excel 中完全重复的行(所有列值相同) /// </summary> /// <param name="inputPath">输入文件路径</param> /// <param name="outputPath">输出文件路径</param> /// <param name="sheetName">工作表名称,默认第一个</param> public static void RemoveDuplicateRows(string inputPath, string outputPath, string sheetName = null) { using (var workbook = new XLWorkbook(inputPath)) { var worksheet = workbook.Worksheet(sheetName ?? workbook.Worksheets.First().Name); var rows = worksheet.RowsUsed(); // 获取所有有数据的行 // 使用 HashSet 存储已见过的行的唯一标识 var seenRows = new HashSet<string>(); var rowsToDelete = new List<int>(); // 存储需要删除的行号 // 遍历所有行(从第1行开始,假设第1行是标题行) for (int rowNum = 2; rowNum <= rows.Last().RowNumber(); rowNum++) { var row = worksheet.Row(rowNum); // 将整行的所有单元格值拼接成字符串,作为唯一标识 string rowKey = ""; foreach (var cell in row.CellsUsed()) { rowKey += cell.GetString() + "|"; } // 如果已经出现过,则标记删除 if (!seenRows.Add(rowKey)) { rowsToDelete.Add(rowNum); } } // 逆序删除行,避免行号变化 for (int i = rowsToDelete.Count - 1; i >= 0; i--) { worksheet.Row(rowsToDelete[i]).Delete(); } // 保存结果 workbook.SaveAs(outputPath); Console.WriteLine($"去重完成!原始行数:{rows.Count()},删除行数:{rowsToDelete.Count},剩余行数:{rows.Count() - rowsToDelete.Count}"); } }}代码说明: - 使用 HashSet<string> 存储已经出现过的行签名- 行签名通过拼接所有单元格的字符串值生成- 删除行时采用逆序方式,避免行号变化导致错误---### 进阶方案:基于指定列去重 + 保留最新记录在实际业务中,我们往往需要根据特定列(如 ID、邮箱)来判断重复,而不是整行完全一样。此外,可能还需要保留日期最新的那条记录。csharpusing ClosedXML.Excel;using System;using System.Collections.Generic;using System.Linq;class AdvancedDeduplicator{ /// <summary> /// 根据指定列去重,并保留日期最新的记录 /// </summary> /// <param name="inputPath">输入文件路径</param> /// <param name="outputPath">输出文件路径</param> /// <param name="keyColumnIndex">作为去重依据的列索引(1-based)</param> /// <param name="dateColumnIndex">日期列索引(1-based),用于判断哪条记录最新</param> /// <param name="hasHeader">是否有标题行</param> public static void RemoveDuplicatesByColumn(string inputPath, string outputPath, int keyColumnIndex, int dateColumnIndex, bool hasHeader = true) { using (var workbook = new XLWorkbook(inputPath)) { var worksheet = workbook.Worksheets.First(); var rows = worksheet.RowsUsed().ToList(); int startRow = hasHeader ? 2 : 1; // 如果有标题,从第2行开始 var lastRow = rows.Last().RowNumber(); // 存储每个 key 对应的最新行号 var latestRowByKey = new Dictionary<string, int>(); var rowsToDelete = new List<int>(); // 第一遍:找出每个 key 对应的最新记录行号 for (int rowNum = startRow; rowNum <= lastRow; rowNum++) { var keyValue = worksheet.Cell(rowNum, keyColumnIndex).GetString().Trim(); if (string.IsNullOrEmpty(keyValue)) continue; // 跳过空 key var dateCell = worksheet.Cell(rowNum, dateColumnIndex); DateTime currentDate; if (!DateTime.TryParse(dateCell.GetString(), out currentDate)) { currentDate = DateTime.MinValue; // 无法解析的日期,当作最早 } if (latestRowByKey.ContainsKey(keyValue)) { // 比较日期,保留最新的 int existingRow = latestRowByKey[keyValue]; var existingDate = worksheet.Cell(existingRow, dateColumnIndex).GetDateTime(); if (currentDate > existingDate) { // 当前行日期更新,替换并标记旧行删除 rowsToDelete.Add(existingRow); latestRowByKey[keyValue] = rowNum; } else { // 已有记录更新,标记当前行删除 rowsToDelete.Add(rowNum); } } else { latestRowByKey[keyValue] = rowNum; } } // 第二遍:删除所有标记的行 var distinctRowsToDelete = rowsToDelete.Distinct().OrderByDescending(r => r); foreach (var rowNum in distinctRowsToDelete) { worksheet.Row(rowNum).Delete(); } workbook.SaveAs(outputPath); Console.WriteLine($"高级去重完成!删除 {distinctRowsToDelete.Count()} 行重复数据"); } }}代码说明: - 通过 Dictionary<string, int> 记录每个 key 对应的最新行号- 使用日期列判断哪条记录是最新的- 支持自定义去重依据列和日期列- 删除操作使用 Distinct().OrderByDescending() 保证行号不冲突---### 实际应用示例下面是一个完整的控制台程序示例,展示如何调用上述两个方法:csharpusing System;class Program{ static void Main(string[] args) { string inputPath = @"C:\Data\原始数据.xlsx"; string outputPath1 = @"C:\Data\完全去重结果.xlsx"; string outputPath2 = @"C:\Data\按列去重结果.xlsx"; // 1. 完全去重(整行重复) ExcelDeduplicator.RemoveDuplicateRows(inputPath, outputPath1); // 2. 按列去重(根据第2列,保留第3列日期最新的) AdvancedDeduplicator.RemoveDuplicatesByColumn(inputPath, outputPath2, keyColumnIndex: 2, dateColumnIndex: 3); Console.WriteLine("所有去重操作完成!"); }}---### 性能优化建议处理大型 Excel 文件时,可以采取以下优化措施:1. 减少单元格访问次数 :将数据一次性读入内存,处理后再写回2. 使用 StringBuilder 拼接行签名 :避免频繁字符串拼接3. 并行处理 :如果文件有多个工作表,可以使用 Parallel.ForEach4. 调整 Excel 文件加载方式 :使用 XLWorkbook(inputPath, XLEventTracking.Disabled) 禁用事件跟踪csharp// 优化示例:禁用事件跟踪和计算引擎using (var workbook = new XLWorkbook(inputPath, XLEventTracking.Disabled)){ // 关闭自动计算 workbook.CalculateMode = XLCalculateMode.Auto; // ... 去重逻辑}---### 常见问题与注意事项1. 空行处理 :在去重前最好先删除空行,避免影响判断2. 数据类型 :日期、数字等类型在拼接字符串时要注意格式一致性3. 内存占用 :对于超大文件(>100MB),建议分批次处理4. 文件格式 :ClosedXML 支持 .xlsx,不支持旧的 .xls 格式,如需支持需要额外转换### 总结本文介绍了使用 C# 和 ClosedXML 库删除 Excel 中重复行的两种方案:基于整行完全去重和基于指定列去重。通过实际代码示例,展示了如何读取 Excel 文件、判断重复、执行删除操作并保存结果。这些方法可以轻松集成到 .NET 应用程序中,实现数据清洗的自动化。在实际项目中,你可以根据业务需求灵活调整去重规则,例如支持多列联合去重、保留特定条件的记录等。希望这些代码能帮助你在数据处理工作中提高效率。

相关推荐
吹什么轩2 小时前
c++复习:map和set的使用
开发语言·c++
必须得开心呀2 小时前
qt生成dump文件并定位异常
开发语言·qt
fpcc2 小时前
跟我学C++中级篇—内存流
开发语言·c++
Cicada1282 小时前
ccvt:一个用 Rust 写的中国地图坐标系互转命令行工具
开发语言·后端·rust
1001101_QIA2 小时前
工控机网络配置
开发语言·数据库·php
程序员雷欧3 小时前
ThreadPoolExecutor 深度解析:从核心参数到源码实现的全面剖析
java·开发语言·jvm
catchadmin3 小时前
对标 npx 的 CPX PHP 的 Composer 包执行器
开发语言·php·composer
一位狮子座的程序员3 小时前
如何用RAG解决AI智能体的知识盲区?
开发语言·c#
吹什么轩4 小时前
c++复习:模拟实现list来更好的理解和应用list
开发语言·c++