首先我們先看put方法:將指定 key
映射到此哈希表中的指定 value
。注意這裏鍵key和值value都不可爲空。
-
public synchronized V put(K key, V value) {
-
-
if (value == null) {
-
throw new NullPointerException();
-
}
-
-
-
-
-
-
-
-
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();
-
tab = table;
-
hash = hash(key);
-
index = (hash & 0x7FFFFFFF) % tab.length;
-
}
-
-
-
Entry<K,V> e = tab[index];
-
tab[index] = new Entry<>(hash, key, value, e);
-
-
count++;
-
return null;
-
}
put方法的整個處理流程是:計算key的hash值,根據hash值獲得key在table數組中的索引位置,然後迭代該key處的Entry鏈表(我們暫且理解爲鏈表),若該鏈表中存在一個這個的key對象,那麼就直接替換其value值即可,否則在將改key-value節點插入該index索引位置處。如下:
首先我們假設一個容量爲5的table,存在8、10、13、16、17、21。他們在table中位置如下:
然後我們插入一個數:put(16,22),key=16在table的索引位置爲1,同時在1索引位置有兩個數,程序對該“鏈表”進行迭代,發現存在一個key=16,這時要做的工作就是用newValue=22替換oldValue16,並將oldValue=16返回。
在put(33,33),key=33所在的索引位置爲3,並且在該鏈表中也沒有存在某個key=33的節點,所以就將該節點插入該鏈表的第一個位置。
在HashTabled的put方法中有兩個地方需要注意:
1、HashTable的擴容操作,在put方法中,如果需要向table[]中添加Entry元素,會首先進行容量校驗,如果容量已經達到了閥值,HashTable就會進行擴容處理rehash(),如下:
-
protected void rehash() {
-
int oldCapacity = table.length;
-
-
Entry<K,V>[] oldMap = table;
-
-
-
int newCapacity = (oldCapacity << 1) + 1;
-
if (newCapacity - MAX_ARRAY_SIZE > 0) {
-
if (oldCapacity == MAX_ARRAY_SIZE)
-
return;
-
newCapacity = MAX_ARRAY_SIZE;
-
}
-
-
-
Entry<K,V>[] newMap = new Entry[];
-
-
modCount++;
-
-
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
-
-
boolean rehash = initHashSeedAsNeeded(newCapacity);
-
-
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;
-
-
if (rehash) {
-
e.hash = hash(e.key);
-
}
-
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
-
e.next = newMap[index];
-
newMap[index] = e;
-
}
-
}
-
}
在這個rehash()方法中我們可以看到容量擴大兩倍+1,同時需要將原來HashTable中的元素一一複製到新的HashTable中,這個過程是比較消耗時間的,同時還需要重新計算hashSeed的,畢竟容量已經變了。這裏對閥值囉嗦一下:比如初始值11、加載因子默認0.75,那麼這個時候閥值threshold=8,當容器中的元素達到8時,HashTable進行一次擴容操作,容量 = 11* 2 + 1 =23,而閥值threshold=23*0.75
= 17,當容器元素再一次達到閥值時,HashTable還會進行擴容操作,一次類推。
2、在計算索引位置index時,HashTable進行了一個與運算過程(hash & 0x7FFFFFFF)下面是計算key的hash值,這裏hashSeed發揮了作用。
-
private int hash(Object k) {
-
return hashSeed ^ k.hashCode();
-
}
相對於put方法,get方法就會比較簡單,處理過程就是計算key的hash值,判斷在table數組中的索引位置,然後迭代鏈表,匹配直到找到相對應key的value,若沒有找到返回null。
-
public synchronized V get(Object key) {
-
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)) {
-
return e.value;
-
}
-
}
-
return null;
-
}