JAVA面试必备(一) HashMap(基于jdk1.8)@TOC
JAVA面试必备(一) HashMap(基于jdk1.8
概述
HashMap(1.8)底层是由Node类型的数组、链表、红黑树组成的,接下来将会分析其put、resize、get、replace方法
1、 讲一下HashMap的put原理:
Put底层其实调用的是putval()方法,在put时,首先会算出当前Node的下标值
具体算法是,(1)算出key的hashCode值,然后再根据算出的值通过扰动函数再算一遍,扰动函数具体做法是:将算出的hashCode值与其高16位再做一次异或运算,这么做原因是因为:再刚开始时,table的length比较小,如16、32.
这样length的高位都会是0,这样直接通过路由寻址公式(table.length-1)&node.hash显然对key的hashcode的高位不公平,因为算出来都是0,如果添加了扰动函数,更合理,也能让哈希算的更加散列。
以上是找出了index值,接下来是真正的put操作了
根据源码可以得知,分为以下几种情况
(1) 如果当前下标没有值,那么就直接将node存储到hash表中
(2) 如果当前下标有值了,且找到了一个与当前要插入的Node的key一致的key,则需要做一次value覆盖的操作
(3) 如果是红黑树形式:待补充
(4) 如果是链表的形式,会做一次for循环遍历,如果遍历到最后也没找到key相同的情况,则加入到当前链表的末尾,如果找到了相同的key则跳出当前循环,做一次覆盖的操作,当遍历了8次后,则树化
如果添加元素超过了阈值,则需要进行一次扩容操作。
2、讲一下resize原理:
- Resize发生再两个阶段:初始化阶段和需要扩容的阶段,都会调用resize()
- Resize源码分析:首先会计算出扩容后table的大小和阈值,
- 如果之前的table已经初始化过了(oldCap>0),则再正常条件下(大小没有超过MAX_CAPACITY)时,会把容量扩大位原来的2倍,阈值也扩大到原来的2倍
- 如果之前的table初始化但是还没赋值,则(oldCap=0),那么会根据初始化时调用的构造方法通过tablesizefor函数来指定其table大小,HashMap构造方法
//1.new HashMap(initCap,loadFactor);
//2.new HashMap(initCap);
//3.new HashMap(map);并且这个map有数据
// 通过tableSizeFor方法(为阈值人为赋值,即oldThr有值,输入值最大的2的幂次数 9---->16)
若初始化时调用HashMap() 这个无参数构造的话,则直接赋值为默认值,容量为16,阈值为12
----以上只是将table的size和阈值指定出来----
----接下来是真正的扩容:-----
会根据table的长度创建一个更大的数组,
第一种情况:如果旧表中的桶中只有一个元素,则直接把值放入新桶中,
第二种情况:桶位是否被树化,待补充
第三种情况:旧表种是链表的情况,则会生成两个链表:低位链表和高位链表
低位链表表示(举例子,假设原表长度为16,则15& 01111为低位11111为高位都会放在15的位置上)
从源码来看,低位链表就是放在之前的位置上(15)了,而高位链表放在了15+oldcap(15+16=31的位置上),插入后滞空,表示再末尾(尾插法)
3、 讲一下get方法:
返回的是getNode方法,需要通过hash算法算出对应的数组下标,如果对应下标没值则返回null,如果有值 三种情况:
(1) 第一种情况:定位出来的桶位元素,就是我们要get的数据,直接返回结果
(2) 当前桶位不止一个元素可能是链表,那么就遍历,找相同的元素,直到找到
(3) 当前桶位是红黑树TODO
4、 讲一下remove方法:
返回的是removeNode方法,其中有一个matchValue参数,matchValue为true则表示只有再key相同value也相同时才删除,remove(key)中的为false
和get类似,先去找有无对应的元素,找到了则赋值给一个临时变量node
如果是链表则做链表删除操作,如果是树 TODO
5、replace方法:两种
- replace(key,value):通过getNode方法找到对应的key,将新value覆盖
- replace(K key, V oldValue, V newValue):首先通过给getNode找到对应的key,然后判断oldValue是不是原来的value,是的话则将新值覆盖即可