当你往HashMap里面put时,你其实在干什么?
1 /** 2 * Associates the specified value with the specified key in this map. 3 * If the map previously contained a mapping for the key, the old 4 * value is replaced. 5 * 6 * @param key key with which the specified value is to be associated 7 * @param value value to be associated with the specified key 8 * @return the previous value associated with <tt>key</tt>, or 9 * <tt>null</tt> if there was no mapping for <tt>key</tt>. 10 * (A <tt>null</tt> return can also indicate that the map 11 * previously associated <tt>null</tt> with <tt>key</tt>.) 12 */ 13 public V put(K key, V value) { 14 if (table == EMPTY_TABLE) { 15 inflateTable(threshold); 16 } 17 // 如果key为空,调用putForNullKey进行处理 18 if (key == null) 19 return putForNullKey(value); 20 //根据key来获得对应的hash值 21 int hash = hash(key); 22 // 搜索指定的hash值在对应的表中的索引 23 int i = indexFor(hash, table.length); 24 // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素 25 for (Entry<K,V> e = table[i]; e != null; e = e.next) { 26 Object k; 27 // 当entry的hash与上面获得的key的hash相等,并且entry的key与上面的key相等(==或者equals),把value赋值给oldValue 28 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 29 V oldValue = e.value; 30 e.value = value; 31 e.recordAccess(this); 32 return oldValue; 33 } 34 } 35 36 modCount++; 37 // 如果 i 索引处的 Entry 为 null,表明此处还没有 Entry 38 // 将 key、value 添加到 i 索引处,并去创建一个entry 39 addEntry(hash, key, value, i); 40 return null; 41 }
根据上面 put 方法的源代码可以看出,当程序试图将一个 key-value 对放入 HashMap 中时,程序首先根据该 key 的 hash() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hash() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但 key 不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部——具体说明继续看 addEntry() 方法的说明。
当向 HashMap 中添加 key-value 对,由其 key 的 hashCode() 返回值决定该 key-value 对(就是 Entry 对象)的存储位置。当两个 Entry 对象的 key 的 hashCode() 返回值相同时,将由 key 通过 eqauls() 比较值决定是采用覆盖行为(返回 true),还是产生 Entry 链(返回 false)。
上面程序中使用的 table 其实就是一个普通数组,每个数组都有一个固定的长度,这个数组的长度就是 HashMap 的容量。HashMap 包含如下几个构造器:
- HashMap():构建一个初始容量为 16,负载因子为 0.75 的 HashMap。
- HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的 HashMap。
- HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。
1 /** 2 * Adds a new entry with the specified key, value and hash code to 3 * the specified bucket. It is the responsibility of this 4 * method to resize the table if appropriate. 5 * 6 * Subclass overrides this to alter the behavior of put method. 7 */ 8 void addEntry(int hash, K key, V value, int bucketIndex) { 9 //如果key-value组成的entry数量超过极限,并且table的bucketIndex索引处不为空 10 if ((size >= threshold) && (null != table[bucketIndex])) { 11 //把table的长度扩大到原来的两倍 12 resize(2 * table.length); 13 //key == null的hash永远是0 14 hash = (null != key) ? hash(key) : 0; 15 bucketIndex = indexFor(hash, table.length); 16 } 17 18 // 获取指定 bucketIndex 索引处的 Entry 19 // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry 20 createEntry(hash, key, value, bucketIndex); 21 }
1 /** 2 * Like addEntry except that this version is used when creating entries 3 * as part of Map construction or "pseudo-construction" (cloning, 4 * deserialization). This version needn't worry about resizing the table. 5 * 6 * Subclass overrides this to alter the behavior of HashMap(Map), 7 * clone, and readObject. 8 */ 9 void createEntry(int hash, K key, V value, int bucketIndex) { 10 Entry<K,V> e = table[bucketIndex]; 11 table[bucketIndex] = new Entry<>(hash, key, value, e); 12 size++; 13 }