HashMap有4個構造函數java
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; this.threshold = tableSizeFor(initialCapacity); } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }
在HashMap中有兩個很重要的參數,容量(Capacity)和負載因子(Load factor).node
Capacity就是bucket的大小,Load factor就是bucket填滿程度的最大比例。若是對迭代性能要求很高的話不要把Capacity設置過大,也不要把Load factor設置太小。當bucket中的entries的數目大於Capacity*Load factor時就須要調整bucket的大小爲當前的2倍。數組
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
put函數大體的思路爲:app
public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
get大體思路以下:函數
當put時,若是發現目前的bucket佔用程度已經超過了Load Factor所但願的比例,那麼就會發生resize。在resize的過程,簡單的說就是把bucket擴充爲2倍,以後從新計算index,把節點再放到新的bucket中性能
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { // 超過最大值就再也不擴充了,就只好隨你碰撞去吧 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 沒超過最大值,就擴充爲原來的2倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 計算新的resize上限 if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { // 把每一個bucket都移動到新的buckets中 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; // 原索引 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } // 原索引+oldCap else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); // 原索引放到bucket裏 if (loTail != null) { loTail.next = null; newTab[j] = loHead; } // 原索引+oldCap放到bucket裏 if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
HashMap基於Map接口實現、容許null鍵/值、非同步、不保證有序(好比插入的順序)、也不保證序不隨時間變化this
能夠總結幾個常見問題:spa
HashMap的工做原理是什麼?code
經過hash的方法,經過put和get存儲和獲取對象。存儲對象時,咱們將K/V傳給put方法時,它調用hashCode計算hash從而獲得bucket位置,進一步存儲,HashMap會根據當前bucket的佔用狀況自動調整容量(超過Load Facotr則resize爲原來的2倍)。獲取對象時,咱們將K傳給get,它調用hashCode計算hash從而獲得bucket位置,並進一步調用equals()方法肯定鍵值對。若是發生碰撞的時候,Hashmap經過鏈表將產生碰撞衝突的元素組織起來,在Java 8中,若是一個bucket中碰撞衝突的元素超過某個限制(默認是8),則使用紅黑樹來替換鏈表,從而提升速度。對象
咱們可使用自定義的對象做爲鍵嗎?
固然你可能使用任何對象做爲鍵,只要它遵照了equals()和hashCode()方法的定義規則,而且當對象插入到Map中以後將不會再改變了。若是這個自定義對象時不可變的,那麼它已經知足了做爲鍵的條件,由於當它建立以後就已經不能改變了。
equals()和hashCode()的都有什麼做用?
經過對key的hashCode()進行hashing,並計算下標( n-1 & hash),從而得到buckets的位置。若是產生碰撞,則利用key.equals()方法去鏈表或樹中去查找對應的節點
若是HashMap的大小超過了負載因子(load factor)定義的容量,怎麼辦?
若是超過了負載因子(默認0.75),則會從新resize一個原來長度兩倍的HashMap,而且從新調用hash方法。
爲何桶的大小爲2的冪次?
以Entry[]數組實現的哈希桶數組,用Key的哈希值取模桶數組的大小可獲得數組下標。
插入元素時,若是兩條Key落在同一個桶(好比哈希值1和17取模16後都屬於第一個哈希桶),Entry用一個next屬性實現多個Entry以單向鏈表存放,後入桶的Entry將next指向桶當前的Entry。
查找哈希值爲17的key時,先定位到第一個哈希桶,而後以鏈表遍歷桶裏全部元素,逐個比較其key值。
當Entry數量達到桶數量的75%時(不少文章說使用的桶數量達到了75%,但看代碼不是),會成倍擴容桶數組,並從新分配全部原來的Entry,因此這裏也最好有個預估值。
取模用位運算(hash & (arrayLength-1))會比較快,因此數組的大小永遠是2的N次方, 你隨便給一個初始值好比17會轉爲32。默認第一次放入元素時的初始值是16。
hashMap遍歷時爲何是亂序?
iterator()時順着哈希桶數組來遍歷,看起來是個亂序。
咱們順便看一下HashSet的實現,它是由HashMap實現的,沒有重複元素的集合。不保證元素的順序,並且HashSet容許使用 null 元素。
package java.util; public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; // HashSet是經過map(HashMap對象)保存內容的 private transient HashMap<E,Object> map; // PRESENT是向map中插入key-value對應的value // 由於HashSet中只須要用到key,而HashMap是key-value鍵值對; // 因此,向map中添加鍵值對時,鍵值對的值固定是PRESENT private static final Object PRESENT = new Object(); // 默認構造函數 public HashSet() { // 調用HashMap的默認構造函數,建立map map = new HashMap<E,Object>(); } // 帶集合的構造函數 public HashSet(Collection<? extends E> c) { // 建立map。 // 爲何要調用Math.max((int) (c.size()/.75f) + 1, 16),從 (c.size()/.75f) + 1 和 16 中選擇一個比較大的樹呢? // 首先,說明(c.size()/.75f) + 1 // 由於從HashMap的效率(時間成本和空間成本)考慮,HashMap的加載因子是0.75。 // 當HashMap的「閾值」(閾值=HashMap總的大小*加載因子) < 「HashMap實際大小」時, // 就須要將HashMap的容量翻倍。 // 因此,(c.size()/.75f) + 1 計算出來的正好是總的空間大小。 // 接下來,說明爲何是 16 。 // HashMap的總的大小,必須是2的指數倍。若建立HashMap時,指定的大小不是2的指數倍; // HashMap的構造函數中也會從新計算,找出比「指定大小」大的最小的2的指數倍的數。 // 因此,這裏指定爲16是從性能考慮。避免重複計算。 map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16)); // 將集合(c)中的所有元素添加到HashSet中 addAll(c); } // 指定HashSet初始容量和加載因子的構造函數 public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<E,Object>(initialCapacity, loadFactor); } // 指定HashSet初始容量的構造函數 public HashSet(int initialCapacity) { map = new HashMap<E,Object>(initialCapacity); } HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor); } // 返回HashSet的迭代器 public Iterator<E> iterator() { // 實際上返回的是HashMap的「key集合的迭代器」 return map.keySet().iterator(); } public int size() { return map.size(); } public boolean isEmpty() { return map.isEmpty(); } public boolean contains(Object o) { return map.containsKey(o); } // 將元素(e)添加到HashSet中 public boolean add(E e) { return map.put(e, PRESENT)==null; } // 刪除HashSet中的元素(o) public boolean remove(Object o) { return map.remove(o)==PRESENT; } public void clear() { map.clear(); } // 克隆一個HashSet,並返回Object對象 public Object clone() { try { HashSet<E> newSet = (HashSet<E>) super.clone(); newSet.map = (HashMap<E, Object>) map.clone(); return newSet; } catch (CloneNotSupportedException e) { throw new InternalError(); } } // java.io.Serializable的寫入函數 // 將HashSet的「總的容量,加載因子,實際容量,全部的元素」都寫入到輸出流中 private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out any hidden serialization magic s.defaultWriteObject(); // Write out HashMap capacity and load factor s.writeInt(map.capacity()); s.writeFloat(map.loadFactor()); // Write out size s.writeInt(map.size()); // Write out all elements in the proper order. for (Iterator i=map.keySet().iterator(); i.hasNext(); ) s.writeObject(i.next()); } // java.io.Serializable的讀取函數 // 將HashSet的「總的容量,加載因子,實際容量,全部的元素」依次讀出 private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in any hidden serialization magic s.defaultReadObject(); // Read in HashMap capacity and load factor and create backing HashMap int capacity = s.readInt(); float loadFactor = s.readFloat(); map = (((HashSet)this) instanceof LinkedHashSet ? new LinkedHashMap<E,Object>(capacity, loadFactor) : new HashMap<E,Object>(capacity, loadFactor)); // Read in size int size = s.readInt(); // Read in all elements in the proper order. for (int i=0; i<size; i++) { E e = (E) s.readObject(); map.put(e, PRESENT); } } }
HashSet的實現依賴於HashMap,若是理解了HashMap的實現,HashSet仍是比較容易理解的。