c# 图解泛型List<T>, HashTable和Dictionary<TKey,TValue>
前辈在代码中使用了HashTable,由于我用的比较少,不能理解,为什么不用Dictionary?看了源码以及查阅资料,总结如下:
首先看看它们的继承体系:

我把list<T>的继承体系也一并画出来,因为c#集合中List<T>和Dictionary<T>这两种数据结构实在太常用了。从上图中可以看到Dictionary和HashTable都继承于IDictionary。既然父辈都相同,那么注定会有很多相似的地方。那么它们又会有哪些不同呢?
这个还得研究源码,先看看HashTable:
private struct bucket {
public Object key;
public Object val;
public int hash_coll; // Store hash code; sign bit means there was a collision.
}
private bucket[] buckets;
HashTable 定义了一个结构体数组,hash_coll里面存储了hash code。那么hash code又是什么东西呢?hash code其实类似于索引。还记得int[],按顺序存储,我们必须知道它确切的存储位置,即在数组中的索引。在HashTable中,Key的类型是object,所以理论上可以是任意类型,但是我们实际上最常用的是Int和String类型。因此,HashTable是可以按字符串索引的。归根结底,微软扩展了数组,自定义了一个数组。这就带来了一个问题。什么问题?存储问题。以前的数组存储,我们按数字索引存储。现在呢,我们按key存储,如何按key存储?这就需要一个方法,把key映射到数组的不同位置上,并且不能重复。我们把这个映射方法称为散列函数GetHashCode。如果hash code出现重复了,我们称为哈希碰撞或者哈希冲突。产生冲突当然需要解决了。解决这一冲突的简单办法,便是不断地尝试其它位置,直到冲突解决。想想我们中午去饭店吃饭的时候,总要找个座位,这个座位必须是空的才行,如果发现这个座位有人,那么我们再去寻找其它的座位。如果所有的座位都满了,我们只能等待别人让出座位。程序若发现数组中的大部分位置都被占了,那么会扩展这个数组,否则会影响性能,总不能把时间花在找座位上。如下图所示:

Dictionary的内部存储结构:
private struct Entry {
public int hashCode; // Lower 31 bits of hash code, -1 if unused
public int next; // Index of next entry, -1 if last
public TKey key; // Key of entry
public TValue value; // Value of entry
}
private int[] buckets;
private Entry[] entries;
从结构体的定义中,我们可以看出,Dictionary比HashTable多了一个next字段。那么这个next字段是做什么用的?
Dictionary处理哈希冲突的方法,是把具有相同的哈希值的元素放到一个逻辑链表里面。那么next字段正是指向下一个元素的索引。这种处理冲突的方法跟化学当中的同位素还是有点相似的。我们把不同的元素放到数组中,每个元素的同位素放到自己的逻辑链表里。具体如何实现,我们看源码:
private void Insert(TKey key, TValue value, bool add) {
if( key == null ) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
if (buckets == null) Initialize();
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
int targetBucket = hashCode % buckets.Length;
int index;
if (freeCount > ) {
index = freeList;
freeList = entries[index].next;
freeCount--;
}
else {
if (count == entries.Length)
{
Resize();
targetBucket = hashCode % buckets.Length;
}
index = count;
count++;
}
entries[index].hashCode = hashCode;
entries[index].next = buckets[targetBucket];
entries[index].key = key;
entries[index].value = value;
buckets[targetBucket] = index;
version++;
我把插入字典的核心代码贴出来。这段代码,不画图不太好理解。首先解释一下,entries是存放元素的数组,buckets也是数组,记录entries数组的索引。假设我们数组大小为5,hashcode的取值范围在1-30之间。
图1为数组的初始状态:buckets一开始全部为-1,entries为空数组

图1
图2:插入hashcode为9的元素
9%5=4,所以buckets[4]=0,记录第一元素的索引值。

图2
图3:插入第二个元素,hashcode=26,26%5=1,所以buckets[1]=1

图3
图4:插入第三个元素,hashcode=25,25%5=0,所以buckets[0]=2

图5:插入第四个元素,hashcode=10, 10%5=0,所以buckets[0]=3

注意:第三个元素指向了第二个元素,因为buckets[0]同时记录了元素2和元素3,所以发生了冲突,此时用到了元素的链表来记录所有冲突的元素。
图6:插入第五个元素,hashcode=5,5%5=0,所以buckets[0]=4

发现了吗?如果发生冲突,新的元素,总是指向前一任。所谓的元素的链表,不是真实的链表结构存储的,而是逻辑上,用Next记录前任元素的索引值罢了,还是用的同一个数组。
好了,Dictionary和HashTable是同源,它们实现了自己的哈希算法。至于两者之间的效率,那得具体看情况了。对于含有大量装箱拆箱的操作,那当然了用泛型字典合适。对于数据量比较小的字符串处理,用HashTable反倒效率可能高一些。具体情况,再具体研究吧,没有一概而论。
c# 图解泛型List<T>, HashTable和Dictionary<TKey,TValue>的更多相关文章
- 泛型与非泛型集合类的区别及使用例程,包括ArrayList,Hashtable,List<T>,Dictionary<Tkey,Tvalue>,SortedList<Tkey,Tvalue>,Queue<T>,Stack<T>等
泛型与非泛型集合类在C#程序中是非常重要的一个基础概念,这里列一个表来进行对比: 非泛型集合类 泛型集合类 描述 ArrayList List<T> 表示具有动态大小的对象数组 Hasht ...
- .net框架-字典对象 Hashtable & Dictionary<TKey,TValue> & SortedList
字典对象: 字典对象是表示键值对的集合 字典对象有Hashtable(.net 1.0)及其泛型版本Dictionary<TKey,TValue> 字典对象还包括SortedList及其泛 ...
- 自定义一个可以被序列化的泛型Dictionary<TKey,TValue>集合
Dictionary是一个键值类型的集合.它有点像数组,但Dictionary的键可以是任何类型,内部使用Hash Table存储键和值.本篇自定义一个类型安全的泛型Dictionary<TKe ...
- C#中数组、集合(ArrayList)、泛型集合List<T>、字典(dictionary<TKey,TValue>)全面对比
C#中数组.集合(ArrayList).泛型集合List<T>.字典(dictionary<TKey,TValue>)全面对比 为什么把这4个东西放在一起来说,因为c#中的这4 ...
- .NET中Dictionary<TKey, TValue>浅析
.NET中Dictionary<TKey, Tvalue>是非常常用的key-value的数据结构,也就是其实就是传说中的哈希表..NET中还有一个叫做Hashtable的类型,两个类型都 ...
- Dictionary<Tkey.TValue>与SortedList
一.概述 表示Key/Value集合,可以添加删除元素,允许按Key来访问元素.是Hashtable的泛型等效类. 它需要一个相等实现来确定键是否相等,可以使用实现了IEqualityComparer ...
- Dictionary<TKey, TValue> 类
C# Dictionary<TKey, TValue> 类 Dictionary<TKey, TValue> 泛型类提供了从一组键到一组值的映射.字典中的每个添加项都由一个值及 ...
- 使用结构struct作为Dictionary<TKey,TValue>的键
我们经常用简单数据类型,比如int作为泛型Dictionary<TKey,TValue>的key,但有时候我们希望自定义数据类型作为Dictionary<TKey,TValue> ...
- .net源码分析 – Dictionary<TKey, TValue>
接上篇:.net源码分析 – List<T> Dictionary<TKey, TValue>源码地址:https://github.com/dotnet/corefx/blo ...
随机推荐
- JAVA死锁
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放.由于线程被无限期地阻塞,因此程序不能正常运行. 简单的说就是:线程死锁时,第一个线程等待第二个线程释放资源,而同时第 ...
- 使用域账号统一管理cisco网络设备
1.思科设备和微软系统整合的背景: 公司内部有一定数量的客户端,为了实现统一化,在管理内部部署了域架构,这样可以通过组策略对客户端进行批量化管理,提高了管理的效率. 同样公司内部有一定数量的网络设备( ...
- HDU 5056
题意略. 巧妙的尺取法.我们来枚举每个字符str[i],计算以str[i]为结尾的符合题意的串有多少个.那么我们需要处理出str[i]的左边界j,在[j,i]之间的串均为符合题意的 串,那么str[i ...
- Flask從入門到入土(二)——請求响应與Flask扩展
———————————————————————————————————————————————————————————— 一.程序和請求上下文 Flask從客戶端收到請求時,要讓視圖函數能訪問一些對象 ...
- UVA - 1631 Locker 记忆化搜索
题意:给定两个密码串,每次可以让1~3个相邻的密码向上或者向下滚动,每个密码是 ,问最少需要多少次滚动可以让原串成为目标串? 思路:假设当前要让第i位密码还原,我们可以同时转动,不同的转动方式会影响后 ...
- SpringMVC的常用注解
在SpringMVC中常用的注解主要都是用于Controller上,所以下面的四大不同类型的注解都是根据它们处理的request的不同内容部分来区分的: 处理requ ...
- jquery 图片转为base64
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...
- Linux CentOS安装配置MySQL数据库
没什么好说的,直接正面刚吧. 安装mysql数据库 a)下载mysql源安装包:wget http://dev.mysql.com/get/mysql57-community-release-el7- ...
- NLP+句法结构(三)︱中文句法结构(CIPS2016、依存句法、文法)
摘录自:CIPS2016 中文信息处理报告<第一章 词法和句法分析研究进展.现状及趋势>P8 -P11 CIPS2016> 中文信息处理报告下载链接:http://cips-uplo ...
- 实时Web与WebSocket实践
引言:实时Web越来越被重视,Google.Facebook等大公司也逐渐开始提供实时性服务.实时Web将是未来最热门的话题之一. 本文选自<基于MVC的JavaScript Web富应用开发 ...