thinking in java (十八) ----- 集合之Map(HashMap HashTable)總結
Map框架圖
Map概括
- Map是鍵值對對映的抽象介面
- AbstractMap實現了Map中的大部分介面,減少了Map實現類的重複程式碼
- HashMap是基於拉鍊法實現的散列表,一般使用在單執行緒程式中
- 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