2.8.1 使用List和Dictionary时提高效率
前面几节讲解了List、Dictionary的源码,知道它们的实质都是数组。Dictionary有两个数组,一个数组用于保存索引,一个数组用于保存数据,List和Dictionary都是在遍历数组。
了解底层逻辑有助于我们更好地运用它们,比如,当使用List插入时,我们知道这是向数组中写入元素,并遍历其后面的数据依次向后移动的过程。了解了这些,每次使用List的Insert时都会更加注意。还有Contains()函数,它是一个以遍历形式来寻找结果的函数,每次使用它,都会从头到尾遍历一次,直到寻找到结果,Remove()也一样,它也是以遍历的形式存在的。如果在代码中使用它们的频率比较高,就会带来不必要的性能消耗,这是大多数人不注意的。我们常常因为不了解或图方便来使用这些接口,而并没有考虑它们带来的性能损耗。
Dictionary也有诸多问题。首先,它是一个使用Hash冲突方案来解决关键字的字典组件,因此Hash值与容器中数组的映射和获取Hash值的函数GetHashCode()比较关键。Hash冲突与数组大小有很大关系,数组越大,Hash冲突率就越小,因此我们在编写程序时应该注意设置Dictionary的初始大小,尽量设置一个合理的大小,而不是什么都不做,任由它自己扩容,这不但会让Hash冲突变得频繁,而且扩容时数组的回收也加重了GC单元的负担。除此之外,在C#中,所有类都继承自Object类,Dictionary使用Object类的GetHashCode()来获取类实例的Hash值,而GetHashCode()是用算法将内存地址转化为哈希值的过程,因此,我们可以认为它只是一个算法,并没有对任何值做缓存,每次调用它都会计算一次Hash值,这是比较隐形的性能损耗。如果频繁使用GetHasCode()作为关键字来提取Value,那么我们应该关注GetHashCode()的算力损耗,并确认是否可以用唯一ID(标识)的方式来代替GetHashCode()算法。