【问题标题】:Best way to check if a key exists in a Dictionary before adding it?在添加之前检查字典中是否存在键的最佳方法?
【发布时间】:2015-10-30 10:27:12
【问题描述】:

当从字典中获取一个你不确定是否存在的键时,你通常会使用TryGetValue 而不是ContainsKey + get indexer 以避免检查键两次的开销。换句话说,这是:

string password;
if (accounts.TryGetValue(username, out password))
{
    // use the password
}

会更喜欢这个:

if (accounts.ContainsKey(username))
{
    string password = accounts[username];
}

如果我想在将某个键设置为某个值之前检查它是否已经存在,该怎么办?例如,我想在用新密码覆盖之前检查用户名是否存在:

if (!accounts.ContainsKey(username))
{
    accounts.Add(username, password);
}
else
{
    Console.WriteLine("Username is taken!");
}

// this doesn't exist
if (!accounts.TrySetValue(username, password))
{
    Console.WriteLine("Username is taken!");
}

有没有比ContainsKeyAdd性能 的替代方案?

【问题讨论】:

  • var password = keyValue.Where(entry => entry.Key.Contains("password")) .Select(item => item.Key).FirstOrDefault();
  • @SanjeevS 感谢您的回答,但 LINQ 甚至比我上面发布的后备解决方案效率低得多。
  • 我不确定这里有什么收获,因为字典在内部基于哈希表,因此通过键获取值(例如使用索引器)具有 O(1) 复杂性.
  • @JamesKo:你在追逐阴影。 TryGetValue 调用私有函数 FindEntryContainsKey 也调用相同的私有 FindEntry 函数。索引器还调用FindEntry。据我所知,FindEntry 看起来非常快。你有没有分析过你的代码?你想解决什么问题?
  • @JamesKo 这就是为什么你事先打电话给ContainsKey。密钥集合是散列的,查找的复杂度为 O(1)。最坏情况的复杂度是 O(n),但只有当你有很多哈希冲突时才会出现这种情况(这就是为什么仔细覆盖 GetHashCode 很重要)。我将加入其他人并建议您分析您的代码。除非你试图做“理论优化”,在这种情况下没有明确的答案。

标签: c# performance dictionary data-structures hashtable


【解决方案1】:

如果您不想覆盖,我认为最好编写自己的扩展方法,例如TryGetValue。没有标准方法。

使用 CuncurrentDictionary,它有 TryAdd 方法,但同步会有开销。

所以,简单的回答——不,没有这样的方法。

【讨论】:

  • 除非您有权访问Dictionary 的私有成员,否则您不能编写这样的扩展方法。
  • @JamesKo 您需要哪些私有方法?你有 ContainsKey。够了
  • Backs,这个问题是关于性能的。如果您调用ContainsKey,然后调用Add,则您正在检查密钥是否存在两次。这就是 TryGetValue 存在的原因,也是它不能满足这个问题的原因。
  • @JamesKo ContainsKey 是 O(1) 操作。并且没有性能问题。由于获取的性能,并没有创建方法 TryGetValue。它是创建而不是 this[key] 方式并检查 KeyNotFoundException msdn.microsoft.com/en-us/library/bb347013(v=vs.110).aspx
  • @JamesKo 是什么原因?你真的有性能问题吗?还是只是为了证明它是可能的?我认为, Dictionary 是非常优化的类。因此,如果您有性能问题 - 添加图形或其他东西以查看问题是否在这里,在检查中。简短的回答 - 不,你不能:)
【解决方案2】:

我倾向于根据需要编写自己的扩展。

例如,GetValueOrDefault 是这样的:

public static V GetValueOrDefault<K, V>(this IDictionary<K, V> @this, K key, Func<V> @default)
{
    return @this.ContainsKey(key) ? @this[key] : @default();
}

可以这样使用:

var password = accounts.GetValueOrDefault(username, () => null);
if (password != null)
{
    //do stuff
}

SetValueIfExists:

public static V SetValueIfExists<K, V>(this IDictionary<K, V> @this, K key, V value)
{
    if (@this.ContainsKey(key))
    {
        @this[key] = value;
    }
}

SetValueIfNotExists:

public static V SetValueIfNotExists<K, V>(this IDictionary<K, V> @this, K key, V value)
{
    if (!@this.ContainsKey(key))
    {
        @this[key] = value;
    }
}

【讨论】:

  • @Enigmativity 哈哈谢谢你,但如果你注意到你的扩展方法和我在我的问题中提到的完全一样。
  • @JamesKo - 是的,但作为一种扩展方法,它使它的使用更加整洁。这不是你想要的吗?还是您想要在语言中加入一些东西?
  • @Enigmativity ContainsKey + indexer 索引键两次。正如我在我的问题中所说,我想知道是否有一种方法可以只索引一次键。此外,您的新扩展方法仍然不是我在问题中要求的 - 我想要SetValueIfNotExists
  • @JamesKo - 那么你一定是在要求字典本身内置的东西。您可以使用智能感知进行检查。没有办法避免双重检查。我在建议从代码中删除感知到的双重检查的方法,但实际上无法删除它。
  • @Enigmativity,你为什么在@this 之前使用@?或者你只是把它叫做你的字典?在同一行中有 2 个这个词指代两个不同的事物,这有点令人困惑
【解决方案3】:

如果您认为插入新名称将是常见情况,而尝试插入重复名称将是极少数情况,您可能只想使用捕获异常的开销。

try
{
    accounts.Add(username, password);
}
catch (ArgumentException)
{
    Console.WriteLine("Username is taken!");
}

如果您使用现有密钥调用Add,则会抛出ArgumentException。即使您经常重复,这仍然可能比您的ContainsKey 检查更有效。

【讨论】:

【解决方案4】:

我知道我迟到了,但是您可以使用一个技巧并在索引器设置之前存储计数并在索引器设置之后检查计数。如果计数相同,则您已覆盖键,否则您已添加新映射:

public static bool AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> 
    dictionary, TKey key, TValue value)
{
    var countBefore = dictionary.Count;
    dictionary[key] = value;
    return countBefore != dictionary.Count;
}

【讨论】:

  • 有趣的想法。但请注意,这在“通常”情况下没有帮助,即您正在检查它是否存在,因为您不想 想要覆盖现有条目。或者,如果您正在覆盖,您想要返回被覆盖的值(例如,您需要处置旧的值)。在第二种情况下,AddOrUpdate 将返回 TValue,并且会这样做:var valueBefore; dictionary.TryGetValue(key, out value); dictionary[key] = value; return valueBefore; 当然,这有 James Ko 试图避免的开销,只是提到它是一个有用的替代品。
  • 确实如此(我只看了标题)。认为您打算在 TryGet 中使用 valueBefore...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-08
  • 2012-03-10
  • 2017-08-26
  • 2020-09-15
  • 1970-01-01
  • 2015-05-05
相关资源
最近更新 更多