[原創]Android系統中經常使用JAVA類源碼淺析之HashMap

因爲是淺析,因此我只分析經常使用的接口,注意是Android系統中的JAVA類,可能和JDK的源碼有區別。數組

首先從構造函數開始,app

  1     /**
  2      * Min capacity (other than zero) for a HashMap. Must be a power of two
  3      * greater than 1 (and less than 1 << 30).
  4      */
  5     private static final int MINIMUM_CAPACITY = 4;
  6 
  7     /**
  8      * Max capacity for a HashMap. Must be a power of two >= MINIMUM_CAPACITY.
  9      */
 10     private static final int MAXIMUM_CAPACITY = 1 << 30;
 11 
 12     /**
 13      * An empty table shared by all zero-capacity maps (typically from default
 14      * constructor). It is never written to, and replaced on first put. Its size
 15      * is set to half the minimum, so that the first resize will create a
 16      * minimum-sized table.
 17      */
 18     private static final Entry[] EMPTY_TABLE
 19             = new HashMapEntry[MINIMUM_CAPACITY >>> 1];
 20 
 21     /**
 22      * The default load factor. Note that this implementation ignores the
 23      * load factor, but cannot do away with it entirely because it's
 24      * mentioned in the API.
 25      *
 26      * <p>Note that this constant has no impact on the behavior of the program,
 27      * but it is emitted as part of the serialized form. The load factor of
 28      * .75 is hardwired into the program, which uses cheap shifts in place of
 29      * expensive division.
 30      */
 31     static final float DEFAULT_LOAD_FACTOR = .75F;
 32 
 33     /**
 34      * The hash table. If this hash map contains a mapping for null, it is
 35      * not represented this hash table.
 36      */
 37     transient HashMapEntry<K, V>[] table;
 38 
 39     /**
 40      * The entry representing the null key, or null if there's no such mapping.
 41      */
 42     transient HashMapEntry<K, V> entryForNullKey;
 43 
 44     /**
 45      * The number of mappings in this hash map.
 46      */
 47     transient int size;
 48 
 49     /**
 50      * Incremented by "structural modifications" to allow (best effort)
 51      * detection of concurrent modification.
 52      */
 53     transient int modCount;
 54 
 55     /**
 56      * The table is rehashed when its size exceeds this threshold.
 57      * The value of this field is generally .75 * capacity, except when
 58      * the capacity is zero, as described in the EMPTY_TABLE declaration
 59      * above.
 60      */
 61     private transient int threshold;
 62 
 63     public HashMap() {
 64         table = (HashMapEntry<K, V>[]) EMPTY_TABLE;
 65         threshold = -1; // Forces first put invocation to replace EMPTY_TABLE
 66     }
 67 
 68     public HashMap(int capacity) {
 69         if (capacity < 0) {
 70             throw new IllegalArgumentException("Capacity: " + capacity);
 71         }
 72 
 73         if (capacity == 0) {
 74             @SuppressWarnings("unchecked")
 75             HashMapEntry<K, V>[] tab = (HashMapEntry<K, V>[]) EMPTY_TABLE;
 76             table = tab;
 77             threshold = -1; // Forces first put() to replace EMPTY_TABLE
 78             return;
 79         }
 80 
 81         if (capacity < MINIMUM_CAPACITY) {
 82             capacity = MINIMUM_CAPACITY;
 83         } else if (capacity > MAXIMUM_CAPACITY) {
 84             capacity = MAXIMUM_CAPACITY;
 85         } else {
 86             capacity = Collections.roundUpToPowerOfTwo(capacity);
 87         }
 88         makeTable(capacity);
 89     }
 90 
 91     public HashMap(int capacity, float loadFactor) {
 92         this(capacity);
 93 
 94         if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
 95             throw new IllegalArgumentException("Load factor: " + loadFactor);
 96         }
 97 
 98         /*
 99          * Note that this implementation ignores loadFactor; it always uses
100          * a load factor of 3/4. This simplifies the code and generally
101          * improves performance.
102          */
103     }

經過三個構造函數的源碼,咱們能夠知道:less

  • HashMap內部實際上使用HashMapEntry數組來實現的。
  • 當調用new HashMap()時,會建立容量爲2的HashMapEntry數組,而且threshold爲-1。
  • 當調用HashMap(int capacity)時,HashMap會將傳入的capacity轉換成最小的大於等於capacity的2的次方,好比:capacity=25,會轉換成32。而且threshold爲總容量的75%,threshold的做用是當entry的數量大於threshold時,進行擴容。
  • HashMap(int capacity, float loadFactory)實際上和HashMap(int capacity)是同樣的,loadFactory參數未被使用(注意這是Android作的修改,實際上JDK中會使用這個參數)。

既然是HashMapEntry數組實現的,咱們簡單看下這個Entry什麼樣,ide

static class HashMapEntry<K, V> implements Entry<K, V> {
        final K key;
        V value;
        final int hash;
        HashMapEntry<K, V> next;

        HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) {
            this.key = key;
            this.value = value;
            this.hash = hash;
            this.next = next;
        }
}

這裏注意關注next屬性,有必定經驗的朋友確定知道,這是單向鏈表的實現,因此實現HashMap的數組的每一項實際上是一個單向鏈表的Head,繼續往下看,函數

接下來咱們分析下put(K key, V value)方法,this

 1     void addNewEntryForNullKey(V value) {
 2         entryForNullKey = new HashMapEntry<K, V>(null, value, 0, null);
 3     }
 4 
 5     private V putValueForNullKey(V value) {
 6         HashMapEntry<K, V> entry = entryForNullKey;
 7         if (entry == null) {
 8             addNewEntryForNullKey(value);
 9             size++;
10             modCount++;
11             return null;
12         } else {
13             preModify(entry);
14             V oldValue = entry.value;
15             entry.value = value;
16             return oldValue;
17         }
18     }
19 
20     private HashMapEntry<K, V>[] makeTable(int newCapacity) {
21         @SuppressWarnings("unchecked") HashMapEntry<K, V>[] newTable
22                 = (HashMapEntry<K, V>[]) new HashMapEntry[newCapacity];
23         table = newTable;
24         threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity
25         return newTable;
26     }
27 
28     private HashMapEntry<K, V>[] doubleCapacity() {
29         HashMapEntry<K, V>[] oldTable = table;
30         int oldCapacity = oldTable.length;
31         if (oldCapacity == MAXIMUM_CAPACITY) {
32             return oldTable;
33         }
34         int newCapacity = oldCapacity * 2;
35         HashMapEntry<K, V>[] newTable = makeTable(newCapacity);
36         if (size == 0) {
37             return newTable;
38         }
39 
40         for (int j = 0; j < oldCapacity; j++) {
41             /*
42              * Rehash the bucket using the minimum number of field writes.
43              * This is the most subtle and delicate code in the class.
44              */
45             HashMapEntry<K, V> e = oldTable[j];
46             if (e == null) {
47                 continue;
48             }
49             int highBit = e.hash & oldCapacity;
50             HashMapEntry<K, V> broken = null;
51             newTable[j | highBit] = e;
52             for (HashMapEntry<K, V> n = e.next; n != null; e = n, n = n.next) {
53                 int nextHighBit = n.hash & oldCapacity;
54                 if (nextHighBit != highBit) {
55                     if (broken == null)
56                         newTable[j | nextHighBit] = n;
57                     else
58                         broken.next = n;
59                     broken = e;
60                     highBit = nextHighBit;
61                 }
62             }
63             if (broken != null)
64                 broken.next = null;
65         }
66         return newTable;
67     }
68 
69     @Override public V put(K key, V value) {
70         if (key == null) {
71             return putValueForNullKey(value);
72         }
73 
74         int hash = Collections.secondaryHash(key);
75         HashMapEntry<K, V>[] tab = table;
76         int index = hash & (tab.length - 1);
77         for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
78             if (e.hash == hash && key.equals(e.key)) {
79                 preModify(e);
80                 V oldValue = e.value;
81                 e.value = value;
82                 return oldValue;
83             }
84         }
85 
86         // No entry for (non-null) key is present; create one
87         modCount++;
88         if (size++ > threshold) {
89             tab = doubleCapacity();
90             index = hash & (tab.length - 1);
91         }
92         addNewEntry(key, value, hash, index);
93         return null;
94     }
95 
96     void addNewEntry(K key, V value, int hash, int index) {
97         table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);
98     }

從put(K key, V value)的源碼咱們能夠獲得以下信息:spa

  • 當添加key爲null的value時,會用單獨的HashMapEntry entryForNullKey對象來儲存。
  • entry數組的索引是經過hash算出來的:int index = hash & (tab.length - 1)。
  • 當發生碰撞時(也就是算出的index上已經存在entry了),會首先檢查是不是同一個hash和key,若是是則更新value,而後直接將old value返回。
  • 新建立的entry會被設置成對應index上的鏈表Head。
  • 當entry數量大於threshold(capacity的75%)時,對數組進行擴容,擴大爲原來的2倍,並從新計算原數組中全部entry的index,而後複製到新數組中。

分析完put後,其餘如get、remove、containsKey等接口就大同小異了,在此直接略過。code

接下來咱們看下Set<K> keySet()接口:orm

    @Override public Set<K> keySet() {
        Set<K> ks = keySet;
        return (ks != null) ? ks : (keySet = new KeySet());
    }

    private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();
        }
        public int size() {
            return size;
        }
        public boolean isEmpty() {
            return size == 0;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            int oldSize = size;
            HashMap.this.remove(o);
            return size != oldSize;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

    Iterator<K> newKeyIterator() { return new KeyIterator();   }

    private final class KeyIterator extends HashIterator
            implements Iterator<K> {
        public K next() { return nextEntry().key; }
    }

從源碼中能夠得出以下結論:對象

  • keySet返回的Set對象實際上和HashMap是強關聯的,對Set接口的調用,實際上操做的仍是HashMap。
  • Set中的iterator實際上也是實現自HashIterator。
  • entrySet()、valueSet()和keySet()的實現原理同樣。

知道HashMap的實現原理後,咱們就能夠知道他的優缺點了:

優勢:讀寫效率高,接近數組的索引方式。

缺陷:會佔用大量的無效內存,爲了減小碰撞,Entry數組的容量只能是2的N次冪,而且當entry數大於總容量的75%時就會擴容兩倍。

 

若有問題,歡迎指出!

轉載請註明出處。

相關文章
相關標籤/搜索