4.1. HashMap的结构
如图4.1.1 、4.1.2 HashMap的底层是使用table数组来存储Node节点。下面来分析一下节点中存储的是啥。
- hash : 这个hash是存储key的哈希值,注意这里存储的是key的哈希值,并不是key-value键值对的哈希值。
- key : 键是程序员存储的key。
- value : value是键所对应的值。
- next : 这里为什么要有一个next?应为会出现哈希值相同,但是内容不同的数据,当出现这样情况的时候,就需要将哈希值相同的Node组成一个链表,存储到数组table的空间中(也就是数组的节点中存储链表)。
图 4.1.1 HashMap的底层存储
图 4.1.2 HashMap的底层存储结构
在这里顺便说一下table的默认大小是16,而且table的大小永远都是2的次幂,这个下面说
图 4.1.3 table的默认大小
4.2. HashMap的存储特点
HashMap具有较快的访问速度,但是遍历顺序是不确定的。
4.2.1. HashMap为什么具有较快的访问速度?
这就要从HashMap的存储方法开始讲起了。我们上面提到Node中存储了key的哈希值,HashMap会将这个哈希值通过计算转换成数组下标。所以当进行搜索数据的时候,HashMap也会将key转换成哈希值,然后直接定位到相同哈希值的数组下标。那下面就来介绍哈希值是怎么通过计算转换为下标的。
图 4.2.1.1 HashMap putVal方法
如上图4.2.1.1中,这是HashMap putVal方法,我没有截全,但是够用。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
上面这段代码就是图中的后两行,其中n是table的长度(存储Node的数组),他先判断table[((n - 1) & hash]的数组空间是否为空,如果为空就直接将Node放进去。所以哈希值转换下标是有一个公式的。
int index = (tab.length - 1) & hash;//将数组长度与哈希值进行按位与
所以平常我们存储数据较少的情况下,搜索时间复杂度可以归为O(1)。那么我们接下来看如果数据很多,HashMap是怎么处理的。
4.2.2. 链式结构与红黑树
图 4.2.2.1 table最大容量
图 4.2.2.2 默认加载因子
图 4.2.2.3 链表转化为树的阈值
图 4.2.2.4 树转化为链表的阈值
图 4.2.2.5 树状处理最小容量表
我们先看上图面几张图
- MAXIMU_CAPACITY: table 最大容量
- DEFAULT_LOAD_FACTOR: 默认加载因子,默认加载因子是当元素数量达到比例的时候进行扩容,例如此处默认是0.75所以在容量达到75%的时候进行扩容。
- TREEIFY_THRESHOLD: 链表转化为树的阈值,当链表中容量达到8的时候,就会转化为红黑树
- UNTREEIFY_THRESHOLD: 当树的容量小于6的时候就会转化为链表
- MIN_TREEIFY_CAPACITY: 最小树化容量,当哈希表中的桶数小于这个值时,不会进行树化,而是扩容 。
- 链表式存储
图 4.2.2.6 链式存储
什么时候进行链式存储?
当我们上面讲了哈希值转换为下标的公式,当哈希值相同的时候,就会出现下标相同的情况,所以链式结构就是为处理这种情况。
红黑树存储转换为链式存储
当红黑树的节点个数小于6的时候,就会再次转化为
- 红黑树
什么时候进行红黑树存储?
红黑树的转换是为了优化单个桶中链表过长导致的性能问题。因此,当某个桶中的链表长度达到 8 时, HashMap 会检查整个哈希表的容量,如果容量足够大(即达到 64 或以上),则将该桶的链表转换为红黑树。其他桶的链表长度未达到阈值的,不会受到影响。
那么关于红黑树的知识在这就不讲了,感兴趣可以搜大佬的文章进行观看。