这篇文章讲的是HashMap,将会深入到源码层面去分析HashMap,加深对HashMap的理解
1.1 HashMap的属性
首先是定义的一些常量
然后是变量
接下来看一下HashMap中的一个内部类Node
可以看到Node是链表结构,这是因为HashMap是以数组+链表的形式来实现的
1.2 构造方法
HashMap有4种构造方法
这里挑第一种构造方法来讲
可以看到构造方法中首先判断初始大小是否合理,并且初始化装载因子和阈值
初始化阈值时调用了 tableSizeFor() 这个方法,点进去看
从注释中可以知道这个方法会返回一个2的整数幂
1.3 put()方法
put 方法是 HashMap 中比较重要的一个方法,我们一起来看看
可以看到它调用了 putVal() 方法,使用 hash() 方法用 key 计算 哈希值,那就看看 hash() 方法怎么计算哈希值的
可以看到hash()方法中得到key的哈希值并且与该值的高16位做异或运算,为什么要做异或运算呢?
因为表的默认初始容量为16,我们要把元素放到散列表中,也就是放到0-15位置上,也就是 tab[i = (n - 1) & hash],而在做 & 运算的时候,只有后四位有效,如果key的哈希值高位变化很大,低位变化很小,那么会导致计算出来的hash值很容易相同
而使key的哈希值的高位也参与运算,可以有效地减少哈希碰撞的可能性
再来看 putVal() 方法
可以看到首先当表为null时,调用resize()初始化
如果没有发生碰撞,直接将元素添加进表中
否则如果是红黑树结构,就调用树的插入方法
如果是链表结构,就寻找key映射的节点,就记录下来并退出循环,如果没有找到就在链表尾部插入节点,如果插入后发现临界值大于TREEIFY_THRESHOLD,则转化为红黑树
最后使用新值覆盖旧值,并返回旧值
1.4 get()方法
可以看到调用了getNode()方法来取值,点进去看看
可以看到如果在桶的首位就可以找到的话那么就直接返回
否则就遍历红黑树和链表进行寻找
1.5 remove()方法
可以看到调用了removeNode()方法来完成删除,点进去看看
可以看到如果在桶的首位就找到了要删除的元素,就记录下来
否则就去红黑树或链表中查找
最后分三种情况删除:1.链表 2.红黑树 3.在桶的首位
1.6 线程安全性
HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,与之对应的是线程安全的HashTable,但是性能太低不建议使用,推荐使用ConcurrentHashMap
1.7 总结
HashMap是基于数组+链表+红黑树来实现的
HashMap中扩容是非常耗费性能的操作,所以在初始化HashMap时最好定一个初始值
装载因子的默认值是0.75,是可以修改的,但是不建议修改,因为过大或过小都会对性能造成影响