这篇文章讲的是HashMap,将会深入到源码层面去分析HashMap,加深对HashMap的理解

深入了解HashMap(源码分析)

1.1 HashMap的属性

首先是定义的一些常量

深入了解HashMap(源码分析)

然后是变量
深入了解HashMap(源码分析)

接下来看一下HashMap中的一个内部类Node

深入了解HashMap(源码分析)

可以看到Node是链表结构,这是因为HashMap是以数组+链表的形式来实现的

1.2 构造方法

HashMap有4种构造方法

深入了解HashMap(源码分析)

这里挑第一种构造方法来讲

深入了解HashMap(源码分析)

可以看到构造方法中首先判断初始大小是否合理,并且初始化装载因子和阈值

初始化阈值时调用了 tableSizeFor() 这个方法,点进去看
深入了解HashMap(源码分析)

从注释中可以知道这个方法会返回一个2的整数幂

1.3 put()方法

put 方法是 HashMap 中比较重要的一个方法,我们一起来看看
深入了解HashMap(源码分析)

可以看到它调用了 putVal() 方法,使用 hash() 方法用 key 计算 哈希值,那就看看 hash() 方法怎么计算哈希值的
深入了解HashMap(源码分析)

可以看到hash()方法中得到key的哈希值并且与该值的高16位做异或运算,为什么要做异或运算呢?

因为表的默认初始容量为16,我们要把元素放到散列表中,也就是放到0-15位置上,也就是 tab[i = (n - 1) & hash],而在做 & 运算的时候,只有后四位有效,如果key的哈希值高位变化很大,低位变化很小,那么会导致计算出来的hash值很容易相同

而使key的哈希值的高位也参与运算,可以有效地减少哈希碰撞的可能性
再来看 putVal() 方法

深入了解HashMap(源码分析)
深入了解HashMap(源码分析)

可以看到首先当表为null时,调用resize()初始化

如果没有发生碰撞,直接将元素添加进表中

否则如果是红黑树结构,就调用树的插入方法

如果是链表结构,就寻找key映射的节点,就记录下来并退出循环,如果没有找到就在链表尾部插入节点,如果插入后发现临界值大于TREEIFY_THRESHOLD,则转化为红黑树

最后使用新值覆盖旧值,并返回旧值

1.4 get()方法

深入了解HashMap(源码分析)

可以看到调用了getNode()方法来取值,点进去看看

深入了解HashMap(源码分析)

可以看到如果在桶的首位就可以找到的话那么就直接返回

否则就遍历红黑树和链表进行寻找

1.5 remove()方法

深入了解HashMap(源码分析)

可以看到调用了removeNode()方法来完成删除,点进去看看

深入了解HashMap(源码分析)
深入了解HashMap(源码分析)

可以看到如果在桶的首位就找到了要删除的元素,就记录下来

否则就去红黑树或链表中查找

最后分三种情况删除:1.链表 2.红黑树 3.在桶的首位

1.6 线程安全性

HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,与之对应的是线程安全的HashTable,但是性能太低不建议使用,推荐使用ConcurrentHashMap

1.7 总结

HashMap是基于数组+链表+红黑树来实现的

HashMap中扩容是非常耗费性能的操作,所以在初始化HashMap时最好定一个初始值

装载因子的默认值是0.75,是可以修改的,但是不建议修改,因为过大或过小都会对性能造成影响

相关文章: