HashTable原理與源碼分析


本文版權歸 遠方的風lyh和博客園共有,歡迎轉載,但須保留此段聲明,並給出原文鏈接,謝謝合作,如有錯誤之處忘不吝批評指正!

HashTable內部存儲結構

 HashTable內部存儲結構為數組+單向鏈表的形式存儲數據,即定義的 Entry<?,?>[] table 變量

   


源碼分析

變量定義

   //使用Entry數組存儲數據 (Entry 單向鏈表)
    private transient Entry<?,?>[] table;
    //已經存儲在table 的 Entry 個數
    private transient int count;
    /****
    * Entry數組擴容閾值(count)  count>=threshold 時擴容 Entry數組會進行擴容 
    * 建議不要設置超過1 更要不設置太大,導致鏈表長度過長 會導致查詢很慢
     * 比如 HashTble initialCapacity =5  loadFactor=0.75 則計算 threshold =3
     * when count >=3 時 table開始擴容(具體如何擴容的看擴容代碼)
     * ***/ 
    private int threshold;
    //負載因子 用於計算 threshold  
    private float loadFactor;
    //記錄修改次數
    private transient int modCount = 0;
    //table數組的最大長度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

Entry點向鏈表結構

/**
 * Hashtable bucket collision list entry
 *單向鏈表
 */
private static class Entry<K,V> implements Map.Entry<K,V> {
    //hash值
    final int hash;
    //key
    final K key;
    //value
    V value;
    //后繼
    Entry<K,V> next;

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

    @SuppressWarnings("unchecked")
    protected Object clone() {
        return new Entry<>(hash, key, value,
                              (next==null ? null : (Entry<K,V>) next.clone()));
    }

    // Map.Entry Ops
    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }

    public V setValue(V value) {
        if (value == null)
            throw new NullPointerException();

        V oldValue = this.value;
        this.value = value;
        return oldValue;
    }

    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;

        return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
           (value==null ? e.getValue()==null : value.equals(e.getValue()));
    }

    public int hashCode() {
        return hash ^ Objects.hashCode(value);
    }

    public String toString() {
        return key.toString()+"="+value.toString();
    }
}

構造函數

  /***
      * 初始化HashTable 指定初始化容量(initialCapacity),負載因子(loadFactor)
      * HashTable初始化核心代碼
      * **/
    public Hashtable(int initialCapacity, float loadFactor) {
        
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load: "+loadFactor);

        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        //初始化Entry數組
        table = new Entry<?,?>[initialCapacity];
        //計算擴容閾值
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

   /***
    * 初始化HashTable 指定初始化容量(initialCapacity)
    * 默認負載因子大小為0.75
    * ***/
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    /***
      * 默認初始化 Entry數組 大小為 11
      * loadFactor =0.75
      ***/    
    public Hashtable() {
        this(11, 0.75f);
    }

    /***
     * 
     * 
     ***/
    public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

put方法

/***
 **添加元素   key value
 ** 注意:put方法是加鎖的 這就是 hashTable 為啥是線程安全的原因 阻塞的
** key、value 均不能為空 ***
*/ public synchronized V put(K key, V value) { // Make sure the value is not null value不能為空 if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; //hashCode取的是key的HashCode key不能為空 int hash = key.hashCode(); //根據hashCode & long最大值散列 再對 數組長度取模獲取到所要插入的數組下標 int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") //查看數組該下標下是否存在元素 如存在便利 value(新)覆蓋key相同的值 Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } //保存 addEntry(hash, key, value, index); return null; } /*** *保存 ***/ private void addEntry(int hash, K key, V value, int index) { //記錄put次數 +1 modCount++; Entry<?,?> tab[] = table; //已存儲的Entry個數 >= 閾值 擴容 if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") //查詢數組原有的Entry鏈表 Entry<K,V> e = (Entry<K,V>) tab[index]; //將Entry<K,V> 保存 並將Entry<K,V>.next 指向原來的Entry鏈表 tab[index] = new Entry<>(hash, key, value, e); //數組長度+1 count++; }

擴容機制

/****
  **擴容
  ** 觸發  count(已存儲的Entry個數) >= threshold(閾值)
  ****/
   protected void rehash() {
    //擴容前 數組(table)長度   
    int oldCapacity = table.length;
    //擴容前的 table數組
    Entry<?,?>[] oldMap = table;

    // overflow-conscious code
    //擴容后的 數組長度為 oldCapacity*2+1  
    int newCapacity = (oldCapacity << 1) + 1;
    //檢查長度是否超過上限
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        if (oldCapacity == MAX_ARRAY_SIZE)
            // Keep running with MAX_ARRAY_SIZE buckets
            return;
        newCapacity = MAX_ARRAY_SIZE;
    }
    // 新建一個數組長度為 oldCapacity*2+1 數組
    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

    modCount++;
    //計算閾值
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    table = newMap;
    //將舊數組中的Entry 重新計算轉移到新數組中
    for (int i = oldCapacity ; i-- > 0 ;) {
        for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
            Entry<K,V> e = old;
            old = old.next;

            int index = (e.hash & 0x7FFFFFFF) % newCapacity;
            e.next = (Entry<K,V>)newMap[index];
            newMap[index] = e;
        }
    }
}

 特點:

    1. key、value 都不允許為空 key不允許為空 因為hashcode取的是key 該對象的HashCode()
    2. HashTable 默認構造方法 容量初始化為 11 負載因子為 0.75   若使用帶負載因子的構造方法創建HashTable 請不要講負載因子設置過大 比如 初始化容量設為 1 負載因子設為1000 這樣會導致查詢很慢
    3. HashTable是數組+單項鏈表Entry<k,v>的結構來存儲數據
    4. 數組table最大長度為 Integer.Max - 8;
    5. 擴容條件 count(HashTbale 存儲鏈表的個數) >= threshold(閾值 計算方法 table.length * loadFactor)
    6. 線程安全


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM