一、哈希表
(一)哈希表的定义
哈希表(Hash table,也叫散列表) 是根据关键码值(Key value)而直接进行访问的数据结构,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。
关键码值(Key value)也可以当成是key的hash值
这个映射函数叫做散列函数
存放记录的数组叫做散列表
数组,链表,哈希表的区别:
- 数组(顺序表):寻址容易,只要使用下标即可;插入和删除难,会有大量的元素进行移动
- 链表:插入与删除容易,但是查询难
- 哈希表:寻址容易,插入删除也容易,综合了数组和链表的特性。
(二)哈希表的图解
Key : {14, 19, 5, 7, 21, 1, 13, 0, 18} 散列表: 大小为13 的数组 a[13]; 散列函数: f(x) = x mod 13; 遇到hash冲突则hash值+1(直到加到没有hash冲突的位置,此处先用简单的方式来解决hash冲突)
- 散列表的大小:是需要根据自己的需求而定的
- 散列函数:为了key计算后的结果hash值,能够不超出定义的散列表的大小
- hash冲突:不同的key通过散列函数计算的hash值一样。自定义的哈希表需要设计好hash冲突解决的方法。
- 装填因子:放入散列表中的数据个数 / 散列表的大小
[1] 假设装填因子=0.7,散列表中的数据个数 / 散列表的大小 = 9/13 = 0.7,如果散列表中数据个数达到了9个,就需要对散列表进行扩容,扩容后再存放新的数据。HashMap设置的装填因子是0.75。
[2] 那么为什么不全部放满了再扩容呢,需要根据装填因子?因为数据越接近散列表的大小,产生冲突的可能会越来越大 - 缺点:扩容需要消耗大量的空间和性能。
扩容会导致散列函数重新变化,[ 比如扩容后大小为50,那么f(x) = x mod 50 ],原本存入哈希表的key值都需要重新计算。
HashMap的扩容就是2的n次方计算的。 - 应用:散列表应用场景,是在数组大小变化的可能性不大的情况下:电话号码,字典,点歌系统,QQ,微信的好友等。
比如系统的电话号码存放有上限值的,这样增删改查就能保持性能最快的一种方式。
(三)哈希表的拉链法
要能够手写一套性能优异的哈希表,需要了解所有的树结构。不同的语言,设计的哈希表也是有些差异的,HashMap就是采用了拉链法。
1、JDK1.8以前:数组 + 链表
优点:
- 查找快:时间复杂度 O(n) ,( O(n/线性表的size) ) ,是一个线性的查找
[e.g.] f(337) = 1,查找到数组角标1,然后再单链表查询到337。- 插入快:查找到数组角标,单链表的插入
- 删除快:查找到数组角标,单链表的删除
缺点:
- 大数据时代,如果数组容量不是很大,很有可能出现某一行链表个数超过几万几十万。(解决方法见数组 + 链表 + 红黑树)