存入键值对就是调用HashMap.put()过程(面试高频问题)
首先放出粗狂的总结
假如是Integer类型就会先查看IntegerCache中是否存在这个数字,有就从缓存中调用,没有则创建新的Integer对象(String类型没有这个过程,其他类型有待考究)
然后进入putValue方法,其中第一个参数是hash,进入hash()方法获得hash值,这里的hash()是每个类型不一样的,Integer的和String的都分别有各自的规则,Integer的就是对值进行与一个数异或,String的没考究
hash结束后,putValue就先创建各种成员变量,主要就是Node<K,V>数组,这个数组的初始化要调用resize()函数,初始化的话通常就是直接给个16大小的Node[]
resize结束后就创建一个Node对象,将key、value、hash都存进去,然后把这个Node存进Node数组中,put就宣告完成了。
我们对下列代码进行调试看看
public static void main(String[] args) {
HashMap<Integer, String> hashMap = new HashMap<>();
hashMap.put(3, "String");
}
因为key是Integer类型,所以先进入Integer的valueOf进行类型检查?
假如在IntegerCache范围内的数就直接调用IntegerCache。
查看IntegerCache源码可以发现,IntegerCache的low是-128,high默认是127?也可以自定义为127内的数
至于这里为什么是i + (-low),暂时还是先不研究吧,反正最终的key值也是3
接着是正式进入了put函数(所以说之前要做的是对key进行类型检查(不知道是不是只对Integer这样?好像是的,后来将Key改成了String类型,没发现有类似的方法调用))
put又调用了putValue方法,里面有一个hash的函数
假如key不为null的话就进入hash函数得到哈希值
这里使用的哈希方法为获得key的hashCode然后进行16位移后的按位异或
key这里是Object类型,可能每个Object都有自己的hashCode吧 都是重写得来的,Integer的hashCode就比较简单,直接返回数字本身了
然后就正式进入了putVal函数
函数伊始就是创建各种变量和变量类
这里两个重点,一个是Node类
一个是resize方法
我们先来大概看一下Node类,Node也是个存储键值对的类,实现了Map.Entry接口
Map是一个接口,Entry也是。他们分别都具有什么要素?
大概可以理解为,Map的方法中更多是针对键值对是,是整体的宏观的。比如size是键值对的个数,isEmpty是有没有键值对,put是存入整一个键值对。(contains方法好像比较难解释了就…)
Entry的方法则更多是单独针对键或者值的,比如getKey就是拿到一个key,getValue就是拿到一个value,他们都是独立的,没有方法是针对键值对进行操作的。
Node,作为实现Entry的类,所以其方法也是但对针对键或者值的。自己的元素就是基本成员变量如哈希值、key、value、下一个节点的引用(这里也能体现出其链表形式的数据结构)。
resize的代码较多,这里就不贴上源码了,大概的功能就是:
假如是创建行为,就给一个初始化的Node<K,V> []数组,这个数组的初始化大小是16(对比ArrayList初始化的数组空间大小为10)
假如是扩容行为,就会进行对threshold和size的比较,这个threshold就是当前Node[]的占有率(关联负载因子,默认0.75),如有需要就进行扩容,没有就不变吧。
因为我的运行代码较短,put后直接就结束了main线程,可以看到还调用了exit方法,方法的内容大概就是结束线程,释放资源这样,这里应该和put是没什么关系的吧?属于题外话。