HashMap----工作原理

来源:https://www.cnblogs.com/zmy-520131499/archive/2019/07/13/11182588.html
-Advertisement-
Play Games

先來些簡單的問題 “你用過HashMap嗎?” “什麼是HashMap?你為什麼用到它?” 幾乎每個人都會回答“是的”,然後回答HashMap的一些特性,譬如HashMap可以接受null鍵值和值,而HashTable則不能;HashMap是非synchronized;HashMap很快;以及Has ...


 

  先來些簡單的問題

  “你用過HashMap嗎?” “什麼是HashMap?你為什麼用到它?”

  幾乎每個人都會回答“是的”,然後回答HashMap的一些特性,譬如HashMap可以接受null鍵值和值,而HashTable則不能;HashMap是非synchronized;HashMap很快;以及HashMap儲存的是鍵值對等等。這顯示出你已經用過HashMap,而且對它相當的熟悉。但是面試官來個急轉直下,從此刻開始問出一些刁鑽的問題,關於HashMap的更多基礎的細節。面試官可能會問出下麵的問題:

  “你知道HashMap的工作原理嗎?” “你知道HashMap的get()方法的工作原理嗎?”

  你也許會回答“我沒有詳查標準的Java API,你可以看看Java源代碼或者Open JDK。”“我可以用Google找到答案。”

  但一些面試者可能可以給出答案,“HashMap是基於hashing的原理,我們使用put(key, value)存儲對象到HashMap中,使用get(key)從HashMap中獲取對象。當我們給put()方法傳遞鍵和值時,我們先對鍵調用hashCode()方法,返回的hashCode用於找到bucket位置來儲存Entry對象。”這裡關鍵點在於指出,HashMap是在bucket中儲存鍵對象和值對象,作為Map.Entry。這一點有助於理解獲取對象的邏輯。如果你沒有意識到這一點,或者錯誤的認為僅僅只在bucket中存儲值的話,你將不會回答如何從HashMap中獲取對象的邏輯。這個答案相當的正確,也顯示出面試者確實知道hashing以及HashMap的工作原理。但是這僅僅是故事的開始,當面試官加入一些Java程式員每天要碰到的實際場景的時候,錯誤的答案頻現。下個問題可能是關於HashMap中的碰撞探測(collision detection)以及碰撞的解決方法:

  “當兩個對象的hashcode相同會發生什麼?” 從這裡開始,真正的困惑開始了,一些面試者會回答因為hashcode相同,所以兩個對象是相等的,HashMap將會拋出異常,或者不會存儲它們。然後面試官可能會提醒他們有equals()和hashCode()兩個方法,並告訴他們兩個對象就算hashcode相同,但是它們可能並不相等。一些面試者可能就此放棄,而另外一些還能繼續挺進,他們回答“因為hashcode相同,所以它們的bucket位置相同,‘碰撞’會發生。因為HashMap使用LinkedList存儲對象,這個Entry(包含有鍵值對的Map.Entry對象)會存儲在LinkedList中。”這個答案非常的合理,雖然有很多種處理碰撞的方法,這種方法是最簡單的,也正是HashMap的處理方法。但故事還沒有完結,面試官會繼續問:

  “如果兩個鍵的hashcode相同,你如何獲取值對象?” 面試者會回答:當我們調用get()方法,HashMap會使用鍵對象的hashcode找到bucket位置,然後獲取值對象。面試官提醒他如果有兩個值對象儲存在同一個bucket,他給出答案:將會遍歷LinkedList直到找到值對象。面試官會問因為你並沒有值對象去比較,你是如何確定確定找到值對象的?除非面試者直到HashMap在LinkedList中存儲的是鍵值對,否則他們不可能回答出這一題。

  其中一些記得這個重要知識點的面試者會說,找到bucket位置之後,會調用keys.equals()方法去找到LinkedList中正確的節點,最終找到要找的值對象。完美的答案!

  許多情況下,面試者會在這個環節中出錯,因為他們混淆了hashCode()和equals()方法。因為在此之前hashCode()屢屢出現,而equals()方法僅僅在獲取值對象的時候才出現。一些優秀的開發者會指出使用不可變的、聲明作final的對象,並且採用合適的equals()和hashCode()方法的話,將會減少碰撞的發生,提高效率。不可變性使得能夠緩存不同鍵的hashcode,這將提高整個獲取對象的速度,使用String,Interger這樣的wrapper類作為鍵是非常好的選擇。

  如果你認為到這裡已經完結了,那麼聽到下麵這個問題的時候,你會大吃一驚。“如果HashMap的大小超過了負載因數(load factor)定義的容量,怎麼辦?”除非你真正知道HashMap的工作原理,否則你將回答不出這道題。預設的負載因數大小為0.75,也就是說,當一個map填滿了75%的bucket時候,和其它集合類(如ArrayList等)一樣,將會創建原來HashMap大小的兩倍的bucket數組,來重新調整map的大小,並將原來的對象放入新的bucket數組中。這個過程叫作rehashing,因為它調用hash方法找到新的bucket位置。

  如果你能夠回答這道問題,下麵的問題來了:“你瞭解重新調整HashMap大小存在什麼問題嗎?”你可能回答不上來,這時面試官會提醒你當多線程的情況下,可能產生條件競爭(race condition)。

  當重新調整HashMap大小的時候,確實存在條件競爭,因為如果兩個線程都發現HashMap需要重新調整大小了,它們會同時試著調整大小。在調整大小的過程中,存儲在LinkedList中的元素的次序會反過來,因為移動到新的bucket位置的時候,HashMap並不會將元素放在LinkedList的尾部,而是放在頭部,這是為了避免尾部遍歷(tail traversing)。如果條件競爭發生了,那麼就死迴圈了。這個時候,你可以質問面試官,為什麼這麼奇怪,要在多線程的環境下使用HashMap呢?:)

  熱心的讀者貢獻了更多的關於HashMap的問題:

  1. 為什麼String, Interger這樣的wrapper類適合作為鍵? String, Interger這樣的wrapper類作為HashMap的鍵是再適合不過了,而且String最為常用。因為String是不可變的,也是final的,而且已經重寫了equals()和hashCode()方法了。其他的wrapper類也有這個特點。不可變性是必要的,因為為了要計算hashCode(),就要防止鍵值改變,如果鍵值在放入時和獲取時返回不同的hashcode的話,那麼就不能從HashMap中找到你想要的對象。不可變性還有其他的優點如線程安全。如果你可以僅僅通過將某個field聲明成final就能保證hashCode是不變的,那麼請這麼做吧。因為獲取對象的時候要用到equals()和hashCode()方法,那麼鍵對象正確的重寫這兩個方法是非常重要的。如果兩個不相等的對象返回不同的hashcode的話,那麼碰撞的幾率就會小些,這樣就能提高HashMap的性能。
  2. 我們可以使用自定義的對象作為鍵嗎? 這是前一個問題的延伸。當然你可能使用任何對象作為鍵,只要它遵守了equals()和hashCode()方法的定義規則,並且當對象插入到Map中之後將不會再改變了。如果這個自定義對象時不可變的,那麼它已經滿足了作為鍵的條件,因為當它創建之後就已經不能改變了。
  3. 我們可以使用CocurrentHashMap來代替HashTable嗎?這是另外一個很熱門的面試題,因為ConcurrentHashMap越來越多人用了。我們知道HashTable是synchronized的,但是ConcurrentHashMap同步性能更好,因為它僅僅根據同步級別對map的一部分進行上鎖。ConcurrentHashMap當然可以代替HashTable,但是HashTable提供更強的線程安全性。

  我個人很喜歡這個問題,因為這個問題的深度和廣度,也不直接的涉及到不同的概念。讓我們再來看看這些問題設計哪些知識點:

  • hashing的概念
  • HashMap中解決碰撞的方法
  • equals()和hashCode()的應用,以及它們在HashMap中的重要性
  • 不可變對象的好處
  • HashMap多線程的條件競爭
  • 重新調整HashMap的大小

  總結

  HashMap的工作原理

  HashMap基於hashing原理,我們通過put()和get()方法儲存和獲取對象。當我們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算hashcode,讓後找到bucket位置來儲存值對象。當獲取對象時,通過鍵對象的equals()方法找到正確的鍵值對,然後返回值對象。HashMap使用LinkedList來解決碰撞問題,當發生碰撞了,對象將會儲存在LinkedList的下一個節點中。 HashMap在每個LinkedList節點中儲存鍵值對對象。

  當兩個不同的鍵對象的hashcode相同時會發生什麼? 它們會儲存在同一個bucket位置的LinkedList中。鍵對象的equals()方法用來找到鍵值對。

  因為HashMap的好處非常多,我曾經在電子商務的應用中使用HashMap作為緩存。因為金融領域非常多的運用Java,也出於性能的考慮,我們會經常用到HashMap和ConcurrentHashMap。你可以查看更多的關於HashMap和HashTable的文章。


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

-Advertisement-
Play Games
更多相關文章
  • 史上最全的,web前端零基礎,系統學習路線圖!學好web前端,前途寬廣!如今隨著“互聯網+”上升到國家戰略,軟體行業與國民經濟關係密,幾乎絕大多數行業的發展都會促進軟體行業的發展。 ...
  • 編譯原理課程設計詞法分析任務書 5)參考文獻: (1)張素琴,呂映芝. 編譯原理[M]., 清華大學出版社 (2)蔣立源、康慕寧等,編譯原理(第2版)[M],西安:西北工業大學出版社 6)課程設計進度安排 1.準備階段(4學時):選擇設計題目、瞭解設計目的要求、查閱相關資料 2.程式模塊設計分析階段 ...
  • 舉個慄子 問題描述 要求有一個簡歷類,必須要有姓名,可以設置性別和年齡,可以設置工作經歷,最終需要三份簡歷。 簡單實現 簡歷類 測試 測試結果 存在的問題 跟手寫簡歷沒有差別,三份簡歷需要三份實例化,如果客戶需要二十份簡歷,那就得實例化二十次。 原型模式 定義 用原型實例指定創建對象的種類,並且通過 ...
  • SpringCloud系列教程 | 第十三篇:Spring Cloud Gateway服務化和過濾器 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如無特殊說明,本系列教程全採用以上版本 上一篇文章服務網關 Spring Cloud Gat ...
  • 最近這破事賊多,都沒有什麼時間寫寫博客,都好久都沒有更新博客了!不過平常看jdk源碼的時候有很大的感觸,就是基礎真的很重要,那什麼是基礎呢?除了java的基本語法之外,最基礎的莫過於原碼,反碼和補碼了以及基本的運算了! 又是我是編程半路出家,最開始的時候學過一點這些東西,當時只是感覺,擦!我是寫代碼 ...
  • Java線程池的原理,主要參數的作用。ThreadPoolExecutor內部有重要的成員變數ctl,類型是AtomicInteger,低29位表示線程池中線程數,通過高3位表示線程池的運行狀態。addWorker的邏輯,runWorker的邏輯 ...
  • Java 流(Stream)、文件(File)和IO Java.io包幾乎包含了所有操作輸入、輸出需要的類。所有這些流類代表了輸入源和輸出目標。 Java.io包中的流支持很多種格式,比如:基本類型、對象、本地化字元集等等。 一個流可以理解為一個數據的序列。輸入流表示從一個源讀取數據,輸出流表示向一 ...
  • Mybatis 持久層框架,數據訪問層 mybatis是一個優秀的基於java的持久層框架,它內部封裝了jdbc,使開發者只需要關註sql語句本身,而不需要花費精力去處理載入驅動,創建連接,創建statement等繁雜的過程,使用ORM思想實現了結果集的封裝 ORM:Object Relationa ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...