package java.util
包中的Collection相關(guān)接口和類如下圖:
僅討論Java.util包中的常見集合類,不涉及java.util的子包c(diǎn)oncurrent中的并發(fā)集合類。
可以這樣簡(jiǎn)單的來對(duì)待容器中集合:
1、 List、Set、Queue三個(gè)接口的意義
首先,接口是一種規(guī)范,按照接口規(guī)范實(shí)現(xiàn)接口的的方法,就能提供所期望的功能。
三個(gè)接口只是規(guī)定了三類不同特性的集合,有時(shí)候?qū)崿F(xiàn)類會(huì)同時(shí)實(shí)現(xiàn)其中的多個(gè)特性,如LinkedList:
LinkedList既實(shí)現(xiàn)了List接口,又(間接)實(shí)現(xiàn)了Queue接口。它是List還是Queue呢?所以要理解這三個(gè)接口存在的意義。
- List:元素有序,元素可重復(fù),添加的元素放在最后(按照插入順序保存元素)
//Appends the specified element to the end of this list
boolean add(E e);
List是有序的Collection,使用此接口能夠精確的控制每個(gè)元素插入的位置。用戶能夠使用索引(元素在List中的位置,類似于數(shù)組下標(biāo))來訪問List中的元素,類似于Java的數(shù)組。List允許有相同的元素。除了具有Collection接口必備的iterator()方法外,List還提供一個(gè)listIterator()方法,返回一個(gè) ListIterator接口,和標(biāo)準(zhǔn)的Iterator接口相比,ListIterator多了一些add()之類的方法,允許添加,刪除,設(shè)定元素, 還能向前或向后進(jìn)行雙向遍歷。
- **Set:元素?zé)o序并且不允許重復(fù)元素 **
// If this set already contains the element, the call leaves the set
//unchanged and returns false. In combination with the
//restriction on constructors, this ensures that sets never contain
//duplicate elements.
boolean add(E e);
Set是一種不包含重復(fù)的元素的Collection,即任意的兩個(gè)元素e1和e2都有e1.equals(e2)=false,Set最多有一個(gè)null元素。
很明顯,Set的構(gòu)造函數(shù)有一個(gè)約束條件,傳入的Collection參數(shù)不能包含重復(fù)的元素。
請(qǐng)注意:必須小心操作可變對(duì)象(Mutable Object)。如果一個(gè)Set中的可變?cè)馗淖兞俗陨頎顟B(tài)導(dǎo)致Object.equals(Object)=true將導(dǎo)致一些問題。
- Queue:元素有序,先進(jìn)先出
它們都是通過擴(kuò)展Collection接口而來。Collection接口規(guī)范了存放一組對(duì)象的方式,而這組對(duì)象在存放時(shí)具有哪些特點(diǎn)(元素有序還是無序,元素允不允許重復(fù)等),則通過這三個(gè)接口進(jìn)一步規(guī)范,實(shí)現(xiàn)了某個(gè)接口就具有某個(gè)接口規(guī)定的特性。而具體的實(shí)現(xiàn)方式的不同,使得各實(shí)現(xiàn)類的應(yīng)用場(chǎng)景不同。
2、各實(shí)現(xiàn)類的特點(diǎn)
先看List:
-
ArrayList
數(shù)據(jù)結(jié)構(gòu)為數(shù)組,訪問快(可以直接通過下標(biāo)訪問),增刪慢,未實(shí)現(xiàn)線程同步。
ArrayList實(shí)現(xiàn)了可變大小的數(shù)組。它允許所有元素,包括null。ArrayList沒有同步。size,isEmpty,get,set方法運(yùn)行時(shí)間為常數(shù)。但是add方法開銷為分?jǐn)偟某?shù),添加n個(gè)元素需要O(n)的時(shí)間。其他的方法運(yùn)行時(shí)間為線性。</br>
每個(gè)ArrayList實(shí)例都有一個(gè)容量(Capacity),即用于存儲(chǔ)元素的數(shù)組的大小。這個(gè)容量可隨著不斷添加新元素而自動(dòng)增加,但是增長(zhǎng)算法 并沒有定義。當(dāng)需要插入大量元素時(shí),在插入前可以調(diào)用ensureCapacity方法來增加ArrayList的容量以提高插入效率。和LinkedList一樣,ArrayList也是非同步的(unsynchronized)。
-
LinkedList
數(shù)據(jù)結(jié)構(gòu)為鏈表,增刪速度快,查詢慢,未實(shí)現(xiàn)線程同步
LinkedList實(shí)現(xiàn)了List接口,允許null元素。此外LinkedList提供額外的get,remove,insert方法在 LinkedList的首部或尾部。這些操作使LinkedList可被用作堆棧(stack),隊(duì)列(queue)或雙向隊(duì)列(deque)。
-
Vector類
數(shù)據(jù)結(jié)構(gòu)為數(shù)組,訪問快(可以直接通過下標(biāo)訪問),增刪慢,實(shí)現(xiàn)線程同步
Vector非常類似ArrayList,但是Vector是同步的。由Vector創(chuàng)建的Iterator,雖然和 ArrayList創(chuàng)建的Iterator是同一接口,但是,因?yàn)閂ector是同步的,當(dāng)一個(gè)Iterator被創(chuàng)建而且正在被使用,另一個(gè)線程改變了 Vector的狀態(tài)(例如,添加或刪除了一些元素),這時(shí)調(diào)用Iterator的方法時(shí)將拋出 ConcurrentModificationException。
再看Set
-
HashSet
數(shù)據(jù)結(jié)構(gòu)為哈希表,元素?zé)o序、不重復(fù),至多有一個(gè)null元素
底層使用 HashMap 來保存所有元素,因此 HashSet 的實(shí)現(xiàn)比較簡(jiǎn)單,相關(guān) HashSet 的操作,基本上都是直接調(diào)用底層 HashMap 的相關(guān)方法來完成。
特點(diǎn)如下
? 不能保證元素的排列順序,順序有可能發(fā)生變化
? 不是同步的
? 集合元素可以是null,但只能放入一個(gè)null
當(dāng)向HashSet結(jié)合中存入一個(gè)元素時(shí),HashSet會(huì)調(diào)用該對(duì)象的hashCode()方法來得到該對(duì)象的hashCode值,然后根據(jù) hashCode值來決定該對(duì)象在HashSet中存儲(chǔ)位置。
簡(jiǎn)單的說,HashSet集合判斷兩個(gè)元素相等的標(biāo)準(zhǔn)是兩個(gè)對(duì)象通過equals方法比較相等,并且兩個(gè)對(duì)象的hashCode()方法返回值相 等
注意,如果要把一個(gè)對(duì)象放入HashSet中,重寫該對(duì)象對(duì)應(yīng)類的equals方法,也應(yīng)該重寫其hashCode()方法。其規(guī)則是如果兩個(gè)對(duì) 象通過equals方法比較返回true時(shí),其hashCode也應(yīng)該相同。另外,對(duì)象中用作equals比較標(biāo)準(zhǔn)的屬性,都應(yīng)該用來計(jì)算 hashCode的值。
-
LinkedHashSet
數(shù)據(jù)結(jié)構(gòu)是哈希表和鏈表,與HashSet相比訪問更快,插入時(shí)性能稍微
LinkedHashSet繼承自HashSet。同樣是根據(jù)元素的hashCode值來決定元素的存儲(chǔ)位置,但是它同時(shí)使用鏈表維護(hù)元素的次序。這樣使得元素看起 來像是以插入順序保存的,也就是說,當(dāng)遍歷該集合時(shí)候,LinkedHashSet將會(huì)以元素的添加順序訪問集合的元素。
LinkedHashSet在迭代訪問Set中的全部元素時(shí),性能比HashSet好,但是插入時(shí)性能稍微遜色于HashSet。
-
TreeSet
數(shù)據(jù)結(jié)構(gòu)是二叉樹(紅黑樹),元素可排序、不重復(fù)
TreeSet是SortedSet接口的唯一實(shí)現(xiàn)類,TreeSet可以確保集合元素處于排序狀態(tài)。TreeSet支持兩種排序方式:自然排序和定制排序,其中自然排序?yàn)槟J(rèn)的排序方式。向TreeSet中加入的應(yīng)該是同一個(gè)類的對(duì)象。
TreeSet判斷兩個(gè)對(duì)象不相等的方式是兩個(gè)對(duì)象通過equals方法返回false,或者通過CompareTo方法比較沒有返回0
</br>自然排序
(1)TreeSet內(nèi)的元素實(shí)現(xiàn)Comparable
接口,重寫該接口的compareTo(Object obj)
方法,以此確定排序。(元素必須實(shí)現(xiàn)該接口,否則程序會(huì)拋出異常)。
(2)當(dāng)重寫元素對(duì)應(yīng)類的equals()
方法時(shí),應(yīng)該保證該方法與compareTo(Object obj)
方法有一致的結(jié)果,即如果兩個(gè)對(duì)象通過equals()
方法比較返回true時(shí),這兩個(gè)對(duì)象通過compareTo(Object obj)
方法比較結(jié)果應(yīng)該也為0(即相等)
</br>定制排序--
自然排序是根據(jù)集合元素的大小,以升序排列,如果要定制排序,應(yīng)該使用Comparator
接口,實(shí)現(xiàn)int compare(T o1,T o2)
方法
個(gè)人看法:兩種方式都是為了排序,一種是元素本身實(shí)現(xiàn)`Comparable`接口,另一種則是當(dāng)元素本身沒有實(shí)現(xiàn)`Comparable`接口時(shí),可以通過`Comparator`接口(傳入構(gòu)造器`TreeSet(Comparator comparator)`),兩者本身沒有什么區(qū)別。
Java : Comparable vs Comparator
In short, there isn't much difference. They are both ends to similar means. In general implement comparable for natural order, (natural order definition is obviously open to interpretation), and write a comparator for other sorting or comparison needs.
最后看Queue
-
ArrayDeque
數(shù)據(jù)結(jié)構(gòu)為數(shù)組,雙端隊(duì)列,在隊(duì)頭隊(duì)尾均可心插入或刪除元素
實(shí)現(xiàn)了DeQueue接口。DeQueue(Double-ended queue)繼承了Queue接口,創(chuàng)建雙向隊(duì)列,靈活性更強(qiáng),可以前向或后向迭代,
-
PriorityQueue
數(shù)據(jù)結(jié)構(gòu)為優(yōu)先級(jí)隊(duì)列,元素不允許null,非同步
優(yōu)先級(jí)隊(duì)列是一種什么樣的數(shù)據(jù)結(jié)構(gòu)
優(yōu)先級(jí)隊(duì)列是不同于先進(jìn)先出隊(duì)列的另一種隊(duì)列。每次從隊(duì)列中取出的是具有最高優(yōu)先權(quán)的元素。
(1)優(yōu)先級(jí)隊(duì)列不是同步的(線程安全版本為PriorityBlockingQueue)
隊(duì)列的獲取操作如poll(),peek()和element()是訪問的隊(duì)列的頭,保證獲取的是最小的元素(根據(jù)指定的排序規(guī)則)
(2)返回的迭代器并不保證提供任何的有序性
(3)優(yōu)先級(jí)隊(duì)列不允許null元素,否則拋出NullPointException。
3、有助于理解的問答
- 遍歷一個(gè)List有哪些不同的方式?
List<String> strList = new ArrayList<>();
//使用for-each循環(huán)
for(String obj : strList){
System.out.println(obj);
}
//using iterator
Iterator<String> it = strList.iterator();
while(it.hasNext()){
String obj = it.next();
System.out.println(obj);
}
使用迭代器更加線程安全,因?yàn)樗梢源_保,在當(dāng)前遍歷的集合元素被更改的時(shí)候,它會(huì)拋出ConcurrentModificationException。
- 在迭代一個(gè)集合的時(shí)候,如何避免ConcurrentModificationException?
在遍歷一個(gè)集合的時(shí)候,我們可以使用并發(fā)集合類來避免ConcurrentModificationException,比如使用CopyOnWriteArrayList,而不是ArrayList。
4、常見面試題
- List和Set的區(qū)別
List有序,允許重復(fù),Set無序,不允許重復(fù) - ArrayList與LinkedList的區(qū)別
ArrayList為數(shù)組結(jié)構(gòu),LinkedList為鏈表結(jié)構(gòu)。所以,一個(gè)訪問快,一個(gè)增刪快。均未實(shí)現(xiàn)線程同步或者說都是線程不安全的。
(1)ArrayList是由Array所支持的基于一個(gè)索引的數(shù)據(jù)結(jié)構(gòu),所以它提供對(duì)元素的隨機(jī)訪問,復(fù)雜度為O(1),但LinkedList存儲(chǔ)一系列的節(jié)點(diǎn)數(shù)據(jù),每個(gè)節(jié)點(diǎn)都與前一個(gè)和下一個(gè)節(jié)點(diǎn)相連接。所以,盡管有使用索引獲取元素的方法,內(nèi)部實(shí)現(xiàn)是從起始點(diǎn)開始遍歷,遍歷到索引的節(jié)點(diǎn)然后返回元素,時(shí)間復(fù)雜度為O(n),比ArrayList要慢。
(2)與ArrayList相比,在LinkedList中插入、添加和刪除一個(gè)元素會(huì)更快,因?yàn)樵谝粋€(gè)元素被插入到中間的時(shí)候,不會(huì)涉及改變數(shù)組的大小,或更新索引。
(3)LinkedList比ArrayList消耗更多的內(nèi)存,因?yàn)長(zhǎng)inkedList中的每個(gè)節(jié)點(diǎn)存儲(chǔ)了前后節(jié)點(diǎn)的引用。
- ArrayList與Vector的區(qū)別
Vector同步,ArrayList不同步。
ArrayList和Vector在很多時(shí)候都很類似。
(1)兩者都是基于索引的,內(nèi)部由一個(gè)數(shù)組支持。
(2)兩者維護(hù)插入的順序,我們可以根據(jù)插入順序來獲取元素。
(3)ArrayList和Vector的迭代器實(shí)現(xiàn)都是fail-fast的。
(4)ArrayList和Vector兩者允許null值,也可以使用索引值對(duì)元素進(jìn)行隨機(jī)訪問。</br>
以下是ArrayList和Vector的不同點(diǎn)。
(1)Vector是同步的,而ArrayList不是。然而,如果你尋求在迭代的時(shí)候?qū)α斜磉M(jìn)行改變,你應(yīng)該使用CopyOnWriteArrayList。
(2)ArrayList比Vector快,它因?yàn)橛型剑粫?huì)過載。
(3)ArrayList更加通用,因?yàn)槲覀兛梢允褂肅ollections工具類輕易地獲取同步列表和只讀列表。
- Array和ArrayList有何區(qū)別?什么時(shí)候更適合用Array?
Array可以容納基本類型和對(duì)象,而ArrayList只能容納對(duì)象。
Array是指定大小的,而ArrayList大小是不固定的。
Array沒有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。盡管ArrayList明顯是更好的選擇,但也有些時(shí)候Array比較好用。</br>
(1)如果列表的大小已經(jīng)指定,大部分情況下是存儲(chǔ)和遍歷它們。
(2)對(duì)于遍歷基本數(shù)據(jù)類型,盡管Collections使用自動(dòng)裝箱來減輕編碼任務(wù),在指定大小的基本類型的列表上工作也會(huì)變得很慢。
(3)如果你要使用多維數(shù)組,使用[][]比List<List<>>更容易。
5、簡(jiǎn)單總結(jié)
如果元素唯一(不允許重復(fù)),使用Set:
支持排序則選擇TreeSet,不支持排序選擇HashSet。
如果元素不唯一(允許重復(fù)),使用List:
查詢多則選擇ArrayList,增刪多則選擇:LinkedList。
Vector雖然是ArrayList的線程同步版本,但還有更好地選擇。
關(guān)于Queue,由于工作中從未接觸過,待補(bǔ)充。
參考
未一一列出,待補(bǔ)充