是否有理由使用ContainsKey而不是TryGetValue ?
本文关键字:TryGetValue ContainsKey 有理由 是否 | 更新日期: 2023-09-27 18:11:38
我已经尝试了一些测试,如果有所有命中或错过并不重要。TryGetValue总是更快。什么时候应该使用ContainsKey?
这取决于您使用的方法。如果你打开参考源代码,你会看到。
public bool TryGetValue(TKey key, out TValue value)
{
int index = this.FindEntry(key);
if (index >= 0)
{
value = this.entries[index].value;
return true;
}
value = default(TValue);
return false;
}
public bool ContainsKey(TKey key)
{
return (this.FindEntry(key) >= 0);
}
就像你看到的TryGetValue
和ContainsKey
一样+一个数组查找
如果你的逻辑只是检查键是否存在于Dictionary
中,而不是与此键相关的其他内容(获取键的值),你应该使用ContainsKey
。
如果您想获取特定键的值,TryGetValue
比
if(dic.ContainsKey(keyValue))
{
dicValue = dic[keyValue]; // here you do more work!
}
Dictionary[key]
的逻辑
public TValue this[TKey key]
{
get {
int i = FindEntry(key);
if (i >= 0) return entries[i].value;
ThrowHelper.ThrowKeyNotFoundException();
return default(TValue);
}
set {
Insert(key, value, false);
}
}
因此,如果您使用ContainsKey
的键,然后使用dic[key]
的值,那么您将在FindEntry
方法+数组查找中执行2次。您有额外一次调用FindEntry
的开销。
从概念上讲,这两种方法是非常不同的。ContainsKey只是检查给定的键是否在字典中。TryGetValue将尝试返回给定键的值,前提是它在字典中。两者都可以很快,这取决于你想做什么。
考虑下面的方法,它从字典中返回一个值或返回string.Empty。
Dictionary<int,string> Dict = new Dictionary<int,string>();
string GetValue1(int key)
{
string outValue;
if (!Dict.TryGetValue(key, out outValue))
outValue = string.Empty;
return outValue;
}
相比string GetValue2(int key)
{
return (Dict.ContainsKey(key))
? Dict[key]
: string.Empty;
}
两者都相对较快,并且在大多数情况下两者之间的性能可以忽略不计(参见下面的单元测试)。如果我们想要挑剔一点,使用TryGetValue需要使用out参数,这比常规参数的开销更大。如果找不到类型,该变量将被设置为默认值,对于字符串则为空。在上面的示例中,没有使用这个空值,但我们还是会产生开销。最后,GetValue1需要使用一个局部变量outValue,而GetValue2则不需要。
BACON指出,在找到值的情况下,GetValue2将使用2次查找,这相对比较昂贵。这是真的,并且还意味着在没有找到键的情况下,GetValue2将执行得更快。因此,如果程序预计大多数查找都将失败,则使用GetValue2。否则使用GetValue1。
如果处理ConcurrentDictionary,使用TryGetValue,因为对它的操作应该是原子的,也就是说,一旦你在多线程环境中检查一个值,当你试图访问这个值时,这个检查可能是不正确的。
下面包含了2个单元测试,测试了使用不同Key类型(int vs string)的字典的两种方法之间的性能。这个基准测试只显示了两种方法之间的差距如何随着上下文的变化而缩小/扩大。
[TestMethod]
public void TestString()
{
int counter = 10000000;
for (var x = 0; x < counter; x++)
DictString.Add(x.ToString(), "hello");
TimedLog("10,000,000 hits TryGet", () =>
{
for (var x = 0; x < counter; x++)
Assert.IsFalse(string.IsNullOrEmpty(GetValue1String(x.ToString())));
}, Console.WriteLine);
TimedLog("10,000,000 hits ContainsKey", () =>
{
for (var x = 0; x < counter; x++)
Assert.IsFalse(string.IsNullOrEmpty(GetValue2String(x.ToString())));
}, Console.WriteLine);
TimedLog("10,000,000 misses TryGet", () =>
{
for (var x = counter; x < counter*2; x++)
Assert.IsTrue(string.IsNullOrEmpty(GetValue1String(x.ToString())));
}, Console.WriteLine);
TimedLog("10,000,000 misses ContainsKey", () =>
{
for (var x = counter; x < counter*2; x++)
Assert.IsTrue(string.IsNullOrEmpty(GetValue2String(x.ToString())));
}, Console.WriteLine);
}
[TestMethod]
public void TestInt()
{
int counter = 10000000;
for (var x = 0; x < counter; x++)
DictInt.Add(x, "hello");
TimedLog("10,000,000 hits TryGet", () =>
{
for (var x = 0; x < counter; x++)
Assert.IsFalse(string.IsNullOrEmpty(GetValue1Int(x)));
}, Console.WriteLine);
TimedLog("10,000,000 hits ContainsKey", () =>
{
for (var x = 0; x < counter; x++)
Assert.IsFalse(string.IsNullOrEmpty(GetValue2Int(x)));
}, Console.WriteLine);
TimedLog("10,000,000 misses TryGet", () =>
{
for (var x = counter; x < counter * 2; x++)
Assert.IsTrue(string.IsNullOrEmpty(GetValue1Int(x)));
}, Console.WriteLine);
TimedLog("10,000,000 misses ContainsKey", () =>
{
for (var x = counter; x < counter * 2; x++)
Assert.IsTrue(string.IsNullOrEmpty(GetValue2Int(x)));
}, Console.WriteLine);
}
public static void TimedLog(string message, Action toPerform, Action<string> logger)
{
var start = DateTime.Now;
if (logger != null)
logger.Invoke(string.Format("{0} Started at {1:G}", message, start));
toPerform.Invoke();
var end = DateTime.Now;
var span = end - start;
if (logger != null)
logger.Invoke(string.Format("{0} Ended at {1} lasting {2:G}", message, end, span));
}
TestInt
的结果10,000,000 hits TryGet Started at ...
10,000,000 hits TryGet Ended at ... lasting 0:00:00:00.3734136
10,000,000 hits ContainsKey Started at ...
10,000,000 hits ContainsKey Ended at ... lasting 0:00:00:00.4657632
10,000,000 misses TryGet Started at ...
10,000,000 misses TryGet Ended at ... lasting 0:00:00:00.2921058
10,000,000 misses ContainsKey Started at ...
10,000,000 misses ContainsKey Ended at ... lasting 0:00:00:00.2579766
对于命中,ContainsKey大约慢25%
对于未命中,TryGetValue比ContainsKey慢约13%
TestString
结果10,000,000 hits TryGet Started at ...
10,000,000 hits TryGet Ended at ... lasting 0:00:00:03.2232018
10,000,000 hits ContainsKey Started at ...
10,000,000 hits ContainsKey Ended at ... lasting 0:00:00:03.6417864
10,000,000 misses TryGet Started at ...
10,000,000 misses TryGet Ended at ... lasting 0:00:00:03.6508206
10,000,000 misses ContainsKey Started at ...
10,000,000 misses ContainsKey Ended at ... lasting 0:00:00:03.4912164
对于命中,ContainsKey大约慢13%
对于未命中,TryGetValue比ContainsKey