【问题标题】:Contains on list is too slow, how to improve?列表中包含的内容太慢,如何改进?
【发布时间】:2016-03-07 20:44:34
【问题描述】:

我有一个对象列表,我希望将其缩减为仅具有包含在单独列表中的属性的对象。

List1 是一个简单字符串的列表。

List2 是包含两个字符串属性的对象列表; A 和 B。

A B 不存在于 List1 中的所有项目都应删除。

这个过程非常依赖时间,需要尽可能快。 目前我有以下实现;

var List1 = new List<String>() {"Around", "9000", "strings"}; //List of about 9000 strings
var List2 = databaseList.ToList(); //Around 2.5 million objects

var reducedList = new HashSet<Object>();            
foreach (var item in List2)
{
    if(List1.Contains(item.A) && List1.Contains(item.B))
    {
        reducedList.Add(item);
    }
}

这个过程大约需要 7 秒才能完成,这对于我目前的要求来说太慢了。

我尝试使用 LINQ 运行它,但结果相同,大约 7 秒。

var reducedList = List2.Where(r => List1.Contains(r.A)).Where(r => List1.Contains(r.B)).ToList();

有什么建议可以改善这一点吗?

编辑: 我无法在 SQL 方面执行此操作,因为我需要比较的 9000 个字符串无法“翻译”成 SQL 查询,但会超出我们 SQL Server 中允许的 2100 个输入参数设置。

【问题讨论】:

  • just 对数据库中的所有项目执行foreach 需要多长时间?您是在计时 foreach 循环,还是两个初始化语句?
  • databaseList 上的 foreach 包含超过 250 万个对象,这让我很困扰;还给出了名称(databaseList),您确定不能在数据库级别进行查询吗?
  • List1 的大小是多少,真的是“大约 9000 个字符串”吗?它也来自 db 吗?
  • 我认为这是第二行(执行 db-query 并从 db 加载所有对象)一直用完。您需要那里的完整对象/行吗?如果没有,请使用投影仅加载所需的列,从而加快查询速度。

标签: c# linq list


【解决方案1】:

我没有尝试过,但可能会提高性能。

var List1 = new List<String>() {"Around", "9000", "strings"};
var List2 = databaseList.ToList(); //Around 2.5 million objects

var reducedList = List2.RemoveAll(i => !List1.Contains(i.A) && !List1.Contains(i.B)).ToList();

【讨论】:

  • List&lt;T&gt;.RemoveAll(Predicate&lt;T&gt;) 返回一个 int,而不是一个列表,所以这不会编译。另外,.Contains(...) 仍然被执行,那为什么会更好呢?
【解决方案2】:

首先,让我们更快地查找第一个列表:

var sought = new HashSet<String>() {"Around", "9000", "strings"};

如果您只是要遍历它,为什么还要在内存中创建一个列表。除非您打算将 List2 用于其他目的,否则它不会做任何事情。

foreach (var item in databaseList)
{
  if(sought.Contains(item.A) && sought.Contains(item.B))
  {
    reducedList.Add(item);
  }
}

还将您的reducedList 哈希列表适当地输入为new HashList&lt;TheActualTypeOfTheItemsHere&gt;

如果该类型未实现IEquatable&lt;T&gt;,则添加该实现。如果这不可能,那么创建一个适当的IEqualityComparer&lt;T&gt; 并在reducedList 的构造函数中使用它。

【讨论】:

  • 包含 9000 个字符串的列表将被转换为 9000 个参数,SQL 类似于 WHERE item.A = value1 OR item.A = value2 OR ...。 SQL Server 也不支持那么多参数。
  • @Maarten 嘿。我什至没有看实际的字符串,心里想“好吧,如果第一个列表中只有几个字符串……”!
  • @Maarten 对于这种用途现在有点清醒了。
【解决方案3】:

正如@Ondrej Svejdar 所指出的,如果databaseList 来自EntityFramework,那么你不应该调用ToList(),因为它会进行数据库查询,返回250 万条记录

var reducedList = databaseList
                 .Where(r => List1.Contains(r.A) && List1.Contains(r.B))
                 .ToList();

如果List1来自数据库(我希望9000是一个很大的手动构建数字),那么考虑使用join操作符。

【讨论】:

  • 如果List1 是内存中的列表,那么这 9000 个项目将被转换为 SQL 参数,这可能无法编译(无论如何都不会在 SQL Server 中编译)。
【解决方案4】:

您的数据库版本可能是个问题: 参考:Slower sqlite 由于我不知道您使用的是什么数据库之王,因此这可能是“无用的”。但是看看你的数据库是否有最新版本,并且看看它的速度有多快。在另一台电脑/浏览器/手机上测试你的程序,看看你是否得到相同的结果。

现在您应该会发现究竟是什么花费了很长时间。尝试删除您的 if 语句,看看速度是否有所提高。如果它没有改善,那么查询速度就是一个问题。如果是这样,请尝试以您不需要使用该 if 的方式查询您的数据库。

【讨论】:

    【解决方案5】:

    您可以尝试在您的对象上实现IEquatable,因为Contains 将调用该方法来查看对象是否在列表中

    请参阅此post 了解更多信息。

    【讨论】:

      【解决方案6】:

      List1 通常有多大?如果它是您的示例中的一个小列表,那么保留一个列表就可以了。如果不是这种情况,则使用 HashSet 可能会获得更好的结果。

      我相信,真正的游戏规则改变者是避免将 250 万个对象加载到 List2 中,只是为了稍后过滤它们。如果 databaseList 是 DbSet 的某种 IEnumerable,您可能会通过以下方式获得更好的结果:

      var List1 = new List<String>() {"Around", "9000", "strings"};
      var reducedList = (from item in databaseList
                         where List1.Contains(item.A) && List1.Contains(item.B)
                         select item).ToList();
      

      对精简列表使用 HashSet 只会减慢插入速度。如果您使用它来避免重复或之后加快查找速度,那么您应该保留它。否则列表会更好。

      【讨论】:

        猜你喜欢
        • 2015-08-28
        • 2012-05-26
        • 2012-04-13
        • 2017-05-20
        • 1970-01-01
        • 2011-07-03
        • 2019-01-04
        • 1970-01-01
        • 2021-11-13
        相关资源
        最近更新 更多