最近參與公司的實(shí)習(xí)生招聘工作,面試了幾位實(shí)習(xí)生,我有一道每次面試都必問(wèn)的題目【HasmMap的工作原理】,但很遺憾,至今還沒遇到令我完全滿意的回答。今天這篇文章就來(lái)回答下HashMap相關(guān)的面試題。
1. 什么是HashMap?
java.util.HashMap是Java語(yǔ)言標(biāo)準(zhǔn)庫(kù)中的一個(gè)容器類,主要用來(lái)存儲(chǔ)鍵值對(duì)。是數(shù)據(jù)結(jié)構(gòu)中哈希表在Java語(yǔ)言的一個(gè)實(shí)現(xiàn)。
哈希表是數(shù)據(jù)結(jié)構(gòu)中的一個(gè)概念,可以存儲(chǔ)n個(gè)元素,取元素的時(shí)間復(fù)雜度為O(1).
一般情況下,從n個(gè)元素中查找一個(gè)元素,時(shí)間復(fù)雜度為O(n). 為何哈希表能有O(1)的時(shí)間復(fù)雜度?
因?yàn)樵卦诖鎯?chǔ)時(shí)已經(jīng)大致確定了位置,查找的時(shí)候可以定位到對(duì)應(yīng)的位置,因此時(shí)間復(fù)雜度為O(1).
2. HashMap中的數(shù)據(jù)結(jié)構(gòu)
HashMap使用數(shù)組+鏈表的方式實(shí)現(xiàn)。
數(shù)組的作用主要是定位元素位置。
鏈表的作用是解決哈希沖突問(wèn)題。遇到hash值相等的key,就把相應(yīng)鍵值使用鏈表的方式全部保存下來(lái)。
3. put操作
put方法定義
public V put(K key, V value)
put操作執(zhí)行過(guò)程:
a. 計(jì)算key的hashcode
b. 根據(jù)hashcode計(jì)算出需要將該鍵值對(duì)存儲(chǔ)的位置-即桶的位置
c. 找到桶的位置后,如果桶未被占用,則存入該鍵值對(duì);若桶中已有數(shù)據(jù),則需要遍歷桶中數(shù)據(jù),并且比較key的equals方法。遇到key相同的,則用新的value,把舊值替換掉(put操作會(huì)將舊值覆蓋掉)。若沒找到,則將該鍵值對(duì)存儲(chǔ)在鏈表的末尾。
4. get操作
get方法定義
public V get(Object key)
get操作執(zhí)行過(guò)程:
a. 計(jì)算key的hashcode
b. 根據(jù)hashcode,定位該元素所在桶的位置
c. 若桶為空,則返回null;若桶不空,則遍歷桶中數(shù)據(jù),比較key的equals方法,若遇到相同的元素,則返回對(duì)于的value中;沒遇到,則返回null
5. 什么樣的類型可以作為HashMap的key?
a. 重寫hashCode() 和 equals() 方法
HashMap在get/put數(shù)據(jù)時(shí),需要調(diào)用hashCode() 和 equals() 方法,因此只有重寫了hashCode() 和 equals() 方法的類,才可以作為HashMap的key。
(hashCode相同,對(duì)象不一定相等;對(duì)象相等,則hashCode必然相同)
b. 不可變性
不可變性也是必要的,若元素可變,則相應(yīng)hashCode也會(huì)發(fā)生變化,調(diào)用get()方法時(shí),可能找不到相應(yīng)的value,甚至可能找到錯(cuò)誤的value。
6. 如何計(jì)算數(shù)組下標(biāo)
在JDK1.8之前的版本中,是通過(guò)key的hashCode()進(jìn)行hashing,然后將( n-1 & hash)來(lái)確定數(shù)組下標(biāo),即桶的位置。
在JDK1.8版本,將hash值的高16位與低16位進(jìn)行異或
JDK 1.8:
(h = key.hashCode()) ^ (h >>>16)
這樣做的目的主要是為了減少哈希沖突的可能性。特別是數(shù)據(jù)量較小時(shí),舊的計(jì)算方式哈希沖突的可能比較大。
7. HashMap的兩個(gè)重要概念
容量(Capacity),即HashMap中數(shù)組的大小,也是桶的個(gè)數(shù)
負(fù)載因子(load factor),也成裝載因子 = hashmap中實(shí)際保存鍵值對(duì)個(gè)數(shù) / hashmap中數(shù)組的大小
隨著HashMap中元素個(gè)數(shù)的增多,負(fù)載因子增大。如果不做額外的處理,則桶中的鏈表會(huì)越來(lái)越長(zhǎng),因此HashMap就無(wú)法保證取元素時(shí)O(1)的時(shí)間復(fù)雜度。因此,必須要調(diào)整HashMap中數(shù)組的大小
HashMap中定義的默認(rèn)負(fù)載因子大小為0.75,當(dāng)負(fù)載因子大于該值時(shí),將對(duì)數(shù)組進(jìn)行擴(kuò)容
8. HashMap其他面試題
HashMap與HashTable區(qū)別:Hashtable中加入了鎖,線程安全;HashMap線程不安全。也正因?yàn)镠ashtable中加入了鎖,導(dǎo)致性能上要差于HashMap。
ConcurrentHashMap:HashMap、Hashtable在性能和安全性上的一個(gè)折中方案。實(shí)現(xiàn)原理是ConcurrentHashMap中將Map劃分為多個(gè)子Map,子Map分別加鎖。對(duì)一個(gè)子Map加鎖以后,不影響對(duì)其他子Map的訪問(wèn)。
9. 為什么要面試這道題目
a. 考察求職者技術(shù)水平.
我一把把這道題目作為第一道面試題來(lái)考察求職者。根據(jù)回答情況,能大致推測(cè)到求職者的水平,方便后續(xù)的面試。
零級(jí)水平:完全不知道。(當(dāng)然,這種可能性很少。畢竟HashMap是非常常用的一個(gè)工具類)
一級(jí)水平:可以回答HashMap的時(shí)間復(fù)雜度,容量,裝載因子等概念
二級(jí)水平:回答出get/put操作的完整過(guò)程。(多數(shù)求職者都沒能完整的回答這個(gè)問(wèn)題。能完全回答出這個(gè)問(wèn)題,說(shuō)明求職者對(duì)哈希的思想有完整的理解,而且很可能閱讀過(guò)HashMap的源碼 - 要想寫好代碼,首先要閱讀優(yōu)秀源代碼的習(xí)慣)
三級(jí)水平:回答出hashCode,equals作用,不可變性,線程安全的Map(基本上滿足公司實(shí)習(xí)生的要求,可以發(fā)offer了)
b. 考察求職者表達(dá)能力
能夠把一件復(fù)雜的事情講的清晰易懂,是件了不起的能力。(筆者在這方面能力有所欠佳)
注:
HashMap中其實(shí)允許null作為key,因?yàn)樵趃et/put操作時(shí)都會(huì)對(duì)null值做特殊的處理。
本文主要是從哈希表的角度來(lái)講述HashMap,因此這些細(xì)節(jié)問(wèn)題并未涉及。