hashmap (jdk 1.7)使用 「數組-鏈表」 方式進行存儲,圖形化表示以下:java
即,前面是一個數組,後面跟一個鏈表,那麼數據結構這個對應到HashMap的代碼裏面是什麼樣子的呢?數組
在HashMap中定義了一個類型爲Entry<K,V>的數組table,上圖就是顯示了這個table。數據結構
/** * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
類型Entry<K,V>的定義以下:app
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; // 省略構造/get/set等函數 }
由Entry<K,V>的定義可知,上圖每一個節點中其實存了4個變量:less
key表示鍵,即存入map的鍵值ide
value表示值,即存入map的值函數
next表示下一個Entry節點學習
hash表示key的哈希值。ui
那麼上圖準確表示應該是:this
對於HashMap,最經常使用的莫過於直接使用默認構造函數建立一個Map對象了
Map<int, String> map = new HashMap<>();
這裏HashMap調用了
/** * Constructs an empty <tt>HashMap</tt> with the default initial capacity * (16) and the default load factor (0.75). */ public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); }
其中,DEFAULT_INITIAL_CAPACITY是
/** * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
DEFAULT_LOAD_FACTOR是
/** * The load factor used when none specified in constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f;
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(); }
瞭解了基本結構以後,看一下HashMap的put()和get()方法是如何實現的。
首先,看put()方法,再瞭解put()方法以前,先了解幾個put()方法會調用的幾個輔助方法:
1. inflateTable(),給表充氣 or 讓表膨脹?原來table對象是空的,因此須要將table對象初始化
/** * Inflates the table. */ private void inflateTable(int toSize) { // Find a power of 2 >= toSize // capacity 表示HashpMap的容量,必須是2的倍數 int capacity = roundUpToPowerOf2(toSize); // threshold 表示須要resize的閾值 threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); // 初始化大小爲capacity的table對象 table = new Entry[capacity]; // 初始化 hashSeed initHashSeedAsNeeded(capacity); }
2. putForNullKey()
/** * Offloaded version of put for null keys */ private V putForNullKey(V value) { // 遍歷table[0],若是已經有key爲null的元素,直接返回對應的value for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 若是沒有key爲null的元素, // 將HashMap的修改次數+1 modCount++; // 將key爲null的元素添加到HashMap中 addEntry(0, null, value, 0); return null; }
疑問:key爲null的元素的hash值必定爲0嗎?
3. hash(),求對象的hash值
/** * Retrieve object hash code and applies a supplemental hash function to the * result hash, which defends against poor quality hash functions. This is * critical because HashMap uses power-of-two length hash tables, that * otherwise encounter collisions for hashCodes that do not differ * in lower bits. Note: Null keys always map to hash 0, thus index 0. */ final int hash(Object k) { int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
4. indexFor(),根據對象的hash值以及HashMap table的長度,尋找該對象的索引位置
/** * Returns index for hash code h. */ static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1); }
這裏沒有使用hash值h對長度length取餘,而是使用的位運算?其實二者結果是同樣的,h % length == h & (length -1)
5. addEntry()
/** * Adds a new entry with the specified key, value and hash code to * the specified bucket. It is the responsibility of this * method to resize the table if appropriate. * * Subclass overrides this to alter the behavior of put method. */ void addEntry(int hash, K key, V value, int bucketIndex) { // size是HashMap中元素的個數 // threshhold = capacity * load factor,表示須要擴容resize的閾值 // 若是size > threshold,而且table在當前索引處有元素,不爲null,則須要擴容HashMap,並重新計算索引值 if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } // 將元素加入到HashMap createEntry(hash, key, value, bucketIndex); }
6. createEntry()
/** * Like addEntry except that this version is used when creating entries * as part of Map construction or "pseudo-construction" (cloning, * deserialization). This version needn't worry about resizing the table. * * Subclass overrides this to alter the behavior of HashMap(Map), * clone, and readObject. */ void createEntry(int hash, K key, V value, int bucketIndex) { // 這裏是獲取某個鏈表的第一個節點e, // 由於每次插入都是往鏈表的頭部插入的,所以e就做爲了新節點的next值 Entry<K,V> e = table[bucketIndex]; // e做爲新節點的next值 table[bucketIndex] = new Entry<>(hash, key, value, e); // HashMap的size加1 size++; }
7. resize()
/** * Rehashes the contents of this map into a new array with a * larger capacity. This method is called automatically when the * number of keys in this map reaches its threshold. * * If current capacity is MAXIMUM_CAPACITY, this method does not * resize the map, but sets threshold to Integer.MAX_VALUE. * This has the effect of preventing future calls. * * @param newCapacity the new capacity, MUST be a power of two; * must be greater than current capacity unless current * capacity is MAXIMUM_CAPACITY (in which case value * is irrelevant). */ void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; // 若是HashMap中table的長度(這裏是指table數組的長度,不是鏈表的長度) // 已經達到了MAXIMUN_CAPACITY = 1 << 30,直接將閾值threshold設置爲Integer的最大值。 // 不在擴容HashMap if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // newCapacity是原table數組長度的2倍 Entry[] newTable = new Entry[newCapacity]; // 將原table中的值遷移到擴容後的newTable中 transfer(newTable, initHashSeedAsNeeded(newCapacity)); // 更新table和閾值threshold table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }
8. transfer(), 將原HashMap中的元素遷移到擴容後的HashMap中
/** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; // 遍歷table數組 for (Entry<K,V> e : table) { // 這裏e是table數組某個鏈表的第一個元素,後面e會依次指向鏈表中全部的元素 // 若是table數組的元素不爲null while(null != e) { Entry<K,V> next = e.next; if (rehash) { // 若是e.key是null,hash值是0 e.hash = null == e.key ? 0 : hash(e.key); } // 獲取元素e在新table中的索引值 int i = indexFor(e.hash, newCapacity); // 將e的next指向新table的第一個元素(這裏仍是要記住,插入鏈表是從頭部插入的) // newTable[i]是鏈表的第一個元素 e.next = newTable[i]; // 將e賦值給鏈表的第一個元素newTable[i],這樣e就取代了鏈表原來的第一個元素,做爲鏈表新的第一個元素,引領鏈表! newTable[i] = e; e = next; } } }
瞭解了上述n個方法以後,是時候看一下HashMap的put()方法的真面目了!
/** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt>. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with <tt>key</tt>.) */ public V put(K key, V value) { // 若是table是空的,須要初始化table if (table == EMPTY_TABLE) { inflateTable(threshold); } // 若是key是null,調用putForNullKey方法插入元素 if (key == null) return putForNullKey(value); // 求key的hash值 int hash = hash(key); // 根據key的hash值和table的長度求元素在table中的索引 int i = indexFor(hash, table.length); // 遍歷table[i]引領的鏈表 // 若是已經存在了相同的key,則更新value並返回old value,不然插入新元素 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; // key已經存在,更新value,返回old value if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // HashMap的修改次數加1,modCount是modified times modCount++; // 插入新元素 addEntry(hash, key, value, i); // 若是key沒有重複,返回值是null return null; }
其次,看一下get()方法,再瞭解get()方法以前,一樣先了解幾個get()方法會調用的幾個輔助方法:
1. getForNullKey()
/** * Offloaded version of get() to look up null keys. Null keys map * to index 0. This null case is split out into separate methods * for the sake of performance in the two most commonly used * operations (get and put), but incorporated with conditionals in * others. */ private V getForNullKey() { if (size == 0) { return null; } for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; }
2. getEntry()
/** * Returns the entry associated with the specified key in the * HashMap. Returns null if the HashMap contains no mapping * for the key. */ final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; }
瞭解了上述2個方法以後,get()方法就比較簡單了
/** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * * <p>More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key==null ? k==null : * key.equals(k))}, then this method returns {@code v}; otherwise * it returns {@code null}. (There can be at most one such mapping.) * * <p>A return value of {@code null} does not <i>necessarily</i> * indicate that the map contains no mapping for the key; it's also * possible that the map explicitly maps the key to {@code null}. * The {@link #containsKey containsKey} operation may be used to * distinguish these two cases. * * @see #put(Object, Object) */ public V get(Object key) { if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); }
最後,再學習一下remove()方法的實現
/** * Removes the mapping for the specified key from this map if present. * * @param key key whose mapping is to be removed from the map * @return the previous value associated with <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt>. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with <tt>key</tt>.) */ public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); } /** * Removes and returns the entry associated with the specified key * in the HashMap. Returns null if the HashMap contains no mapping * for this key. */ final Entry<K,V> removeEntryForKey(Object key) { if (size == 0) { return null; } // 找到hash值 int hash = (key == null) ? 0 : hash(key); // 求索引位置 int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; while (e != null) { Entry<K,V> next = e.next; Object k; // 尋找到key的位置,是當前的e if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { // HashMap的修改次數加1 modCount++; // HashMap的size減1 size--; // 若是是鏈表的第一個元素,next是null,直接將table[i]設置爲null if (prev == e) table[i] = next; else // prev的next是e,next是e.next,即[prev]-> [e] -> [next] // prev.next = next,即[prev] -> [next],直接將元素e移除掉了 prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }