文章目錄
- 常用集合類的使用
- HashMap與HashTable的區別?
- JDK1.8以後HashMap的put方法的具體流程?
- ArrayList、LinkList、Vetor的區別
- HashMap、HashTable、ConcurrenHashMap的區別
- HashMap 和 ConcurrentHashMap 的區別?
- Set和List的區別
- Set如何保證元素不重複
- Java8中stream相關用法
- 不同版本JDK的HashMap的實現的區別以及原因
- Collection和Collections的區別
- Arrays.asList獲得的List使用時需要注意什麼
- fail-fast和fail-safe
- CopyOnWriteArrayList、ConcurrentSkipListMap
常用集合類的使用
- 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的實現的區別以及原因
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異常。而如果不進行迭代遍歷,而是併發修改集合類,則可能會出現其他的異常如數組越界異常。