为什么要指定HashMap的容量?
首先创建HashMap时,指定容量比如1024后,并不是HashMap的size不是1024,而是0,插入多少元素,size就是多少;
然后如果不指定HashMap的容量,要插入768个元素,第一次容量为16,需要持续扩容多次到1024,才能保存1024*0.75=768个元素;
所以你如果想插入1000个元素,你得需要指定容量2048,2048*0.75=1536>1000;
而HashMap扩容是非常非常消耗性能的,Java7中每次先创建2倍当前容量的新数组,然后再将老数组中的所有元素再次通过hash&(length-1)的方式散列到HashMap中;
Java8中HashMap虽然不需要重新根据hash值散列后的新数组下标,但是由于需要遍历每个Node数组的链表或红黑树中的每个元素,根据元素key的hash值与原来老数组的容量的关系来决定放到新Node数组哪一半(2倍扩容),还是需要时间的;
具体Java8 HashMap resize源码怎么扩容以及如何避免Java7 HashMap死循环的可简单参考:JAVA7与JAVA8中的HASHMAP和CONCURRENTHASHMAP知识点总结
HashMap指定容量初始化后,底层Hash数组已经被分配内存了吗?
Java8 HashMap指定容量构造方法源码,会调整threshold为2的整数次幂,比如指定容量为1000,则threshold为1024:
public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }
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); }
/** * Returns a power of two size for the given target capacity. */ 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; }
可以根据源码看到,在指定的HashMap初始容量后,底层的Hash数组Node<K,V>[] table此时并没有被初始化,依旧为null;
那么是什么时候被初始化的呢?答案是第一次put元素的时候。
put源码如下:
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; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; ...... ......
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { 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; ...... ......
上面代码的加红字体,首先第一次调用put方法插入key-value,会调用resize方法,由于指定了HashMap的容量,那么这里会将底层的Hash数组Node<K,V>[] table初始化容量为上面所说的2的整数次幂;
resize源码,Java7 HashMap是根据元素Hash值重新散列,Java8 HashMap却不一样,这里put、resize方法源码只截取了第一次指定容量扩容相关的源码,详细的内容可简单参考:JAVA7与JAVA8中的HASHMAP和CONCURRENTHASHMAP知识点总结