前面我们通过继承一个HaseSet把一个Set集合扩展为一个Map。其实我们扩展的Map本质上是一个HashMap。
HashMap和HashSet之间也有很多相似之处,HashSet采用Hash算法来决定集合元素的存储位置,这样可以保证快速的存,取集合元素。对于HashMap而已,系统仅将value作为key的附属物而已,系统采用Hash算法来决定key的存储位置,这样可以保证快速的存,取集合key,而value仅仅总是作为key的附属。
<code class="hljs vbnet has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">HashMap<<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span>,<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">Double</span>> map = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> HashMap<<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span>,<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">Double</span>>(); map.put(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"java"</span>,<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">100</span>);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>
对于如上的代码,当执行map.put(“Java”,100);时。系统将调用”java”的hashCode()方法得到其hashCode()值。然后根据hashCode值系统来决定其存储位置。
HashMap类的put(K key,V value)方法源码:
<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"> <span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/** * The table, resized as necessary. Length MUST Always be a power of two. */</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">transient</span> Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//用来存储Entry </span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> loadFactor; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//负载因子 当数组容量的占用比例超过负载因子时,数组就会扩容。 </span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> class Entry<K,V> implements Map.Entry<K,V> { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> K key; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//用来存储Key </span> V value; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//用来存储Value </span> Entry<K,V> next; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//用来指向:相同hashCode的Key,但是不同Key对象 </span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> hash; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//Key对象的hashCode值 </span> <span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/** * Creates new entry. */</span> Entry(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } } } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> V <span class="hljs-title" style="box-sizing: border-box;">put</span>(K key, V value) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (table == EMPTY_TABLE) { inflateTable(threshold); } <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//当key为null,调用putForNullKey来处理 </span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (key == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> putForNullKey(value); <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//根据key的hashCode计算hash值</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> hash = hash(key); <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//搜索制定hash值在对于table中的索引</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> i = indexFor(hash, table.length); <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//如果i处索引不为null。则通过循环不断遍历e元素的下一个元素 </span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (Entry<K,V> e = table[i]; e != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>; e = e.next) { Object k; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//找到指定key与需要放入key相等(即两者hash值相同,equals返回true)</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> oldValue; } } <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//如果i索引处的entry为null。表明此次没有entry。直接插入在此次即可</span> modCount++; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//添加key,value到索引i处</span> addEntry(hash, key, value, i); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>; } </code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li><li style="box-sizing: border-box; padding: 0px 5px;">47</li><li style="box-sizing: border-box; padding: 0px 5px;">48</li><li style="box-sizing: border-box; padding: 0px 5px;">49</li><li style="box-sizing: border-box; padding: 0px 5px;">50</li><li style="box-sizing: border-box; padding: 0px 5px;">51</li><li style="box-sizing: border-box; padding: 0px 5px;">52</li><li style="box-sizing: border-box; padding: 0px 5px;">53</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li><li style="box-sizing: border-box; padding: 0px 5px;">47</li><li style="box-sizing: border-box; padding: 0px 5px;">48</li><li style="box-sizing: border-box; padding: 0px 5px;">49</li><li style="box-sizing: border-box; padding: 0px 5px;">50</li><li style="box-sizing: border-box; padding: 0px 5px;">51</li><li style="box-sizing: border-box; padding: 0px 5px;">52</li><li style="box-sizing: border-box; padding: 0px 5px;">53</li></ul>
上面代码中用到了一个重要的内部接口Map.Entry,每个Map.Entry其实就是一个key-value。从HashMap的源码也可以看出,程序根本没有考虑过Map.Entry中value.而是仅仅考虑key而已,即根据key计算出Entry的存储位置。这也得出一个结论:完全可以把Map集合中的value当成key的附属物而已,当系统确定了key的存储位置之后,value也就被确定了。
从HashMap的put方法源代码也可以看出,当程序试图将一个key-value对放入HashMap中时,首先根据key的hashCode()返回值决定该Entry的存储位置,如果两个key的hash值相同,那么它们的存储位置相同。如果这个两个key的equalus比较返回true。那么新添加的Entry的value会覆盖原来的Entry的value,key不会覆盖。如果这两个equals返回false,那么这两个Entry会形成一个Entry链,并且新添加的Entry位于Entry链的头部。
如果key的equals返回true,那么他们两个的hash值一定相同。即当两者的hashCode()相同时,系统再次调用equals来确定是覆盖还是形成Entry链。
上面程序还是调用了addEntry(hash,key,value,i);代码。其中addEntry是HashMap提供的一个包访问权限的方法,该方法仅用于添加一个key-value对。源码如下:
<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> addEntry(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> hash, K key, V <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">value</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> bucketIndex) { <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//获取指定bucketIndex处的Entry</span> Entry<K,V> e = table[bucketIndex]; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//将新创建的Entry放入bucketIndex索引处,并让新Entry指向原来的Entry </span> table[bucketIndex] = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Entry<K,V>(hash, key, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">value</span>, e); <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//如果Map中的key-value对的数量超过了极限</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(size++>=threshold){ <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//扩充table对象的长度到2倍</span> resize(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>*table.length); } } </code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul>
上述代码有一个非常优雅的设计,系统总是将新添加的Entry对象放在bucketIndex索引处。如果bucketIndex索引处已经有了一个Entry对象。这新添加的Entry指向该Entry(即产生一个Entry链)。如果bucketIndex处没有Entry对象,则说明上述代码e变量为null。即新放入的Entry对象指向null,即没有产生Entry链。