Java集合容器面試題

主題 鏈接
Java基礎知識 面試題
Java集合容器 面試題
Java併發編程 面試題
Java底層知識 面試題
Java常用框架 面試題
計算機網絡 面試題
數據庫 面試題
RabbitMQ 面試題
Redis 面試題

面試套路

  1. Java中常用的集合類哪些?
  2. 你最常用的是哪些?
  3. 說一說實現原理
  4. 怎麼擴容、怎麼處理hash衝突、不同jdk版本的區別,有沒有線程安全的集合類

Java常用集合類有哪些?

  • Collection接口的子接口包括:Set接口和List接口
  • Map接口的實現類主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
  • Set接口的實現類主要有:HashSet、TreeSet、LinkedHashSet等
  • List接口的實現類主要有:ArrayList、LinkedList、Stack以及Vector等

HashMap與HashTable的區別?

  • HashMap沒有考慮同步,是線程不安全的;HashTable使用了synchronized關鍵字,是線程安全的;
  • HashMap允許K/V都爲null;後者K/V都不允許爲null;
  • HashMap繼承自AbstractMap類;而Hashtable繼承自Dictionary類;

JDK1.8以後HashMap的put方法的具體流程?

當 HashMap 中有大量的元素都存放到同一個桶中時,這個桶下有一條長長的鏈表,這個時候 HashMap 就相當於一個單鏈表,假如單鏈表有 n 個元素,遍歷的時間複雜度就是 O(n),完全失去了它的優勢。
針對這種情況,JDK 1.8 中引入了 紅黑樹(查找時間複雜度爲 O(logn))來優化這個問題。
在這裏插入圖片描述
在這裏插入圖片描述

ArrayList、LinkList、Vetor的區別?

List主要有ArrayList、LinkedList與Vector幾種實現。

  • ArrayList
    是一個可改變大小的數組.其大小可以動態地增長.內部的元素可以直接通過get與set方法進行訪問,因爲ArrayList本質上就是一個數組.
  • LinkedList
    是一個雙鏈表,在添加和刪除元素時具有比ArrayList更好的性能.但在get與set方面弱於ArrayList.這些都是指數據量很大或者操作很頻繁的情況下的對比
  • Vector
    和ArrayList類似,但屬於強同步類。如果你的程序本身是線程安全的(thread-safe,沒有在多個線程之間共享同一個集合/對象),那麼使用ArrayList是更好的選擇。
  • Vector和ArrayList在更多元素添加進來時會請求更大的空間。Vector每次請求其大小的雙倍空間,而ArrayList每次對size增長50%.而 LinkedList 還實現了 Queue 接口,該接口比List提供了更多的方法,包offer(),peek(),poll()等. 注意:默認情況下ArrayList的初始容量非常小,所以如果可以預估數據量的話,分配一個較大的初始值屬於最佳實踐,這樣可以減少調整大小的開銷。

HashMap、HashTable的區別?

  • 線程安全: HashTable 中的方法是同步的,而HashMap中的方法在默認情況下是非同步的。在多線程併發的環境下,可以直接使用HashTable,但是要使用HashMap的話就要自己增加同步處理了。
  • 繼承關係: HashTable是基於陳舊的Dictionary類繼承來的。HashMap繼承的抽象類AbstractMap實現了Map接口。
  • 允不允許null值:HashTable中,key和value都不允許出現null值,否則會拋出NullPointerException異常。HashMap中,null可以作爲鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應的值爲null。
  • 默認初始容量和擴容機制: HashTable中的hash數組初始大小是11,增加的方式是old*2+1。HashMap中hash數組的默認大小是16,而且一定是2的指數。
  • 哈希值的使用不同 : HashTable直接使用對象的hashCode。 HashMap重新計算hash值。
  • 遍歷方式的內部實現上不同 : Hashtable、HashMap都使用了 Iterator。而由於歷史原因,Hashtable還使用了Enumeration的方式 。 HashMap 實現Iterator,支持fast-fail,Hashtable的 Iterator 遍歷支持fast-fail,用 Enumeration不支持 fast-fail

HashMap 和 ConcurrentHashMap 的區別?

  • ConcurrentHashMap和HashMap的實現方式不一樣,雖然都是使用桶數組實現的,但是還是有區別,ConcurrentHashMap對桶數組進行了分段,而HashMap並沒有。
  • ConcurrentHashMap在每一個分段上都用鎖進行了保護。HashMap沒有鎖機制。所以,前者線程安全的,後者不是線程安全的。

PS:以上區別基於jdk1.8以前的版本。

不同版本JDK的HashMap的實現的區別以及原因

(1)鏈表元素的插入方式不一樣

  • JDK1.7用的是頭插法,而JDK1.8及之後使用的都是尾插法
  • 因爲JDK1.7是用單鏈表進行的縱向延伸,當採用頭插法時會容易出現逆序且環形鏈表死循環問題。
  • 在JDK1.8之後是因爲加入了紅黑樹使用尾插法,能夠避免出現逆序且鏈表死循環的問題。

(2)擴容後數據存儲位置的計算方式不一樣

  • 在JDK1.7的時候是直接用hash值和需要擴容的二進制數進行&(這裏就是爲什麼擴容的時候爲啥一定必須是2的多少次冪的原因所在,因爲如果只有2的n次冪的情況時最後一位二進制數才一定是1,這樣能最大程度減少hash碰撞)(hash值 & length-1)
  • 在JDK1.8的時候直接用了JDK1.7的時候計算的規律,也就是擴容前的原始位置+擴容的大小值=JDK1.8的計算方式,而不再是JDK1.7的那種異或的方法。但是這種方式就相當於只需要判斷Hash值的新增參與運算的位是0還是1就直接迅速計算出了擴容後的儲存方式。

(3)數據結構不一樣

  • JDK1.7的時候使用的是數組+ 單鏈表的數據結構。
  • 在JDK1.8及之後時,使用的是數組+鏈表+紅黑樹的數據結構(當鏈表的深度達到8的時候,也就是默認閾值,就會自動擴容把鏈表轉成紅黑樹的數據結構來把時間複雜度從O(n)變成O(logN)提高了效率)

(4)爲什麼在JDK1.8中HashMap把鏈表轉化爲紅黑樹的閾值是8?

  • 由於treenodes的大小大約是常規節點的兩倍,因此我們僅在容器包含足夠的節點以保證使用時才使用它們,當它們變得太小(由於移除或調整大小)時,它們會被轉換回普通的node節點,容器中節點分佈在hash桶中的頻率遵循泊松分佈,桶的長度超過8的概率非常非常小。所以作者應該是根據概率統計而選擇了8作爲閥值(Java註釋中的解釋)

Hashmap的結構,1.7和1.8有哪些區別

Collection和Collections的區別

  • Collection:是集合類的上層接口。本身是一個Interface,裏面包含了一些集合的基本操作。Collection接口是Set接口和List接口的父接口
  • Collections:Collections是一個集合框架的幫助類,裏面包含一些對集合的排序,搜索以及序列化的操作。

Arrays.asList獲得的List使用時需要注意什麼

Arrays.asList得到的List它的長度是不能改變的。當你向這個List添加或刪除一個元素時(例如 list.add(“d”);)程序就會拋出異常(java.lang.UnsupportedOperationException)。
public static List asList(T… a) {
return new ArrayList<>(a);
}

當你看到這段代碼時可能覺得沒啥問題啊,不就是返回了一個ArrayList對象嗎?問題就出在這裏。這個ArrayList不是java.util包下的,而是java.util.Arrays.ArrayList,顯然它是Arrays類自己定義的一個內部類!這個內部類沒有實現add()、remove()方法,而是直接使用它的父類AbstractList的相應方法。而AbstractList中的add()和remove()是直接拋出java.lang.UnsupportedOperationException異常的!

Fail-fast和Fail-safe

  • 線程不安全的類,併發情況下可能會出現快速失敗;線程安全的類,可能會出現安全失敗
  • 一個線程在遍歷,另一個線程在添加、刪除或修改,就會出現併發修改的問題
  • 當遍歷時檢測到併發修改,就會拋出異常:concurrentmodificationException,這就是快速失敗
  • ArrayList.iterator()返回一個迭代器對象,其中使用一個int類型的expectedModCount記錄狀態,當發生添加、刪除、修改操作時會更改這個值,當遍歷時調用next()會檢查這個值跟開始遍歷時是否一致,發現expectedModCount發生了變化,就意味着有併發修改,這時候就拋出異常iterator.remove()方法沒有進行modCount值的檢查,並且手動把expectedModCount值修改成了modCount值,這又保證了下一次迭代的正確。
  • fail-safe是一個概念,併發容器的併發修改不會拋出異常,這和其實現有關。併發容器的iterate方法返回的iterator對象,內部都是保存了該集合對象的一個快照副本,並且沒有modCount等數值做檢查。這也造成了併發容器的iterator讀取的數據是某個時間點的快照版本。你可以併發讀取,不會拋出異常,但是不保證你遍歷讀取的值和當前集合對象的狀態是一致的!這就是安全失敗的含義。

CopyOnWriteArrayList、ConcurrentSkipListMap

  • ConcurrentSkipListMap和ConcurrentSkipListSet是TreeMap和TreeSet的有序容器的併發版本
  • ConcurrentSkipListMap的底層是通過跳錶來實現的。跳錶(Skiplist)是一個鏈表,但是通過使用“跳躍式”查找的方式使得插入、讀取數據時複雜度變成了O(logn),跳錶以空間換時間,是基於鏈表實現的一種類似“二分”的算法。
  • CopyOnWriteArrayList使用了一種叫寫時複製的方法,當有新元素添加到CopyOnWriteArrayList時,先從原有的數組中拷貝一份出來,然後在新的數組做寫操作,寫完之後,再將原來的數組引用指向到新數組,合適讀多寫少的場景

ConcurrentSkipListMap與CopyOnWriteArrayList

Hashmap 什麼時候進行擴容呢?

  • 默認大小爲16、負載因子爲0.75,即超過12就擴容,容量擴大一倍
  • 新建hashmap時設置初始大小,假如有1000個元素,不能設置1000,因爲元素數量爲750時就會自動擴容,要避免自動擴容,要讓元素數量不超過初始容量的0.75
  • 擴容時會重新計算元素在數組中的位置,儘量避免擴容
  • Hash的公式—> index = HashCode(Key) & (Length - 1)
  • 因爲resize的賦值方式,也就是使用了單鏈表的頭插入方式,同一位置上新元素總會被放在鏈表的頭部位置,在舊數組中同一條Entry鏈上的元素,通過重新計算索引位置後,有可能被放到了新數組的不同位置上。會形成環形列表。
  • 使用頭插會改變鏈表的上的順序,但是如果使用尾插,在擴容時會保持鏈表元素原本的順序,就不會出現鏈表成環的問題了。Java8在同樣的前提下並不會引起死循環,原因是擴容轉移後前後鏈表順序不變,保持之前節點的引用關係。

HashMap的默認初始化長度是多少?爲什麼?

  • 默認是16,可以是其他2的冪
  • hash函數可以將任意長度的輸入經過變化以後得到固定長度的輸出,如果兩個元素不相同,但是hash函數的值相同,這兩個元素就是一個碰撞
  • 爲了減少hash值的碰撞,需要實現一個儘量均勻分佈的hash函數,在HashMap中index = key的hashcode值 & length-1
  • 長度16或者其他2的冪時,length - 1的值是所有二進制位全爲1,這種情況下index的結果等同於hashcode後幾位的值,只要輸入的hashcode本身分佈均勻,hash算法的結果就是均勻的
  • 所以HashMap的默認長度爲16,是爲了降低hash碰撞的機率

哈希表如何解決Hash衝突?

在這裏插入圖片描述

爲什麼 HashMap 中 String、Integer 這樣的包裝類適合作爲 key 鍵

在這裏插入圖片描述

HashMap 中的 key若爲 Object類型, 則需實現哪些方法?

在這裏插入圖片描述

List、Map、Set 三個接口,存取元素時,各有什麼特點?

在這裏插入圖片描述

Set 裏的元素是不能重複的,那麼用什麼方法來區分重複與否呢? 是用 == 還是equals()? 它們有何區別?

在這裏插入圖片描述

Java 集合類框架的最佳實踐有哪些?

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章