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 時,客戶端代碼不用改變。這就是針對抽象編程。以上都是基礎且非常重要,希望大家掌握。

轉載至:http://blog.csdn.net/jackie03/article/details/7312481

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