爲了更好的理解JDK的哈希表實現,首先研究Map接口和AbstractMap抽象類,打好基礎很重要~
Map接口
先來研究下Map接口,附上了註釋的Map接口定義如下:
/**
Map接口是鍵、值對接口,Map中的鍵是唯一的
*/
public interface Map<K,V> {
/**
Map的鍵值對數量,如果鍵值對數量大於Integer.MAX_VALUE,該方法將會返回Integer.Max_VALUE
*/
int size();
/**
該Map是否存在鍵值對
*/
boolean isEmpty();
/**
該Map是否包含鍵key,當該Map存在鍵k,該方法返回值爲:
key == null ? k == null : key.equals(k);
*/
boolean containsKey(Object key);
/**
該Map是否存在鍵值對的值爲value,當該Map存在值爲v,該方法返回值爲:
value == null ? v == null : value.equals(v);
*/
boolean containsValue(Object value);
/**
返回該Map中鍵爲key的值,若不存在,返回null
*/
V get(Object key);
/**
將鍵值對k、value加入到Map中,若已經存在鍵key,則將新的value值關聯到鍵key
*/
V put(K key, V value);
/**
刪除鍵key對應的鍵值對,返回刪除之前key關聯的value,若不存在key,返回null
*/
V remove(Object key);
/**
將m的鍵值對加入到本Map中
*/
void putAll(Map<? extends K, ? extends V> m);
/**
刪除本Map中所有鍵值對
*/
void clear();
/**
返回鍵的集合
*/
Set<K> keySet();
/**
返回值的集合
*/
Collection<V> values();
/**
鍵值對的集合
*/
Set<Map.Entry<K, V>> entrySet();
/**
鍵值對
*/
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
}
boolean equals(Object o);
/**
該Map的哈希碼
*/
int hashCode();
}
AbstractMap抽象類
AbstractMap抽象類實現了Map接口,它提供了一個實現Map接口的框架,簡化了子類實現Map接口所需要做的很多通用的功能,子類可以繼承該抽象類來決定具體怎麼實現鍵值對的映射,該抽象類定義如下(只列出方法的聲明,具體實現下面還要詳細介紹):
public abstract class AbstractMap<K,V> implements Map<K,V> {
//鍵的集合
transient volatile Set<K> keySet = null;
//值的集合
transient volatile Collection<V> values = null;
protected AbstractMap() {
}
public int size() {
}
public boolean isEmpty() {
}
public boolean containsValue(Object value) {
}
public boolean containsKey(Object key) {
}
public V get(Object key) {
}
public V put(K key, V value) {
}
public V remove(Object key) {
}
public void putAll(Map<? extends K, ? extends V> m) {
}
public void clear() {
}
public Set<K> keySet() {
}
public Collection<V> values() {
}
public abstract Set<Entry<K,V>> entrySet();
public boolean equals(Object o) {
}
public int hashCode() {
}
public String toString() {
}
protected Object clone() throws CloneNotSupportedException {
}
private static boolean eq(Object o1, Object o2) {
}
public static class SimpleEntry<K,V>
implements Entry<K,V>, java.io.Serializable
{
}
public static class SimpleImmutableEntry<K,V>
implements Entry<K,V>, java.io.Serializable
{
}
}
觀察AbstractMap的結構,除了entrySet()這個方法定義爲抽象的,Map接口中的其它方法都已經給出實現。
另外,該抽象類包含了兩個靜態內部類:SimpleEntry和SimpleImmutableEntry。
該抽象類有兩個字段:keySet和values,分別存儲鍵集合和值集合。這兩個字段爲何是transient修飾的?這是因爲子類會決定如何去序列化,例如HashMap就實現了writeObject方法和readObject方法。
下面我們對每一個方法進行解釋:
size方法
size方法的源碼實現如下:
public int size() {
return entrySet().size();
}
該方法調用了entrySet方法,entrySet方法返回的是鍵值對的集合,該方法也是AbstractMap唯一沒有實現的方法(定義爲抽象的)。
isEmpty方法
該方法的實現非常簡單,通過調用size方法,確定鍵值對的數量是否爲0即可,源碼實現如下:
public boolean isEmpty() {
return size() == 0;
}
containsValue方法
該方法的源碼實現如下:
public boolean containsValue(Object value) {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (value==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getValue()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (value.equals(e.getValue()))
return true;
}
}
return false;
}
該方法實現的基本思想是首先取鍵值對集合的迭代器,然後根據value值是否爲null分別處理。
- 如果value爲空,迭代器每次迭代的時候只要判斷當前鍵值對的值是否爲null即可,如果爲null表示該Map包含值爲null的鍵值對,返回true,否則繼續遍歷剩下的鍵值對。
- 如果value非空,迭代器每次迭代的時候通過調用value的equals方法判斷是否和當前鍵值對的值相等,如果相等表示該Map包含值爲value的鍵值對,返回true,否則繼續遍歷剩下的鍵值對。
containsKey方法
該方法的源碼實現如下:
public boolean containsKey(Object key) {
Iterator<Map.Entry<K,V>> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return true;
}
}
return false;
}
該方法的實現與containsValue的實現基本相同,不再說明。
由containsValue和containsKey兩個方法可知,AbstractMap的實現是支持鍵和值爲null的場景,這點需要注意。
get方法
該方法的源碼實現如下:
public V get(Object key) {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return e.getValue();
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return e.getValue();
}
}
return null;
}
該方法的實現和containsKey和containsValue方法的實現基本相同,不再說明。
put方法
AbstractMap對該方法的實現僅是拋出異常,子類需要去實現。因爲具體的實現方法是由子類來決定的,因此這裏拋出異常是合理的。源碼如下:
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
remove方法
remove方法根據鍵key查找鍵值對,然後調用迭代器的remove方法刪除該項。
public V remove(Object key) {
//鍵值對的迭代器
Iterator<Entry<K,V>> i = entrySet().iterator();
//correctEntry代表最終找到的那個鍵值對,若沒有找到則爲null
Entry<K,V> correctEntry = null;
if (key==null) {
//key爲null的情況
while (correctEntry==null && i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
//找到了鍵爲key的鍵值對,while循環將結束
correctEntry = e;
}
} else {
//key不爲null的情況
while (correctEntry==null && i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
correctEntry = e;
}
}
//oldValue表示的是該方法的返回值,即將要刪除的鍵值對的值
V oldValue = null;
if (correctEntry !=null) {
oldValue = correctEntry.getValue();
//調用迭代器的remove方法刪除
i.remove();
}
return oldValue;
}
putAll方法
putAll方法通過調用put方法將m中的所有鍵值對加入到本map
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
clear方法
clear方法首先通過entrySet方法得到鍵值對的set集合,然後通過set集合的clear方法清空本map
public void clear() {
//其實是通過調用鍵值對set的clear方法清空的
entrySet().clear();
}
keySet方法
該方法獲得鍵的set,源碼如下:
public Set<K> keySet() {
if (keySet == null) {
//若成員變量爲空將會創建一個
keySet = new AbstractSet<K>() {
//實現AbstractSet的迭代器方法
public Iterator<K> iterator() {
//創建一個迭代器
return new Iterator<K>() {
//通過外部類的entrySet方法獲得鍵值對的迭代器
private Iterator<Entry<K,V>> i = entrySet().iterator();
public boolean hasNext() {
return i.hasNext();
}
public K next() {
return i.next().getKey();
}
public void remove() {
i.remove();
}
};
}
//實現AbstractSet的size方法
public int size() {
//其實是調用外部類的size方法
return AbstractMap.this.size();
}
//實現AbstractSet的isEmpty方法
public boolean isEmpty() {
//調用外部類的isEmpty方法
return AbstractMap.this.isEmpty();
}
//實現AbstractSet的clear方法
public void clear() {
//調用外部類的clear方法
AbstractMap.this.clear();
}
//實現AbstractSet的conains方法
public boolean contains(Object k) {
//通過外部類的containsKey實現
return AbstractMap.this.containsKey(k);
}
};
}
return keySet;
}
該方法首先判斷成員變量keySet是否爲空,若不爲空,直接返回,否則將初始化一個keySet。
可以看到,最終其實是根據鍵值對的迭代器實現的。
values方法
該方法返回值的集合,實現方式和keySet類似,只不過這裏返回的是Collection,而keySet返回的是Set,源碼如下:
public Collection<V> values() {
if (values == null) {
//若成員變量values爲空,則根據AbstractCollection創建一個
values = new AbstractCollection<V>() {
//實現AbstractCollection的迭代器方法
public Iterator<V> iterator() {
//新建一個迭代器
return new Iterator<V>() {
//通過外部類的entrySet方法獲得鍵值對的迭代器
private Iterator<Entry<K,V>> i = entrySet().iterator();
public boolean hasNext() {
return i.hasNext();
}
public V next() {
return i.next().getValue();
}
public void remove() {
i.remove();
}
};
}
//實現AbstractCollection的size方法
public int size() {
//通過調用外部類的size方法實現
return AbstractMap.this.size();
}
//實現AbstractCollection的isEmpty方法
public boolean isEmpty() {
//通過調用外部類的isEmpty方法實現
return AbstractMap.this.isEmpty();
}
//實現AbstractColleciton的clear方法
public void clear() {
//通過調用外部類的clear方法實現
AbstractMap.this.clear();
}
//實現AbstractColleciton的contains方法
public boolean contains(Object v) {
//通過調用外部類的containsValue方法實現
return AbstractMap.this.containsValue(v);
}
};
}
return values;
}
ketSet方法和values方法最終都是通過AbstractMap的entrySet方法實現的,通過entrySet方法獲得鍵值對的迭代器。
equals方法
該方法判斷兩個AbstractMap是否相同,通過比較每一個鍵值對來確認兩個AbstractMap是否相等,只有所有的鍵值對的鍵、值都相等,兩個AbstractMap才相等,源碼實現如下:
public boolean equals(Object o) {
if (o == this)
//若o是對象本身,返回true
return true;
if (!(o instanceof Map))
//如果o不是一種Map,返回false
return false;
Map<K,V> m = (Map<K,V>) o;
if (m.size() != size())
//兩個AbstractMap的長度不相等,肯定不相同,返回false
return false;
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
//迭代本AbstractMap
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
//當前鍵值對的值爲null
//如果m存在鍵爲key且對應的值不爲空的鍵值對,或者m沒有鍵爲key的鍵值對,說明這兩個Map不相等
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
//當前鍵值對的值不爲空,通過比較該值和另一個AbstractMap的鍵爲key對應的值是否相等即可
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
//所有判斷都通過,說明兩個AbstractMap相等
return true;
}
hashCode方法
該方法計算該AbstractMap對象的哈希碼,源碼實現如下:
public int hashCode() {
int h = 0;
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext())
//將鍵值對哈希碼加起來
h += i.next().hashCode();
return h;
}
其實是將每個鍵值對的哈希碼加起來實現的。
toString方法
該方法實現比較簡單,不再說明
clone方法
該方法克隆一個本對象的拷貝,字段keySet和values都設置爲空,源碼實現如下:
protected Object clone() throws CloneNotSupportedException {
//首先調用父類的clone方法
AbstractMap<K,V> result = (AbstractMap<K,V>)super.clone();
//字段都設置爲空
result.keySet = null;
result.values = null;
return result;
}
該方法protected修飾,希望子類能夠重寫該方法。
以上介紹了AbstractMap的主要方法,該抽象類還定義了兩個靜態內部類: SimpleEntry和SimpleImmutableEntry類,這兩個類在EnumMap、IdentityHashMap和WeakHashMap使用的,以後研究這幾個類時再來說明這兩個靜態內部類。
參考
- jdk源碼