1. 程式人生 > >【java集合】HashMap常見面試題

【java集合】HashMap常見面試題

【java集合】HashMap常見面試題

2017年05月25日 18:20:56 閱讀數:3155 標籤: java hashmap 更多

個人分類: java集合

版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/u012512634/article/details/72735183

一、HashMap原理

1.1 HashMap特性?

  HashMap的特性:HashMap儲存鍵值對,實現快速存取資料;允許null鍵/值;非同步;不保證有序(比如插入的順序)。實現map介面。

1.2 HashMap的原理,內部資料結構?

  HashMap是基於hashing的原理,底層使用雜湊表(陣列 + 連結串列)實現。裡邊最重要的兩個方法put、get,使用put(key, value)儲存物件到HashMap中,使用get(key)從HashMap中獲取物件。
  儲存物件時,我們將K/V傳給put方法時,它呼叫hashCode計算hash從而得到bucket位置,進一步儲存,HashMap會根據當前bucket的佔用情況自動調整容量(超過Load Facotr則resize為原來的2倍)。獲取物件時,我們將K傳給get,它呼叫hashCode計算hash從而得到bucket位置,並進一步呼叫equals()方法確定鍵值對。如果發生碰撞的時候,Hashmap通過連結串列將產生碰撞衝突的元素組織起來,在Java 8中,如果一個bucket中碰撞衝突的元素超過某個限制(預設是8),則使用紅黑樹來替換連結串列,從而提高速度。

1.3 講一下 HashMap 中 put 方法過程?

1.對key的hashCode做hash操作,然後再計算在bucket中的index(1.5 HashMap的雜湊函式);
2.如果沒碰撞直接放到bucket裡;
3.如果碰撞了,以連結串列的形式存在buckets後;
4.如果節點已經存在就替換old value(保證key的唯一性)
5.如果bucket滿了(超過閾值,閾值=loadfactor*current capacity,load factor預設0.75),就要resize。

1.4 get()方法的工作原理?

  通過對key的hashCode()進行hashing,並計算下標( n-1 & hash),從而獲得buckets的位置。如果產生碰撞,則利用key.equals()方法去連結串列中查詢對應的節點。

1.5 HashMap中hash函式怎麼是是實現的?還有哪些 hash 的實現方式?

  1. 對key的hashCode做hash操作(高16bit不變,低16bit和高16bit做了一個異或);
  2. h & (length-1); //通過位操作得到下標index。

  還有數字分析法、平方取中法、分段疊加法、 除留餘數法、 偽隨機數法。

1.6 HashMap 怎樣解決衝突?

  HashMap中處理衝突的方法實際就是鏈地址法,內部資料結構是陣列+單鏈表。

1.6.1 擴充套件問題1:當兩個物件的hashcode相同會發生什麼?

  因為兩個物件的Hashcode相同,所以它們的bucket位置相同,會發生“碰撞”。HashMap使用連結串列儲存物件,這個Entry(包含有鍵值對的Map.Entry物件)會儲存在連結串列中。

1.6.2 擴充套件問題2:拋開 HashMap,hash 衝突有那些解決辦法?

  開放定址法、鏈地址法、再雜湊法。

1.7 如果兩個鍵的hashcode相同,你如何獲取值物件?

  重點在於理解hashCode()與equals()。
  通過對key的hashCode()進行hashing,並計算下標( n-1 & hash),從而獲得buckets的位置。兩個鍵的hashcode相同會產生碰撞,則利用key.equals()方法去連結串列或樹(java1.8)中去查詢對應的節點。

1.8 針對 HashMap 中某個 Entry 鏈太長,查詢的時間複雜度可能達到 O(n),怎麼優化?

  將連結串列轉為紅黑樹,實現 O(logn) 時間複雜度內查詢。JDK1.8 已經實現了。

1.9 如果HashMap的大小超過了負載因子(load factor)定義的容量,怎麼辦?

  擴容。這個過程也叫作rehashing,因為它重建內部資料結構,並呼叫hash方法找到新的bucket位置。
  大致分兩步:
  1.擴容:容量擴充為原來的兩倍(2 * table.length);
  2.移動:對每個節點重新計算雜湊值,重新計算每個元素在陣列中的位置,將原來的元素移動到新的雜湊表中。
  
補充:
loadFactor:載入因子。預設值DEFAULT_LOAD_FACTOR = 0.75f;
capacity:容量;
threshold:閾值=capacity*loadFactor。當HashMap中儲存資料的數量達到threshold時,就需要將HashMap的容量加倍(capacity*2);
size:HashMap的大小,它是HashMap儲存的鍵值對的數量。

1.10 為什麼String, Interger這樣的類適合作為鍵?

  String, Interger這樣的類作為HashMap的鍵是再適合不過了,而且String最為常用。
  因為String物件是不可變的,而且已經重寫了equals()和hashCode()方法了。
  1.不可變性是必要的,因為為了要計算hashCode(),就要防止鍵值改變,如果鍵值在放入時和獲取時返回不同的hashcode的話,那麼就不能從HashMap中找到你想要的物件。不可變性還有其他的優點如執行緒安全。
  注:String的不可變性可以看這篇文章《【java基礎】淺析String》
  2.因為獲取物件的時候要用到equals()和hashCode()方法,那麼鍵物件正確的重寫這兩個方法是非常重要的。如果兩個不相等的物件返回不同的hashcode的話,那麼碰撞的機率就會小些,這樣就能提高HashMap的效能。

二、HashMap與HashTable區別

  Hashtable可以看做是執行緒安全版的HashMap,兩者幾乎“等價”(當然還是有很多不同)。Hashtable幾乎在每個方法上都加上synchronized(同步鎖),實現執行緒安全。

2.1 區別

  1.HashMap繼承於AbstractMap,而Hashtable繼承於Dictionary;
  2.執行緒安全不同。Hashtable的幾乎所有函式都是同步的,即它是執行緒安全的,支援多執行緒。而HashMap的函式則是非同步的,它不是執行緒安全的。若要在多執行緒中使用HashMap,需要我們額外的進行同步處理;
  3.null值。HashMap的key、value都可以為null。Hashtable的key、value都不可以為null;
  4.迭代器(Iterator)。HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以當有其它執行緒改變了HashMap的結構(增加或者移除元素),將會丟擲ConcurrentModificationException。
  5.容量的初始值和增加方式都不一樣:HashMap預設的容量大小是16;增加容量時,每次將容量變為“原始容量x2”。Hashtable預設的容量大小是11;增加容量時,每次將容量變為“原始容量x2 + 1”;
  6.新增key-value時的hash值演算法不同:HashMap新增元素時,是使用自定義的雜湊演算法。Hashtable沒有自定義雜湊演算法,而直接採用的key的hashCode()。
  7.速度。由於Hashtable是執行緒安全的也是synchronized,所以在單執行緒環境下它比HashMap要慢。如果你不需要同步,只需要單一執行緒,那麼使用HashMap效能要好過Hashtable。

2.2 能否讓HashMap同步?

  HashMap可以通過下面的語句進行同步:Map m = Collections.synchronizeMap(hashMap);