1. 程式人生 > >thinking in java (十八) ----- 集合之Map(HashMap HashTable)總結

thinking in java (十八) ----- 集合之Map(HashMap HashTable)總結

Map框架圖

Map概括

  1. Map是鍵值對對映的抽象介面
  2. AbstractMap實現了Map中的大部分介面,減少了Map實現類的重複程式碼
  3. HashMap是基於拉鍊法實現的散列表,一般使用在單執行緒程式中
  4. HashTable是基於拉鍊法實現的散列表,一般使用在多執行緒程式中

HashMap,HashTable異同

  • 共同點

都是散列表,都是基於拉鍊法實現的

儲存的思想是:通過table陣列儲存,陣列的每一個元素都是一個Entry,而一個Entry就是一個單向連結串列,Entry連結串列中的每一個節點就儲存了KV鍵值對資料

新增KV鍵值對:首先根據Key,通過雜湊演算法得到雜湊值,在計算出陣列相對的索引index,然後根據索引值找到table陣列中的Entry,再遍歷單向連結串列,將key和連結串列中的key進行對比,如果已經有存在相同的key了,就用該value取代原value,如果不存在的話,就新建一個KV節點,並且將該節點放在連結串列的表頭位置

刪除鍵值對:首先根據key計算出雜湊值,再計算出索引,根據索引找到Entry,如果節點KV存在,就刪除完事了。

我們更多關注的是不同點:

  • 1,繼承和實現的方式不同

HashMap繼承與AbstractMap ,實現了Map,Cloneable,Serializable介面

HashMap繼承與Dictionary,實現了Map,Cloneable,Serializable介面

HashMap原始碼

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable { ... }

Hashtable原始碼

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable { ... }

我們可以看出:

1,.1:都實現了Map介面,意味著都是鍵值對操作,支援新增KV,獲取K,獲取V,獲取map大小,清空map等基礎的map操作

實現了Cloneable介面,可以被克隆

實現了Serializable介面,支援序列化,能夠通過序列化去傳輸

1.2:HashMap繼承於AbstractMap,而HashTable繼承於Dictionary

Dictionary是一個抽象類,直接繼承於Object,沒有實現任何介面,雖然Dictionary也支援新增KV,獲取V,獲取大小等基本操作,但是API函式沒有Map的多,而且Dictionary一般是通過Enumeration(列舉)去遍歷,然而由於實現了Map介面,所以也支援Iterator遍歷,

  • 2,執行緒安全不同

HashTable的幾乎所有函式都是同步的,即是支援執行緒安全,支援多執行緒

HashMap是非同步的,不是執行緒安全,如果要在多執行緒中使用HashMap,需要我們額外進行同步處理,對HashMap的同步處理可以使用Collections類提供的synchronizedMap靜態方法,或者直接使用JDK 5.0之後提供的java.util.concurrent包裡的ConcurrentHashMap類。

  • 3,對null值處理的不同

HashMap的鍵值對都可以為null(null鍵會放在table[0],而且table[0]處只會容納一個key為null的值,當有多個key為null的值插入的時候,table[0]會保留最後插入的value。)

HashTable的鍵值對都不可以為null(丟擲空指標異常)

// 將“key-value”新增到HashMap中
public V put(K key, V value) {
    // 若“key為null”,則將該鍵值對新增到table[0]中。
    if (key == null)
        return putForNullKey(value);
    // 若“key不為null”,則計算該key的雜湊值,然後將其新增到該雜湊值對應的連結串列中。
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        // 若“該key”對應的鍵值對已經存在,則用新的value取代舊的value。然後退出!
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    // 若“該key”對應的鍵值對不存在,則將“key-value”新增到table中
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

// putForNullKey()的作用是將“key為null”鍵值對新增到table[0]位置
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;
            // recordAccess()函式什麼也沒有做
            e.recordAccess(this);
            return oldValue;
        }
    }
    // 新增第1個“key為null”的元素都table中的時候,會執行到這裡。
    // 它的作用是將“設定table[0]的key為null,值為value”。
    modCount++;
    addEntry(0, null, value, 0);
    return null;
}
// 將“key-value”新增到Hashtable中
public synchronized V put(K key, V value) {
    // Hashtable中不能插入value為null的元素!!!
    if (value == null) {
        throw new NullPointerException();
    }

    // 若“Hashtable中已存在鍵為key的鍵值對”,
    // 則用“新的value”替換“舊的value”
    Entry tab[] = table;
    // Hashtable中不能插入key為null的元素!!!
    // 否則,下面的語句會丟擲異常!
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            V old = e.value;
            e.value = value;
            return old;
        }
    }

    // 若“Hashtable中不存在鍵為key的鍵值對”,
    // (01) 將“修改統計數”+1
    modCount++;
    // (02) 若“Hashtable實際容量” > “閾值”(閾值=總的容量 * 載入因子)
    //  則調整Hashtable的大小
    if (count >= threshold) {
        // Rehash the table if the threshold is exceeded
        rehash();

        tab = table;
        index = (hash & 0x7FFFFFFF) % tab.length;
    }

    // (03) 將“Hashtable中index”位置的Entry(連結串列)儲存到e中 Entry<K,V> e = tab[index];
    // (04) 建立“新的Entry節點”,並將“新的Entry”插入“Hashtable的index位置”,並設定e為“新的Entry”的下一個元素(即“新Entry”為連結串列表頭)。        
    tab[index] = new Entry<K,V>(hash, key, value, e);
    // (05) 將“Hashtable的實際容量”+1
    count++;
    return null;
}
  • 4,支援的遍歷種類不同

HashMap只支援Iterator(迭代器),HashTable支援Iterator,還支援Enumeration(列舉器)

  • 5,通過Iterator遍歷的時候,便利順序不同

HashMap是“從前到後”的遍歷陣列,再對陣列某一項的連結串列,從表頭開始遍歷

HashTable是“從後往前”遍歷陣列,,,,,,,

  • 6,容量初始值和增加方式不同

HashMap預設的容量大小是16;增加容量時,每次將容量變為“原始容量x2”
Hashtable預設的容量大小是11;增加容量時,每次將容量變為“原始容量x2 + 1”。

HashMap的載入因子是0.75,初始容量16

// 預設的初始容量是16,必須是2的冪。
static final int DEFAULT_INITIAL_CAPACITY = 16;

// 預設載入因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;

// 指定“容量大小”的建構函式
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

如果超過容量,將容量變為原始容量乘以2

// 新增Entry。將“key-value”插入指定位置,bucketIndex是位置索引。
void addEntry(int hash, K key, V value, int bucketIndex) {
    // 儲存“bucketIndex”位置的值到“e”中
    Entry<K,V> e = table[bucketIndex];
    // 設定“bucketIndex”位置的元素為“新Entry”,
    // 設定“e”為“新Entry的下一個節點”
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    // 若HashMap的實際大小 不小於 “閾值”,則調整HashMap的大小
    if (size++ >= threshold)
        resize(2 * table.length);
}

HashTable的初始容量為11,載入因子0.75

// 預設建構函式。
public Hashtable() {
     // 預設建構函式,指定的容量大小是11;載入因子是0.75
     this(11, 0.75f);
}

 超過容量,變為容量的2倍加1

// 調整Hashtable的長度,將長度變成原來的(2倍+1)
// (01) 將“舊的Entry陣列”賦值給一個臨時變數。
// (02) 建立一個“新的Entry陣列”,並賦值給“舊的Entry陣列”
// (03) 將“Hashtable”中的全部元素依次新增到“新的Entry陣列”中
protected void rehash() {
    int oldCapacity = table.length;
    Entry[] oldMap = table;

    int newCapacity = oldCapacity * 2 + 1;
    Entry[] newMap = new Entry[newCapacity];

    modCount++;
    threshold = (int)(newCapacity * loadFactor);
    table = newMap;

    for (int i = oldCapacity ; i-- > 0 ;) {
        for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
            Entry<K,V> e = old;
            old = old.next;

            int index = (e.hash & 0x7FFFFFFF) % newCapacity;
            e.next = newMap[index];
            newMap[index] = e;
        }
    }
}
  • 7,新增kv時候的雜湊演算法不同

HashMap新增元素時,是使用自定義的雜湊演算法。
Hashtable沒有自定義雜湊演算法,而直接採用的key的hashCode()。

  • 8,部分API不同

Hashtable支援contains(Object value)方法,而且重寫了toString()方法
而HashMap不支援contains(Object value)方法,沒有重寫toString()方法。

 

摘自:https://www.cnblogs.com/skywang12345/p/3311126.html