问题描述
我有两个数据表:
-
dt
:从 CSV 文件填充超过 170 万行 -
dataStructure.Tables["AccountData"]
:从数据库查询也是大约一百万行
我使用以下代码遍历并比较每组行的数据。代码需要 48 小时才能完成。我将应用程序的属性更改为 x64,以允许它使用更多的 Process Memory 。它现在使用大约 2.5GB 。
我的问题是,有没有更有效的方法来减少运行时间?
//set is_legal column value for each row
foreach (DataRow row in dt.Rows)
{
var acctNum = row[0].ToString().Replace(""", "");
foreach (DataRow queryRow in dataStructure.Tables["AccountData"].Rows)
{
var queryAcctNum = queryRow[0].ToString();
if (acctNum.Equals(queryAcctNum))
{
row[12] = "Y";
Console.WriteLine("Yes count: " + cnt);
}
else
{
row[12] = "N";
}
}
cnt++;
};
如何填充 dataStructure.Tables["AccountData"]
:
//Read each row from the table and output the results into the data set
while (readFile.Read())
{
//Create a row to hold data
DataRow datarow = dataStructure.Tables["AccountData"].NewRow();
datarow["AccountNumber"] = readFile.GetString(0).Trim();
datarow["LegalStatus"] = readFile.GetString(1);
//add the row to the data table AccountData
dataStructure.Tables["AccountData"].Rows.Add(datarow);
}
最佳解决思路
你的内部循环似乎是不必要的。为什么不创建查找:
var knownAccountNumbers = new HashSet<string>(
dataStructure.Tables["AccountData"].Rows
.Cast<DataRow>()
.Select(row => row[0].ToString()));
现在你的循环是简单的:
foreach (DataRow row in dt.Rows)
{
var accountNumber = row[0].ToString().Replace(""", "");
row[12] = knownAccountNumbers.Contains(accountNumber) ? "Y" : "N";
}
我想我记得读一次,HashSet
的内存使用量是每个条目 12 个字节,条目大小。所以你看的是 12MB + 1,000,000 *(2 * accountNumber.Length) 。所以基本上没有什么事情的宏伟计划。然而,您正在获得不断的时间查询,这对于这种工作应该是一个巨大的好处。
命名时应该更加小心。不要缩写例如 acctNum
– > accountNumber
。
次佳解决思路
该代码似乎被破坏,您找不到匹配后不会 break;
,所以所有记录都可能有 row[12] == "N"
。
你应该真正在 accountNumber
上加入:
var matchingRows =
from DataRow row in dt.Rows
let rowKey = row[0].ToString().Replace(""", "")
join DataRow queryRow in dataStructure.Tables["AccountData"].Rows
on rowKey equals queryRow[0]
select row;
foreach(var row in matchingRows)
{
row[12] = "Y";
}
这样,您将停止在第一场比赛中进行搜索,并只更新匹配的行。
第三种解决思路
几点评论:
避免内存密集型数据类型
而不是使用 DataTable for dt,只需从 csv 直接读取一行一行 (readLine(),然后拆分 (‘,’),这将大大减少内存使用量,而不是一次加载所有 170 万行,当你一次只使用一个。
排序的数据更快
按帐号对 dataStructure 进行排序。之后,您可以进行二进制搜索以查找帐号,并在您重复它们之后进行休息。这将大大减少你的循环时间。如果您可以在将数据读入 dataStructure 之前获取数据库进行排序,更好。
替代想法
您也可以尝试将所有 dt 加载到 dataStructure 运行的同一数据库中的临时表中,然后使用存储过程进行更新。数据库将能够比 C#中的循环更有效地执行此更新
第四种思路
知道您正在迭代 dataStructure.Tables["AccountData"]
的所有记录,无论您是否找到匹配项。你真的应该 break
出这个循环,如果 acctNum.Equals(queryAcctNum)
。这应该加快你的任务很多 (至少如果可以找到数据) 。
另一个可能的增强可能是对表的行进行排序,并存储内循环的最后找到的”index” 以将其用作下一次迭代的开始。这将需要将循环从 foreach
更改为 could be faster anyway 的正常 for
循环。
假设 dt
的第一列也被命名为”AccountNumber”,这应该加快进程
dt.DefaultView.Sort = "AccountNumber";
var accountDataTable = dataStructure.Tables["AccountData"];
accountDataTable.DefaultView.Sort = "AccountNumber";
int numberOfAccountDataRows = accountDataTable.Rows.Count;
int currentIndex = 0;
foreach (DataRow row in dt.Rows)
{
var acctNum = row[0].ToString().Replace(""", "");
for(int i = currentIndex; i < numberOfAccountDataRows; i++)
{
var queryAcctNum = accountDataTable.Rows[i][0].ToString();
if (acctNum.Equals(queryAcctNum))
{
row[12] = "Y";
currentIndex = i;
break;
}
}
}
第五种思路
免责声明:我不在 C#中编码,所以你可能会失望。如果有人愿意将其移植到 C#,请做,这可能会帮助 OP 更多。
比较两个 data-sets 进行包含/排除可以在流上进行,只要它们被排序。
该算法接近合并排序的合并通过; 在 pseudo-code
left = /*sorted stream 1*/
right = /*sorted stream 2*/
while not left.empty() and not right.empty():
if left.current() == right.current():
print "Common item", left.current()
left.moveToNext()
right.moveToNext()
continue
if left.current() < right.current():
print "Left specific item", left.current()
left.moveToNext()
continue
if left.current() > right.current():
print "Right specific item", right.current()
right.moveToNext()
continue
while not left.empty()
print "Left specific item", left.current()
left.moveToNext()
while not right.empty()
print "Right specific item", right.current()
right.moveToNext()
优点:
-
恒定 memory
-
快速
缺点:
-
需要预先排列两个流
如果您可以轻松地按排序顺序获取这两个流,那么它可能会获胜。
如果您必须准备数据集 (排序),那么对于非常大的数据集 (几乎不适合内存) 仍然是有利的。
如果两个数据集都很容易适合内存,而且一个没有排序,那么使用另一个解决方案 (在 hash-map 和 look-up 中将较小的一个解压缩到其中,同时遍历较大的一个) 。
参考文献
注:本文内容整合自 Google/Baidu/Bing 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。