Java 集合

image

上圖是java 集合框架的接口繼承圖,集合中有兩個基本接口:Collection 和 Map。Collection 實現了 Iterable 接口,所以所有實現了 Iterable 接口的類都能使用 Iterator 迭代器。

image

image

上圖是Java 集合框架的類的繼承圖

實體集合/映射 接口 重複項 有序/排序 元素調用方法 實現類數據結構 值是否可爲NULL 是否爲同步
ArrayList List 可以重複 插入排序 equals() 可調大小數組 YES NO
LinkedList List 可以重複 插入排序 equals() 鏈表 YES NO
Vector List 可以重複 插入排序 equals() 可調大小數組 YES
HashSet Set 元素唯一 無順序 equals()、hashCode() Hash表
LinkedHashSet Set 元素唯一 插入排序 equals()、hashCode() Hash表和雙向鏈表
TreeSet SortedSet 元素唯一 排序 equals()、compareTo() 紅黑樹
HashMap Map 鍵唯一 無順序 equals()、hashCode() Hash表
LinkedHashMap Map 鍵唯一 鍵插入順序/條目訪問順序 equals()、hashCode() Hash表和雙向鏈表
Hashtable Map 鍵唯一 無順序 equals()、hashCode() Hash表 NO YES
TreeMap SortedMap 鍵唯一 鍵序排列 equals()、compareTo() 紅黑樹

對JAVA的集合的理解是相對於數組:
  數組是大小固定的,並且同一個數組只能存放類型一樣的數據(基本類型/引用類型),JAVA集合可以存儲和操作數目不固定的一組數據。 所有的JAVA集合都位於 java.util包中!
  只有實現了 Iterable 接口的類才能使用 iterator() 方法返回一個 Iterator , Collection 繼承了Iterable接口,所以List,Set,Queue等都實現了Iterable接口並且覆寫了接口中的iterator()方法,所以才能使用迭代器輸出。另外對於foreach語句,只有實現了Iterable接口的類纔可以使用。
JDK源碼Iterable接口:

public interface Iterable<T> {
    Iterator<T> iterator();
}

JDK源碼Iterator接口:

public interface Iterator<T> {
    boolean hasNext();
    E next();
    void remove(); 
}

一個類要實現Iterable接口,主要是實現iterator()方法,通過返回一個實現了Iterator接口的匿名內部類對象。例如:

public class IterablerClass<t> implements Iterable<t>{
      ......
    @Override
    public Iterator<t> iterator() {
        return new Iterator<t>() {
            private Integer index = 0;
            @Override
            publicboolean hasNext() {...}
            @Override
            public String  next(){...}
            @Override
            public void remove(){...}
         }
        ......
 }

1.1 Collection接口

Collection是最基本的集合接口,聲明瞭適用於JAVA集合(只包括Set和List)的通用方法。 Set 和List 都繼承了Collection。

1.1.1 Collection接口的方法:

  1. boolean add(Object o) :向集合中加入一個對象的引用
  2. void clear() :刪除集合中所有的對象,即不再持有這些對象的引用
  3. boolean isEmpty() :判斷集合是否爲空
  4. boolean contains(Object o) : 判斷集合中是否持有特定對象的引用
  5. Iterartor iterator() :返回一個Iterator對象,可以用來遍歷集合中的元素
  6. boolean remove(Object o) :從集合中刪除一個對象的引用
  7. int size() :返回集合中元素的數目
  8. Object[] toArray() : 返回一個數組,該數組中包括集合中的所有元素

關於:Iterator() 和toArray() 方法都用於集合的所有的元素,前者返回一個Iterator對象,後者返回一個包含集合中所有元素的數組。

1.1.2 Iterator接口聲明瞭如下方法:

  1. hasNext() :判斷集合中元素是否遍歷完畢,如果沒有,就返回true
  2. next() :返回下一個元素
  3. remove() :從集合中刪除上一個有next()方法返回的元素。

1.2 List(列表)

List的特徵是其元素以線性方式存儲,集合中可以存放重複對象。

1.2.1 List接口主要實現類包括:

● ArrayList() : 代表長度可以改變得數組。可以對元素進行隨機的訪問,向ArrayList()中插入與刪除元素的速度慢。
● LinkedList(): 在實現中採用鏈表數據結構。插入和刪除速度快,訪問速度慢。
對於List的隨機訪問來說,就是隻隨機來檢索位於特定位置的元素。 List 的 get(int index) 方法放回集合中由參數index指定的索引位置的對象,下標從“0” 開始。最基本的兩種檢索集合中的所有對象的方法:
1: for循環和get()方法:

  for(int i=0; i < list.size();i++){  
     System.out.println(list.get(i));  
   }  

2: 使用 迭代器(Iterator):

   Iterator it=list.iterator();  
   while(it.hashNext()){  
      System.out.println(it.next());  
  }  

1.2.2 List的功能方法

實際上有兩種List:一種是基本的ArrayList,其優點在於隨機訪問元素,另一種是更強大的LinkedList,它並不是爲快速隨機訪問設計的,而是具有一套更通用的方法。
● List:次序是List最重要的特點:它保證維護元素特定的順序。List爲Collection添加了許多方法,使得能夠向List中間插入與移除元素(這隻推 薦LinkedList使用。)一個List可以生成ListIterator,使用它可以從兩個方向遍歷List,也可以從List中間插入和移除元 素。
● ArrayList:由數組實現的List。允許對元素進行快速隨機訪問,但是向List中間插入與移除元素的速度很慢。ListIterator只應該用來由後向前遍歷 ArrayList,而不是用來插入和移除元素。因爲那比LinkedList開銷要大很多。
● LinkedList :對順序訪問進行了優化,向List中間插入與刪除的開銷並不大。隨機訪問則相對較慢。(使用ArrayList代替。)還具有下列方 法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 這些方法 (沒有在任何接口或基類中定義過)使得LinkedList可以當作堆棧、隊列和雙向隊列使用。

1.3 Set(集合)

Set是最簡單的一種集合。集合中的對象不按特定的方式排序,並且沒有重複對象。 Set接口主要實現了兩個實現類:
● HashSet: HashSet類按照哈希算法來存取集合中的對象,存取速度比較快
● TreeSet :TreeSet類實現了SortedSet接口,能夠對集合中的對象進行排序,對象要實現comparable 或 comparator接口。
Set 的用法:存放的是對象的引用,沒有重複對象

  Set set = new HashSet();  
  String s1 = new String("hello");  
  String s2 = s1;  
  String s3 = new String("world");  
  set.add(s1);  
  set.add(s2);  
  set.add(s3);  
  System.out.println(set.size());//打印集合中對象的數目 爲 2。 

Set 的 add()方法是如何判斷對象是否已經存放在集合中?

  boolean isExists=false;  
  Iterator iterator=set.iterator();  
  while(it.hasNext()){  
      String oldStr=it.next();  
      if(newStr.equals(oldStr)){   
          isExists=true;   
      }  
  } 

Set的功能方法
Set具有與Collection完全一樣的接口,因此沒有任何額外的功能,不像前面有兩個不同的List。實際上Set就是Collection,只 是行爲不同。(這是繼承與多態思想的典型應用:表現不同的行爲。)Set不保存重複的元素(至於如何判斷元素相同則較爲負責)
Set : 存入Set的每個元素都必須是唯一的,因爲Set不保存重複元素。加入Set的元素必須定義equals()方法以確保對象的唯一性。Set與Collection有完全一樣的接口。Set接口不保證維護元素的次序。
● HashSet:爲快速查找設計的Set。存入HashSet的對象必須定義hashCode()。
● TreeSet: 保存次序的Set, 底層爲樹結構。使用它可以從Set中提取有序的序列。
● LinkedHashSet:具有HashSet的查詢速度,且內部使用鏈表維護元素的順序(插入的次序)。於是在使用迭代器遍歷Set時,結果會按元素插入的次序顯示。

1.HashSet的底層用哈希散列表來存儲對象(默認長度爲16的數組)

    Set set=new HashSet();
      set.add(obj);

內部存儲過程爲 : 定義h=obj.hashCode,得到obj對象的哈希碼h,再對h進行hash散列運算,對數組長度進行求餘,假如長度爲16,則返回一個0-15之間的值,然後這個值就是存在HashSet數組中的下標。如果下標位置沒有對象(不起衝突),則把obj加到該位置;如果已近有對象(起衝突),則用equals判斷兩對象是否相等,相等則捨棄obj,不相等,則把obj以節點(鏈表)的方式加在該對象下面。

2.HashSet是如何判斷元素重複的
HashSet不能添加重複的元素,當調用add(Object)方法時候,首先會調用Object的hashCode方法判hashCode是否已經存在,如不存在則直接插入元素;如果已存在則調用Object對象的equals方法判斷是否返回true,如果爲true則說明元素已經存在,如爲false則插入元素。
查看了JDK源碼,發現HashSet竟然是藉助HashMap來實現的,利用HashMap中Key的唯一性,來保證HashSet中不出現重複值。具體參見代碼:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    private transient HashMap<E,Object> map;
    private static final Object PRESENT = new Object();
    public HashSet() {
        map = new HashMap<E,Object>();
    }
    public boolean contains(Object o) {
        return map.containsKey(o);
    }
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
}

由此可見,HashSet中的元素實際上是作爲HashMap中的Key存放在HashMap中的。下面是HashMap類中的put方法:

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
}

從這段代碼中可以看出,HashMap中的Key是根據對象的hashCode() 和 euqals()來判斷是否唯一的。
結論:爲了保證HashSet中的對象不會出現重複值,在被存放元素的類中必須要重寫hashCode()和equals()這兩個方法。

1.4 Map(映射)

Map 是一種把鍵對象和值對象映射的集合,它的每一個元素都包含一對鍵對象和值對象。 Map沒有繼承於Collection接口 從Map集合中檢索元素時,只要給出鍵對象,就會返回對應的值對象。

1.4.1 Map 的常用方法:

1 添加,刪除操作:

  Object put(Object key, Object value): 向集合中加入元素   
  Object remove(Object key): 刪除與KEY相關的元素   
  void putAll(Map t):  將來自特定映像的所有元素添加給該映像   
  void clear():從映像中刪除所有映射   

2 查詢操作:

Object get(Object key):獲得與關鍵字key相關的值 。Map集合中的鍵對象不允許重複,也就說,任意兩個鍵對象通過equals()方法比較的結果都是false.,但是可以將任意多個鍵獨享映射到同一個值對象上。

Map的功能方法

  1. 方法put(Object key, Object value) 添加一個“值”(想要得東西)和與“值”相關聯的“鍵”(key)(使用它來查找)。
  2. 方法get(Object key) 返回與給定“鍵”相關聯的“值”。可以用containsKey()和containsValue()測試Map中是否包含某個“鍵”或“值”。

標準的Java類庫中包含了幾種不同的Map:HashMap, TreeMap, LinkedHashMap, WeakHashMap, IdentityHashMap。它們都有同樣的基本接口Map,但是行爲、效率、排序策略、保存對象的生命週期和判定“鍵”等價的策略等各不相同。
執行效率是Map的一個大問題。看看get()要做哪些事,就會明白爲什麼在ArrayList中搜索“鍵”是相當慢的。而這正是HashMap提高速 度的地方。HashMap使用了特殊的值,稱爲“散列碼”(hash code),來取代對鍵的緩慢搜索。“散列碼”是“相對唯一”用以代表對象的int值,它是通過將該對象的某些信息進行轉換而生成的。所有Java對象都 能產生散列碼,因爲hashCode()是定義在基類Object中的方法。
HashMap就是使用對象的hashCode()進行快速查詢的。此方法能夠顯着提高性能。
● Map : 維護“鍵值對”的關聯性,使你可以通過“鍵”查找“值”
● HashMap:Map基於散列表的實現。插入和查詢“鍵值對”的開銷是固定的。可以通過構造器設置容量capacity和負載因子load factor,以調整容器的性能。
● LinkedHashMap: 類似於HashMap,但是迭代遍歷它時,取得“鍵值對”的順序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一點。而在迭代訪問時發而更快,因爲它使用鏈表維護內部次序。
● TreeMap : 基於紅黑樹數據結構的實現。查看“鍵”或“鍵值對”時,它們會被排序(次序由Comparabel或Comparator決定)。TreeMap的特點在 於,你得到的結果是經過排序的。TreeMap是唯一的帶有subMap()方法的Map,它可以返回一個子樹。
● WeakHashMao :弱鍵(weak key)Map,Map中使用的對象也被允許釋放: 這是爲解決特殊問題設計的。如果沒有map之外的引用指向某個“鍵”,則此“鍵”可以被垃圾收集器回收。
● IdentifyHashMap: : 使用==代替equals()對“鍵”作比較的hash map。專爲解決特殊問題而設計。

1.5 其他特徵

List,Set,Map將持有對象一律視爲Object型別。
Collection、List、Set、Map都是接口,不能實例化。
繼承自它們的 ArrayList, Vector, HashTable, HashMap是具象class,這些纔可被實例化,Vector, HashTable是線程安全的,但效率比ArrayList,HashMap低。
vector容器確切知道它所持有的對象隸屬什麼型別。vector不進行邊界檢查。

總結

  1. 如果涉及到堆棧,隊列等操作,應該考慮用List,對於需要快速插入,刪除元素,應該使用LinkedList,如果需要快速隨機訪問元素,應該使用ArrayList。
  2. 如果程序在單線程環境中,或者訪問僅僅在一個線程中進行,考慮非同步的類,其效率較高,如果多個線程可能同時操作一個類,應該使用同步的類。
  3. 在除需要排序時使用TreeSet,TreeMap外,都應使用HashSet,HashMap,因爲他們 的效率更高。
  4. 要特別注意對哈希表的操作,作爲key的對象要正確複寫equals和hashCode方法。
  5. 容器類僅能持有對象引用(指向對象的指針),而不是將對象信息copy一份至數列某位置。一旦將對象置入容器內,便損失了該對象的型別信息。
  6. 儘量返回接口而非實際的類型,如返回List而非ArrayList,這樣如果以後需要將ArrayList換成LinkedList時,客戶端代碼不用改變。這就是針對抽象編程。

注意

  1. Collection沒有get()方法來取得某個元素。只能通過iterator()遍歷元素。
  2. Set和Collection擁有一模一樣的接口。
  3. List,可以通過get()方法來一次取出一個元素。使用數字來選擇一堆對象中的一個,get(0)…。(add/get)
  4. 一般使用ArrayList。用LinkedList構造堆棧stack、隊列queue。
  5. Map用 put(k,v) / get(k),還可以使用containsKey()/containsValue()來檢查其中是否含有某個key/value。
    HashMap會利用對象的hashCode來快速找到key。
  6. Map中元素,可以將key序列、value序列單獨抽取出來。
    使用keySet()抽取key序列,將map中的所有keys生成一個Set。
    使用values()抽取value序列,將map中的所有values生成一個Collection。
    爲什麼一個生成Set,一個生成Collection?那是因爲,key總是獨一無二的,value允許重複。

ListIterator和Iterator

  1. 使用範圍不同,Iterator可以應用於所有的集合,Set、List和Map和這些集合的子類型。而ListIterator只能用於List及其子類型。
  2. ListIterator有add方法,可以向List中添加對象,而Iterator不能。
  3. ListIterator和Iterator都有hasNext()和next()方法,可以實現順序向後遍歷,但是ListIterator有hasPrevious()和previous()方法,可以實現逆向(順序向前)遍歷。Iterator不可以。
  4. ListIterator可以定位當前索引的位置,nextIndex()和previousIndex()可以實現。Iterator沒有此功能。
  5. 都可實現刪除操作,但是ListIterator可以實現對象的修改,set()方法可以實現。Iterator僅能遍歷,不能修改。
發佈了48 篇原創文章 · 獲贊 11 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章