今天被问到了常见的三个需要数组扩容的类。分别是StringBuffer/StringBuilder,ArrayList与HashMap。这篇文章就先从源码入手,分析一下HashMap扩容的原理实现。
HashMap的来源
集合类可以分为两大块,分别是Collection与Map。Map下有AbstractMap抽象类实现了Map接口,而HashMap在继承了AbstractMap类的同时实现了Map接口。
相关常量
HashMap有以下四个常量和数据组扩容机制有关。
1.设置初始的数组容量为2的4次方:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
2.设置最大的容量为2的30次方:static final int MAXIMUM_CAPACITY = 1 << 30;
3.设置默认的负载因子为0.75:static final float DEFAULT_LOAD_FACTOR = 0.75f;
4.设置将list转换为tree的计数阈值,该值必须大于2,而且应该最少为8:static final int TREEIFY_THRESHOLD = 8;
put()函数
首先看一下增加元素的入口,put()函数。
首先是key.hashCode()。hashCode()是Object类中的一个由native修饰的函数,其主要作用是返回对象的哈希值。随后将这个哈希值与其本身的高16位进行异或运算。这一步的操作是为了让得到的值均匀的分布在\[0,数组长度-1]之间。
随后将运算过的哈希值,key,value一起传入putVal()函数。
putVal()函数
首先看这一段代码。
随后看第二个if,其中有tab\[i=(n-1)\&hash]。这里的tab就是存值的数组,而(n-1)\&hash是与哈希算法相关的一个计算式,下篇文章会细讲。整体的意思就是计算出传入的值的索引,如果数组在索引的位置处为null,就把该值存入到数组这个索引对应的位置处。
如果该位置已经存在元素,那么就会进入到else当中。
在else if中,判断如果目前是红黑树的数据结构,那么就按照红黑树的方式进行存储。若都不是,则进入到else中。
扩展的有些多了,重新回到正题。刚才说了第一个跳出死循环的方法就是遍历到链表尾部。那么第二个跳出死循环的方法就是如同第一个if的条件一样,判断出目前传入的新值与链表中某一个已有的值是相同的key值。