默认初始容量为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
最大容量为2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
默认负载因子为0.75f
static final float DEFAULT_LOAD_FACTOR = 0.75f;
THRESHOLD 阀值,临界值,hashmap实际容量达到阀值后进行扩容。
hashMap的构造函数
1.无参构造,使用默认的初始容量16,默认的负载因子0.75f
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }
2.指定初始容量
public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }
3.指定初始容量和负载因子,如果指定的初始容量大于支持的最大容量2^30次方则重设初始容量为2^30次方。
public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); // jdk1.7此处 threshold = initialCapacity;阀值直接等于初始容量,会在第一次put时重设阀值。 }
然后设置阀值
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
对设置的初始容量加一后进行一系列的右移然后或运算,如设置初始容量为9,结果为16,设置初始容量为16,则结果为16。也就是说找到 小于等于(n-1)*2的最大的2的次方的值。
所以如果不指定初始容量,则初始容量和阀值都为16。
4.直接根据接收一个map的构造函数创建map
public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }
put方法,才初始化node数组
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i;
// transient Node<K,V>[] table; 如果node数组没有初始化则进行一个resize。 if ((tab = table) == null || (n = tab.length) == 0)
//对数组执行扩容操作。 n = (tab = resize()).length;
// 对数组长度减一和key的hash值进行与运算得到数组下标,查询此下标是否有值。 if ((p = tab[i = (n - 1) & hash]) == null)
// 没值就新建一个node将key,value放在此下标上 tab[i] = newNode(hash, key, value, null); else {
// 运算得到的数组对应的下标已经有值了,则判断已经存在的值的key和将要保存的key是否都相同 Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
// 如果相同,则将旧值p赋给e e = p; else if (p instanceof TreeNode)
// 如果旧值是一个树结点,则将新值放进这个红黑树中。 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else {
// 是链表结构 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) {
// 将新的key,value放在链接的新的节点上 p.next = newNode(hash, key, value, null);
// TREEIFY_THRESHOLD =8,如果链表的长度为8,则转为红黑树。 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
resize方法:在第一次put元素的时候就执行一次。final Node<K,V>[] resize() Node<K,V>[] oldTab = table;
//第一次put时table为null int oldCap = (oldTab == null) ? 0 : oldTab.length; // 旧的node数组的长度 int oldThr = threshold; // 旧的阀值 int newCap, newThr = 0; if (oldCap > 0) {
// 如果旧的数组的长度大于2^30次方,则旧的阀值为int型的最大值即为2^31次方 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; }
// 新的数组的长度为旧的数组长度的两倍,如果旧的数组的长度的两倍小于最大容量2^30,且旧的数组的长度大于等于默认初始容量16,则新的阀值为旧的阀值的两倍。 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold }
//如果旧的阀值大于0,且node数组没有初始化,即刚创建hashMap,且指定了初始容量。第一次put。则,新的容量等于旧的阀值 else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; // 16 else {
// zero initial threshold signifies using defaults
// 即创建HashMap用的是无参构造,还未初始化数组,则设置数组长度为默认容量16,阀值为默认负载因子0.75f*16=12
newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); }
// 只有在用hashmap的带参构造创建map且第一次put数组没有初始化时。此时新数组的长度为创建map时的阀值。 if (newThr == 0) {
// 修改创建map时赋的阀值,为其自身的0.75.而这时创建的数组的长度为创建map时赋的阀值,如创建时指定了初始容量为9,则会创建一个初始容量为16的数组,后将阀值设为12. float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
创建红黑树替换之前的链表
/** * Replaces all linked nodes in bin at index for given hash unless * table is too small, in which case resizes instead. */ final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null; do { TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab); } }
1.7和1.8的区别
1、(无参构造不同)1.8的无参构造没有初始化阀值,在第一次put时如果阀值未初始化的话才设数组的容量为默认初始容量,阀值为容量的0.75。1.7的无参构造和带参构造都指定了了阀值等于初始容量。
2、(带参构造的初始阀值不同)用带参构造的话1.7的阀值等于初始容量,第一次put时设为f(n)*0.75。1.8用带参构造创建map时,阀值为f(n)即小于等于(n-1)*2的最大的2的次方的值,n为指定的初始容量或为默认初始容量
3、(数组长度的值来源不同,但结果相同)1.7和1.8都是在第一次put时初始化数组。不同的是1.7直接将数组的长度设为了f(n),将阀值设为了长度的0.75。1.8是在第一次put中的resize中初始化数组,如果是带参构造创建的map则将初始阀值设为数组的长度,再修改阀值为其自身的0.75
4、(数据结构不同)1.7的数据结构为数组+链表。1.8的数据结构为数组+链表+红黑树。当链表的长度大于8时,将链表修改为红黑树,将原来链表数据复制进去
5、(resize方法不同)1.8数组未初始化时也是通过resize进行初始化的
未完待续...