亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

HashMap的底層實現原理是什么

發布時間:2021-02-20 16:55:58 來源:億速云 閱讀:281 作者:Leah 欄目:開發技術

這篇文章給大家介紹HashMap的底層實現原理是什么,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

1.HashMap的常用方法

//  Hashmap存值:----------------------------------》 .put("key","value"); ----------》無返回值。
//
//  Hashmap取值:----------------------------------》 .get("key");-------------------》 返回Value的類型。
//
//  Hashmap判斷map是否為空:-----------------------》 .isEmpty(); -------------------》返回boolean類型。
//
//  Hashmap判斷map中是否存在這個key:--------------》.containsKey("key");------------》返回boolean類型。
//
//  Hashmap判斷map中是否含有value:----------------》.containsValue("value");-------》返回boolean類型。
//
//  Hashmap刪除這個key值下的value:----------------》.remove("key");-----------------》返回Value的類型。
//
//  Hashmap顯示所有的value值:---------------------》.values(); --------------------》返回Value的類型。
//
//  Hashmap顯示map里的值得數量:-------------------》.size(); ----------------------》返回int類型
//
//  HashMap顯示當前已存的key:---------------------》 .keySet();-------------------》返回Key的類型數組。
//
//  Hashmap顯示所有的key和value:-----------------》.entrySet());------------------》返回Key=Value類型數組。
//
//  Hashmap添加另一個同一類型的map:--------------》.putAll(map); -----------------》(參數為另一個同一類型的map)無返回值。
//
//  Hashmap刪除這個key和value:------------------》.remove("key", "value");-------》(如果該key值下面對應的是該value值則刪除)返回boolean類型。
//
//  Hashmap替換這個key對應的value值(JDK8新增):---》.replace("key","value");-------》返回被替換掉的Value值的類型。
//
//  克隆Hashmap:-------------------------------》.clone(); ---------------------》返回object類型。
//
//  清空Hashmap:-------------------------------》.clear(); ---------------------》無返回值。

2.HashMap的幾個重要知識點

  • HashMap是無序且不安全的數據結構。

  • HashMap 是以key–value對的形式存儲的,key值是唯一的(可以為null),一個key只能對應著一個value,但是value是可以重復的。

  • HashMap 如果再次添加相同的key值,它會覆蓋key值所對應的內容,這也是與HashSet不同的一點,Set通過add添加相同的對象,不會再添加到Set中去。

  • HashMap 提供了get方法,通過key值取對應的value值,但是HashSet只能通過迭代器Iterator來遍歷數據,找對象。

二、JDK7與JDK8的HashMap區別

既然講HashMap,那就不得不說一下JDK7與JDK8(及jdk8以后)的HashMap有什么區別:

  • jdk8中添加了紅黑樹,當鏈表長度大于等于8的時候鏈表會變成紅黑樹

  • 鏈表新節點插入鏈表的順序不同(jdk7是插入頭結點,jdk8因為要把鏈表變為紅 黑樹所以采用插入尾節點)

  • hash算法簡化 ( jdk8 )

  • resize的邏輯修改(jdk7會出現死循環,jdk8不會)

三、HashMap的容量與擴容機制

1.HashMap的默認負載因子

/**
  * The load factor used when none specified in constructor.
  */
 static final float DEFAULT_LOAD_FACTOR = 0.75f;
 /**
  *默認的負載因子是0.75f,也就是75% 負載因子的作用就是計算擴容閾值用,比如說使用
  *無參構造方法創建的HashMap 對象,他初始長度默認是16 閾值 = 當前長度 * 0.75 就
  *能算出閾值,當當前長度大于等于閾值的時候HashMap就會進行自動擴容
  */

面試的時候,面試官經常會問道一個問題:為什么HashMap的默認負載因子是0.75,而不是0.5或者是整數1呢?
答案有兩種:

  • 閾值(threshold) = 負載因子(loadFactor) x 容量(capacity) 根據HashMap的擴容機制,他會保證容量(capacity)的值永遠都是2的冪 為了保證負載因子x容量的結果是一個整數,這個值是0.75(4/3)比較合理,因為這個數和任何2的次冪乘積結果都是整數。

  • 理論上來講,負載因子越大,導致哈希沖突的概率也就越大,負載因子越小,費的空間也就越大,這是一個無法避免的利弊關系,所以通過一個簡單的數學推理,可以測算出這個數值在0.75左右是比較合理的

2.HashMap的擴容機制

寫數據之后會可能觸發擴容,HashMap結構內,我記得有一個記錄當前數據量的字段,這個數據量字段到達擴容閾值的話,它就會觸發擴容的操作

閾值(threshold) = 負載因子(loadFactor) x 容量(capacity)
當HashMap中table數組(也稱為桶)長度 >= 閾值(threshold) 就會自動進行擴容。

擴容的規則是這樣的,因為table數組長度必須是2的次方數,擴容其實每次都是按照上一次tableSize位運算得到的就是做一次左移1位運算,
假設當前tableSize是16的話 16轉為二進制再向左移一位就得到了32 即 16 << 1 == 32 即擴容后的容量,也就是說擴容后的容量是當前
容量的兩倍,但記住HashMap的擴容是采用當前容量向左位移一位(newtableSize = tableSize << 1),得到的擴容后容量,而不是當前容量x2

問題又來了,為什么計算擴容后容量要采用位移運算呢,怎么不直接乘以2呢?
這個問題就比較簡單了,因為cpu畢竟它不支持乘法運算,所有的乘法運算它最終都是再指令層面轉化為了加法實現的,這樣效率很低,如果用位運算的話對cpu來說就非常的簡潔高效。

3.HashMap中散列表數組初始長度

 /**
  * The default initial capacity - MUST be a power of two.
  */
 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

 /**
  * HashMap中散列表數組初始長度為 16 (1 << 4)
  * 創建HashMap的時候可以設置初始化容量和設置負載因子,
  * 但HashMap會自動優化設置的初始化容量參數,確保初始化
  * 容量始終為2的冪
  */

老問題又來了,為啥HashMap中初始化大小為什么是16呢?

首先我們看hashMap的源碼可知當新put一個數據時會進行計算位于table數組(也稱為桶)中的下標:

int index =key.hashCode()&(length-1);

hahmap每次擴容都是以 2的整數次冪進行擴容

因為是將二進制進行按位于,(16-1) 是 1111,末位是1,這樣也能保證計算后的index既可以是奇數也可以是偶數,并且只要傳進來的key足夠分散,均勻那么按位于的時候獲得的index就會減少重復,這樣也就減少了hash的碰撞以及hashMap的查詢效率。

那么到了這里你也許會問? 那么就然16可以,是不是只要是2的整數次冪就可以呢?

答案是肯定的。那為什么不是8,4呢? 因為是8或者4的話很容易導致map擴容影響性能,如果分配的太大的話又會浪費資源,所以就使用16作為初始大小。

四、HashMap的結構

JDK7與JDK8及以后的HashMap結構與存儲原理有所不同:
Jdk1.7:數組 + 鏈表 ( 當數組下標相同,則會在該下標下使用鏈表)
Jdk1.8:數組 + 鏈表 + 紅黑樹 (預值為8 如果鏈表長度 >=8則會把鏈表變成紅黑樹 )
Jdk1.7中鏈表新元素添加到鏈表的頭結點,先加到鏈表的頭節點,再移到數組下標位置
Jdk1.8中鏈表新元素添加到鏈表的尾結點
(數組通過下標索引查詢,所以查詢效率非常高,鏈表只能挨個遍歷,效率非常低。jdk1.8及以
上版本引入了紅黑樹,當鏈表的長度大于或等于8的時候則會把鏈表變成紅黑樹,以提高查詢效率)

五、HashMap存儲原理與存儲流程

1.HashMap存儲原理

  • 獲取到傳過來的key,調用hash算法獲取到hash值

  • 獲取到hash值之后調用indexFor方法,通過獲取到的hash值以及數組的長度算

  • 出數組的下標 (把哈希值和數組容量轉換為二進,再在數組容量范圍內與哈希值

  • 進行一次與運算,同為1則1,不然則為0,得出數組的下標值,這樣可以保證計算出的數組下標不會大于當前數組容量)

  • 把傳過來的key和value存到該數組下標當中。

  • 如該數組下標下以及有值了,則使用鏈表,jdk7是把新增元素添加到頭部節點 jdk8則添加到尾部節點。

2.HashMap存儲流程

前面尋址算法都是一樣的,根據key的hashcode經過高低位異或之后的值,再按位與 &(table.lingth - 1),得到一個數組下標,然后根據這個數組下標內的狀況,狀況不同,然后情況也不同,大概分為了4種狀態:

( 1.)第一種就是數組下標下內容為空:
這種情況沒什么好說的,為空據直接占有這個slot槽位就好了,然后把當前.put方法傳進來的key和value包裝成一個node對象,放到這個slot中就好了。

( 2.)第二種情況就是數組下標下內容不為空,但它引用的node還沒有鏈化:
這種情況下先要對比一下這個node對象的key與當前put對象的key是否完全.相等,如果完全相等的情況下,就行進行replace操作,把之前的槽位中node.下的value替換成新的value就可以了,否則的話這個put操作就是一個正兒.八經的hash沖突,這種情況在slot槽位后面追加一個node就可以了,用尾插法 ( 前面講過,jdk7是把新增元素添加到頭部節點,而jdk8則添加到尾部節點)。

( 3.)第三種就是該數組下標下內容已經被鏈化了:
這種情況和第二種情況處理很相似,首先也是迭代查找node,看看鏈表上中元素的key,與當前傳過來的key是否完全一致,如果完全一致的話還是repleace操作,用put過來的新value替換掉之前node中的value,否則的話就是一致迭代到鏈表尾節點也沒有匹配到完全一致的node,就和之前的一樣,把put進來數據包裝成node追加到鏈表的尾部,再檢查一下當前鏈表的長度,有沒有達到樹化閾值,如果達到了閾值就調用一個樹化方法,樹化操作都是在這個方法里完成的。

( 4.)第四種情況就是沖突很嚴重的情況下,這個鏈表已經轉化成紅黑樹了:
紅黑樹就比較復雜 要將清楚這個紅黑樹還得從TreeNode說起 TreeNode繼承了Node結構,在Node基礎上加了幾個字段,分別是指向父節點parent字段,指向左子節點left字段,指向右子節點right字段,還有一個表示顏色的red字段,這就是TreeNode的基本結構,然后紅黑樹的插入操作,首先找到一個合適的插入點,就是找到插入節點的父節點,然后紅黑樹它又滿足二叉樹的所有特性,所以找這個父節點的操作和二叉樹排序是完全一致的,然后說一下這個二叉樹排序,其實就是二分查找算法映射出來的結構,就是一個倒立的二叉樹,然后每個節點都可以有自己的子節點,本且左節點小于但前節點,右節點大于當前節點,然后每次向下查找一層就能那個排除掉一半的數據,查找效率非常的高效,當查找的過程中也是分情況的。

首先第一種情況就是一直向下探測,直到查詢到左子樹或者右子樹位null,說明整個樹中,并沒有發現node鏈表中的key與當前put key一致的TreeNode,那此時探測節點就是插入父節點的所在了,然后就是判斷插入節點的hash值和父節點的hash值大小決定插入到父節點的左子樹還是右子樹。當然插入會打破平衡,還需要一個紅黑樹的平衡算法保持平衡。

其次第二種情況就是根節點在向下探測過程中發現TreeNode中key與當前put的key完全一致,然后就也是一次repleace操作,替換value。

六、jdk8中HashMap為什么要引入紅黑樹?

其實主要就是為了解決jdk1.8以前hash沖突所導致的鏈化嚴重的問題,因為鏈表結構的查詢效率是非常低的,他不像數組,能通過索引快速找到想要的值,鏈表只能挨個遍歷,當hash沖突非常嚴重的時候,鏈表過長的情況下,就會嚴重影響查詢性能,本身散列列表最理想的查詢效率為O(1),當時鏈化后鏈化特別嚴重,他就會導致查詢退化為O(n)為了解決這個問題所以jdk8中的HashMap添加了紅黑樹來解決這個問題,當鏈表長度>=8的時候鏈表就會變成紅黑樹,紅黑樹其實就是一顆特殊的二叉排序樹嘛,這個時間復雜…反正就是要比列表強很多

七、擴容后的新table數組,那老數組中的這個數據怎么遷移呢

遷移其實就是挨個桶位推進遷移,就是一個桶位一個桶位的處理,主要還是看當前處理桶位的數據狀態把,這里也是分了大概四種狀態:
這四種的遷移規則都不太一樣

(1.)第一種就是數組下標下內容為空:
這種情況下就沒什么可說的,不用做什么處理。

( 2.)第二種情況就是數組下標下內容不為空,但它引用的node還沒有鏈化:
當slot它不為空,但它引用的node還沒有鏈化的時候,說明這個槽位它沒有發生過hash沖突,直接遷移就好了,根據新表的tableSize計算出他在新表的位置,然后存放進去就好了。

( 3.)第三種就是slot內儲存了一個鏈化的node:
當node中next字段它不為空,說明槽位發生過hash沖突,這個時候需要把當前槽位中保存的這個鏈表拆分成兩個鏈表,分別是高位鏈和低位鏈

(4.)第四種就是該槽位儲存了一個紅黑樹的根節點TreeNode對象:
這個就很復雜了,本文章暫時不做過多的介紹(博主還沒整明白 =_=! )

關于HashMap的底層實現原理是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

财经| 云南省| 滦南县| 修武县| 普宁市| 中超| 平武县| 增城市| 梅河口市| 台中县| 昂仁县| 眉山市| 正定县| 简阳市| 宣城市| 毕节市| 长海县| 灵宝市| 宣汉县| 宕昌县| 万安县| 中江县| 开平市| 永顺县| 平原县| 彭泽县| 上犹县| 将乐县| 镇江市| 秦皇岛市| 阿拉善右旗| 九寨沟县| 平乡县| 镇康县| 乌拉特后旗| 嘉善县| 礼泉县| 盘锦市| 泾川县| 泰安市| 志丹县|