HashMap的put和get原理,結合源碼分析詳細分析

HashMap(java7)java

   public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {} 數組

  HashMap的數據結構是數組+鏈表,從上面的源碼能夠看出來,hashMap繼承了AbstractMap<K,V>的抽象類,實現了Map<K,V>的接口。安全

1、put方法:數據結構

  Java源碼:app

 1 /**
 2  * Hash table based implementation of the <tt>Map</tt> interface.  This
 3  * implementation provides all of the optional map operations, and permits
 4  * <tt>null</tt> values and the <tt>null</tt> key.  (The <tt>HashMap</tt>
 5  * class is roughly equivalent to <tt>Hashtable</tt>, except that it is
 6  * unsynchronized and permits nulls.)  This class makes no guarantees as to
 7  * the order of the map; in particular, it does not guarantee that the order
 8  * will remain constant over time.
 9  *
10  * <p>This implementation provides constant-time performance for the basic
11  * operations (<tt>get</tt> and <tt>put</tt>), assuming the hash function
12  * disperses the elements properly among the buckets.  Iteration over
13  * collection views requires time proportional to the "capacity" of the
14  * <tt>HashMap</tt> instance (the number of buckets) plus its size (the number
15  * of key-value mappings).  Thus, it's very important not to set the initial
16  * capacity too high (or the load factor too low) if iteration performance is
17  * important.
18  *
19  * <p>An instance of <tt>HashMap</tt> has two parameters that affect its
20  * performance: <i>initial capacity</i> and <i>load factor</i>.  The
21  * <i>capacity</i> is the number of buckets in the hash table, and the initial
22  * capacity is simply the capacity at the time the hash table is created.  The
23  * <i>load factor</i> is a measure of how full the hash table is allowed to
24  * get before its capacity is automatically increased.  When the number of
25  * entries in the hash table exceeds the product of the load factor and the
26  * current capacity, the hash table is <i>rehashed</i> (that is, internal data
27  * structures are rebuilt) so that the hash table has approximately twice the
28  * number of buckets.
29  *
30  * <p>As a general rule, the default load factor (.75) offers a good tradeoff
31  * between time and space costs.  Higher values decrease the space overhead
32  * but increase the lookup cost (reflected in most of the operations of the
33  * <tt>HashMap</tt> class, including <tt>get</tt> and <tt>put</tt>).  The
34  * expected number of entries in the map and its load factor should be taken
35  * into account when setting its initial capacity, so as to minimize the
36  * number of rehash operations.  If the initial capacity is greater
37  * than the maximum number of entries divided by the load factor, no
38  * rehash operations will ever occur.
39  *
40  * <p>If many mappings are to be stored in a <tt>HashMap</tt> instance,
41  * creating it with a sufficiently large capacity will allow the mappings to
42  * be stored more efficiently than letting it perform automatic rehashing as
43  * needed to grow the table.
44  *
45  * <p><strong>Note that this implementation is not synchronized.</strong>
46  * If multiple threads access a hash map concurrently, and at least one of
47  * the threads modifies the map structurally, it <i>must</i> be
48  * synchronized externally.  (A structural modification is any operation
49  * that adds or deletes one or more mappings; merely changing the value
50  * associated with a key that an instance already contains is not a
51  * structural modification.)  This is typically accomplished by
52  * synchronizing on some object that naturally encapsulates the map.
53  */

  而後咱們再看一下,HashMap的組成,主要的是實現方法爲put和get方法,主要的參數有capacity(桶的容量)和load factor(加載因子)。上面是官網的描述,大體意思爲:ide

  一、HashMap實現了Map的接口,<K,V>對中的key和value均可覺得空,除了不是線程安全的和容許爲null外,幾乎是與HashTable同樣的。同時也不能保證其的順序。性能

  二、一個hashmap對象主要有兩個參數capacity(桶的容量)和load factor(加載因子),默認load factor爲0.75是在時間和空間上性能最優的。測試

1  /**
2      * Constructs an empty <tt>HashMap</tt> with the default initial capacity
3      * (16) and the default load factor (0.75).
4      */
5     public HashMap() {
6         this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
7     }

  上面的是不帶參數的HashMap的構造器,也是咱們建立對象的時候常常使用的,默認數組或者桶的容量爲16,加載因子爲0.75。ui

 1    /**
 2      * Constructs an empty <tt>HashMap</tt> with the specified initial
 3      * capacity and the default load factor (0.75).
 4      *
 5      * @param  initialCapacity the initial capacity.
 6      * @throws IllegalArgumentException if the initial capacity is negative.
 7      */
 8     public HashMap(int initialCapacity) {
 9         this(initialCapacity, DEFAULT_LOAD_FACTOR);
10     }

  咱們也能夠自定義數組的容量,加載因子默認爲0.75。this

/**
     * 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)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

  同時也能夠既修改容量有修改加載因子,可是最好不要修改。

 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         if (key == null)
18             return putForNullKey(value);
19         int hash = hash(key);
20         int i = indexFor(hash, table.length);
21         for (Entry<K,V> e = table[i]; e != null; e = e.next) {
22             Object k;
23             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
24                 V oldValue = e.value;
25                 e.value = value;
26                 e.recordAccess(this);
27                 return oldValue;
28             }
29         }
30 
31         modCount++;//
32         addEntry(hash, key, value, i);
33         return null;
34     }
  
 1  /**
 2      * Offloaded version of get() to look up null keys.  Null keys map
 3      * to index 0.  This null case is split out into separate methods
 4      * for the sake of performance in the two most commonly used
 5      * operations (get and put), but incorporated with conditionals in
 6      * others.
 7      */
 8     private V getForNullKey() {
 9         if (size == 0) {
10             return null;
11         }
12         for (Entry<K,V> e = table[0]; e != null; e = e.next) {
13             if (e.key == null)
14                 return e.value;
15         }
16         return null;
17     }
1     /**
2      * Returns index for hash code h.返回hashCode值
3      */
4     static int indexFor(int h, int length) {
5         // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
6         return h & (length-1);
7

解析:首先判斷table,也就是數組是否爲空,爲空的話就去使用inflateTable的方法(這裏很少解釋)初始化hashmap。

   若是table不爲空的話,就判斷key是否爲空,爲空的話就將放到數組的index=0的位置,若是value不爲空則返回value值。

   若是key不爲空的話,就經過key獲取hash值,經過hash值和table的長度與運算獲取hashCode值。

   經過hashCode的遍歷entry<K,V>的鍵值對,若是key的hash值相等 而且key.equals(e.key)也相等的話,就將新的value替換掉舊的,返回舊值。

   在put的過程當中modCount記錄修改的次數,用於fail-fast容錯。

  那麼會有人問了,爲何table.length的默認長度爲16,而不是15或者14呢?

  首先,經過計算你就會發現hash取模使用&比使用%,獲得的hash會更均勻,而且性能更好。例如:table.length=15

  h&(length-1)                                            hash                                                     length - 1                        結果

  8 & (15-1)                                                 1000                                                        1110                                1000

  9 & (15-1)                                                 1001                                                         1110                                1000

  -------------------------------------------------------------------------------------------------------------------------------------------------------

  8 & (16-1)                                                 1000                                                         1111                                  1000  

  9 & (16-1)                                                  1001                                                         1111                                  1001

  從上面的結果你能夠發現,當table.length=15的時候,會出現hash碰撞的現象,而且全部結果爲最後一位1的永遠都不會出現,既0001,1001,1011,0011

  0101,1111這幾個位置都不會存放數據,會出現存放不均勻,而且浪費資源的現象。若是將8和9放到同一個位置當咱們get的時候,要遍歷鏈表,下降了查          詢效率。

 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         if ((size >= threshold) && (null != table[bucketIndex])) {
10             resize(2 * table.length);
11             hash = (null != key) ? hash(key) : 0;
12             bucketIndex = indexFor(hash, table.length);
13         }
14 
15         createEntry(hash, key, value, bucketIndex);
16     }
17 
18     /**
19      * Like addEntry except that this version is used when creating entries
20      * as part of Map construction or "pseudo-construction" (cloning,
21      * deserialization).  This version needn't worry about resizing the table.
22      *
23      * Subclass overrides this to alter the behavior of HashMap(Map),
24      * clone, and readObject.
25      */
26     void createEntry(int hash, K key, V value, int bucketIndex) {
27         Entry<K,V> e = table[bucketIndex];
28         table[bucketIndex] = new Entry<>(hash, key, value, e);
29         size++;
30     }

  當咱們在addEntry的時候,新的添加的entry都會放到第一個位置,而且指向下一個entry,每個entry中存放一個key-value和next引用。

2、get方法:

  Java源碼:

 1  /**
 2      * Returns the value to which the specified key is mapped,
 3      * or {@code null} if this map contains no mapping for the key.
 4      *
 5      * <p>More formally, if this map contains a mapping from a key
 6      * {@code k} to a value {@code v} such that {@code (key==null ? k==null :
 7      * key.equals(k))}, then this method returns {@code v}; otherwise
 8      * it returns {@code null}.  (There can be at most one such mapping.)
 9      *
10      * <p>A return value of {@code null} does not <i>necessarily</i>
11      * indicate that the map contains no mapping for the key; it's also
12      * possible that the map explicitly maps the key to {@code null}.
13      * The {@link #containsKey containsKey} operation may be used to
14      * distinguish these two cases.
15      *
16      * @see #put(Object, Object)
17      */
18     public V get(Object key) {
19         if (key == null)
20             return getForNullKey();
21         Entry<K,V> entry = getEntry(key);
22 
23         return null == entry ? null : entry.getValue();
24     }
25 
26     /**
27      * Offloaded version of get() to look up null keys.  Null keys map
28      * to index 0.  This null case is split out into separate methods
29      * for the sake of performance in the two most commonly used
30      * operations (get and put), but incorporated with conditionals in
31      * others.
32      */
33     private V getForNullKey() {
34         if (size == 0) {
35             return null;
36         }
37         for (Entry<K,V> e = table[0]; e != null; e = e.next) {
38             if (e.key == null)
39                 return e.value;
40         }
41         return null;
42     }
 1    /**
 2      * Returns the entry associated with the specified key in the
 3      * HashMap.  Returns null if the HashMap contains no mapping
 4      * for the key.
 5      */
 6     final Entry<K,V> getEntry(Object key) {
 7         if (size == 0) {
 8             return null;
 9         }
10 
11         int hash = (key == null) ? 0 : hash(key);
12         for (Entry<K,V> e = table[indexFor(hash, table.length)];
13              e != null;
14              e = e.next) {
15             Object k;
16             if (e.hash == hash &&
17                 ((k = e.key) == key || (key != null && key.equals(k))))
18                 return e;
19         }
20         return null;
21     }

解析:

  (1)get的時候,若是key==null,判斷map的長度也爲空的話,則返回null,若是map長度不爲空,則e也不空,遍歷table[0],返回e.value

  (2)getEntry的時候,首先要獲取hash(key)的值,經過hash&table.length獲取到的hashCode值獲得entry在桶中存放的位置,判斷若是若是傳入的key的hash與要得到key的hash相等的話而且key.equals(e.key)y也相等,則返回entry,若是返回的jentry不爲空的話,則getValue的值。

3、map中的元素不重複,元素無序

  測試:

1  Map map = new HashMap();
2         map.put("123","456");
3         map.put("abc","456");
4         map.put("dd","456");
5         map.put("ss","789");
6         Object s = map.put("123","777");
7         System.out.println(map);//key不可重複,重複的話會將原來的value值覆蓋,可是value的值不是惟一的,每一個key都有惟一的一個value與之對應
8         System.out.println(s);//返回舊的value值456

若有錯誤,請多多指出,謝謝!

相關文章
相關標籤/搜索