HashMap简介
- 数据无序、底层由数组+链表+红黑树实现(JDK8开始)、容量是2的指数幂、初始大小为16(不指定长度)
- 发生冲突时通过拉链法处理,当链表大于阈值时(阈值默认为8),将链表转化为红黑树
- 时间复杂度:哈希查找O(1),哈希冲突多O(n),红黑树O(log n),平均O(1)
HashMap里面设计的算法(put和get操作原理)
- 主要涉及哈希算法和寻址算法。哈希算法是在put过程中,根据传入key值获取对应hash值的一种方法;寻址算法在put和get过程中都有所运用,即根据hash值获得下标位置的方法。
哈希算法
- 根据一个key,通过计算得到它对应的hash值(即将hash code向右移16位,再与原数值进行异或运算)(每个虚拟机对hash的计算都不一样,有些跟地址有关)
- 直接使用hashcode做hash指的话,通过寻址算法所得结果有许多相同
- 通过哈希算法的优化,一定程度避免了hash冲突,让数据更加散列
寻址算法
- 根据hash值计算数组下标位置
- 一个key的hash值,用这个hash值跟数组长度取模,就可以得到下标位置, 实际在源码中,采用了与运算代( 两个都是1,结果为1;不然是0 )替了取模(因为对于现代的处理器来说,除法和求余数(模运算)是最慢的动作)
- 公式:(n - 1) & hash(n为数组长度)
put和get操作过程详解
put过程
- 根据哈希算法获取哈希值
- 哈希数组为空,则创建数组,默认初始大小为16
- 对哈希值进行寻址运算,获取在数组中应该存入的下标位置,此时分为四种情况
- 如果该下标位置为空,则直接存入key,value键值对
- 如果不为空,只有一个元素且key相等,满足相等条件,替换旧值,不相等则拉链处理
- 如果该位置存了一个TreeNode,则说明是红黑树,执行树的插入操作
- 如果是一个链表,则遍历链表,如果中途有相等则替换,否则将key,value键值对插入链表最后;如果链表长度大于阈值 ,则转化为红黑树
get过程
- 通过寻址算法找到key值应该对应的下标
- 如果节点为空则返回空
- 再判断首节点.key 是否和目标值相同, 相同则直接返回(首节点不用区分链表还是红黑树)
- 首节点.next为空, 则直接返回空
- 首节点是树形节点, 则进入红黑树数的取值流程, 并返回结果
- 进入链表的取值流程, 并返回结果
HashMap的容量大小
如详解里面所提到的,HashMap的容量大小一般为2的指数次幂,在未指定初始大小的时候的默认大小为16,其原因有以下几点
- 当n为2的指数幂时,根据寻址算法中的计算公式,n减1后换算成2进制,则每一位都为1,与hash进行与运算,就可 以得到(0~n-1)范围内的每一个index
- 当n不为2的指数次幂时,减1后换算成2进制,会出现0,与hash进行与运算,会导致 (0~n-1)范围内的某些index永远得不到
- 总结:当n不为2的指数幂时,有些地址永远得不到
加载因子和转化阈值
-
加载因子又名负载因子、装载因子 -
加载因子代表哈希表在其容量到达一定程度后,需要进行扩容 -
负载因子越大表示散列表的装填程度越高,反之愈小 负载因子越大, 对空间的利用更充分,但哈希碰撞机会变大,后果是查找效率的降低 负载因子太小,对空间造成严重浪费,但数据稀疏使碰撞机会变小,查询效率提高 -
加载因子0.75:根据泊松分布,加载因子为0.75时,节点出现在频率在Hash桶(表)中遵循参数平均为0.5的泊松分布。(源码没有解释,网上没统一答案) -
转化阈值8:用0.75作为加载因子时,桶中元素到达8个的时候,概率已经变得非常小,超过8个是几乎不可能的
|