JavaSE_坚持读源码_HashMap对象_put_Java1.7

时间:2021-08-13 16:48:34

当你往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     }