hashmap
是基於map
的鍵值對對映表,底層是通過陣列、鍊錶、紅黑樹(jdk1.8加入)來實現的。
hashmap結構
hashmap
中儲存元素,是將key
和value
封裝成了乙個node
,先以乙個node
陣列的來儲存,通過key
的hashcode
來計算hash
值,根據hash
值和hashmap
的大小確定存入元素在陣列中的位置。當hashcode
相同時,即產生了相同的陣列索引位置,那麼就會通過單向鍊錶的形式來繼續儲存。
static class nodeimplements map.entry
// 省略部分**...
}複製**
hashmap
中所有的對映都儲存在節點node
中,同時為了解決發生hash
碰撞的衝突,節點可以持有下乙個節點的引用,以形成乙個單向鍊錶。
hashmap結構圖(jdk1.7及之前)
在jdk1.8,hashmap
又做了一些改動,當陣列table
某個索引位置的上鍊表的長度大於8的話,則會將鍊錶轉化為紅黑樹。
static final class treenodeextends linkedhashmap.entry
// 省略部分**...
}複製**
同樣地,對映的key-value就儲存在treenode
中。parent
、left
、right
持有相應節點的引用形成紅黑樹。
hashmap結構圖(jdk1.8)
hashmap原始碼分析
主要屬性:
transient node table; // 陣列
transient int size; // 大小
int threshold // 擴容閾值
final float loadfactor; // 載入因子,預設值為0.75複製**
構造方法:
// 使用預設的初始容量和載入因子
public hashmap
() // 指定初始容量,使用預設的載入因子
public hashmap(int initialcapacity)
// 用現有的map來構造乙個新的hashmap
public hashmap(map<? extends k, ? extends v> m)
// 根據自定義的初始容量和載入因子來構造hashmap
public hashmap(int initialcapacity, float loadfactor) else
if (initialcapacity < default_initial_capacity)
if (loadfactor <= 0 || float.isnan(loadfactor))
throw new illegalargumentexception("illegal load factor: " +
loadfactor);
threshold = initialcapacity;
init();
}複製**
建構函式主要是設定hashmap
的初始容量,以及擴容的載入因子。hashmap(map<? extends k, ? extends v> m)
建構函式根據已有的對映來構造新的hashmap
,它同樣採用的預設的載入因子,並將m
中的元素新增到新構造的hashmap
中。
資料存放:
public void putall(map<? extends k, ? extends v> m)
final void putmapentries(map<? extends k, ? extends v> m, boolean evict)
else
if (s > threshold)
resize();
for (map.entry<? extends k, ? extends v> e : m.entryset())
}}複製**
putall
方法直接呼叫putmapentries
。putmapentries
方法中先根據已有的map
中的元素數量對新構造的hashmap
進行擴容,然後遍歷舊的map
,取出元素存放到新的hashmap
中。
// 存放key-value
public v put(k key, v value)
// 根據key的hashcode來計算hash值
static final int hash(object key)
final v putval(int hash, k key, v value, boolean onlyifabsent,
boolean evict)
// 如果鍊錶中存在與新加入的元素key相同,則直接替換掉
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;}}
v oldvalue = e.value;
if (!onlyifabsent || oldvalue == null)
e.value = value;
afternodeaccess(e);
return oldvalue;}}
++modcount;
// 新增完成後,檢查是否需要擴容
if (++size > threshold)
resize();
afternodeinsertion(evict);
return null;
}複製**
put
方法的主要邏輯:根據新增節點的hash
值計算計算它在陣列中的位置i
,判斷tab[i]
是否為空,為空則直接加入;不為空的話,需要判斷該節點的key
是否與新加入的節點的key
相同,相同的話直接替換;如果不同則需要判斷tab[i]
節點是否是紅黑樹節點,如果是紅黑樹節點,則直接加入到紅黑樹中;如果不是紅黑樹節點,那肯定就是鍊錶的第乙個節點了,遍歷鍊錶,在遍歷的過程中還需要判斷是否與鍊錶中已有節點的key
相同,如果相同,同樣直接替換掉,都不同的話就直接新增到鍊錶的末尾。並且呢,加入鍊錶後還需要判斷鍊錶的長度是否超過了閾值8,超過了的話,需要將鍊錶轉換為紅黑樹。
hashmap
在新增資料的時候,會判斷當前資料量是否超過設定的閾值,如果超過的話會進行擴容,在擴容過程中會將已新增的資料進行重新新增,以致原來新增元素的順序和位置都改變了,所以hashmap
不能保證元素的存入取出順序。
刪除資料:
// 根據key刪除資料
public v remove(object key)
// 根據key-value刪除資料
@override
public boolean remove(object key, object value)
// 刪除節點
final noderemovenode(int hash, object key, object value,
boolean matchvalue, boolean movable)
p = e;
} while ((e = e.next) != null);}}
// 匹配的刪除節點node不為null的話,刪除node
if (node != null && (!matchvalue || (v = node.value) == value ||
(value != null && value.equals(v))))
}return null;
}複製**
remove
的邏輯和加入元素的邏輯相似,依次從陣列、紅黑樹、鍊錶中找到匹配的刪除節點來刪除。
clear
方法:
public void clear
() }複製**
clear
方法要簡單些,直接遍歷陣列tab,將陣列中所有元素都置空即可。
最後對於hashmap
,我們只要知道了它的底層結構,要理解它的實現原理還是非常簡單。在jdk1.8之後,加入了紅黑樹的結構,使hashmap
的效率比之前的版本又優化了很多,關於鍊錶轉化為紅黑樹,以及紅黑樹轉鍊錶的具體實現等細節後續再做分析。
Java類集框架 HashMap原始碼分析
hashmap是基於map的鍵值對對映表,底層是通過陣列 鍊錶 紅黑樹 jdk1.8加入 來實現的。hashmap結構 hashmap中儲存元素,是將key和value封裝成了乙個node,先以乙個node陣列的來儲存,通過key的hashcode來計算hash值,根據hash值和hashmap的大...
Java 類集框架
主要方法 add 增加資料 clear 清空資料 contains 是否包含某個資料 isempty 是否為空 remove 移除某個資料 size 獲取集合中的資料個數 toarray 轉換為物件陣列 iterator 例項化父介面iterator 物件陣列使用remove 和contains 時...
java基礎 類集框架二
學習筆記 set hashset treeset 一 set簡介 set 元素是無序 存入和取出的順序不一定一致 元素不可以重複 hashset 底層資料結構是雜湊表 hashset是如何保證元素唯一性的呢?如果元素的hashcode值相同,才會判斷equals是否為true 如果元素的hashco...