一、集合

最近把基礎知識溫習了一遍,梳理彙總下知識點,結合網上查詢的資料,做一系列筆記,加深理解和記憶。

一、集合

1、集合架構圖,圖片來自java集合百度百科:

上述類圖中,實線邊框的是實現類,比如ArrayList,LinkedList,HashMap等,折線邊框的是抽象類,比如AbstractCollection,AbstractList,AbstractMap等,而點線邊框的是接口,比如Collection,Iterator,List等。

如圖:Iterator接口是一切集合的原點,這是一個用於遍歷集合中元素的接口,主要包含hashNext(),next(),remove()三種方法,也就是說所有的集合都實現Iterator,可以遍歷並使用這3個方法。它的一個子接口LinkedIterator在它的基礎上又添加了三種方法,分別是add(),previous(),hasPrevious()。也就是說如果是先Iterator接口,那麼在遍歷集合中元素的時候,只能往後遍歷,被遍歷後的元素不會在遍歷到,通常無序集合實現的都是這個接口,比如HashSet,HashMap;而那些元素有序的集合,實現的一般都是LinkedIterator接口,實現這個接口的集合可以雙向遍歷,既可以通過next()訪問下一個元素,又可以通過previous()訪問前一個元素,比如ArrayList。

還有一個特點就是抽象類的使用。如果要自己實現一個集合類,去實現那些抽象的接口會非常麻煩,工作量很大。這個時候就可以使用抽象類,這些抽象類中給我們提供了許多現成的實現,我們只需要根據自己的需求重寫一些方法或者添加一些方法就可以實現自己需要的集合類,工作強度大大降低。

2、集合三大類

如圖:集合3大類:Set/List/Map

1、Set特點:

一個不包含重複元素的 collection。更確切地講,set 不包含滿足 e1.equals(e2) 的元素對 e1e2,並且最多包含一個 null 元素。正如其名稱所暗示的,此接口模仿了數學上的 set 抽象。

在所有構造方法以及 add、equals 和 hashCode 方法的協定上,Set 接口還加入了其他規定,這些規定超出了從 Collection 接口所繼承的內容。出於方便考慮,它還包括了其他繼承方法的聲明(這些聲明的規範已經專門針對 Set 接口進行了修改,但是沒有包含任何其他的規定)。

對這些構造方法的其他規定是(不要奇怪),所有構造方法必須創建一個不包含重複元素的 set(正如上面所定義的)。

注:如果將可變對象用作 set 元素,那麼必須極其小心。如果對象是 set 中某個元素,以一種影響 equals 比較的方式改變對象的值,那麼 set 的行爲就是不確定的。此項禁止的一個特殊情況是不允許某個 set 包含其自身作爲元素。

某些 set 實現對其所包含的元素有所限制。例如,某些實現禁止 null 元素,而某些則對其元素的類型所有限制。試圖添加不合格的元素會拋出未經檢查的異常,通常是 NullPointerException 或 ClassCastException。試圖查詢不合格的元素是否存在可能會拋出異常,也可能簡單地返回 false;某些實現會採用前一種行爲,而某些則採用後者。概括地說,試圖對不合格元素執行操作時,如果完成該操作後不會導致在 set 中插入不合格的元素,則該操作可能拋出一個異常,也可能成功,這取決於實現的選擇。此接口的規範中將這樣的異常標記爲“可選”。

此接口是 Java Collections Framework 的成員。

Set的主要實現類:

HashSet:

HashSet的底層是採用HashMap實現的,HashSet不是key value結構,僅僅是存儲不重複的元素,相當於簡化版的HashMap,只是包含HashMap中的key而已。HashSet類直接實現Set接口,其底層其實是包裝了一個HashMap去實現的,HashSet採用HashCode算法來存取集合中的元素,往HashSet添加元素的時候,HashSet會先調用元素的hashCode方法得到元素的哈希值 ,然後通過元素 的哈希值經過移位等運算,就可以算出該元素在哈希表中 的存儲位置。

情況1: 如果算出元素存儲的位置目前沒有任何元素存儲,那麼該元素可以直接存儲到該位置上。

情況2: 如果算出該元素的存儲位置目前已經存在有其他的元素了,那麼會調用該元素的equals方法與該位置的元素再比較一次,如果equals返回的是true,那麼該元素與這個位置上的元素就視爲重複元素,不允許添加,如果equals方法返回的是false,那麼該元素運行添加

LinkedHashSet: 

LinkedHashSet是具有可預知迭代順序的Set接口的哈希表和鏈接列表實現。此實現與HashSet的不同之處在於,後者維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可爲插入順序或是訪問順序。對於LinkedHashSet而言,它繼承與HashSet、又基於LinkedHashMap來實現的。LinkedHashSet底層使用LinkedHashMap來保存所有元素,它繼承與HashSet,其所有的方法操作上又與HashSet相同,因此LinkedHashSet 的實現上非常簡單,只提供了四個構造方法,並通過傳遞一個標識參數,調用父類的構造器,底層構造一個LinkedHashMap來實現,在相關操作上與父類HashSet的操作相同,直接調用父類HashSet的方法即可。

TreeSet:

TreeSet是一個有序的集合,基於TreeMap實現,支持兩種排序方式:自然排序和定製排序。在構造方法中會創建一個TreeMap實例,用於存放元素,另外TreeSet是有序的,也提供了制定比較器的構造函數,如果沒有提供比較器,則採用key的自然順序進行比較大小,如果指定的比較器,則採用指定的比較器,進行key值大小的比較。

2、List特點

先看API上的介紹:

有序的 collection(也稱爲序列)。此接口的用戶可以對列表中每個元素的插入位置進行精確地控制。用戶可以根據元素的整數索引(在列表中的位置)訪問元素,並搜索列表中的元素。

與 set 不同,列表通常允許重複的元素。更確切地講,列表通常允許滿足 e1.equals(e2) 的元素對 e1 和 e2,並且如果列表本身允許 null 元素的話,通常它們允許多個 null 元素。難免有人希望通過在用戶嘗試插入重複元素時拋出運行時異常的方法來禁止重複的列表,但我們希望這種用法越少越好。

List 接口在 iterator、add、remove、equals 和 hashCode 方法的協定上加了一些其他約定,超過了 Collection 接口中指定的約定。爲方便起見,這裏也包括了其他繼承方法的聲明。

List 接口提供了 4 種對列表元素進行定位(索引)訪問方法。列表(像 Java 數組一樣)是基於 0 的。注意,這些操作可能在和某些實現(例如 LinkedList 類)的索引值成比例的時間內執行。因此,如果調用者不知道實現,那麼在列表元素上迭代通常優於用索引遍歷列表。

List 接口提供了特殊的迭代器,稱爲 ListIterator,除了允許 Iterator 接口提供的正常操作外,該迭代器還允許元素插入和替換,以及雙向訪問。還提供了一個方法來獲取從列表中指定位置開始的列表迭代器。

List 接口提供了兩種搜索指定對象的方法。從性能的觀點來看,應該小心使用這些方法。在很多實現中,它們將執行高開銷的線性搜索。

List 接口提供了兩種在列表的任意位置高效插入和移除多個元素的方法。

注意:儘管列表允許把自身作爲元素包含在內,但建議要特別小心:在這樣的列表上,equals 和 hashCode 方法不再是定義良好的。

某些列表實現對列表可能包含的元素有限制。例如,某些實現禁止 null 元素,而某些實現則對元素的類型有限制。試圖添加不合格的元素會拋出未經檢查的異常,通常是 NullPointerException 或 ClassCastException。試圖查詢不合格的元素是否存在可能會拋出異常,也可能簡單地返回 false;某些實現會採用前一種行爲,而某些則採用後者。概括地說,試圖對不合格元素執行操作時,如果完成該操作後不會導致在列表中插入不合格的元素,則該操作可能拋出一個異常,也可能成功,這取決於實現的選擇。此接口的規範中將這樣的異常標記爲“可選”。

List的主要實現類:

Vector:這是1.2版本就出現的容器,有點事線程安全,缺點是速度慢(即非多線程),基本很少使用。

ArrayList:List 接口的大小可變數組的實現。實現了所有可選列表操作,並允許包括 null 在內的所有元素。除了實現 List 接口外,此類還提供一些方法來操作內部用來存儲列表的數組的大小。(此類大致上等同於 Vector 類,除了此類是不同步的。)ArrayList本質市一個數組容器,有默認長度,當添加元素個數超過默認長度時,會重新根據一定規則重新定義一個長度適合長的數組,先把老數組裏面的值複製到新數組裏面,要添加的值放到後面的空角標。所以arrayList特點是:增刪速度較慢,因爲不停的創建新數組。但是因爲是數組,有下標,所以查詢速度快,剛剛和下面講到的LinkedList情況相反,LinkedList特點是增刪快,查詢慢,根據情況使用。

LinkedList:接口的鏈接列表實現。實現所有可選的列表操作,並且允許所有元素(包括 null)。除了實現 List 接口外,LinkedList 類還爲在列表的開頭及結尾 get、remove 和 insert 元素提供了統一的命名方法。這些操作允許將鏈接列表用作堆棧、隊列雙端隊列。LinkedList是鏈表結構,即每個元素保留上個元素的內存地址和下個元素的內存地址,元素在內存上不是連續的,像使用以跟鏈子連接起來,所以查詢慢,例如你查詢第12個元素,要從0開始,查到1的地址,在根據1的地址查詢2的地址,以此類推,因此查詢速度慢。其增刪原理是例如我要在5的位置增加一個元素,其原理是找到元素5,把元素5的內存地址賦給該元素的下個元素的值,把自己的上個元素地址覆蓋該元素的上個元素地址,把添加的元素內存地址覆蓋原來的元素5的上個元素內存地址,同時把該元素的內存地址賦給元素4的下個元素地址。一次增刪只改變相鄰元素的綁定的內存地址,所以速度快。

3、Map特點

先看API上的介紹:

將鍵映射到值的對象。一個映射不能包含重複的鍵;每個鍵最多隻能映射到一個值。

Map 接口提供三種collection 視圖,允許以鍵集、值集或鍵-值映射關係集的形式查看某個映射的內容。映射順序 定義爲迭代器在映射的 collection 視圖上返回其元素的順序。某些映射實現可明確保證其順序,如 TreeMap 類;另一些映射實現則不保證順序,如 HashMap 類。

注:將可變對象用作映射鍵時必須格外小心。當對象是映射中某個鍵時,如果以影響 equals 比較的方式更改了對象的值,則映射的行爲將是不確定的。此項禁止的一種特殊情況是不允許某個映射將自身作爲一個鍵包含。雖然允許某個映射將自身作爲值包含,但請格外小心:在這樣的映射上 equals 和 hashCode 方法的定義將不再是明確的。

所有通用的映射實現類應該提供兩個“標準的”構造方法:一個 void(無參數)構造方法,用於創建空映射;一個是帶有單個 Map 類型參數的構造方法,用於創建一個與其參數具有相同鍵-值映射關係的新映射。實際上,後一個構造方法允許用戶複製任意映射,生成所需類的一個等價映射。儘管無法強制執行此建議(因爲接口不能包含構造方法),但是 JDK 中所有通用的映射實現都遵從它。

此接口中包含的“破壞”方法可修改其操作的映射,如果此映射不支持該操作,這些方法將拋出 UnsupportedOperationException。如果是這樣,那麼在調用對映射無效時,這些方法可以(但不要求)拋出 UnsupportedOperationException。例如,如果某個不可修改的映射(其映射關係是“重疊”的)爲空,則對該映射調用 putAll(Map) 方法時,可以(但不要求)拋出異常。

某些映射實現對可能包含的鍵和值有所限制。例如,某些實現禁止 null 鍵和值,另一些則對其鍵的類型有限制。嘗試插入不合格的鍵或值將拋出一個未經檢查的異常,通常是 NullPointerException 或 ClassCastException。試圖查詢是否存在不合格的鍵或值可能拋出異常,或者返回 false;某些實現將表現出前一種行爲,而另一些則表現後一種。一般來說,試圖對不合格的鍵或值執行操作且該操作的完成不會導致不合格的元素被插入映射中時,將可能拋出一個異常,也可能操作成功,這取決於實現本身。這樣的異常在此接口的規範中標記爲“可選”。

此接口是 Java Collections Framework 的成員。

Collections Framework 接口中的很多方法是根據 equals 方法定義的。例如,containsKey(Object key) 方法的規範中寫道:“當且僅當此映射包含針對滿足 (key==null ? k==null : key.equals(k)) 的鍵 k 的映射關係時,返回 true”。 應將此規範解釋爲:調用具有非空參數 key 的 Map.containsKey 將導致對任意的鍵 k 調用 key.equals(k)。實現可隨意進行優化,以避免調用 equals,例如,可首先比較兩個鍵的哈希碼(Object.hashCode() 規範保證哈希碼不相等的兩個對象不會相等)。一般來說,只要實現者認爲合適,各種 Collections Framework 接口的實現可隨意利用底層 Object 方法的指定行爲。

Map的主要實現類:

ConcurrentHashMap 線程安全的Map;

HashMap:HashMap採用了數組和鏈表的數據結構,能在查詢和修改方便繼承了數組的線性查找和鏈表的尋址修改。HashMap的關鍵是hashcode算法,根據hashcode算出key的數組角標。但是當hashcode重複了真麼辦?例如:第一個鍵值對A進來,通過計算其key的hash得到的index=0,記做:Entry[0] = A。一會後又進來一個鍵值對B,通過計算其index也等於0,現在怎麼辦?HashMap會這樣做:B.next = A,Entry[0] = B,如果又進來C,index也等於0,那麼C.next = B,Entry[0] = C;這樣我們發現index=0的地方其實存取了A,B,C三個鍵值對,他們通過next這個屬性鏈接在一起。

LinkedHashMap :LinkedHashMap是HashMap的子類,實現的原理跟HashMap差不多,唯一的區別就是LinkedHashMap多了一個雙向循環鏈表。可以記錄元素的添加順序,因此可以按照添加順序遍歷,有序的。

TreeMap :

TreeMap的底層使用了紅黑樹來實現,像TreeMap對象中放入一個key-value 鍵值對時,就會生成一個Entry對象,這個對象就是紅黑樹的一個節點,其實這個和HashMap是一樣的,一個Entry對象作爲一個節點,只是這些節點存放的方式不同。存放每一個Entry對象時都會按照key鍵的大小按照二叉樹的規範進行存放,所以TreeMap中的數據是按照key從小到大排序的。

程序添加新節點時,總是從樹的根節點開始比較,即將根節點當成當前節點。如果新增節點大於當前節點並且當前節點的右節點存在,則以右節點作爲當前節點,如果新增節點小於當前節點並且當前節點的左子節點存在,則以左子節點作爲當前節點;如果新增節點等於當前節點,則用新增節點覆蓋當前節點,並結束循環 直到某個節點的左右子節點不存在,將新節點添加爲該節點的子節點。如果新節點比該節點大,則添加其爲右子節點。如果新節點比該節點小,則添加其爲左子節點;

總結:

1、三者的特點和區別

List:1.可以允許重複的對象。

    2.可以插入多個null元素。

        3.是一個有序容器,保持了每個元素的插入順序,輸出的順序就是插入的順序。

        4.常用的實現類有 ArrayList、LinkedList 和 Vector。ArrayList 最爲流行,它提供了使用索引的隨意訪問,而 LinkedList 則對於經常需要從 List 中添加或刪除元素的場合更爲合適。

 Set:1.不允許重複對象

     2. 無序容器,你無法保證每個元素的存儲順序,TreeSet通過 Comparator  或者 Comparable 維護了一個排序順序。

         3. 只允許一個 null 元素

         4.Set 接口最流行的幾個實現類是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基於 HashMap 實現的 HashSet;TreeSet 還實現了 SortedSet 接口,因此 TreeSet 是一個根據其 compare() 和 compareTo() 的定義進行排序的有序容器。

Map:1.Map不是collection的子接口或者實現類。Map是一個接口。

        2.Map 的 每個 Entry 都持有兩個對象,也就是一個鍵一個值,Map 可能會持有相同的值對象但鍵對象必須是唯一的。

        3. TreeMap 也通過 Comparator  或者 Comparable 維護了一個排序順序。

        4. Map 裏你可以擁有隨意個 null 值但最多只能有一個 null 鍵。

        5.Map 接口最流行的幾個實現類是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)

2、什麼場景下使用list,set,map:

       1. 如果你經常會使用索引來對容器中的元素進行訪問,那麼 List 是你的正確的選擇。如果你已經知道索引了的話,那麼 List 的實現類比如 ArrayList 可以提供更快速的訪問,如果經常添加刪除元素的,那麼肯定要選擇LinkedList。

       2. 如果你想容器中的元素能夠按照它們插入的次序進行有序存儲,那麼還是 List,因爲 List 是一個有序容器,它按照插入順序進行存儲。

       3. 如果你想保證插入元素的唯一性,也就是你不想有重複值的出現,那麼可以選擇一個 Set 的實現類,比如 HashSet、LinkedHashSet 或者 TreeSet。所有 Set 的實現類都遵循了統一約束比如唯一性,而且還提供了額外的特性比如 TreeSet 還是一個 SortedSet,所有存儲於 TreeSet 中的元素可以使用 Java 裏的 Comparator 或者 Comparable 進行排序。LinkedHashSet 也按照元素的插入順序對它們進行存儲。

       4. 如果你以鍵和值的形式進行數據存儲那麼 Map 是你正確的選擇。你可以根據你的後續需要從 Hashtable、HashMap、TreeMap 中進行選擇。

3、其他

      1、集合排序的方法

            1. Collections.sort(List list):改方法爲自然排序,參與排序的對象需實現comparable接口,重寫其compareTo()方法

            Collections.sort(List list,Comparator c):該方法種叫定製排序,或自定義排序,需編寫匿名內部類或定義一個比較類,先new一個Comparator接口的比較器對象c,同時實現compare()其方法; 然後將比較器對象c傳給Collections.sort()方法的參數列表中,實現排序功能;

      說明:第一種方法不夠靈活,實體類實現了comparable接口後,會增加耦合,如果在項目中不同的位置需要根據不同的屬性調用排序方法時,需要反覆修改比較規則(按name還是按age),二者只能選擇其一,會起衝突.第二種就很好地解決了這個問題.在需要的地方,創建個比較類的實例,重寫其比較方法即可.

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