《java集合》--HashMap

HashMap

參考:Java8 HashMap實現原理探究

特點

  • 基於Hash表的Map接口實現
  • 線程非安全,並且允許key與value都爲null值,HashTable與之相反,爲線程安全,key與value都不允許null值。
  • 不保證映射的順序,特別是它不保證順序恆久不變,resize時會重排
  • 當數組沒有鏈表存在時,HashMap性能最好爲O(1)。而最差爲O(threshould)即所有元素存在一個鏈表上。
  • hashMap會根據存入的size和負載英子*數組初始容量進行擴容

數據結構

HashMap實際上是一個鏈表散列的數據結構,即數組和鏈表的結合體,HashMap底層就是一個數組結構,數組中的每一項又是一個鏈表

image

jdk8優化,如果數組某個位置鏈表長度超過8個會轉成紅黑樹,jdk8之前一直是鏈表,鏈表查詢的複雜度是O(n),而紅黑樹由於其自身的特點,查詢的複雜度是O(log(n)),會提高遍歷性能

image

源碼解析

  • 構造函數初始化

    1、initialCapacity:數組的初始化長度,2的n次方

    2、loadFactor:負載英子,默認是0.75

    3、threshold:最大容量,threshold=initialCapacity*loadFactor,當entry的數量超過capacity*loadFactor時,容器將自動擴容並重新哈希。

    4、在對HashMap進行迭代時,需要遍歷整個table以及後面跟的衝突鏈表。因此對於迭代比較頻繁的場景,不宜將HashMap的初始大小設的過大。

    5、增大負載因子可以減少數組所佔用的內存空間,但會增加查詢數據的時間開銷,而查詢是最頻繁的的操作(HashMap 的 get() 與 put()方法都要用到查詢);減小負載因子會提高數據查詢的性能,但會增加Hash表所佔用的內存空間。

    6、將對向放入到HashMap或HashSet中時,有兩個方法需要特別關心:hashCode()和equals()。hashCode()方法決定了對象會被放到哪個bucket裏,當多個對象的哈希值衝突時,equals()方法決定了這些對象是否是“同一個對象”。所以,如果要將自定義的對象放入到HashMap或HashSet中,需要@Override hashCode()和equals()方法。


// 以指定初始化容量、負載因子創建 HashMap   
 public HashMap(int initialCapacity, float loadFactor)   
 {   
   // 初始容量不能爲負數  
   if (initialCapacity < 0)   
     throw new IllegalArgumentException(   
    "Illegal initial capacity: " +   
       initialCapacity);   
   // 如果初始容量大於最大容量,table數組長度爲最大長度
   if (initialCapacity > MAXIMUM_CAPACITY)   
     initialCapacity = MAXIMUM_CAPACITY;   
   // 負載因子必須大於 0 的數值  
   if (loadFactor <= 0 || Float.isNaN(loadFactor))   
     throw new IllegalArgumentException(   
     loadFactor);   
   // 計算出大於 initialCapacity 的最小的 2 的 n 次方值。  
   int capacity = 1;   
   while (capacity < initialCapacity)   
     capacity <<= 1;   
   this.loadFactor = loadFactor;   
   // 設置容量極限等於容量 * 負載因子  
   threshold = (int)(capacity * loadFactor);   
   // 底層是一個數組  
   table = new Entry[capacity]; 
   init();   
 }

//

Entry對象,實現了單向鏈表
static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;//key
        V value;//值
        Entry<K,V> next;//next指針
        final int hash;//key的hash值


        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
        //重寫了hashCode方法
        public final int hashCode() {
            return (key==null   ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }
}
  • put方法
public V put(K key, V value) {
    // HashMap允許存放null鍵和null值。
    // 當key爲null時,調用putForNullKey方法,將value放置在數組第一個位置。  
    if (key == null)
        return putForNullKey(value);
    // 根據key的keyCode重新計算hash值。
    int hash = hash(key.hashCode());
    // 搜索指定hash值在對應table中的索引。
    int i = indexFor(hash, table.length);
    // 如果 i 索引處的 Entry 不爲 null,通過循環不斷遍歷 e 元素的下一個元素。
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    // 如果i索引處的Entry爲null,表明此處還沒有Entry。
    modCount++;
    // 將key、value添加到i索引處。
    addEntry(hash, key, value, i);
    return null;
}
處理key值爲空的情況
private V putForNullKey(V value) {
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        if (e.key == null) {
            V oldValue = e.value;
            e.value = value;
            //LinkedHashMap 有不同的處理
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(0, null, value, 0);
    return null;
}
根據key的hash值進行hash運算,如果key的hash值一樣,調用HashMap的hash運算後得到的結果是一樣的
static int hash(int h)   
{   
    h ^= (h >>> 20) ^ (h >>> 12);   
    return h ^ (h >>> 7) ^ (h >>> 4);   
}
HashMap對key值得hash值運算後獲取table數組的下標table數組的長度總是2的n次方,默認是16,通過 & 運算得到的就是數組的下標,最大不會超過數組的長度,保證數組長度總是2的n次方,這樣長度-1後二進制所有位置都是1,跟key的hash值進行&運算時會更加均勻的分佈entry到數組上。
static int indexFor(int h, int length)   
{   
    return h & (length-1);   
}
將entry存入數組中,如果數組該位置上已經存放有其他元素了,那麼在這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾。如果數組該位置上沒有元素,就直接將該元素放到此數組中的該位置上
void addEntry(int hash, K key, V value, int bucketIndex) {
    //獲取數組當前位置的entry
    Entry<K,V> e = table[bucketIndex];
    //將要保存的entry存入數組當前位置,之前的entry的指針作爲當前entry的一個next屬性
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    //size+1並且和允許最大比較,如果超過則擴展數組的長度爲之前的2倍
    if (size++ >= threshold)
        resize(2 * table.length);
}
擴展數組的長度,每次擴展原數組長度的2倍,保證數組的長度爲2的n次方,擴展的實質是創建一個2倍長度的新的數組,原數組中的entry必須重新計算其在新數組中的位置,並放進去,這就是resize。
 void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    //如果已經是所支持的最大長度,設置最大存儲的entry數量爲Interger的最大值
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
    //創建新的
    Entry[] newTable = new Entry[newCapacity];
    transfer(newTable);
    table = newTable;
    threshold = (int)(newCapacity * loadFactor);
}
//原數組上entry通過hash計算存放到新的數組上面
void transfer(Entry[] newTable) {
    Entry[] src = table;
    int newCapacity = newTable.length;
    for (int j = 0; j < src.length; j++) {
        Entry<K,V> e = src[j];
        if (e != null) {
            src[j] = null;
            do {
                Entry<K,V> next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            } while (e != null);
        }
    }
}
  • get方法
public V get(Object key) {
    if (key == null)
        return getForNullKey();
    //計算hash值
    int hash = hash(key.hashCode());
    //遍歷數組
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        //比較key值得hash屬性,相同的hash代表一個key
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
            return e.value;
    }
    return null;
}
//單獨處理key爲空的情況
private V getForNullKey() {
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        if (e.key == null)
            return e.value;
    }
    return null;
}
  • remove方法
public V remove(Object key) {
    Entry<K,V> e = removeEntryForKey(key);
    return (e == null ? null : e.value);
}


final Entry<K,V> removeEntryForKey(Object key) {

    int hash = (key == null) ? 0 : hash(key.hashCode());
    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;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k)))) {
            modCount++;
            //長度-1
            size--;
            //操作鏈表
            if (prev == e)
                table[i] = next;
            else
                prev.next = next;
            e.recordRemoval(this);
            return e;
        }
        prev = e;
        e = next;
    }

    return e;
}
  • 迭代器實現
final class KeyIterator extends HashIterator
    implements Iterator<K> {
    public final K next() { return nextNode().key; }
}

final class ValueIterator extends HashIterator
    implements Iterator<V> {
    public final V next() { return nextNode().value; }
}

final class EntryIterator extends HashIterator
    implements Iterator<Map.Entry<K,V>> {
    public final Map.Entry<K,V> next() { return nextNode(); }
}
abstract class HashIterator {
    Node<K,V> next;        // next entry to return
    Node<K,V> current;     // current entry
    int expectedModCount;  // for fast-fail
    int index;             // current slot

    HashIterator() {
        expectedModCount = modCount;
        Node<K,V>[] t = table;
        current = next = null;
        index = 0;
        if (t != null && size > 0) { 
            //遍歷table直到找到第一個元素,指向next
            do {} while (index < t.length && (next = t[index++]) ==null);
        }
    }

    public final boolean hasNext() {
        return next != null;
    }

    final Node<K,V> nextNode() {
        Node<K,V>[] t;
        Node<K,V> e = next;
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        if (e == null)
            throw new NoSuchElementException();
        //current用來記錄上一個,刪除的時候用
        if ((next = (current = e).next) == null && (t = table) != null) {
            do {} while (index < t.length && (next = t[index++]) ==null);
        }
        return e;
    }

    public final void remove() {
        Node<K,V> p = current;
        if (p == null)
            throw new IllegalStateException();
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        current = null;
        K key = p.key;
        //調用hashMap的方法刪除
        removeNode(hash(key), key, null, false, false);
        expectedModCount = modCount;
    }
}

HashSet

hashSet是對HashMap的簡單包裝,對HashSet的函數調用都會轉換成合適的HashMap方法,因此HashSet的實現非常簡單,只有不到300行代碼。這裏不再贅述。

//HashSet是對HashMap的簡單包裝
public class HashSet<E>
{
    ......
    private transient HashMap<E,Object> map;//HashSet裏面有一個HashMap
    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
    public HashSet() {
        map = new HashMap<>();
    }
    ......
    public boolean add(E e) {//簡單的方法轉換
        return map.put(e, PRESENT)==null;
    }
    ......
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章