我已经尝试了一些测试,如果有所有的点击或错过也没关系。 TryGetValue总是更快。什么时候应该使用ContainsKey?
答案 0 :(得分:17)
这取决于你使用的方法是什么。如果您打开Reference source,您会看到。
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);
}
}
因此,如果您将密钥与FindEntry
一起使用,那么您将在ContainsKey
方法+数组查找中进行2次,然后使用dic[key]
获取值。你有一次额外调用FindEntry
的费用。
答案 1 :(得分:7)
从概念上讲,这两种方法非常不同。 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参数,这比常规参数更有开销。如果未找到该类型,则此变量将设置为默认值,对于字符串,该变量为null。在上面的示例中,未使用此空值,但无论如何我们都会产生开销。最后,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的TryGetValue
慢约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的TryGetValue
慢约13%对于未命中,TryGetValue比ContainsKey慢约4.6%