HashMap源碼分析

?????? 最近一直特別忙,好不容易閑下來了。準備把HashMap的知識總結一下,很久以前看過HashMap源碼。一直想把集合類的知識都總結一下,加深自己的基礎。我覺的java的集合類特別重要,能夠深刻理解和應用這些集合類能夠讓自己寫的程序上一步臺階。

??????? 本文主要根據自己學習與使用HashMap來解析HashMap的源碼,深入到HashMap的內部結構和實現,增強自己的基礎知識。同時會借鑒網上的相關資料,深入理解HashMap.

HashMap的內部存儲結構

??????? 一提到HashMap我們就知道鍵值對,即一個鍵對應一個值。可能我們經常會使用HashMap,但是并不關注里面的內部實現。今天我們就來學習一下HashMap的內部實現。

???????? HashMap是一種以鍵值對存儲數據的容器,每個對象在java中都會有一個hashCode,HashMap正是借每個對象的HashCode來組織鍵值對的存儲,因為HashCode的特性,使得HashMap以非常快速和高效地地根據鍵值key進行數據的存取。鍵值對的具體實現在HashMap內部會封裝成一個Entry[] table,Entry[] table是鍵值對的表現形式。下圖為HashMap的存儲結構。HashMap中Value可以相同,但是鍵不可以相同.

?????? 上圖可以看出來HashMap是數組+鏈表的存儲結構,數組的每一個元素是一個鏈表的表頭。這樣的結構能夠綜合2個經常使用的數據結構的特點,數組查找、遍歷快,鏈表增加、刪除快。

HashMap的屬性

?????? static final int DEFAULT_INITIAL_CAPACITY = 16;//默認初始化加載容量,即table數組的長度。

?????? static final int MAXIMUM_CAPACITY = 1 << 30;//最大的容量。1左移30位,2的30次方:1073741824

?????? static final float DEFAULT_LOAD_FACTOR = 0.75f;//默認加載因子為0.75

?????? transient Entry[] table;//table數組,上圖的黃色部分。Entry為藍色部分

?????? transient int size;//HashMap存儲元素的數量。

?????? int threshold;//閥值? table的長度*加載因子(默認wei0.75)

?????? final float loadFactor;//加載因子

?????? transient volatile int modCount;//修改次數

?????? 注:如果用transient聲明一個實例變量,當對象存儲時,它的值不需要維持。換句話來說就? 是,用transient關鍵字標記的成員變量不參與序列化過程。

????????????? volatile為同步變量,保證每次都讀取的值是最新的。

HashMap的構造方法

??????? public HashMap() {//最常用的改造方法

????????????? this.loadFactor = DEFAULT_LOAD_FACTOR;//默認加載因子,0.75

????????????? threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);//閥值??? 默認的容量*加載英子。12

????????????? table = new Entry[DEFAULT_INITIAL_CAPACITY];//table數組的默認為16,容量為16,切記容量不等于HashMap存儲的元素數量,容易混淆。

????????????? init();//初始化方法,里面是個空

????????? }

??????? 默認的構造方法開辟16個大小的空間。還有另外一個構造方法我們使用的比較少,當你覺的默認的HashMap的存儲空間浪費時(容量達到3/4時HashMap就會調用resize擴大為原來的2倍),可以使用下面一個。

?????? public HashMap(int initialCapacity, float loadFactor) {//初始化容量,加載因子

?????????????? if (initialCapacity < 0)//容量不能小于0

????????????????????? throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);

????????????? if (initialCapacity > MAXIMUM_CAPACITY)//大于允許最大的容量時,設置未最大值

????????????????????? initialCapacity = MAXIMUM_CAPACITY;

???????????? if (loadFactor <= 0 || Float.isNaN(loadFactor))//加載因子小于大于0或者不是數字的時候,報非法加載因子異常

???????????? throw new IllegalArgumentException("Illegal load factor: " +

???????????? loadFactor);

???????????? // Find a power of 2 >= initialCapacity

??????????? int capacity = 1;

??????????? while (capacity < initialCapacity)//實際的開辟的空間要大于傳入的第一個參數的值。

??????????? capacity <<= 1;//這是一個重點,capacity才是容量。

??????????? this.loadFactor = loadFactor;//加載因子設置為傳入的值

??????????? threshold = (int)(capacity * loadFactor);//閾值

?????????? table = new Entry[capacity];//數組

?????????? init();

??????? }

???????? 此外還有2個構造方法基本上很少用到,感興趣的可以自己看看。HashMap最重要的方法是put和get方法,下面我們重點看看這2個方法。

HashMap的put方法分析

???????? public V put(K key, V value) {//很熟悉的方法

????????????? if (key == null)//如果key==null,直接設置空的

???????????????????? return putForNullKey(value);

????????????? int hash = hash(key.hashCode());//獲得hash值

????????????? int i = indexFor(hash, table.length);//根據hash值找到此鍵值對應該放在數組的第幾個位置。

????????????? for (Entry 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;

???? }

????????? put方法會先判斷鍵是不是空,如果為空就調putForNullKey方法。方法如下:

????????? private V putForNullKey(V value) {

??????????????? for (Entry e = table[0]; e != null; e = e.next) {//獲得第一個鏈表,遍歷查找是否原來就存儲為null的鍵值對

???????????????? if (e.key == null) {//如果存在

?????????????????????? V oldValue = e.value;//記錄下老的值

?????????????????????? e.value = value;//把新值設置進去

?????????????????????? e.recordAccess(this);

?????????????????????? return oldValue;//返回老值

?????????????????? }

?????????????? }

???????????????? modCount++;

??????????????? addEntry(0, null, value, 0);//Key?為null,則將Entry放置到第一桶table[0]中

??????????????? return null;

???????? }

????????? 計算Hash值的方法如下:

?????????? static int hash(int h) {//根據特定的hashcode?重新計算hash值

????????????????????? h ^= (h >>> 20) ^ (h >>> 12);

?????????????????????? return h ^ (h >>> 7) ^ (h >>> 4);

????????????? }

??????????? static int indexFor(int h, int length) {//匹配到具體的桶當中,

?????????????????????? return h & (length-1);//相當于int i = hash %Entry[].length;得到i后,就是在Entry數組中的位置

???????????? }

?????? 整個put方法的流程如下:

??????? 首先判斷鍵是否為空,如果為空在判斷是否已經存在鍵為空的鍵值對,存在就更新值,不存在就新增;

??????? 如果鍵不為空,則獲取這個Key的hashcode值,根據此值確定鍵值對的存儲位置;遍歷所在桶中的Entry鏈表,查找其中是否已經有了以Key值為Key存儲的Entry對象,若已存在,定位到對應的Entry,其中的Value值更新為新的Value值;返回舊值;若不存在,則根據鍵值對 創建一個新的Entry對象,然后添加到這個桶的Entry鏈表的頭部。當前的HashMap的大小(即Entry節點的數目)是否超過了閥值,若超過了閥值(threshold),則增大HashMap的容量(即Entry[] table 的大小),并且重新組織內部各個Entry排列。

HashMap的get方法分析

???????? public V get(Object key) {

????????????? if (key == null)//如果鍵等于null,調用getForNullKey

???????????????????? return getForNullKey();

???????????? int hash = hash(key.hashCode());//獲得hash值,

???????????? for (Entry e = table[indexFor(hash, table.length)];

??????????????????? e != null;

???????????? e = e.next) {//indexfor計算存儲位置,根據位置遍歷鏈表

??????????????????? Object k;

??????????? if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

??????????????????? return e.value;//拿到對應的值

???????????? }

?????????????????? return null;

?????? }

???????? 如果鍵等于null,調用getForNullKey

???????? private V getForNullKey() {

???????????????? for (Entry e = table[0]; e != null; e = e.next) {//鍵為null的鍵值對存儲在數組的第一個元素,

???????????????? if (e.key == null)//如果存在則返回值

???????????????????????? return e.value;

??????????? }

??????????????????????? return null;//不存在返回null

}

?????? get方法比較簡單,比較容易理解。主要流程如下:

??????? 首先判斷鍵是否為空,如果為空在table[0]中取鍵為空的鍵值對,如果不存在為空的則返回null。

??????? 如果鍵不為空,則獲取這個Key的hashcode值,根據此值確定該鍵值對的存儲位置;遍歷所在桶中的Entry鏈表,查找其中是否已經有了以Key值為Key存儲的Entry對象,若已存在,定位到對應的Entry,獲得Value值;返回舊值;若不存在,則返回空。

HashMap的總結

??????? 本文主要講了HashMap的存儲結構以及基本屬性、構造方法,同時分析了比較常用的put和get方法。其他方法請讀者自行查看。在看HashMap的源碼時,我認為以下幾個方面比較重要,能夠理解到以下的幾點,搞定HashMap基本不成問題。

???????? HashMap的存儲結構,以及為什么要這樣進行存儲。

???????? HashMap的各個屬性的含義,以及其擴容的策略(擴容算法)。

???????? Hash值的計算.確定桶的位置。

???????? HashMap中的value可以相同,鍵不可以相同。

???????? HashMap是非線程安全的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • HashMap 是 Java 面試必考的知識點,面試官從這個小知識點就可以了解我們對 Java 基礎的掌握程度。網...
    野狗子嗷嗷嗷閱讀 6,686評論 9 107
  • 一、基本數據類型 注釋 單行注釋:// 區域注釋:/* */ 文檔注釋:/** */ 數值 對于byte類型而言...
    龍貓小爺閱讀 4,288評論 0 16
  • HashMap<K,V> Entry<K,V> table = new Entry<K,V>(expectedNu...
    _挑燈看劍_閱讀 232評論 0 0
  • 5.1、對于HashMap需要掌握以下幾點 Map的創建:HashMap() 往Map中添加鍵值對:即put(Ob...
    rochuan閱讀 694評論 0 0
  • 荒蕪中,出現一幢略顯破舊的小樓,復古,精致,在正中上方,圓圓的一個“旅”字。 別無去處,那就進去吧。 小旅館的樣子...
    凌空于夢閱讀 434評論 0 2