HashMap原始碼分析

2021-10-01 18:58:31 字數 4798 閱讀 8842

雜湊表(hash table)也叫雜湊表,是一種非常重要的資料結構,應用場景及其豐富,許多快取技術(比如 memcached)的核心其實就是在記憶體中維護一張大的雜湊表。

在討論雜湊表之前,我們先大概了解下其他資料結構在新增,查詢等基礎操作執行效能

**陣列:**採用一段連續的儲存單元來儲存資料。對於制定下標的查詢,時間複雜度為o(1);通過給定值進行查詢,需要遍歷陣列,逐一比對給定關鍵字和陣列元素,時間複雜度為o(n),當然,對於有序陣列,則可採用二分查詢,插值查詢,斐波那契查詢等方式,可將查詢複雜度提高為o(logn);對於一般的插入刪除操作,涉及到陣列元素的移動,其平均複雜度也為o(n)

**線性鍊錶:**對於鍊錶的新增,刪除等操作(在找到指定操作位置後),僅需處理結點間的引用即可,時間複雜度為o(1),而查詢操作需要遍歷鍊錶逐一進行比對,複雜度為o(n)。

**二叉樹:**對一棵相對平衡的有序二叉樹,對其進行插入,查詢,刪除等操作,平均複雜度為o(logn)。

**雜湊表:**相比上述幾種資料結構,在雜湊表中進行新增,刪除,查詢等湊在哦,效能十分之搞,不考慮雜湊衝突的情況下,僅需一次定位即可完成,時間複雜度為o(1)

資料結構的物理儲存結構只有兩種:順序儲存結構鏈式儲存結構(棧、佇列、樹、圖等是從邏輯結構中去抽象,對映到記憶體中),雜湊表的主幹就是陣列

比如我們要新增或查詢某個元素,我們通過把當前元素的關鍵字 通過某個函式對映到陣列中的某個位置,通過陣列下標一次定位就可完成操作。這個函式可以簡單描述為:儲存位置 = f(關鍵字),這個函式f一般稱為雜湊函式,這個函式的設計好壞會直接影響到雜湊表的優劣。

雜湊衝突

然而萬事無完美,如果兩個不同的元素,通過雜湊函式得出的實際儲存位址相同怎麼辦?也就是說,當我們對某個元素進行雜湊運算,得到乙個儲存位址,然後要進行插入的時候,發現已經被其他元素占用了,其實這就是所謂的雜湊衝突,也叫雜湊碰撞。前面我們提到過,雜湊函式的設計至關重要,好的雜湊函式會盡可能地保證 計算簡單和雜湊位址分布均勻,但是,我們需要清楚的是,陣列是一塊連續的固定長度的記憶體空間,再好的雜湊函式也不能保證得到的儲存位址絕對不發生衝突。那麼雜湊衝突如何解決呢?雜湊衝突的解決方案有多種:開放定址法(發生衝突,繼續尋找下一塊未被占用的儲存位址),再雜湊函式法,鏈位址法,而hashmap即是採用了鏈位址法,也就是陣列 鍊錶的方式。

hashmap的主幹是乙個entry陣列。entry是hashmap的基本組成單元,每乙個entry包含乙個key-value鍵值對。(其實所謂map其實就是儲存了兩個物件之間的對映關係的一種集合)

entry是hashmap中的乙個靜態內部類,以下是對應的**,主要用於儲存資料節點。

/**

* basic hash bin node, used for most entries. (see below for

* treenode subclass, and in linkedhashmap for its entry subclass.)

*/static

class

node

implements

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由陣列 鍊錶組成,陣列是hashmap的主體,鍊錶則是主要為了解決雜湊衝突而存在的,如果定位到的陣列位置不含鍊錶(當前entry的next指向null),那麼查詢,新增等操作很快,僅需一次定址即可;如果定位到的陣列包含鍊錶,對於新增操作,其時間複雜度為o(n),首先遍歷鍊錶,存在即覆蓋,否則新增;對於查詢操作來講,仍需遍歷鍊錶,然後通過key物件的equals方法逐一比對查詢。所以,效能考慮,**hashmap中的鍊錶出現越少,效能才會越好。**jdk8之後,將鍊錶更改為紅黑樹(當目前位置儲存的結點大於8的時候會轉換)。

其他引數以及固定變數

//	預設的初始化容量

static

final

int default_initial_capacity =

1<<4;

// aka 16

// 最大容量

static

final

int maximum_capacity =

1<<30;

// 預設的負載因子

static

final

float default_load_factor =

0.75f

;// hash衝突之後,鍊錶儲存的結點大於這個值之後轉變為紅黑樹儲存

static

final

int treeify_threshold =8;

// 在進行擴容的時候小於此值的時候,將紅黑樹轉換為鍊錶儲存

static

final

int untreeify_threshold =6;

// 可以對容器進行treeified的最小表容量。(否則,如果乙個bin中有太多節點,就會重新調整表的大小。)至少4 * treeify_threshold,以避免調整大小和treeification閥值之間的衝突。

static

final

int min_treeify_capacity =64;

// 雜湊桶,在第一次使用的時候進行初始化,容量總是2的倍數

transient node

table;

// 包含的真實 key-value 數目

transient

int size;

// hashmap被改變的次數,由於hashmap非執行緒安全,在對hashmap進行迭代時,如果期間其他執行緒的參與導致hashmap的結構發生變化了(比如put,remove等操作),需要丟擲異常concurrentmodificationexception

transient

int modcount;

// 閾值,當table == {}時,該值為初始容量(初始容量預設為16);當table被填充了,也就是為table分配記憶體空間後,threshold一般為 capacity*loadfactory。hashmap在進行擴容時需要參考threshold。

int threshold;

// hash表的負載因子

final

float loadfactor;

hashmap的四個建構函式

//	使用給定的值進行容量和負載因子的初始化

public

hashmap

(int initialcapacity,

float loadfactor)

// 使用給定的值進行容量初始化,預設負載因子

public

hashmap

(int initialcapacity)

// 空方法,使用預設的負載因子進行構造,

public

hashmap()

public

hashmap

(map<

?extendsk,

?extends

v> m)

注意:hashmap在進行物件構造的時候,還沒有進行空間分配,只有在第一次進行put()的時候才進行的空間分配,也就是在resize()中進行的。

//	hashmap的put方法

public v put

(k key, v value)

// 實現map.put和其相關的方法,

final v putval

(int hash, k key, v value,

boolean onlyifabsent,

boolean evict)

if(e.hash == hash &&

((k = e.key)

== key ||

(key != null && key.

equals

(k))))

break

; p = e;}}

// 如果e不為空,則說明key重複,直接進行value覆蓋即可

if(e != null)

} modcount;

// 判斷是否進行擴容

HashMap原始碼分析

public hashmap int initialcapacity,float loadfactor 2 接下來是重要的put方法,put方法用於將鍵值對儲存到map中,讓我們來具體分析一下。public v put k key,v value if key null 若key為null,則將va...

HashMap 原始碼分析

1 getentry object key 方法 final entrygetentry object key return null 根據key的hash值計算出索引,得到table中的位置,然後遍歷table處的鍊錶 for entrye table indexfor hash,table.le...

HashMap原始碼分析

public v put k key,v value if key null return putfornullkey value int hash hash key int i indexfor hash,table.length for entrye table i e null e e.nex...