abstractmap 中的內部類
hashmap 是乙個採用雜湊表實現的鍵值對集合,繼承自 abstractmap,實現了 map 介面。hashmap 的特殊儲存結構使得在獲取指定元素前需要經過雜湊運算,得到目標元素在雜湊表中的位置,然後再進行少量比較即可得到元素,這使得 hashmap 的查詢效率極高。
當發生雜湊衝突(碰撞)的時候,hashmap 採用拉鍊法進行解決,因此 hashmap 的底層實現是陣列+鍊錶
除了不允許 null 並且同步,hashtable 幾乎和他一樣預設初始容量:16,必須是 2 的整數次方
static final int default_initial_capacity = 1 << 4;
預設載入因子的大小:0.75,可不是隨便的,結合時間和空間效率考慮得到的
static final float default_load_factor = 0.75f;
最大容量: 2^ 30 次方
static final int maximum_capacity = 1 << 30;
雜湊表的載入因子
final float loadfactor;
當前 hashmap 修改的次數,這個變數用來保證 fail-fast 機制
transient int modcount;
閾值,下次需要擴容時的值,等於 容量*載入因子
int threshold;
樹形閾值:jdk 1.8 新增的,當使用 樹 而不是列表來作為桶時使用。必須必 2 大
static final int treeify_threshold = 8;
非樹形閾值:也是 1.8 新增的,擴容時**乙個樹形桶的閾值,要比 treeify_threshold 小
static final int untreeify_threshold = 6;
樹形最小容量:桶可能是樹的雜湊表的最小容量。至少是 treeify_threshold 的 4 倍,這樣能避免擴容時的衝突
static final int min_treeify_capacity = 64;
快取的 鍵值對集合(另外兩個檢視:keyset 和 values 是在 abstractmap 中宣告的)
transient set> entryset;
雜湊表中的鍊錶陣列
transient node table;
鍵值對的數量
final float loadfactor;
由於 hashmap 擴容開銷很大(需要建立新陣列、重新雜湊、分配等等),因此與擴容相關的兩個因素
以上兩個因素成為了 hashmap 最重要的部分之一,它們決定了 hashmap 什麼時候擴容。
hashmap 的預設載入因子為 0.75,這是在時間、空間兩方面均衡考慮下的結果:
- 載入因子太大的話發生衝突的可能就會大,查詢的效率反而變低
- 太小的話頻繁 rehash,導致效能降低
transient node table;
node 實現static class nodeimplements map.entry
public
final k getkey()
public
final v getvalue()
public
final string tostring()
public
final
inthashcode()
public
final v setvalue(v newvalue)
public
final
boolean
equals(object o)
return
false;
}}
hashmap 中的新增方法: put()//新增指定的鍵值對到 map 中,如果已經存在,就替換
public v put(k key, v value)
final v putval(int hash, k key, v value, boolean onlyifabsent,
boolean evict)
//如果找到要替換的節點,就停止,此時 e 已經指向要被替換的節點
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;}}
//存在要替換的節點
if (e != null)
}++modcount;
//如果超出閾值,就得擴容
if (++size > threshold)
resize();
afternodeinsertion(evict);
return
null;
}
插入邏輯
先呼叫 hash() 方法計算雜湊值
然後呼叫 putval() 方法中根據雜湊值進行相關操作
如果當前雜湊表內容為空,新建乙個雜湊表
如果要插入的桶中沒有元素,新建個節點並放進去
否則從桶中第乙個元素開始查詢雜湊值對應位置
如果桶中第乙個元素的雜湊值和要新增的一樣,替換,結束查詢
如果第乙個元素不一樣,而且當前採用的還是 jdk 8 以後的樹形節點,呼叫 puttreeval() 進行插入
否則還是從傳統的鍊錶陣列中查詢、替換,結束查詢
當這個桶內鍊錶個數大於等於 8,就要呼叫 treeifybin() 方法進行樹形化
最後檢查是否需要擴容
關鍵方法
hashmap 中的雜湊函式: hash()
hashmap 中通過將傳入鍵的 hashcode 進行無符號右移 16 位,然後進行按位異或,得到這個鍵的雜湊值。
static
final
int hash(object key)
hashmap 中的初始化/擴容方法: resize()
每次新增時會比較當前元素個數和閾值:
if (++size > threshold)
resize();
tips
hashmap 中的獲取方法: get()
時間複雜度一般跟鍊錶長度有關,因此雜湊演算法越好,元素分布越均勻,get() 方法就越快,不然遍歷一條長鍊表,太慢了。
不過在 jdk 1.8 以後 hashmap 新增了紅黑樹節點,優化這種極端情況下的效能問題。
當多執行緒併發訪問乙個雜湊表時,需要在外部進行同步操作,否則會引發資料不同步問題。你可以選擇加鎖,也可以考慮用 collections.synchronizedmap 包一層,變成個執行緒安全的 map
// 最好在初始化時就這麼做。
map m = collections.synchronizedmap(new hashmap(...));
當 hashmap 中有大量的元素都存放到同乙個桶中時,這時候雜湊表裡只有乙個桶,這個桶下有一條長長的鍊錶,這個時候 hashmap 就相當於乙個單鏈表,假如單鏈表有 n 個元素,遍歷的時間複雜度就是 o(n),完全失去了它的優勢。針對這種情況,jdk 1.8 中引用了 紅黑樹(時間複雜度為 o(logn)) 優化這個問題。
hashmap 的新增、獲取時需要通過 key 的 hashcode() 進行 hash(),然後計算下標 ( n-1 & hash),從而獲得要找的同的位置。當發生衝突(碰撞)時,利用 key.equals() 方法去鍊錶或樹中去查詢對應的節點。
在 jdk 1.8 的實現中,是通過 hashcode() 的高16位異或低16位實現的:(h = k.hashcode()) ^ (h >>> 16)
主要是從速度、功效、質量 來考慮的,這麼做可以在桶的 n 比較小的時候,保證高低 bit 都參與到 hash 的計算中,同時位運算不會有太大的開銷。
JAVA 集合類的認識(2) Map 介面
keysetkeyset 是乙個 map 中鍵 key 的集合,以 set 的形式儲存,不允許重複,因此鍵儲存的物件需要重寫 equals 和 hashcode 方法。可以通過 map.keyset 方法獲得。set set map.keyset for object key set valuesv...
java的集合類
由collection介面派生的兩個介面是list和set set set介面同樣是collection介面的乙個子介面,它表示數學意義上的集合概念。set中不包含重複的元素,即set中不存兩個這樣的元素e1和e2,使得e1.equals e2 為true。由於set介面提供的資料結構是數學意義上集...
Java集合類的總結
集合類庫考慮到 容納自己物件 的問題,並將其分割成兩個明確的概念 1 集合 collection 一組單獨的元素。乙個list 列表 必須按特定的順序容納元素,而乙個set 集 不可包含任何重複的元素。2 對映 map 一系列 鍵 值 對,如雜湊表身上的充分體現。map可以和陣列一樣,擴充到多維,可...