Java容器筆記(二):不同集合實(shí)現(xiàn)類的特點(diǎn)與區(qū)別

package java.util包中的Collection相關(guān)接口和類如下圖:

Collection.png

僅討論Java.util包中的常見集合類,不涉及java.util的子包c(diǎn)oncurrent中的并發(fā)集合類。
可以這樣簡(jiǎn)單的來對(duì)待容器中集合:


Collection_common.png

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.png

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),可以前向或后向迭代,

ArrayDeque.png

優(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ǔ)充

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,778評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,795評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,993評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,229評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評(píng)論 1 286
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,687評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,990評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容