HashMap和HashSet(深入HashMap源码分析HashMap元素的存储)

来源:互联网 发布:淘宝好货源 编辑:程序博客网 时间:2024/06/05 09:15

前面我们通过继承一个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链。

0 0
原创粉丝点击