HashMap實現原理及常見問題

来源:https://www.cnblogs.com/zfinding/archive/2019/01/23/10306895.html
-Advertisement-
Play Games

1.簡介 HashMap是基於哈希表的Map介面的實現,用來存放鍵值對(Entry<Key,Value>),並提供可選的映射操作。使用put(Key,Value)存儲對象到HashMap中,使用get(Key)從hashMap中獲取對象。 2.底層結構 HashMap的底層是由數組加鏈表實現的,因為 ...


1.簡介

  HashMap是基於哈希表的Map介面的實現,用來存放鍵值對(Entry<Key,Value>),並提供可選的映射操作。使用put(Key,Value)存儲對象到HashMap中,使用get(Key)從hashMap中獲取對象。

2.底層結構

  HashMap的底層是由數組加鏈表實現的,因為對鏈表頭部進行增刪操作,所以也稱為棧式鏈結構。鏈表由 Entry<Key,Value>對象作為結點,我們把存儲該鏈表的數組位置稱之為,那麼桶數量就等於數組的長度。存放數據時時通過key的hashCode來計算hash,得到的hash作為數組的索引(也就是桶位置)存放對象,當hashCode相同時,則稱之為哈希衝突,也可稱為“碰撞”。此時通過“拉鏈法”解決衝突。

//Entry源碼
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; }

 

補充:在jdk1.8版本之後,在解決哈希衝突時有了較大的變化,當鏈表長度大於閾值(預設為8)時,將鏈表轉化為紅黑樹,以減少搜索時間。

3.原理分析

  下麵由存取數據的過程進行原理分析:

  (1) put(key,value)

在使用put方法傳遞 Entry<Key,Value>時,會對Key調用HashCode()方法,接著會對得到的哈希值再次進行計算,以jdk1.8版本為例,源代碼如下。
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

  從這裡我們可以看出hash方法對Key調用HashCode()方法,將得到的哈希值高16位不變,高16位與低16位進行異或運算。這樣的目的是通過對哈希值的擾動,儘可能減少碰撞的發生。

  此時的哈希值還不能作為數組的索引來存放數據,最後還會對擾動後的hash與(數組長度-1)進行取模運算,即(n - 1) & hash 這裡n為數組長度,假設n為16 那麼(n-1)的二進位為1111,將之與hash進行與位運算,1111截取hash後四位,並保證只是截取操作,截取後的hash與截取前的hash後四位相同,這就保證最後得到的hash能作為索引使數據在數組長度內儘可能均勻分佈,減少碰撞,這種方式和hash%n取餘的結果差不多又不太一樣,通俗點將,取模操作要求n-1的二進位是111...都是1這種形式,也就必須要求n的值必須為2的次冪,這也解釋了為什麼HashMap規定數組的長度必須是2的次冪的原因。

  重覆上述:使用put方法傳遞 Entry<Key,Value>時,會對Key計算hash索引,先判斷數組table[hash]是否為null,若為null 則入 Entry<Key,Value>,若不為空,就說明發生了碰撞,此時要存入的Entry對象的Key和桶中的Entry對象的Key的hash相同,但是它們可能並不相等,所以會調用equals方法將要存入的Key與桶中的Key一一比對,若均不相等,則存入 Entry(頭插入法),如果相等,會覆蓋原來的Entry.這種解決碰撞的方式就是前面所說的“拉鏈法”。

  通過對存儲過程的原理分析,那獲取數據就簡單了,在調用get(Key)方法時,同樣計算Key的hash,通過計算好的hash找到桶位置,然後遍歷鏈表通過key.equals方法直到找到Value值。

常見問題 

(1)關於擴容:

  loadfactor: 預設0.75f,代表桶填充程度,loadFactor越趨近於1,那麼數組中存放的數據(entry)也就越多,也就越密,也就是會讓鏈表的長度增加,導致查找元素效率低,loadFactor越趨近於0,數組的利用率越低,存放的數據會很分散。loadFactor的預設值為0.75f是官方給出的一個比較好的臨界值。

  capacity: 數組長度,必須為2的次冪,預設為16。hashMap構造中可指定初始長度,會通過一個演算法轉化成2的次冪

  threshold: threshold = capacity * loadFactor,當entry的數量>=threshold的時候,那麼就要考慮對數組的擴容了,這個的意思就是 衡量數組是否需要擴增的一個標準,擴容後,會重新對所有數據進行重新計算,重新存放,這個過程叫做rehash。

      

//HashMap保證數組長度為2的次冪的演算法
static
final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

}

  (2)HashMap與HashTable主要區別為不支持同步和允許null作為key和value,所以如果你想要保證線程安全,可以使用ConcurrentHashMap代替而不是線程安全的HashTable,因為HashTable基本已經被淘汰。

  (3)如果兩個線程都發現HashMap需要調整大小,它們會同時嘗試調整大小,在這個過程中,存儲在鏈表中的元素次序會反過來,因為移動到新的桶位置的時候,hashMap並不會將元素放在尾部,而是放在頭部,這是為了避免尾部遍歷,如果條件競爭發生,會發生死迴圈.(註:jdk1.8已經解決了死迴圈的問題。)

(4)key多用String的原因:String是final的,並且重寫了hashMap和equals方法,不可變可以防止鍵的改變,重寫那兩個方法可以減少碰撞的幾率.

  

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 博客皮膚選擇 SimpleMemory 因為簡單,所以愛~ 2. 在”頁面定製CSS代碼“中填入以下內容 重要提示:側邊欄藍色風格,有的模塊可能遺漏,需要請自行在CSS樣式中的“側邊欄”加上對應DIV的id或者class 3. 在“首頁Html代碼”中填入以下內容: 說明:此樣式非首創,收集了 ...
  • 作為普通的網民來說,一般不需要知道也不用關心什麼是盜鏈,不過如果你是網站的開發者或維護者,就不得不重視盜鏈的問題了。如果你剛剛開發完一個沒有防盜鏈的帶有文件下載功能的網站,掛上internet,然後上傳幾個時下非常熱門的軟體或電影併在網站內公佈下載地址,讓MSN上的所有好友都來體驗一下你的傑作。 不 ...
  • 參考:https://www.cnblogs.com/lewis0077/p/5133812.html(深入解析策略模式) 參考:https://www.cnblogs.com/lewis0077/p/5133812.html(深入解析策略模式) 定義: 策略模式定義了一系列的演算法,並將每一個演算法封 ...
  • 前言 在上一篇中我們學習了結構型模式的組合模式和過濾器模式。本篇則來學習下結構型模式最後的兩個模式, 享元模式和代理模式。 享元模式 簡介 享元模式主要用於減少創建對象的數量,以減少記憶體占用和提高性能。這種類型的設計模式屬於結構型模式,它提供了減少對象數量從而改善應用所需的對象結構的方式。 用通俗的 ...
  • 基本語法 1.Grovvy的註釋分為//和/**/和java的一樣. 2.Grovvy語法可以不已分號結尾. 3.單引號,裡面的內容嚴格的對應java中的String,不對$符號進行轉義. 4.雙引號“ ”的內容中如果有$號的話,會先對錶達式先求值. 5.三個引號中的字元串支持隨意的換行. 定義變數 ...
  • 1. 手機APP數據 寫在前面 繼續練習pyspider的使用,最近搜索了一些這個框架的一些使用技巧,發現文檔竟然挺難理解的,不過使用起來暫時沒有障礙,估摸著,要在寫個5篇左右關於這個框架的教程。今天教程中增加了圖片的處理,你可以重點學習一下。 2. 手機APP數據 頁面分析 咱要爬取的網站是 這個 ...
  • Java反射,註解,以及動態代理 基礎 最近在準備實習面試,被學長問到了Java反射,註解和動態代理的內容,發現有點自己有點懵,這幾天查了很多資料,就來說下自己的理解吧【如有錯誤,望指正】 Java反射 首先,我們得弄清一個,什麼是反射(Reflection)。簡單的來說,反射就是讓我們在程式運行的 ...
  • 實現跳轉的方法: 1.Php中header的函數 2js中location函數 3.Html中的meta函數 引入message.html <meta http-equiv="Refresh" content ="<?php echo $wait;?> ;url=<?php echo $url;?> ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...