本文内容
- 哈希表
- 实现,关键参数,遍历方式,put操作过程
- 多线程场景下的问题
- 与其他容器的区别
- HashTable
- HashSet
- ConcurrentHashMap
哈希表:
存储 键值对 的一种数据结构,用空间换时间,查询时间复杂度O(1),最坏O(n)
解决冲突的方法:链表法、线性再散列、二次再散列
实现
用链表法解决冲突,jdk8引入了红黑树,所以底层实现是 数组+链表/红黑树,下表列出jdk8做的改进
jdk8 | jdk7 | |
扩容时索引计算方式 | 根据规律,原始 或 原始位置+原始容量 | 重新计算 |
插入方式 | 头插 | 尾插 |
判断resize的时机 | 插入元素后 | 插入元素前 |
结构 | 引入了红黑树 |
关键参数
- loadfactor:负载因子,表中存的元素数目/表的长度,作为判断是否需要扩容的依据
- length:表长,一定要是2的幂次方,因为是用length-1来求位置的,为了利用所有位置
- treeify_threshold, untreeify_threshold:结构转化的阈值,默认8,6
- 最大待扩容长度:2^30
遍历方式
- entrySet,得到键值对集合
- 迭代器
- keySet
- values
put操作过程
- 计算Key的hashcode、位置
- 无冲突时直接插入
- 有冲突时,根据数组元素是否是TreeNode类型,判定用链表or树的方法
-
比较对象相等的流程
- 比较hashcode(),不等就返回false;
- ==,比较地址,相同则返回true;
- equals比较
- 根据阈值以及当前链表数判断是否需要进行结构变化
- put完毕后判断是否需要扩容
- 创建新表
- 将每个节点rehash到新表上
多线程场景下的问题
- 未同步、加锁,多线程put,get的问题
- jdk7以前头插法会出现死锁
与其他容器的区别
与HashSet的区别
HashSet内部有一个HashMap,用一个常量Object充当所有key的value
与HashTable的区别
与 ConcurrentHashMap的区别,ConcurrentHashMap做了很多事来保证线程安全