在.Net 模仿java 的过程中,抛弃了 HashMap,所以我们今天分析下Dictionary、HashTable、HashSet区别。
处理碰撞,即碰撞到同一个Bucket槽上:
Hashtable和Dictionary从数据结构上来说都属于Hashtable(哈希表),都是对关键字(键值)进行散列操作,将关键字散列到Hashtable的某一个槽位中去,不同的是处理碰撞的方法。散列函数有可能将不同的关键字散列到Hashtable中的同一个槽中去,这个时候我们称发生了碰撞,为了将数据插入进去,我们需要另外的方法来解决这个问题。
Dictionary采用链表法处理碰撞:
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
//将HashCode的返回值转化为数组索引
int bucketIndex = hashCode % buckets.Length;
通过Hash算法来碰撞到指定的Bucket上,碰撞到同一个Bucket槽上所有数据形成一个单链表。
HashTable采用开放寻址法方法中的双重散列处理碰撞:
双重散列:
h(k,i) = (h1(k) +i*h2(k)) mod m (其中i = 0,1,…,m-1),其中h1和h2为辅助散列函数。初始探查位置为T(h1(k)), 后续的探查位置在此基础上加上偏移量h2(k)模m 。
取m为根据构造函数设定的初始容量,获取一个大于并接近的素数,并取h1(k) = k mod m, h2(k) = 1 + (k mod (m - 1))
适用场景:
经过测试,测试数据会有波动性,但基本能反应整体情况:
插入性能:List < HashTable < Dictionary < LinkedList
遍历性能:HashTable < Dictionary < LinkedList < List
删除性能:List < HashTable < LinkedList < Dictionary
经过测试,对于值类型(不包括 Object)的 Dictionary<TKey, TValue> 的性能优于 Hashtable,所以推荐使用Dictionary。
Dictionary和HashTable的区别
1:单线程程序中推荐使用 Dictionary, 有泛型优势, 且读取速度较快, 容量利用更充分。
2:多线程程序中推荐使用 Hashtable, 默认的 Hashtable 允许单线程写入, 多线程读取, 对 Hashtable 进一步调用 Synchronized() 方法可以获得完全线程安全的类型;而 Dictionary 非线程安全, 必须人为使用 lock 语句进行保护, 效率大减.
3:对于值类型,特定类型(不包括 Object)的 Dictionary<TKey, TValue> 的性能优于 Hashtable,这是因为 Hashtable 的元素属于 Object 类型,所以在存储或检索值类型时通常发生装箱和拆箱操作。
Dictionary和HashTable的相同点
Hashtable 类和 Dictionary<TKey, TValue> 泛型类实现 IDictionary 接口
Dictionary<TKey, TValue> 泛型类还实现 IDictionary<TKey, TValue>泛型接口。
因此,这些集合中的每个元素都是一个键/值对。
Dictionary<TKey, TValue> 类与 Hashtable 类的功能相同。
Dictionary、HashTable和List区别
我们清楚List<T>是对数组做了一层包装,我们在数据结构上称之为线性表,而线性表的概念是,在内存中的连续区域,除了首节点和尾节点外,每个节点都有着其唯一的前驱结点和后续节点。我们在这里关注的是连续这个概念。
而HashTable、Dictionary,是根据Key而根据Hash算法分析产生的内存地址,因此在宏观上是不连续的,虽然微软对其算法也进行了很大的优化。
由于这样的不连续,在遍历时,Dictionary必然会产生大量的内存换页操作,而List只需要进行最少的内存换页即可,这就是List和Dictionary在遍历时效率差异的根本原因。
所以根据Key的查找Dictionary、HashTable的效率是高于 List 的, 但是遍历的话则List效率更好。
HashSet
先来了解下HashSet<T>类,主要被设计用来存储集合,做高性能集运算,例如两个集合求交集、并集、差集等。从名称可以看出,它是基于Hash的,可以简单理解为没有Value的Dictionary。
HashSet<T>不能用索引访问,不能存储重复数据。
HashSet<T>和与List<T>的比较
HashSet<T>最大的优势是检索的性能,简单的说它的Contains方法的性能在大数据量时比List<T>好得多。曾经做过一个测试,将800W条int类型放在List<int>集合中,使用Contains判断是否存在,速度巨慢,而放在HashSet<int>性能得到大幅提升。
在内部算法实现上,HashSet<T>的Contains方法复杂度是O(1),List<T>的Contains方法复杂度是O(n),后者数据量越大速度越慢,而HashSet<T>不受数据量的影响。
所以在集合的目的是为了检索的情况下,我们应该使用HashSet<T>
代替List<T>
。比如一个存储关键字的集合,运行的时候通过其Contains方法检查输入字符串是否关键字。