JDK7HashMap的存儲結構以及時間複雜度

我們知道HashMap是基於Hash表來設計的,他的底層是數組和鏈表的結合體,那麼HashMap的最大的特點就是快,因爲是由鍵找值。
(1)什麼是HashMap以及HashMap的構成
HashMap是基於哈希表的Map接口實現,用來存儲鍵值對,線程不安全因此也很快,
允許null值null鍵。HashMap實際上是一個數組和鏈表的結合體。
鍵值對Map.Entry存放在鏈表裏面,數組裏面存放的是鏈表的節點。
如下圖:
在這裏插入圖片描述
如果感覺這張圖不給力再看看這張:
在這裏插入圖片描述

(2)HashMap的基本存儲原理以及存儲內容的組成
基本原理,首先有一個下標範圍較大的數組來存儲元素,然後通過hashCode()方法來獲得關鍵字的哈希值,這個哈希值實際上就是數組的數組的下標。數組存儲的元素是一個Entry類,有3個數據域,key,value,next,next指針指向下一個Entry.例如,
第一個鍵值對A進來,通過hashCode()計算其key的hash得到index=0,那麼Entry[0]=A, 第二個鍵值對B的index也等於0,那麼B.next=A,Entry[0]=B;如果第三個鍵值對C的index=0,那麼C.next=B,Entry[0]=C.對於不同的元素可能計算出了相同的函數值,這樣就產生了衝突,所以解決衝突的方法實際上是“鏈地址法”。
(3)HashMap的存取過程
HashMap使用put方法來存儲對象,使用get方法來獲取對象。使用put方法時,先用hashCode()計算出bucket位置,然後看該位置有沒有Entry,如果沒有,直接把該Entry存儲在這個位置,如果已經有Entry存在,說明出現衝突了,先將這個位置上的鏈表鏈接上,然後將該Entry存儲到該位置。
獲取鍵值對時,也要先使用hashCode計算出鍵值對的bucket位置,如果該位置爲空,說明沒找到,否則,因爲鏈表中存放的鍵值對,所以調用equals()來與元素關鍵字比較去找正確的Entry。
(4)HashMap大小存在的問題
在多線程的環境下,存在條件競爭的問題,因爲兩個線程都發現HashMap要調整大小了,他們會同時試着調整大小,有可能會出現死鎖。在調整大小的過程中,存儲在LinkedList中的元素的次序會反過來。但是爲什麼要在多線程中使用HashMap呢,HashMap是線程不安全的。HashTable是線程安全的,但是ConcurrentHashMap同步性能更好,因爲它僅僅因爲同步級別對map的一部分進行上鎖。
(5)HashMap的時間複雜度
說了一大堆,還有一個問題沒有說,就是HashMap的時間複雜度。
不用說,存數據的時間複雜度是O(1),因爲只需要首先根據Key計算哈希值,其實就是數組的下標,找到存放的位置,然後把key-value鏈表節點鏈接上去就OK了。
取數據的時間複雜度最好是O(1),最差是O(n),爲什麼呢?因爲如果根據key首先得到哈希地址,發現該下標處位置已經被佔用,那麼我們要找的節點到底在鏈表的哪個位置,如果該位置就只有一個節點或者要找的節點就在表頭,那麼就不用再往後遍歷鏈表了,所以最好情況下時間複雜度是O(1);如果要找到的節點在鏈表表尾,那麼就需要一個一個遍歷鏈表,所以此時最差情況下爲O(n)。但是如果我們的鏈表長度都很短,那麼時間效率就會很高,其實即使是O(n)這個級別的複雜度正常也還是可以接受的。所以來總結一下,map的put方法的時間複雜度爲O(1),get方法的時間複雜度爲O(1)~ O(n)。
那麼containKey()方法的時間複雜度呢,其實和get方法的時間複雜度是一樣的,也是O(1)~O(n),首先我們也是要根據key計算出對應的哈希地址,如果該地址處沒有佔用,那麼可以直接返回false,說明Map中沒有這個key;如果已經被佔用,那就開始在鏈表上遍歷Entry,比較key,所以這和剛纔的get方法是一樣的,從原理上就是一樣的,那麼時間複雜度自然也是一樣的。
containValue()的時間複雜度和containKey()的時間複雜度不是一個級別的,因爲他要依賴於key,所以他的時間複雜度爲O(n).

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章