HashMap中put()方法實現原理
突然想解剖HashMap實現原理,Map連結串列的作者原始碼如何實現?也可以豐富一下自己的程式設計思想,也想讓讀者看見如何觀看別人原始碼的思路和方法。所以心血來潮的我,就來解析HashMap底層原理!
送給讀者的話:一個合格的程式設計師一定要學會觀看別人程式碼。這樣子自己的開發也會很多思路和方法。希望國內軟體開發越來越好。
首先看類
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
.....
}
可以看出類繼承了AbstractMap抽象泛型類,實現了Map泛型介面和Cloneable、Serializable介面
AbstractMap抽象泛型類
public abstract class AbstractMap<K,V> implements Map<K,V> {
....
}
看出AbstractMap抽象類實現了Map泛型介面
注意:如果抽象類繼承了泛型介面,要麼改寫抽象類為抽象泛型類,要麼刪除泛型介面中的泛型定義
接著看Map泛型介面
import java.util.Collection;
import java.util .Set;
public interface Map<K,V> {
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
V remove(Object key);
void putAll(Map<? extends K, ? extends V> m);
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);
int hashCode();
}
可以看出Map泛型介面定義了一些連結串列的操作,在作者的註釋中看見了這樣子一句話which was a totally abstract class rather than an interface.
(是一個完全抽象的類,而不是介面)那麼可以理解作者用這個Map介面為開發連結串列做了建模,並且在介面中抽象了Entry泛型實體容器,Entry容器用來儲存值,將所有的要存入Map連結串列中的值都看成一個Entry,在抽象類AbstractMap中實現了操作連結串列的方法,只是初步形成抽象連結串列Map,並不是實際Map。
AbstractMap抽象類中put方法(之後都以解析put方法為例)
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
在作者對put方法只是聲明瞭一個UnsupportedOperationException異常,作者對異常解釋:@throws UnsupportedOperationException if the <tt>put</tt> operation is not supported by this map
(如果put方法的操作不受這個Map的支援)那麼就會丟擲該異常。
這裡可以看出一個註釋是一個良好的程式設計習慣。
然後最關鍵的HashMap類
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
......
}
參照JDK官方文件說明:
- Cloneable類:
一個類實現Cloneable介面,以指示Object.clone()方法,該方法對於該類的例項進行現場複製是合法的。
在不實現Cloneable介面的例項上呼叫物件的克隆方法導致丟擲異常CloneNotSupportedException 。
按照慣例,實現此介面的類應使用公共方法覆蓋Object.clone (受保護)。 有關覆蓋此方法的詳細資訊,請參閱Object.clone() 。
注意,此介面不包含clone方法。 因此,只能通過實現該介面的事實來克隆物件是不可能的。 即使克隆方法被反射地呼叫,也不能保證它成功。
- Serializable類:
類的序列化由實現java.io.Serializable介面的類啟用。 不實現此介面的類將不會使任何狀態序列化或反序列化。 可序列化類的所有子型別都是可序列化的。 序列化介面沒有方法或欄位,僅用於標識可序列化的語義。
為了允許序列化不可序列化的子型別,子型別可能承擔儲存和恢復超型別的公共,受保護和(如果可訪問)包欄位的狀態的責任。 子型別可以承擔此責任,只有當它擴充套件的類具有可訪問的無引數建構函式來初始化類的狀態。 如果不是這樣,宣告一個類Serializable是一個錯誤。 錯誤將在執行時檢測到。
在反序列化期間,非可序列化類的欄位將使用該類的public或protected no-arg建構函式進行初始化。 對於可序列化的子類,必須可以訪問no-arg建構函式。 可序列化子類的欄位將從流中恢復。
在序列化和反序列化過程中需要特殊處理的類必須採用精確簽名的特殊方法。
官方文件中可以看出Map連結串列的作者使用Cloneable重寫裡面的方法,使其容器中的Key與傳輸過來的Key相等並且找到連結串列中的value。
序列化:
當兩個程序在進行遠端通訊時,彼此可以傳送各種型別的資料。無論是何種型別的資料,都會以二進位制序列的形式在網路上傳送。傳送方需要把這個物件轉換為位元組序列,才能在網路上傳送;接收方則需要把位元組序列再恢復為物件。
把物件轉換為位元組序列的過程稱為物件的序列化。
把位元組序列恢復為物件的過程稱為物件的反序列化。
AbstractMap中put方法
...
static final Entry<?,?>[] EMPTY_TABLE = {};
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
...
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
定義了一個常量Entry列舉型別的泛型陣列(HashMap容器 )。
所有的儲存和獲取key/value都是在EMPTY_TABLE中獲取
static final Entry<?,?>[] EMPTY_TABLE = {};
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
進入put方法,執行了一次判斷:如果還未初始化,則進行一次初始化。
...
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
...
看下列程式碼可以看出,如果key的值為空,則把把value值放到第一個位置
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
將Key放入第一個位置方法
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;
e.recordAccess(this);
return oldValue;
}
}
如果Key不為空,將Key轉換為hash值,然後根據上一步的hashcode值,以及陣列的長度,確定出該key所處的下標值,for迴圈遍歷Entry陣列。首先會判斷這個數組裡面所有的元素是的hash和key的地址是否一樣,如果hash和key地址一樣,那麼再去判斷key值是否一樣,如果key值也一樣,則會覆蓋原先key的value。
/**
* This method is invoked whenever the value in an entry is
* overwritten by an invocation of put(k,v) for a key k that's already
* in the HashMap.
*/
void recordAccess(HashMap<K,V> m) {
}
This method is invoked whenever the value in an entry is overwritten by an invocation of put(k,v) for a key k that's already in the HashMap.
翻譯:每當條目中的值被put(k,v)的呼叫覆蓋到HashMap中的鍵k時,就會呼叫該方法。
如果不一樣,則在Entry陣列中插入一個連結串列。此方法就是在Entry容器中新增一個連結串列
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}