JAVA 集合類的認識(3) HashMap

2021-08-21 13:12:05 字數 4462 閱讀 3759

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可以和陣列一樣,擴充到多維,可...