深入了解HashMap(源码分析)

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

//hashmap类继承图

1.1 HashMap的属性

首先是定义的一些常量

在这里插入图片描述

然后是变量
//hashmap变量属性截图

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

//内部类Node截图

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

1.2 构造方法

HashMap有4种构造方法

//4种构造方法截图

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

//第一种构造方法截图

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

初始化阈值时调用了 tableSizeFor() 这个方法,点进去看
//tableSizeFor()方法截图

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

1.3 put()方法

put 方法是 HashMap 中比较重要的一个方法,我们一起来看看
//put方法截图

可以看到它调用了 putVal() 方法,使用 hash() 方法用 key 计算 哈希值,那就看看 hash() 方法怎么计算哈希值的
//hash()方法截图

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

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

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

//putval()方法截图
在这里插入图片描述

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

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

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

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

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

1.4 get()方法

//get()方法截图

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

//getNode()方法截图

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

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

1.5 remove()方法

//remove()方法截图

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

//removeNode()方法截图
在这里插入图片描述

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

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

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

1.6 线程安全性

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

1.7 总结

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

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

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

猜你喜欢

转载自blog.csdn.net/weixin_41660213/article/details/108460102