面試題: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幾種實現。
這三者都實現了List 接口,使用方式也很相似,主要區別在於因爲實現方式的不同,所以對不同的操作具有不同的效率。

  • ArrayList
    是一個可改變大小的數組.當更多的元素加入到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、ConcurrenHashMap的區別

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以前的版本。

Set和List的區別

List,Set都是繼承自Collection接口。都是用來存儲一組相同類型的元素的。

  • List特點:元素有放入順序,元素可重複 。有順序,即先放入的元素排在前面。
  • Set特點:元素無放入順序,元素不可重複。無順序,即先放入的元素不一定排在前面。不可重複,即相同元素在set中只會保留一份。所以,有些場景下,set可以用來去重。不過需要注意的是,set在元素插入時是要有一定的方法來判斷元素是否重複的。這個方法很重要,決定了set中可以保存哪些元素。

Set如何保證元素不重複

hashCode() + equals()

Java8中stream相關用法

Stream不是集合元素,更不是數據結構,它跟數據的存儲沒有任何關係,它只是一種針對數據的計算而存在的,可以把它看成是更高級的迭代器。

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

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

快速失敗(fail-fast)與安全失敗(fail-safe)
在Collection集合的各個類中,有線程安全和線程不安全這2大類的版本。對於線程不安全的類,併發情況下可能會出現fail-fast情況;而線程安全的類,可能出現fail-safe的情況。

當一個或多個線程正在遍歷一個集合Collection的時候(Iterator遍歷),而此時另一個線程修改了這個集合的內容(如添加,刪除或者修改)。這就是併發修改的情況。

fail-fast機制:當遍歷一個集合對象時,如果集合對象的結構被修改了,就會拋出ConcurrentModificationExcetion異常。迭代器的快速失敗行爲應該僅用於檢測 bug。
舉例

ArrayList繼承自AbstractList類,AbstractList內部有一個字段modCount,代表修改的次數。
當使用ArrayList.iterator()返回一個迭代器對象時。迭代器對象有一個屬性expectedModCount,它被賦值爲該方法調用時modCount的值。這意味着,這個值是modCount在這個時間點的快照值,expectedModCount值在iterator對象內部不會再發送變化!在得到迭代器之後,如果我們使用ArrayList的add、remove等方法,會使得modCount的值自增(發生了變化),而iterator內部的expectedModCount值卻還是之前的快照值。
而iterator的方法實現中,在調用next方法時,第一步就是檢查modCount值和迭代器內部的expectedModCount值是否相等!當集合對象被修改後,值不相等,所以在調用next方法的時候,就拋出了ConcurrentModificationException異常。

爲什麼說迭代器的fail-fast機制是盡最大努力地拋出ConcurrentModificationException異常呢?
原因就是上面的舉例,只有在迭代過程中修改了元素的結構,當再調用next()方法時纔會拋出該異常。也就是說,如果迭代過程中發生了修改,但之後沒有調用next()迭代,該異常就不會拋出了!(該異常的機制是告訴你,當前迭代器要進行操作是有問題的,因爲集合對象現在的狀態發生了改變!)
所以Fail-Safe 迭代的缺點是:首先是iterator不能保證返回集合更新後的數據,因爲其工作在集合克隆上,而非集合本身。其次,創建集合拷貝需要相應的開銷,包括時間和內存。在java.util.concurrent 包中集合的迭代器,如 ConcurrentHashMap, CopyOnWriteArrayList等默認爲都是Fail-Safe。

爲什麼iterator.remove()方法可行呢?
remove方法沒有進行modCount值的檢查,並且手動把expectedModCount值修改成了modCount值,這又保證了下一次迭代的正確。

Fail-Safe 迭代的出現,是爲了解決fail-fast拋出異常處理不方便的情況。fail-safe是針對線程安全的集合類。

fail-fast發生時,程序會拋出異常,而fail-safe是一個概念,併發容器的併發修改不會拋出異常,這和其實現有關。
併發容器的iterate方法返回的iterator對象,內部都是保存了該集合對象的一個快照副本,並且沒有modCount等數值做檢查。這也造成了併發容器的iterator讀取的數據是某個時間點的快照版本。你可以併發讀取,不會拋出異常,但是不保證你遍歷讀取的值和當前集合對象的狀態是一致的!這就是安全失敗的含義。

多線程下的情況
當然,如果多線程下使用迭代器也會拋出ConcurrentModificationException異常。而如果不進行迭代遍歷,而是併發修改集合類,則可能會出現其他的異常如數組越界異常。

CopyOnWriteArrayList、ConcurrentSkipListMap

ConcurrentSkipListMap與CopyOnWriteArrayList

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