1. 程式人生 > >java中各種集合解析以及區別

java中各種集合解析以及區別

一,java中各種集合的關係圖

Collection 介面的介面 物件的集合
├ List 子介面 按進入先後有序儲存 可重複
│├ LinkedList 介面實現類 連結串列 插入刪除 沒有同步 執行緒不安全
│├ ArrayList 介面實現類 陣列 隨機訪問 沒有同步 執行緒不安全
│└ Vector 介面實現類 陣列 同步 執行緒安全
│   └ Stack
└ Set 子介面 僅接收一次,並做內部排序

├ HashSet

│   └ LinkedHashSet
└ TreeSet

對於 List ,關心的是順序,它保證維護元素特定的順序(允許有相同元素),使用此介面能夠精確的控制每個元素插入的位置。使用者能夠使用索引(元素在 List 中的位置,類似於陣列下標)來訪問 List 中的元素。

對於 Set ,只關心某元素是否屬於 Set (不 允許有相同元素 ),而不關心它的順序。

Map 介面 鍵值對的集合
├ Hashtable 介面實現類 同步 執行緒安全
├ HashMap 介面實現類 沒有同步 執行緒不安全

│├ LinkedHashMap

│└ WeakHashMap

├ TreeMap
└ IdentifyHashMap

對於 Map ,最大的特點是鍵值對映,且為一一對映,鍵不能重複,值可以,所以是用鍵來索引值。 方法 put(Object key, Object value) 新增一個“值” ( 想要得東西 ) 和與“值”相關聯的“鍵” (key) ( 使用它來查詢 ) 。方法 get(Object key) 返回與給定“鍵”相關聯的“值”。

Map 同樣對每個元素儲存一份,但這是基於 ” 鍵 ” 的, Map 也有內建的排序,因而不關心元素新增的順序。如果新增元素的順序對你很重要,應該使用 LinkedHashSet 或者 LinkedHashMap.

對於效率, Map 由於採用了雜湊雜湊,查詢元素時明顯比 ArrayList 快。

簡單的總結如下

Collection 是物件集合, Collection 有兩個子介面 List 和 Set,List 可以通過下標 (1,2..) 來取得值,值可以重複,而 Set 只能通過遊標來取值,並且值是不能重複的

ArrayList , Vector , LinkedList 是 List 的實現類

ArrayList 是執行緒不安全的, Vector 是執行緒安全的,這兩個類底層都是由陣列實現的

LinkedList 是執行緒不安全的,底層是由連結串列實現的

Map 是鍵值對集合

HashTable 和 HashMap 是 Map 的實現類
HashTable 是執行緒安全的,不能儲存 null 值
HashMap 不是執行緒安全的,可以儲存 null 值

二,詳細介紹

1 , List 介面
   List 是有序的 Collection ,次序是 List 最重要的特點:它保證維護元素特定的順序。使用此介面能夠精確的控制每個元素插入的位置。使用者能夠使用索引(元素在 List 中的位置,類似於陣列下標)來訪問 List 中的元素,這類似於 Java 的陣列。和下面要提到的 Set 不同, List 允許有相同的元素。
   除了具有 Collection 介面必備的 iterator() 方法外, List 還提供一個 listIterator() 方法,返回一個 ListIterator 介面,和標準的 Iterator 介面相比, ListIterator 多了一些 add() 之類的方法,允許新增,刪除,設定元素, 還能向前或向後遍歷。
  實現 List 介面的常用類有 LinkedList , ArrayList , Vector 和 Stack 。其中,最常用的是 LinkedList 和 ArrayList 兩個。
LinkedList 類
    LinkedList 實現了 List 介面,允許 null 元素。此外 LinkedList 提供額外的 addFirst(), addLast(), getFirst(), getLast(), removeFirst(), removeLast(), insertFirst(), insertLast() 方法在 LinkedList 的首部或尾部,這些方法(沒有在任何介面或基類中定義過)使 LinkedList 可被用作堆疊( stack ),佇列( queue )或雙向佇列( deque )。

注意 LinkedList 沒有同步方法。如果多個執行緒同時訪問一個 List ,則必須自己實現訪問同步。一種解決方法是在建立 List 時構造一個同步的 List :
     List list = Collections.synchronizedList(new LinkedList(…));

特點:對順序訪問進行了優化,向 List 中間插入與刪除的開銷並不大。隨機訪問則相對較慢。 ( 使用 ArrayList 代替。 )

ArrayList 類
   ArrayList 是由陣列實現的 List ,並且實現了可變大小的陣列。它允許所有元素,包括 null 。 ArrayList 沒有同步。 size , isEmpty , get , set 方法執行時間為常數。但是 add 方法開銷為分攤的常數,新增 n 個元素需要 O(n) 的時間。其他的方法執行時間為線性。
   每個 ArrayList 例項都有一個容量( Capacity ),即用於儲存元素的陣列的大小。這個容量可隨著不斷新增新元素而自動增加,但是增長演算法並沒有定義。當需要插入大量元素時,在插入前可以呼叫 ensureCapacity 方法來增加 ArrayList 的容量以提高插入效率。
  和 LinkedList 一樣, ArrayList 也是非同步的( unsynchronized )。

特點:允許對元素進行快速隨機訪問,但是向 List 中間插入與移除元素的速度很慢。 ListIterator 只應該用來由後向前遍歷 ArrayList, 而不是用來插入和移除元素。因為那比 LinkedList 開銷要大很多。

Vector 類
    Vector 非常類似 ArrayList ,但是 Vector 是同步的。由 Vector 建立的 Iterator ,雖然和 ArrayList 建立的 Iterator 是同一介面,但是,因為 Vector 是同步的,當一個 Iterator 被建立而且正在被使用,另一個執行緒改變了 Vector 的狀態(例如,新增或刪除了一些元素),這時呼叫 Iterator 的方法時將丟擲 ConcurrentModificationException ,因此必須捕獲該異常。

 Stack 類: Stack 繼承自 Vector ,實現一個後進先出的堆疊。 Stack 提供 5 個額外的方法使得 Vector 得以被當作堆疊使用。基本的 push 和 pop 方法,還有 peek 方法得到棧頂的元素, empty 方法測試堆疊是否為空, search 方法檢測一個元素在堆疊中的位置。 Stack 剛建立後是空棧。 

2 , Set 介面
   Set 具有與 Collection 完全一樣的介面,因此沒有任何額外的功能,不像前面有幾個不同的 List 。實際上 Set 就是 Collection ,只是行為不同。(這是繼承與多型思想的典型應用:表現不同的行為)。其次, Set 是一種不包含重複的元素的 Collection ,加入 Set 的元素必須定義 equals() 方法以確保物件的唯一性 ( 即任意的兩個元素 e1 和 e2 都有 e1.equals(e2)=false ),與 List 不同的是, Set 介面不保證維護元素的次序。最後, Set 最多有一個 null 元素。
  很明顯, Set 的建構函式有一個約束條件,傳入的 Collection 引數不能包含重複的元素。
  請注意:必須小心操作可變物件( Mutable Object )。如果一個 Set 中的可變元素改變了自身狀態導致 Object.equals(Object)=true 將導致一些問題。

HashSet 類

為快速查詢設計的 Set 。存入 HashSet 的物件必須定義 hashCode() 。

LinkedHashSet 類:具有 HashSet 的查詢速度,且內部使用連結串列維護元素的順序 ( 插入的次序 ) 。於是在使用迭代器遍歷 Set 時,結果會按元素插入的次序顯示。

TreeSet 類

儲存次序的 Set, 底層為樹結構。使用它可以從 Set 中提取有序的序列。

二、 Map 介面
   請注意, Map 沒有繼承 Collection 介面, Map 提供 key 到 value 的對映,你可以通過“鍵”查詢“值”。一個 Map 中不能包含相同的 key ,每個 key 只能對映一個 value 。 Map 介面提供 3 種集合的檢視, Map 的內容可以被當作一組 key 集合,一組 value 集合,或者一組 key-value 對映。
方法 put(Object key, Object value) 新增一個“值” ( 想要得東西 ) 和與“值”相關聯的“鍵” (key) ( 使用它來查詢 ) 。方法 get(Object key) 返回與給定“鍵”相關聯的“值”。可以用 containsKey() 和 containsValue() 測試 Map 中是否包含某個“鍵”或“值”。 標準的 Java 類庫中包含了幾種不同的 Map : HashMap, TreeMap, LinkedHashMap, WeakHashMap, IdentityHashMap 。它們都有同樣的基本介面 Map ,但是行為、效率、排序策略、儲存物件的生命週期和判定“鍵”等價的策略等各不相同。

Map 同樣對每個元素儲存一份,但這是基於 ” 鍵 ” 的, Map 也有內建的排序,因而不關心元素新增的順序。如果新增元素的順序對你很重要,應該使用 LinkedHashSet 或者 LinkedHashMap.

執行效率是 Map 的一個大問題。看看 get() 要做哪些事,就會明白為什麼在 ArrayList 中搜索“鍵”是相當慢的。而這正是 HashMap 提高速度的地方。 HashMap 使用了特殊的值,稱為“雜湊碼” (hash code) ,來取代對鍵的緩慢搜尋。“雜湊碼”是“相對唯一”用以代表物件的 int 值,它是通過將該物件的某些資訊進行轉換而生成的(在下面總結二:需要的注意的地方有更進一步探討)。所有 Java 物件都能產生雜湊碼,因為 hashCode() 是定義在基類 Object 中的方法 。 HashMap 就是使用物件的 hashCode() 進行快速查詢的。此方法能夠顯著提高效能。

Hashtable 類
   Hashtable 繼承 Map 介面,實現一個 key-value 對映的雜湊表。任何非空( non-null )的物件都可作為 key 或者 value 。  新增資料使用 put(key, value) ,取出資料使用 get(key) ,這兩個基本操作的時間開銷為常數。
Hashtable 通過初始化容量 (initial capacity) 和負載因子 (load factor) 兩個引數調整效能。通常預設的 load factor 0.75 較好地實現了時間和空間的均衡。增大 load factor 可以節省空間但相應的查詢時間將增大,這會影響像 get 和 put 這樣的操作。
使用 Hashtable 的簡單示例如下,將 1 , 2 , 3 放到 Hashtable 中,他們的 key 分別是 ”one” , ”two” , ”three” :
     Hashtable numbers = new Hashtable();
     numbers.put(“one”, new Integer(1));
     numbers.put(“two”, new Integer(2));
     numbers.put(“three”, new Integer(3));
  要取出一個數,比如 2 ,用相應的 key :
     Integer n = (Integer)numbers.get(“two”);
     System.out.println(“two = ” + n);
   由於作為 key 的物件將通過計算其雜湊函式來確定與之對應的 value 的位置,因此任何作為 key 的物件都必須實現 hashCode 方法和 equals 方法。 hashCode 方法和 equals 方法繼承自根類 Object ,如果你用自定義的類當作 key 的話,要相當小心,按照雜湊函式的定義,如果兩個物件相同,即 obj1.equals(obj2)=true ,則它們的 hashCode 必須相同,但如果兩個物件不同,則它們的 hashCode 不一定不同,如果兩個不同物件的 hashCode 相同,這種現象稱為衝突,衝突會導致操作雜湊表的時間開銷增大,所以儘量定義好的 hashCode() 方法,能加快雜湊表的操作。
  如果相同的物件有不同的 hashCode ,對雜湊表的操作會出現意想不到的結果(期待的 get 方法返回 null ),要避免這種問題,只需要牢記一條:要同時複寫 equals 方法和 hashCode 方法,而不要只寫其中一個。
   Hashtable 是同步的。

HashMap 類
    HashMap 和 Hashtable 類似,也是基於散列表的實現。不同之處在於 HashMap 是非同步的,並且允許 null ,即 null value 和 null key 。將 HashMap 視為 Collection 時( values() 方法可返回 Collection ),插入和查詢“鍵值對”的開銷是固定的,但其迭代子操作時間開銷和 HashMap 的容量成比例。因此,如果迭代操作的效能相當重要的話,不要將 HashMap 的初始化容量 (initial capacity) 設得過高,或者負載因子 (load factor) 過低。

   LinkedHashMap 類:類似於 HashMap ,但是迭代遍歷它時,取得“鍵值對”的順序是其插入次序,或者是最近最少使用 (LRU) 的次序。只比 HashMap 慢一點。而在迭代訪問時發而更快,因為它使用連結串列維護內部次序。

WeakHashMap 類:弱鍵( weak key ) Map 是一種改進的 HashMap ,它是為解決特殊問題設計的,對 key 實行 “ 弱引用 ” ,如果一個 key 不再被外部所引用(沒有 map 之外的引用),那麼該 key 可以被垃圾收集器 (GC) 回收。

TreeMap 類

基於紅黑樹資料結構的實現。檢視“鍵”或“鍵值對”時,它們會被排序 ( 次序由 Comparabel 或 Comparator 決定 ) 。 TreeMap 的特點在於,你得到的結果是經過排序的。 TreeMap 是唯一的帶有 subMap() 方法的 Map ,它可以返回一個子樹。

IdentifyHashMap 類

使用 == 代替 equals() 對“鍵”作比較的 hash map 。專為解決特殊問題而設計。

總結一:比較

1 ,陣列 (Array) ,陣列類 (Arrays)

Java 所有“儲存及隨機訪問一連串物件”的做法, array 是最有效率的一種。但缺點是容量固定且無法動態改變。 array 還有一個缺點是,無法判斷其中實際存有多少元素, length 只是告訴我們 array 的容量。

Java 中有一個數組類 (Arrays) ,專門用來操作 array 。陣列類 (arrays) 中擁有一組 static 函式。

equals() :比較兩個 array 是否相等。 array 擁有相同元素個數,且所有對應元素兩兩相等。

fill() :將值填入 array 中。

sort() :用來對 array 進行排序。

binarySearch() :在排好序的 array 中尋找元素。

System.arraycopy() : array 的複製。

若編寫程式時不知道究竟需要多少物件,需要在空間不足時自動擴增容量,則需要使用容器類庫, array 不適用。

2 ,容器類與陣列的區別

容器類僅能持有物件引用(指向物件的指標),而不是將物件資訊 copy 一份至數列某位置。一旦將物件置入容器內,便損失了該物件的型別資訊。

3 ,容器 (Collection) 與 Map 的聯絡與區別

Collection 型別,每個位置只有一個元素。

Map 型別,持有 key-value 對 (pair) ,像個小型資料庫。

Collections 是針對集合類的一個幫助類。提供了一系列靜態方法實現對各種集合的搜尋、排序、執行緒完全化等操作。相當於對 Array 進行類似操作的類—— Arrays 。

如, Collections.max(Collection coll); 取 coll 中最大的元素。

Collections.sort(List list); 對 list 中元素排序 

List , Set , Map 將持有物件一律視為 Object 型別。

Collection 、 List 、 Set 、 Map 都是介面,不能例項化。繼承自它們的 ArrayList, Vector, HashTable, HashMap 是具象 class ,這些才可被例項化。

vector 容器確切知道它所持有的物件隸屬什麼型別。 vector 不進行邊界檢查。

總結二:需要注意的地方

1 、 Collection 只能通過 iterator() 遍歷元素,沒有 get() 方法來取得某個元素。

2 、 Set 和 Collection 擁有一模一樣的介面。但排除掉傳入的 Collection 引數重複的元素。

3 、 List ,可以通過 get() 方法來一次取出一個元素。使用數字來選擇一堆物件中的一個, get(0)… 。 (add/get)

4 、 Map 用 put(k,v) / get(k) ,還可以使用 containsKey()/containsValue() 來檢查其中是否含有某個 key/value 。

HashMap 會利用物件的 hashCode 來快速找到 key 。

雜湊碼 (hashing) 就是將物件的資訊經過一些轉變形成一個獨一無二的 int 值,這個值儲存在一個 array 中。我們都知道所有儲存結構中, array 查詢速度是最快的。所以,可以加速查詢。發生碰撞時,讓 array 指向多個 values 。即,陣列每個位置上又生成一個槤表。

5 、 Map 中元素,可以將 key 序列、 value 序列單獨抽取出來。

使用 keySet() 抽取 key 序列,將 map 中的所有 keys 生成一個 Set 。

使用 values() 抽取 value 序列,將 map 中的所有 values 生成一個 Collection 。

為什麼一個生成 Set ,一個生成 Collection ?那是因為, key 總是獨一無二的, value 允許重複。

總結三:如何選擇

從效率角度:

在各種 Lists ,對於需要快速插入,刪除元素,應該使用 LinkedList (可用 LinkedList 構造堆疊 stack 、佇列 queue ),如果需要快速隨機訪問元素,應該使用 ArrayList 。最好的做法是以 ArrayList 作為預設選擇。 Vector 總是比 ArrayList 慢,所以要儘量避免使用。

在各種 Sets 中, HashSet 通常優於 HashTree (插入、查詢)。只有當需要產生一個經過排序的序列,才用 TreeSet 。 HashTree 存在的唯一理由:能夠維護其內元素的排序狀態。

在各種 Maps 中 HashMap 用於快速查詢。

最後,當元素個數固定,用 Array ,因為 Array 效率是最高的。

所以結論:最常用的是 ArrayList , HashSet , HashMap , Array 。

更近一步分析:

如果程式在單執行緒環境中,或者訪問僅僅在一個執行緒中進行,考慮非同步的類,其效率較高,如果多個執行緒可能同時操作一個類,應該使用同步的類。
要特別注意對雜湊表的操作,作為 key 的物件要同時正確複寫 equals 方法和 hashCode 方法。
儘量返回介面而非實際的型別,如返回 List 而非 ArrayList ,這樣如果以後需要將 ArrayList 換成 LinkedList 時,客戶端程式碼不用改變。這就是針對抽象程式設計。以上都是基礎且非常重要,希望大家掌握。