问题描述

我有两个数据表:

  • 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#中的循环更有效地执行此更新

第四种思路

基于 @Johnbot comment

知道您正在迭代 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 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。