【问题标题】:Intersection() and Except() is too slow with large collections of custom objectsIntersection() 和 except() 对于大量自定义对象来说太慢了
【发布时间】:2013-11-01 20:47:14
【问题描述】:

我正在从另一个数据库导入数据。

我的过程是将远程数据库中的数据导入到名为remoteDataList<DataModel> 中,并将本地数据库中的数据导入到名为localDataList<DataModel> 中。

然后我使用 LINQ 创建一个不同的记录列表,以便我可以更新本地数据库以匹配从远程数据库中提取的数据。像这样:

var outdatedData = this.localData.Intersect(this.remoteData, new OutdatedDataComparer()).ToList();

然后我使用 LINQ 创建一个记录列表,这些记录在remoteData 中不再存在,但在localData 中确实存在,因此我将它们从本地数据库中删除。

像这样:

var oldData = this.localData.Except(this.remoteData, new MatchingDataComparer()).ToList();

然后我使用 LINQ 执行与上述相反的操作,将新数据添加到本地数据库。

像这样:

var newData = this.remoteData.Except(this.localData, new MatchingDataComparer()).ToList();

每个集合导入大约 7 万条记录,3 次 LINQ 操作中的每一个都需要 5 到 10 分钟才能完成。 我怎样才能加快速度?

这是集合使用的对象:

internal class DataModel
{
        public string Key1{ get; set; }
        public string Key2{ get; set; }

        public string Value1{ get; set; }
        public string Value2{ get; set; }
        public byte? Value3{ get; set; }
}

用于检查过时记录的比较器:

class OutdatedDataComparer : IEqualityComparer<DataModel>
{
    public bool Equals(DataModel x, DataModel y)
    {
        var e =
            string.Equals(x.Key1, y.Key1) &&
            string.Equals(x.Key2, y.Key2) && (
                !string.Equals(x.Value1, y.Value1) ||
                !string.Equals(x.Value2, y.Value2) ||
                x.Value3 != y.Value3
                );
        return e;
    }

    public int GetHashCode(DataModel obj)
    {
        return 0;
    }
}

用于查找新旧记录的比较器:

internal class MatchingDataComparer : IEqualityComparer<DataModel>
{
    public bool Equals(DataModel x, DataModel y)
    {
        return string.Equals(x.Key1, y.Key1) && string.Equals(x.Key2, y.Key2);
    }

    public int GetHashCode(DataModel obj)
    {
        return 0;
    }
}

【问题讨论】:

  • 你应该真的实现哈希码。
  • 哈希码用于在哈希表中定位对象,这可能是ExceptIntersect在内部用于查找匹配对象的方法。通过返回一个常数值,所有对象将具有相同的位置,并且对匹配的搜索降级为所有候选对象之间的线性搜索。您需要根据用于相等的属性正确实现GetHashCode
  • 正确。我添加了一个哈希码,操作需要一瞬间!谢谢。

标签: c# performance algorithm linq collections


【解决方案1】:

使用恒定的哈希码会破坏性能。这是 Intersect 使用的内部代码(通过反编译器获得)

public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
    if (first == null)
    {
        throw Error.ArgumentNull("first");
    }
    if (second == null)
    {
        throw Error.ArgumentNull("second");
    }
    return Enumerable.IntersectIterator<TSource>(first, second, comparer);
}

private static IEnumerable<TSource> IntersectIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
    Set<TSource> set = new Set<TSource>(comparer);
    foreach (TSource current in second)
    {
        set.Add(current);
    }
    foreach (TSource current2 in first)
    {
        if (set.Remove(current2))
        {
            yield return current2;
        }
    }
    yield break;
}

看到它在内部使用了Set,如果你实现了哈希码会大大提高它的性能。

MatchingDataCompaer 是两者中比较容易的一个,所以我会为你做一个。

internal class MatchingDataComparer : IEqualityComparer<DataModel>
{
    public MatchingDataComparer()
    {
        comparer = StringComparer.Ordnal; //Use whatever comparer you want.
    }

    private readonly StringComparer comparer;

    public bool Equals(DataModel x, DataModel y)
    {
        return comparer.Equals(x.Key1, y.Key1) && comparer.Equals(x.Key2, y.Key2);
    }

    //Based off of the advice from http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode
    public int GetHashCode(DataModel obj)
    {    
        unchecked // Overflow is fine, just wrap
        {
            int hash = 17;
            hash = hash * 23 + comparer.GetHashCode(obj.Key1);
            hash = hash * 23 + comparer.GetHashCode(obj.Key2);
            return hash;
        }
    }
}

您可能会在OutdatedDataComparer 中使用来自MatchingDataComparer 的哈希码函数,它可能不是“最佳”哈希码1,但它会是“合法” 2 一个,并且会比硬编码的 0 快很多。


1.或者可能是,我不确定如何包含第三个&amp;&amp; 条件
2. 如果a.Equals(b) == truea.GetHashCode() == b.GetHashCode()
如果a.Equals(b) == false 那么a.GetHashCode() == b.GetHashCode() || a.GetHashCode() != b.GetHashCode()

【讨论】:

  • 感谢斯科特的详细解释!我实现了哈希码,现在操作很快。
猜你喜欢
  • 1970-01-01
  • 2016-05-15
  • 2020-08-08
  • 2012-06-16
  • 2018-09-21
  • 1970-01-01
  • 1970-01-01
  • 2019-07-29
  • 2012-07-22
相关资源
最近更新 更多