当程序执行
Map map = new HashMap();
的时候,会先执行HashMap的默认无参数构造方法
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
这里的DEFAULT_INITIAL_CAPACITY是指新建HashMap的初始容量大小,默认是static final int DEFAULT_INITIAL_CAPACITY = 1 << 4(也就是16)。DEFAULT_LOAD_FACTOR是指负载因子,也就是这个容量装满到什么程度,默认是static final float DEFAULT_LOAD_FACTOR = 0.75f。然后程序就会执行以下程序进行new HashMap的初始化:
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) // 如果初始容量小于0,则异常
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) // 如果初始容量大于极限(2的30次方),则异常
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor)) // 负载因子小于0或是空,则异常
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
注意,在HashMap类里面,有以下两个声明:
/**
* An empty table instance to share when the table is not inflated.
*/
static final Entry<?,?>[] EMPTY_TABLE = {};
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
这是定义了一个Entry的空数组。当我们使用put方法,将元素放进HashMap里的时候,程序执行以下代码:
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold); // 当第一次执行put方法的时候,这个Entry数组是空的,那么就以上述提到的HashMap的初始容量大小来定义这个Entry数组的长度
}
if (key == null)
return putForNullKey(value); // 如果key是null,则以hash为0,将这个key-value组成的Entry存到数组
int hash = hash(key); // 如果Key不为0,则计算其hash
int i = indexFor(hash, table.length); // 根据计算得到的hash和Entry数组的长度,计算这个key-value组
for (Entry<K,V> e = table[i]; e != null; e = e.next) { //成的Entry应该存入数组的哪一个下标位置上
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value; // 遍历数组当前已经存在的元素,如果数组在当前下标上已经存放了一个
e.value = value; // Entry,则用equals比较key,如果equals返回true,则新的value替换旧的
e.recordAccess(this);
return oldValue; // 返回旧的value
}
}
modCount++;
addEntry(hash, key, value, i); // 如果equals返回false,则新的Entry也会存放在数组在这个下标位置上
return null;
}
上述说到,如果新添加的key-value组成的Entry跟已经存在的key-value组成的Entry的hash一样,而且通过equals比较两者的Key返回的是false。那么,它们都会被存放在上述table这个Entry数组相同位置上,这两者会形成链表结构,新增的在表头,先加的在表尾。
所以总的来说,HashMap其实就是一个数组,如果没有指定初始容量大小,则这个数组初始长度就是16,数组中每个元素都是Entry组成的链表结构。新增key-value到HashMap时,先通过key计算出其hash值,通过hash值得到这对key-value应该存入数组中的哪个位置(也就是数组的下标),如果该下标的位置上已经存放了一对或多对key-value,则遍历并通过equals方法比较key是否返回true,如果是,则替换;如果否,则新的key-value(就是一个Entry)跟该下标的位置上已经存放Entry组成链表结构,新增的在链头。
由于key-value存放在数组中的下标是通过key的hash值得到的,所以HashMap并不保证顺序。而且上述提到一个叫负载因子的东西,新HashMap初始化时默认是0.75,也就是说,当这个HashMap储存元素个数达到了总容量(也就是数组的长度)的0.75时,HashMap就是自动扩容为原容量的2倍。就是默认情况下,HashMap储存元素个数达到了16*0.75=12时,HashMap的容量大小会扩展成16*2=32。然后重新计算每个元素在数组中的位置。所以如果我们使用HashMap时预知到存放键值对的个数时,给定初始容量大小,就可以避免扩容造成的性能消耗。