博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
.net集合类的研究-哈希表(一)--Hashtable,Dictionary<TKey,TValue>
阅读量:5101 次
发布时间:2019-06-13

本文共 4394 字,大约阅读时间需要 14 分钟。

今天来探究哈希表,.net内置的哈希表容器是Hashtable类,而Dictionary<TKey,TValue>是对应的泛型哈希表.

哈希表-Hashtable的实例

一般我们实例化ArrayList或List<T>的时候,如果不指定容量,则其内部是赋值为一个静态的空数组。当有添加操作时,会实例化为一个长度为4的数组,如果容量满了以后,再添加,就会自动扩充为两倍的容量。

哈希表也有一个类似的情况,new Hashtable()如果不指定长度,则会将内置bucket数组的长度默认指定为11。如果给定一个值如new Hashtable(20),也并不会将bucket数组长度设置为20,而是循环内部一个素数数组,设置为刚好大于20的素数(也叫质数),此处是23。可知,哈希表内部存取数组长度总是一个素数。相关代码:

 
public
Hashtable(
int
capacity,
float
loadFactor)
{
......
//默认值为11
int
num2
=
(num
>
11.0
)
?
HashHelpers.GetPrime((
int
) num) :
11
;
this
.buckets
=
new
bucket[num2];
......
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
internal
static
int
GetPrime(
int
min)
{
......
//循环素数数组找出刚好大于min的素数
for
(
int
i
=
0
; i
<
primes.Length; i
++
)
{
int
num2
=
primes[i];
if
(num2
>=
min)
{
return
num2;
}
}
......
}
//初始化素数数组
static
HashHelpers()
{
primes
=
new
int
[] {
3
,
7
,
11
,
0x11
,
0x17
,
0x1d
,
0x25
,
0x2f
,
0x3b
,
0x47
,
0x59
,
0x6b
,
0x83
,
0xa3
,
0xc5
,
0xef
,
0x125
,
0x161
,
0x1af
,
0x209
,
0x277
,
0x2f9
,
0x397
,
0x44f
,
0x52f
,
0x63d
,
0x78b
,
0x91d
,
0xaf1
,
0xd2b
,
0xfd1
,
0x12fd
,
0x16cf
,
0x1b65
,
0x20e3
,
0x2777
,
0x2f6f
,
0x38ff
,
0x446f
,
0x521f
,
0x628d
,
0x7655
,
0x8e01
,
0xaa6b
,
0xcc89
,
0xf583
,
0x126a7
,
0x1619b
,
0x1a857
,
0x1fd3b
,
0x26315
,
0x2dd67
,
0x3701b
,
0x42023
,
0x4f361
,
0x5f0ed
,
0x72125
,
0x88e31
,
0xa443b
,
0xc51eb
,
0xec8c1
,
0x11bdbf
,
0x154a3f
,
0x198c4f
,
0x1ea867
,
0x24ca19
,
0x2c25c1
,
0x34fa1b
,
0x3f928f
,
0x4c4987
,
0x5b8b6f
,
0x6dda89
};
}

从源码可以得出一个结论:Hashtable总是创建一个比我们预想中要大一些的数组。

哈希表-Hashtable的存取方式

Hashtable内部包含了一个bucket类型的数组变量,bucket是一个结构,用来保存Key,Value和哈希值。

 
private
struct
bucket
{
public
object
key;
public
object
val;
public
int
hash_coll;
}
public
class
Hashtable :
//
相关接口
{
private
IEqualityComparer _keycomparer;
private
object
_syncRoot;
private
bucket[] buckets;
  //....
}

从上面代码可以看出Hashtable跟ArrayList,List<T>一样,内部都是用Array数组进行存储数据。既然同样是数组,Hashtable和ArrayList存取方式有什么不同?

当Hashtable添加数据时,(例如:hash.Add(key)) 首先调用当前键值key的GetHashCode()方法,得到哈希值(该值为Int32),取该哈希值的绝对值跟内部数组(上面代码的buckets)的总容量进行余运算(就是'%'操作),得到一个小于总容量的值,把该值当做数组序号,存在对应的位置,通过Key获取数组序号的过程叫映射。

当然,两个不同的key值有可能的得到相同的序号,这叫哈希冲突,在此不做分析。

下面是部分源码:

 
private
void
Insert(
object
key,
object
nvalue,
bool
add)
{
  ......
uint
num3
=
this
.InitHash(key,
this
.buckets.Length,
out
num,
out
num2);
int
num4
=
0
;
int
index
=
-
1
;
  //num6就是通过余计算得到的数组序号。
int
num6
=
(
int
) (num
%
this
.buckets.Length);
  ......
}
private
uint
InitHash(
object
key,
int
hashsize,
out
uint
seed,
out
uint
incr)
{
  //获取key在CLR中的HashCode值,并取绝对值。
uint
num
=
(
uint
) (
this
.GetHash(key)
&
0x7fffffff
);
seed
=
num;
  //此处针对哈希冲突
incr
=
1
+
((
uint
) (((seed
>>
5
)
+
1
)
%
(hashsize
-
1
)));
return
num;
}

从Hashtable里取值的,也是通过映射Key值,找到数组中对应的存放索引位置,直接位置取出值,整个过程无需循环,时间复杂度为O(1),而ArrayList查找一个值则需要循环整个数组去逐个匹配,时间复杂度为O(n),由此可以看到哈希表在查询中的优势。

既然Hashtable本身也是数组存储,查询速度又快,那还要ArrayList做什么?存储数据的时候都用Hashtable不就行了?有句话说,XX打开了一扇门,就必然要关闭很多窗,现在分析一下Hashtable关闭的那些窗。

首先,哈希表不能有重复键值。

其次,哈希表会占用更多内存空间。

第一点显而易见,现在分析第二点。

哈希表-Hashtable添加时的内部扩充方式

我们知道ArrayList和List<T>在添加数值时,如果容量不足,会新建一个二倍容量的数组,然后把原数组数据复制过去,达到容量扩充的目的,所以使用ArrayList和List <T>的时候,建议在实例化方法里指定容量。

那么Hashtable在添加操作时碰到内部数组容量不足会怎么做?

其实处理方法和ArrayList基本相似,也是二倍扩充,但略有不同。不同点在于:

首先,扩充的大小比二倍略大。上面在实例化Hashtable那一部分中提到,由于哈希算法本身的特点,数组长度总是一个素数,所以当需要扩充时,新的数组长度是刚好大于原数组二倍长度的素数,也就是说略大于二倍。

其次,数组在未存满的情况下就要扩充。Hashtable有一个加载因子为0.72f。实际能存储的数据量等于内部Array长度* 0.72f。也就是说一个长度为11的数组,当存了7个数据以后,就不能再存储,需要要扩充容量。代码如下:

 
public
Hashtable(
int
capacity,
float
loadFactor)
{
......
//初始状态loafFactor=1f
this
.loadFactor
=
0.72f
*
loadFactor;
......
int
num2
=
(num
>
11.0
)
?
HashHelpers.GetPrime((
int
) num) :
11
;
this
.buckets
=
new
bucket[num2];
//实际可加载量
this
.loadsize
=
(
int
) (
this
.loadFactor
*
num2);
......
}
//Hashtable的Add方法内部调用了Insert方法
private
void
Insert(
object
key,
object
nvalue,
bool
add)
{
......
if
(
this
.count
>=
this
.loadsize)
{
     //内部容量扩充的方法
this
.expand();
}
......
}

也就是说在Hashtable中,假如初始化了100个位置,只有72个可用,其他28个不能用,但仍然要占着地方。

由此我们可以知道,如果保存的数据量相同,Hashtable要比ArrayList占用更多的内存空间。

哈希表-泛型Dictionary<TKey,TValue>和Hashtable比较

.net 2.0之后出现了泛型,泛型避免了装箱拆箱造成的损失,在大多数情况下,使用泛型集合要比传统的集合有较高的性能。List<T>对应ArrayList,Dictionary<TKey,TValue>对应Hashtable。

Dictionary<TKey,TValue>除了泛型带来的优势以外,还在哈希值算法,哈希冲突检测上做了改进,也没有了100个位置28个不能用的空间浪费。所以当碰到哈希表使用时,优先考虑Dictionary<TKey,TValue>。

最后总结:哈希表在查询方面有明显优势,但会占用多一点的内存空间,当业务中的集合有大量的查找操作时,优先考虑哈希表,一旦决定使用哈希表,优先考虑泛型哈希表。

转载于:https://www.cnblogs.com/hkncd/archive/2011/05/06/2035684.html

你可能感兴趣的文章
List删除行问题
查看>>
Linux 基础知识
查看>>
开辟新空间输入成绩
查看>>
Android屏幕适配
查看>>
c#使用XSLT将xml文档转换为html文档
查看>>
管道符、重定向、环境变量
查看>>
python日期,时间函数
查看>>
Timus 1146. Maximum Sum
查看>>
shell脚本学习总结02--数组
查看>>
[HDU3683 Gomoku]
查看>>
【工具相关】iOS-Reveal的使用
查看>>
整体二分——[Poi2011]Meteors
查看>>
数据库3
查看>>
delphi之事件
查看>>
windows server 2008 r2 安装
查看>>
Enigma –> Sadness
查看>>
存储分类
查看>>
下一代操作系统与软件
查看>>
【iOS越狱开发】如何将应用打包成.ipa文件
查看>>
[NOIP2013提高组] CODEVS 3287 火车运输(MST+LCA)
查看>>