为什么检查字典是否包含键更快,而不是捕捉异常,以防它不't?

想象一下代码:

public class obj
{
// elided
}


public static Dictionary<string, obj> dict = new Dictionary<string, obj>();

方法1

public static obj FromDict1(string name)
{
if (dict.ContainsKey(name))
{
return dict[name];
}
return null;
}

方法2

public static obj FromDict2(string name)
{
try
{
return dict[name];
}
catch (KeyNotFoundException)
{
return null;
}
}

我很好奇这两个函数的性能是否有差异,因为第一个函数应该比第二个函数慢——假设它需要两次检查字典是否包含值,而第二个函数只需要访问字典一次,但哇,实际上是相反的:

循环1 000 000个值(其中10 000个存在,90 000个不存在):

第一个函数:306毫秒

第二个函数:20483毫秒

为什么呢?

编辑:正如您可以在这个问题下面的评论中注意到的那样,在没有0个不存在的键的情况下,第二个函数的性能实际上比第一个函数略好。但一旦存在至少1个或多个不存在的密钥,第二个密钥的性能就会迅速下降。

133707 次浏览

一方面,抛出异常本身代价就很高,因为堆栈必须被解除等等 另一方面,通过键来访问字典中的值是很便宜的,因为这是一个快速的O(1)操作

BTW:正确的方法是使用TryGetValue

obj item;
if(!dict.TryGetValue(name, out item))
return null;
return item;

只访问字典一次而不是两次 如果你真的想在键不存在时只返回null,上面的代码可以进一步简化:

obj item;
dict.TryGetValue(name, out item);
return item;

这是有效的,因为如果不存在带有name的键,TryGetValueitem设置为null

字典是专门为进行超快速键查找而设计的。它们被实现为哈希表,并且相对于其他方法,它们的条目越多速度越快。使用异常引擎只应该在您的方法未能完成您设计的任务时使用,因为它是一个大型对象集,为您提供了许多处理错误的功能。我曾经构建了一个完整的库类,其中所有内容都被try catch块包围,并且惊讶地看到调试输出为600多个异常中的每个异常都包含单独的一行!