散列(Hash)是一種用於以常數時間進行查找,刪除,插入的技術
Hashtable源碼分析:
根據JDK源碼: 此類實現了一個哈希表,該表將鍵映射到相應的值,任何非null對象都可以用作鍵和值,(HashMap可以以null作爲值和鍵,並且非同步,除了這兩點 他們基本相同),正是因爲這種鍵和值一一對應的關係才保證了哈希表的查找時間花費常數時間,即主要花費在將鍵通過hash()確定散列到表的位置。
影響Hashtable的性能有初始容量和加載因子。加載因子 是對哈希表在其容量自動增加之前可以達到多滿的一個尺度(默認0.75)。初始容量和加載因子這兩個參數只是對該實現的提示。關於何時以及是否調用 rehash 方法的具體細節則依賴於該實現。
初始容量主要控制空間消耗與執行 rehash
操作所需要的時間損耗之間的平衡。如果初始容量大於 Hashtable 所包含的最大條目數除以加載因子,則永遠 不會發生rehash
操作。但是,將初始容量設置太高可能會浪費空間。
如果很多條目要存儲在一個 Hashtable
中,那麼與根據需要執行自動 rehashing 操作來增大表的容量的做法相比,使用足夠大的初始容量創建哈希表或許可以更有效地插入條目。
分析源碼:
上面的圖片就是class Hashtable的具體抽象。每個哈希表裏都有一個Entry數組,Entry是一個私有的內部靜態類
private static class Entry<K,V> implements Map.Entry<K,V> {
int hash;
final K key;
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;
}
}
每一個Entry都有一個指向下一個Entry的指針,主要作爲碰撞鏈,當加載因子大於1時就意味着必然存在碰撞,即不同的鍵匹配到了相同的位置。
接着看構造函數
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;
table = new Entry[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
initHashSeedAsNeeded(initialCapacity);
}
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
public Hashtable() {
this(11, 0.75f);
}
我們可以看到Hashtable的默認容量是11 加載因子是0.75.在看幾個重要的方法:
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = hash(key);
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;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
put()方法首先對key的hash做了一個與操作,保證其是一個正整數,通過hash(key)得到哈希值,然後得到該映射在哈希表的位置,首先看看key是否存在,存在則更新並將其返回,不存在則插入鏈表前,因爲這樣不僅方便而且還因爲常常發生這樣的事實:新近插入的元素最有可能不久又被訪問。之後進行一次判定,在擋哈希表中的總個數超過哈希表表當前容量*加載因子(就是theeshold)的時候調用rehash()方法,並進行擴建和重新確定下標。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;
}
}
}
容量*2了,並且進行內部的重組。
modCount和expectedModCount則和其他容器裏的一樣,用於拋出 ConcurrentModificationException,防止併發發生不確定行爲。