HashMap從入門到入土

HashMap源碼閱讀

介紹:

  • 繼承自Map.Entry<K,V>
  • 無序、允許爲null、非同步

結構圖:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XQYRSLti-1593679820875)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200702162547.png)]

分析

參數

  • 默認容量16
  • 最大容量2的31次方
  • 默認的填充因子0.75(太大查詢效率低,太小數組利用率低)
  • 當bucket上的結點大於8時會轉成紅黑樹
  • 當bucket上的結點小於6時會轉換爲鏈表
  • 樹化參數64,當小於64時只是簡單的擴容,大於64是會樹化
  • Node:靜態內部類,用來表示鍵值對
  • TreeNode:紅黑樹節點

關鍵概念

  • 存儲容器

    • 因爲hashmap內部是用一個數組來保存內容的,數組定義如下

      transient Node<K,V>[] table;
      
  • Node類型

    • table是一個Node類型的數組,Node是其中定義的靜態內部類,主要包含hash、key、value、和next屬性

       static class Node<K,V> implements Map.Entry<K,V> {
              final int hash;
              final K key;
              V value;
              Node<K,V> next;
       }
      
  • TreeNode

    • 當桶內的鏈表達到8的時候,會將鏈表轉化爲紅黑樹,就是TreeNode類型,也就是HashMap中定義的靜態內部類
     static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
            TreeNode<K,V> parent;  // red-black tree links
            TreeNode<K,V> left;
            TreeNode<K,V> right;
            TreeNode<K,V> prev;    // needed to unlink next upon deletion
            boolean red;
     }
    

常見問題

HashMap數據結構:

​ 哈希表結構(鏈表散列:數組+鏈表),結合數組和鏈表的有點,當鏈表長度超過8時,鏈表轉化爲紅黑樹

HashMap 工作原理

​ HashMap的底層是hash數組和單向鏈表實現的,數組中的每個元素都是鏈表,由Node內部類(實現Map.Entry接口)實現,HashMap通過put&get方法存儲和獲取

存儲對象的時候,將k/v傳給put()方法:

在這裏插入圖片描述

  • 初始化table:判斷table是否爲空或者爲null
  • 計算hash值:通過高16位和低16位的異或運算(讓高16位也參入進來以便減低衝突)
  • 插入或更新節點:根據(n-1)&hash計算得到插入的數組下標i然後進行判斷
    • table[i]==null:沒有hash衝突直接新建節點添加
    • table[i]!=null:判斷首個節點和key是否一樣
      • 相同:直接更新value
      • 不相同:判斷是否是紅黑樹
        • 紅黑樹:直接在樹中插入鍵值對
        • 鏈表:遍歷鏈表判斷是否已經存在此key:
          • 存在更新
          • 不存在就插入(1.8是尾插入),然後判斷是否轉爲紅黑樹
  • 擴容:根據size和數組長度*負載因子來判斷是否需要擴容

獲取對象時,將 K 傳給 get() 方法:

  • 調用 hash(K) 方法(計算 K 的 hash 值)從而獲取該鍵值所在鏈表的數組下標;
  • 確定桶的位置後,會出現三種情況
    • 單節點類型:只要不發生哈希碰撞就是這種類型
    • 鏈表類型:遍歷鏈表,知道找到key相等的Node
    • 紅黑樹類型:使用紅黑樹專用的快速查找方法

hashCode 是定位的,存儲位置;equals是定性的,比較兩者是否相等。

爲什麼要一起重寫hashCode()和equal()方法

假設都不重寫

這樣hashMap就可以存在key相同的值

只重寫hashCode

無法正確地與鏈表元素進行相等判斷,從而無法保證去重

只重寫equals()方法

映射到HashMap中不同數組下標,無法保證去重

當兩個對象的hashCode相同會發生什麼

​ 因爲hashCode相同,但是不一定就是相等的(需要根據equals方法比較),所以兩個對象所在數組的下標相同,碰撞就此發生。

hash的實現

​ 在jdk1.8中,是通過hashCode()的高16位異或低16位實現的:(h=k.hashCode())^h(>>>16),主要從速度、功效和質量來考慮的,減少系統開銷,也不會造成因爲高位沒有參數下標的計算從而引起的碰撞

使用異或運算符

​ 保證了對象的hashCode32位值只要有一位發生改變,整個hash()返回值就會改變,儘可能減少碰撞

數組擴容的過程

​ 創建一個新的數組,其容量爲舊數組的兩倍,並重新計算舊數組中結點的存儲位置。

​ 結點在新數組中的存儲位置只有兩種:原下標或者原下標+舊數組的大小

拉鍊法導致鏈表過深問題爲什麼使用紅黑樹不選擇二叉樹,爲啥不一直使用紅黑樹

​ 因爲二叉查詢樹在特殊情況下回編程一條線性結構(造成一樣的問題),導致遍歷查詢緩慢,而紅黑樹可以解決這個問題。

​ 紅黑樹在插入新數據後,可能需要左旋、右旋、變色這些操作來保持平衡,但是爲了保持平衡需要付出一些代價,所以在鏈表長度較小(8)時,不使用紅黑樹

紅黑樹的介紹

  • 每個結點非紅即黑
  • 根節點總是黑色的
  • 如果節點是紅色的,則其子節點必須是黑色的
  • 每個葉子節點都是黑色的空節點(NIL節點)
  • 從根節點到葉子節點或空節點的每條路徑,必須包含相同數目的黑色節點

HashMap、LinkedHashMap、TreeMap有什麼區別與使用場景

HashMap:(使用最多)

  • 區別:
  • 使用場景:在map中插入、刪除和定位元素

LinkedHashMap:

  • 區別:保存了記錄的插入順序,在用Iterator遍歷時,先取到的記錄肯定是先插入的,遍歷比HashMap慢
  • 使用場景:在需要輸出的順序和輸入的順序相同的情況下

TreeMap:

  • 區別:實現SortMap接口,能夠把它保存的記錄根據鍵排序(默認升序)
  • 使用場景:需要按自然順序或自定義順序遍歷鍵的情況

HashMap和HashTable區別

  • HashMap是線程不安全的,HashTable是線程安全的,導致效率也低下
  • HashMap最多隻允許一條記錄的鍵爲null,與韓旭旭多條記錄的值爲null,而HashTable不允許
  • HashMap默認初始化數組大小爲16,擴容爲兩倍、HashTable爲11,擴容爲兩倍+1

最後

  • 如果覺得看完有收穫,希望能給我點個贊,這將會是我更新的最大動力,感謝各位的支持
  • 歡迎各位關注我的公衆號【java冢狐】,專注於java和計算機基礎知識,保證讓你看完有所收穫,不信你打我
  • 如果看完有不同的意見或者建議,歡迎多多評論一起交流。感謝各位的支持以及厚愛。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章