1 开放寻址法
如冲突则根据指定步长向后寻找直到找到空位或没有位置
步长算法:
1.1 线性探查 步长为1 如threadlocal
key1:hash(key)+0
key2:hash(key)+1
key3:hash(key)+2
1.2 平方探测法 计算下一次的步长
key1:hash(key)+0
key2:hash(key)+1^2
key3:hash(key)+2^2
1.3 随机探测法 步长为随机生成的数 疑问:查询时如何查?如遍历查询有何意义?
缺点:
- 查找性能不好:这种方法建立起来的哈希表,当冲突多的时候数据容易堆集在一起,这时候对查找不友好;
- 删除节点不能真实删除浪费内存:删除结点的时候不能简单将结点的空间置空,否则将截断在它填入散列表之后的同义词结点查找路径。因此如果要删除结点,只能在被删结点上添加删除标记,而不能真正删除结点;
- 如果哈希表的空间已经满了,还需要建立一个溢出表,来存入多出来的元素。
- 为了减少冲突,负载因子要小些,导致内存浪费
2 再哈希
有多个不同的Hash函数,当发生冲突时,使用第二个,第三个,….,等哈希函数 直到不再冲突
也存在不能删除的问题? 如删除某元素后,查询另一个key调用第二个哈希函数后查询为null 无法确定继续处理还是key不存在?
缺点: 增加了计算时间
3 建立公共溢出区
将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表
缺点: 如未能命中哈希表 需要遍历查找溢出表 查询效率低
4 拉链法(或成树法, 树是一种特殊的链表。树形转化后 插入更慢(相比于头插法) 查询更快(冲突节点数目较多时))
如出现冲突,将其作为链表一个节点加入头或者尾巴处(分为头插法和尾插法)
拉链法的优点:
- 处理冲突的方式简单,且无堆集现象,一般不会发生冲突,因此平均查找长度较短;
- 由于拉链法中各链表上的结点空间是动态申请的,所以它更适合造表前无法确定表长的情况;
- 删除结点操作易于实现,只要简单地删除链表上的相应的结点即可。
拉链法的缺点:需要额外的存储空间。
从HashMap的底层结构中我们可以看到,HashMap采用是数组+链表/红黑树的组合来作为底层结构,也就是开放地址法+链地址法的方式来实现HashMap。