DataTable数据重排性能优化求助:替代嵌套循环提速方案
问题描述
将Excel文件导入DataTable(dtImport)后,需要将其中数据重排至另一个DataTable(dtImportParsed)。当前通过嵌套for循环实现重排逻辑,但处理36列、4000行的表格需耗时30-40分钟,急需提升处理速度的替代方案。
现有代码如下:
for (int c = 2; c < dtImport.Columns.Count; c++) //for each date column { for (int r = 1; r < dtImport.Rows.Count; r++) { if (dtImportParsed.Rows.Count == 0) { DataRow dataRowImport = dtImportParsed.NewRow(); dataRowImport["Date"] = dtImport.Columns[c].ColumnName.ToString().Trim(); dataRowImport["account_id"] = dtImport.Rows[r]["account_id"].ToString().Trim(); dataRowImport[dtImport.Rows[r]["Event Name"].ToString().Trim()] = dtImport.Rows[r][c].ToString().Trim(); dtImportParsed.Rows.Add(dataRowImport); } else { for (int i = 0; i < dtImportParsed.Rows.Count; i++) { if (dtImportParsed.Rows[i]["account_id"].ToString() == dtImport.Rows[r]["account_id"].ToString()) { if (dtImportParsed.Rows[i]["Date"].ToString() == dtImport.Columns[c].ColumnName.ToString()) { dtImportParsed.Rows[i][dtImport.Rows[r]["Event Name"].ToString().Trim()] = dtImport.Rows[r][c].ToString().Trim(); break; } } else if (i == dtImportParsed.Rows.Count - 1) { DataRow dataRowImport = dtImportParsed.NewRow(); dataRowImport["Date"] = dtImport.Columns[c].ColumnName.ToString().Trim(); dataRowImport["account_id"] = dtImport.Rows[r]["account_id"].ToString().Trim(); dataRowImport[dtImport.Rows[r]["Event Name"].ToString().Trim()] = dtImport.Rows[r][c].ToString().Trim(); dtImportParsed.Rows.Add(dataRowImport); } } } } }
性能瓶颈分析
现有代码采用三层嵌套循环:外层遍历日期列,中层遍历数据行,内层遍历目标表的所有行进行匹配查找。随着目标表行数增加,内层循环的遍历成本呈线性增长,最终导致整体时间复杂度达到O(MNK)(M为日期列数,N为源表行数,K为目标表行数),处理大量数据时效率极低。
优化方案:使用字典实现O(1)查找
通过创建一个Dictionary,以(account_id, Date)的组合作为唯一键,对应目标表中的DataRow,这样每次匹配查找的时间复杂度降为O(1),整体时间复杂度可优化至O(M*N),能大幅提升处理速度。
优化后的代码如下:
// 定义字典,键为(account_id, Date)的元组,值为对应的DataRow var rowLookup = new Dictionary<(string AccountId, string Date), DataRow>(); // 遍历所有日期列(从第3列开始,索引2) for (int c = 2; c < dtImport.Columns.Count; c++) { string currentDate = dtImport.Columns[c].ColumnName.Trim(); // 遍历所有数据行(从第2行开始,索引1) for (int r = 1; r < dtImport.Rows.Count; r++) { DataRow sourceRow = dtImport.Rows[r]; string accountId = sourceRow["account_id"].ToString().Trim(); string eventName = sourceRow["Event Name"].ToString().Trim(); string cellValue = sourceRow[c].ToString().Trim(); // 构造查找键 var lookupKey = (AccountId: accountId, Date: currentDate); if (rowLookup.TryGetValue(lookupKey, out DataRow targetRow)) { // 找到已有行,直接赋值对应事件列 targetRow[eventName] = cellValue; } else { // 未找到,创建新行并加入字典和目标表 targetRow = dtImportParsed.NewRow(); targetRow["Date"] = currentDate; targetRow["account_id"] = accountId; targetRow[eventName] = cellValue; dtImportParsed.Rows.Add(targetRow); rowLookup.Add(lookupKey, targetRow); } } }
额外优化建议
- 提前缓存列索引:如果源表的
account_id和Event Name列位置固定,可以提前获取它们的列索引,用索引访问代替列名访问,进一步减少字符串查找开销。 - 关闭目标表的事件通知:在批量添加行前,调用
dtImportParsed.BeginLoadData(),完成后调用dtImportParsed.EndLoadData(),避免每次添加行时触发不必要的事件。 - 减少字符串Trim操作:如果源数据本身没有多余空格,可以直接去掉
.Trim(),减少字符串处理开销。
内容的提问来源于stack exchange,提问作者Jason Mills
相关产品推荐
相关产品推荐

