Java集合類: Set、List、Map、Queue使用場景梳理

Andrew.Hann發表於2014-04-26

本文主要關注Java程式設計中涉及到的各種集合類,以及它們的使用場景

 

相關學習資料

http://files.cnblogs.com/LittleHann/java%E9%9B%86%E5%90%88%E6%8E%92%E5%BA%8F%E5%8F%8Ajava%E9%9B%86%E5%90%88%E7%B1%BB%E8%AF%A6%E8%A7%A3%28collection%E3%80%81list%E3%80%81map%E3%80%81set%29.rar
http://blog.sina.com.cn/s/blog_a345a8960101k9vx.html
http://f51889920.iteye.com/blog/1884810

 

目錄

1. Java集合類基本概念
2. Java集合類架構層次關係
3. Java集合類的應用場景程式碼

 

1. Java集合類基本概念

在程式設計中,常常需要集中存放多個資料。從傳統意義上講,陣列是我們的一個很好的選擇,前提是我們事先已經明確知道我們將要儲存的物件的數量。一旦在陣列初始化時指定了這個陣列長度,這個陣列長度就是不可變的,如果我們需要儲存一個可以動態增長的資料(在編譯時無法確定具體的數量),java的集合類就是一個很好的設計方案了。

集合類主要負責儲存、盛裝其他資料,因此集合類也被稱為容器類。所以的集合類都位於java.util包下,後來為了處理多執行緒環境下的併發安全問題,java5還在java.util.concurrent包下提供了一些多執行緒支援的集合類。

在學習Java中的集合類的API、程式設計原理的時候,我們一定要明白,"集合"是一個很古老的數學概念,它遠遠早於Java的出現。從數學概念的角度來理解集合能幫助我們更好的理解程式設計中什麼時候該使用什麼型別的集合類。

Java容器類類庫的用途是"儲存物件",並將其劃分為兩個不同的概念:

1) Collection
一組"對立"的元素,通常這些元素都服從某種規則
   1.1) List必須保持元素特定的順序
   1.2) Set不能有重複元素
   1.3) Queue保持一個佇列(先進先出)的順序
2) Map
一組成對的"鍵值對"物件

Collection和Map的區別在於容器中每個位置儲存的元素個數:

1) Collection 每個位置只能儲存一個元素(物件)
2) Map儲存的是"鍵值對",就像一個小型資料庫。我們可以通過""找到該鍵對應的""

 

2. Java集合類架構層次關係

1. Interface Iterable
迭代器介面,這是Collection類的父介面。實現這個Iterable介面的物件允許使用foreach進行遍歷,也就是說,所有的Collection集合物件都具有"foreach可遍歷性"。這個Iterable介面只
有一個方法: iterator()。它返回一個代表當前集合物件的泛型<T>迭代器,用於之後的遍歷操作 1.1 Collection Collection是最基本的集合介面,一個Collection代表一組Object的集合,這些Object被稱作Collection的元素。Collection是一個介面,用以提供規範定義,不能被例項化使用 1) Set Set集合類似於一個罐子,"丟進"Set集合裡的多個物件之間沒有明顯的順序。Set繼承自Collection介面,不能包含有重複元素(記住,這是整個Set類層次的共有屬性)。 Set判斷兩個物件相同不是使用"=="運算子,而是根據equals方法。也就是說,我們在加入一個新元素的時候,如果這個新元素物件和Set中已有物件進行注意equals比較都返回false,  
  則Set就會接受這個新元素物件,否則拒絕。 因為Set的這個制約,在使用Set集合的時候,應該注意兩點:
1) 為Set集合裡的元素的實現類實現一個有效的equals(Object)方法、2) 對Set的建構函式,傳入的Collection引數不能包
  含重複的元素
1.1) HashSet HashSet是Set介面的典型實現,HashSet使用HASH演算法來儲存集合中的元素,因此具有良好的存取和查詢效能。當向HashSet集合中存入一個元素時,HashSet會呼叫該物件的
     hashCode()方法來得到該物件的hashCode值,然後根據該HashCode值決定該物件在HashSet中的儲存位置。 值得主要的是,HashSet集合判斷兩個元素相等的標準是兩個物件通過equals()方法比較相等,並且兩個物件的hashCode()方法的返回值相等
1.1.1) LinkedHashSet LinkedHashSet集合也是根據元素的hashCode值來決定元素的儲存位置,但和HashSet不同的是,它同時使用連結串列維護元素的次序,這樣使得元素看起來是以插入的順序儲存的。
       當遍歷LinkedHashSet集合裡的元素時,LinkedHashSet將會按元素的新增順序來訪問集合裡的元素。 LinkedHashSet需要維護元素的插入順序,因此效能略低於HashSet的效能,但在迭代訪問Set裡的全部元素時(遍歷)將有很好的效能(連結串列很適合進行遍歷)
1.2) SortedSet 此介面主要用於排序操作,即實現此介面的子類都屬於排序的子類 1.2.1) TreeSet TreeSet是SortedSet介面的實現類,TreeSet可以確保集合元素處於排序狀態 1.3) EnumSet EnumSet是一個專門為列舉類設計的集合類,EnumSet中所有元素都必須是指定列舉型別的列舉值,該列舉型別在建立EnumSet時顯式、或隱式地指定。EnumSet的集合元素也是有序的,
     它們以列舉值在Enum類內的定義順序來決定集合元素的順序
2) List List集合代表一個元素有序、可重複的集合,集合中每個元素都有其對應的順序索引。List集合允許加入重複元素,因為它可以通過索引來訪問指定位置的集合元素。List集合預設按元素
   的新增順序設定元素的索引
2.1) ArrayList ArrayList是基於陣列實現的List類,它封裝了一個動態的增長的、允許再分配的Object[]陣列。 2.2) Vector Vector和ArrayList在用法上幾乎完全相同,但由於Vector是一個古老的集合,所以Vector提供了一些方法名很長的方法,但隨著JDK1.2以後,java提供了系統的集合框架,就將
     Vector改為實現List介面,統一歸入集合框架體系中
2.2.1) Stack Stack是Vector提供的一個子類,用於模擬""這種資料結構(LIFO後進先出) 2.3) LinkedList implements List<E>, Deque<E>。實現List介面,能對它進行佇列操作,即可以根據索引來隨機訪問集合中的元素。同時它還實現Deque介面,即能將LinkedList當作雙端佇列
     使用。自然也可以被當作"棧來使用" 3) Queue Queue用於模擬"佇列"這種資料結構(先進先出 FIFO)。佇列的頭部儲存著佇列中存放時間最長的元素,佇列的尾部儲存著佇列中存放時間最短的元素。新元素插入(offer)到佇列的尾部,
   訪問元素(poll)操作會返回佇列頭部的元素,佇列不允許隨機訪問佇列中的元素。結合生活中常見的排隊就會很好理解這個概念
3.1) PriorityQueue PriorityQueue並不是一個比較標準的佇列實現,PriorityQueue儲存佇列元素的順序並不是按照加入佇列的順序,而是按照佇列元素的大小進行重新排序,這點從它的類名也可以
     看出來
3.2) Deque Deque介面代表一個"雙端佇列",雙端佇列可以同時從兩端來新增、刪除元素,因此Deque的實現類既可以當成佇列使用、也可以當成棧使用 3.2.1) ArrayDeque 是一個基於陣列的雙端佇列,和ArrayList類似,它們的底層都採用一個動態的、可重分配的Object[]陣列來儲存集合元素,當集合元素超出該陣列的容量時,系統會在底層重
       新分配一個Object[]陣列來儲存集合元素
3.2.2) LinkedList 1.2 Map Map用於儲存具有"對映關係"的資料,因此Map集合裡儲存著兩組值,一組值用於儲存Map裡的key,另外一組值用於儲存Map裡的value。key和value都可以是任何引用型別的資料。Map的key不允
許重複,即同一個Map物件的任何兩個key通過equals方法比較結果總是返回false。 關於Map,我們要從程式碼複用的角度去理解,java是先實現了Map,然後通過包裝了一個所有value都為null的Map就實現了Set集合 Map的這些實現類和子介面中key集的儲存形式和Set集合完全相同(即key不能重複) Map的這些實現類和子介面中value集的儲存形式和List非常類似(即value可以重複、根據索引來查詢)
1) HashMap 和HashSet集合不能保證元素的順序一樣,HashMap也不能保證key-value對的順序。並且類似於HashSet判斷兩個key是否相等的標準也是: 兩個key通過equals()方法比較返回true、
   同時兩個key的hashCode值也必須相等
1.1) LinkedHashMap LinkedHashMap也使用雙向連結串列來維護key-value對的次序,該連結串列負責維護Map的迭代順序,與key-value對的插入順序一致(注意和TreeMap對所有的key-value進行排序進行區
分)
2) Hashtable 是一個古老的Map實現類 2.1) Properties Properties物件在處理屬性檔案時特別方便(windows平臺上的.ini檔案),Properties類可以把Map物件和屬性檔案關聯起來,從而可以把Map物件中的key-value對寫入到屬性文
     件中,也可以把屬性檔案中的"屬性名-屬性值"載入到Map物件中 3) SortedMap 正如Set介面派生出SortedSet子介面,SortedSet介面有一個TreeSet實現類一樣,Map介面也派生出一個SortedMap子介面,SortedMap介面也有一個TreeMap實現類 3.1) TreeMap TreeMap就是一個紅黑樹資料結構,每個key-value對即作為紅黑樹的一個節點。TreeMap儲存key-value對(節點)時,需要根據key對節點進行排序。TreeMap可以保證所有的
     key-value對處於有序狀態。同樣,TreeMap也有兩種排序方式: 自然排序、定製排序 4) WeakHashMap WeakHashMap與HashMap的用法基本相似。區別在於,HashMap的key保留了對實際物件的"強引用",這意味著只要該HashMap物件不被銷燬,該HashMap所引用的物件就不會被垃圾回收。
  但WeakHashMap的key只保留了對實際物件的弱引用,這意味著如果WeakHashMap物件的key所引用的物件沒有被其他強引用變數所引用,則這些key所引用的物件可能被垃圾回收,當垃
  圾回收了該key所對應的實際物件之後,WeakHashMap也可能自動刪除這些key所對應的key-value對 5) IdentityHashMap IdentityHashMap的實現機制與HashMap基本相似,在IdentityHashMap中,當且僅當兩個key嚴格相等(key1 == key2)時,IdentityHashMap才認為兩個key相等 6) EnumMap EnumMap是一個與列舉類一起使用的Map實現,EnumMap中的所有key都必須是單個列舉類的列舉值。建立EnumMap時必須顯式或隱式指定它對應的列舉類。EnumMap根據key的自然順序
  (即列舉值在列舉類中的定義順序)

 

 

3. Java集合類的應用場景程式碼

學習了集合類的基本架構框架之後,我們接著來學習它們各自的應用場景、以及細節處的注意事項

0x1: Set

HashSet

import java.util.*; 

//類A的equals方法總是返回true,但沒有重寫其hashCode()方法。不能保證當前物件是HashSet中的唯一物件
class A
{
    public boolean equals(Object obj)
    {
        return true;
    }
}

//類B的hashCode()方法總是返回1,但沒有重寫其equals()方法。不能保證當前物件是HashSet中的唯一物件
class B
{
    public int hashCode()
    {
        return 1;
    }
}

//類C的hashCode()方法總是返回2,且有重寫其equals()方法
class C
{
    public int hashCode()
    {
        return 2;
    }
    public boolean equals(Object obj)
    {
        return true;
    }
}
public class HashSetTest
{
    public static void main(String[] args) 
    {
        HashSet books = new HashSet();
        //分別向books集合中新增兩個A物件,兩個B物件,兩個C物件
        books.add(new A());
        books.add(new A());

        books.add(new B());
        books.add(new B());

        books.add(new C());
        books.add(new C());
        System.out.println(books);
    }
}

result:

[B@1, B@1, C@2, A@3bc257, A@785d65]

可以看到,如果兩個物件通過equals()方法比較返回true,但這兩個物件的hashCode()方法返回不同的hashCode值時,這將導致HashSet會把這兩個物件儲存在Hash表的不同位置,從而使物件可以新增成功,這就與Set集合的規則有些出入了。所以,我們要明確的是: equals()決定是否可以加入HashSet、而hashCode()決定存放的位置,它們兩者必須同時滿足才能允許一個新元素加入HashSet
但是要注意的是: 如果兩個物件的hashCode相同,但是它們的equlas返回值不同,HashSet會在這個位置用鏈式結構來儲存多個物件。而HashSet訪問集合元素時也是根據元素的HashCode值來快速定位的,這種鏈式結構會導致效能下降。

所以如果需要把某個類的物件儲存到HashSet集合中,我們在重寫這個類的equlas()方法和hashCode()方法時,應該儘量保證兩個物件通過equals()方法比較返回true時,它們的hashCode()方法返回值也相等

LinkedHashSet

import java.util.*; 

public class LinkedHashSetTest
{
    public static void main(String[] args) 
    {
        LinkedHashSet books = new LinkedHashSet();
        books.add("Java");
        books.add("LittleHann");
        System.out.println(books);

        //刪除 Java
      books.remove("Java");
        //重新新增 Java
        books.add("Java");
        System.out.println(books);
    }
}

元素的順序總是與新增順序一致,同時要明白的是,LinkedHashSetTest是HashSet的子類,因此它不允許集合元素重複

TreeSet

import java.util.*;

public class TreeSetTest
{
    public static void main(String[] args) 
    {
        TreeSet nums = new TreeSet();
        //向TreeSet中新增四個Integer物件
        nums.add(5);
        nums.add(2);
        nums.add(10);
        nums.add(-9);

        //輸出集合元素,看到集合元素已經處於排序狀態
        System.out.println(nums);

        //輸出集合裡的第一個元素
        System.out.println(nums.first());

        //輸出集合裡的最後一個元素
        System.out.println(nums.last());

        //返回小於4的子集,不包含4
        System.out.println(nums.headSet(4));

        //返回大於5的子集,如果Set中包含5,子集中還包含5
        System.out.println(nums.tailSet(5));

        //返回大於等於-3,小於4的子集。
        System.out.println(nums.subSet(-3 , 4));
    }
}

與HashSet集合採用hash演算法來決定元素的儲存位置不同,TreeSet採用紅黑樹的資料結構來儲存集合元素。TreeSet支援兩種排序方式: 自然排序、定製排序
1. 自然排序:

TreeSet會呼叫集合元素的compareTo(Object obj)方法來比較元素之間的大小關係,然後將集合元素按升序排序,即自然排序。如果試圖把一個物件新增到TreeSet時,則該物件的類必須實現Comparable介面,否則程式會丟擲異常。

當把一個物件加入TreeSet集合中時,TreeSet會呼叫該物件的compareTo(Object obj)方法與容器中的其他物件比較大小,然後根據紅黑樹結構找到它的儲存位置。如果兩個物件通過compareTo(Object obj)方法比較相等,新物件將無法新增到TreeSet集合中(牢記Set是不允許重複的概念)。

注意: 當需要把一個物件放入TreeSet中,重寫該物件對應類的equals()方法時,應該保證該方法與compareTo(Object obj)方法有一致的結果,即如果兩個物件通過equals()方法比較返回true時,這兩個物件通過compareTo(Object obj)方法比較結果應該也為0(即相等)

看到這裡,我們應該明白:

1) 對與Set來說,它定義了equals()為唯一性判斷的標準,而對於到了具體的實現,HashSet、TreeSet來說,它們又會有自己特有的唯一性判斷標準,只有同時滿足了才能判定為唯一性
2) 我們在操作這些集合類的時候,對和唯一性判斷有關的函式重寫要重點關注

 2. 定製排序

TreeSet的自然排序是根據集合元素的大小,TreeSet將它們以升序排序。如果我們需要實現定製排序,則可以通過Comparator介面的幫助(類似PHP中的array_map回撥處理函式的思想)。該介面裡包含一個int compare(T o1, T o2)方法,該方法用於比較大小

import java.util.*;

class M
{
    int age;
    public M(int age)
    {
        this.age = age;
    }
    public String toString()
    {
        return "M[age:" + age + "]";
    }
}

public class TreeSetTest4
{
    public static void main(String[] args) 
    {
        TreeSet ts = new TreeSet(new Comparator()
        {
            //根據M物件的age屬性來決定大小
            public int compare(Object o1, Object o2)
            {
                M m1 = (M)o1;
                M m2 = (M)o2;
                return m1.age > m2.age ? -1
                    : m1.age < m2.age ? 1 : 0;
            }
        });    
        ts.add(new M(5));
        ts.add(new M(-3));
        ts.add(new M(9));
        System.out.println(ts);
    }
}

看到這裡,我們需要梳理一下關於排序的概念

1) equals、compareTo決定的是怎麼比的問題,即用什麼field進行大小比較
2) 自然排序、定製排序、Comparator決定的是誰大的問題,即按什麼順序(升序、降序)進行排序
它們的關注點是不同的,一定要注意區分

EnumSet

 

import java.util.*;

enum Season
{
    SPRING,SUMMER,FALL,WINTER
}
public class EnumSetTest
{
    public static void main(String[] args) 
    {
        //建立一個EnumSet集合,集合元素就是Season列舉類的全部列舉值
        EnumSet es1 = EnumSet.allOf(Season.class);
        //輸出[SPRING,SUMMER,FALL,WINTER]
        System.out.println(es1);

        //建立一個EnumSet空集合,指定其集合元素是Season類的列舉值。
        EnumSet es2 = EnumSet.noneOf(Season.class); 
        //輸出[]
        System.out.println(es2); 
        //手動新增兩個元素
        es2.add(Season.WINTER);
        es2.add(Season.SPRING);
        //輸出[SPRING,WINTER]
        System.out.println(es2);

        //以指定列舉值建立EnumSet集合
        EnumSet es3 = EnumSet.of(Season.SUMMER , Season.WINTER); 
        //輸出[SUMMER,WINTER]
        System.out.println(es3);

        EnumSet es4 = EnumSet.range(Season.SUMMER , Season.WINTER); 
        //輸出[SUMMER,FALL,WINTER]
        System.out.println(es4);

        //新建立的EnumSet集合的元素和es4集合的元素有相同型別,
        //es5的集合元素 + es4集合元素 = Season列舉類的全部列舉值
        EnumSet es5 = EnumSet.complementOf(es4); 
        //輸出[SPRING]
        System.out.println(es5);
    }
}

以上就是Set集合類的程式設計應用場景。那麼應該怎樣選擇何時使用這些集合類呢?

1) HashSet的效能總是比TreeSet好(特別是最常用的新增、查詢元素等操作),因為TreeSet需要額外的紅黑樹演算法來維護集合元素的次序。只有當需要一個保持排序的Set時,才應該使用TreeSet,否則都應該使用HashSet
2) 對於普通的插入、刪除操作,LinkedHashSet比HashSet要略慢一點,這是由維護連結串列所帶來的開銷造成的。不過,因為有了連結串列的存在,遍歷LinkedHashSet會更快
3) EnumSet是所有Set實現類中效能最好的,但它只能儲存同一個列舉類的列舉值作為集合元素
4) HashSet、TreeSet、EnumSet都是"執行緒不安全"的,通常可以通過Collections工具類的synchronizedSortedSet方法來"包裝"該Set集合。
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));

 

0x2: List

ArrayList

如果一開始就知道ArrayList集合需要儲存多少元素,則可以在建立它們時就指定initialCapacity大小,這樣可以減少重新分配的次數,提供效能,ArrayList還提供瞭如下方法來重新分配Object[]陣列

1) ensureCapacity(int minCapacity): 將ArrayList集合的Object[]陣列長度增加minCapacity
2) trimToSize(): 調整ArrayList集合的Object[]陣列長度為當前元素的個數。程式可以通過此方法來減少ArrayList集合物件佔用的記憶體空間
import java.util.*;

public class ListTest
{
    public static void main(String[] args) 
    {
        List books = new ArrayList();
        //向books集合中新增三個元素
        books.add(new String("輕量級Java EE企業應用實戰"));
        books.add(new String("瘋狂Java講義"));
        books.add(new String("瘋狂Android講義"));
        System.out.println(books);

        //將新字串物件插入在第二個位置
        books.add(1 , new String("瘋狂Ajax講義"));
        for (int i = 0 ; i < books.size() ; i++ )
        {
            System.out.println(books.get(i));
        }

        //刪除第三個元素
        books.remove(2);
        System.out.println(books);

        //判斷指定元素在List集合中位置:輸出1,表明位於第二位
        System.out.println(books.indexOf(new String("瘋狂Ajax講義")));  ////將第二個元素替換成新的字串物件
        books.set(1, new String("LittleHann"));
        System.out.println(books);

        //將books集合的第二個元素(包括)
        //到第三個元素(不包括)擷取成子集合
        System.out.println(books.subList(1 , 2));
    }

Stack

注意Stack的後進先出的特點

import java.util.*;

public class VectorTest
{
    public static void main(String[] args) 
    {
        Stack v = new Stack();
        //依次將三個元素push入"棧"
        v.push("瘋狂Java講義");
        v.push("輕量級Java EE企業應用實戰");
        v.push("瘋狂Android講義");

        //輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰 , 瘋狂Android講義]
        System.out.println(v);

        //訪問第一個元素,但並不將其pop出"棧",輸出:瘋狂Android講義
        System.out.println(v.peek());

        //依然輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰 , 瘋狂Android講義]
        System.out.println(v);

        //pop出第一個元素,輸出:瘋狂Android講義
        System.out.println(v.pop());

        //輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰]
        System.out.println(v);
    }
}

LinkedList

import java.util.*;

public class LinkedListTest
{
    public static void main(String[] args) 
    {
        LinkedList books = new LinkedList();

        //將字串元素加入佇列的尾部(雙端佇列)
        books.offer("瘋狂Java講義");

        //將一個字串元素加入棧的頂部(雙端佇列)
        books.push("輕量級Java EE企業應用實戰");

        //將字串元素新增到佇列的頭(相當於棧的頂部)
        books.offerFirst("瘋狂Android講義");

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

        //訪問、並不刪除棧頂的元素
        System.out.println(books.peekFirst());
        //訪問、並不刪除佇列的最後一個元素
        System.out.println(books.peekLast());
        //將棧頂的元素彈出"棧"
        System.out.println(books.pop());
        //下面輸出將看到佇列中第一個元素被刪除
        System.out.println(books);
        //訪問、並刪除佇列的最後一個元素
        System.out.println(books.pollLast());
        //下面輸出將看到佇列中只剩下中間一個元素:
        //輕量級Java EE企業應用實戰
        System.out.println(books);
    }
}

從程式碼中我們可以看到,LinkedList同時表現出了雙端佇列、棧的用法。功能非常強大

 

0x3: Queue

PriorityQueue

import java.util.*;

public class PriorityQueueTest
{
    public static void main(String[] args) 
    {
        PriorityQueue pq = new PriorityQueue();
        //下面程式碼依次向pq中加入四個元素
        pq.offer(6);
        pq.offer(-3);
        pq.offer(9);
        pq.offer(0);

        //輸出pq佇列,並不是按元素的加入順序排列,
        //而是按元素的大小順序排列,輸出[-3, 0, 9, 6]
        System.out.println(pq);
        //訪問佇列第一個元素,其實就是佇列中最小的元素:-3
        System.out.println(pq.poll());
    }
}

PriorityQueue不允許插入null元素,它還需要對佇列元素進行排序,PriorityQueue的元素有兩種排序方式

1) 自然排序:
採用自然順序的PriorityQueue集合中的元素物件都必須實現了Comparable介面,而且應該是同一個類的多個例項,否則可能導致ClassCastException異常
2) 定製排序
建立PriorityQueue佇列時,傳入一個Comparator物件,該物件負責對佇列中的所有元素進行排序
關於自然排序、定製排序的原理和之前說的TreeSet類似

 ArrayDeque

import java.util.*;

public class ArrayDequeTest
{
    public static void main(String[] args) 
    {
        ArrayDeque stack = new ArrayDeque();
        //依次將三個元素push入"棧"
        stack.push("瘋狂Java講義");
        stack.push("輕量級Java EE企業應用實戰");
        stack.push("瘋狂Android講義");

        //輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰 , 瘋狂Android講義]
        System.out.println(stack);

        //訪問第一個元素,但並不將其pop出"棧",輸出:瘋狂Android講義
        System.out.println(stack.peek());

        //依然輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰 , 瘋狂Android講義]
        System.out.println(stack);

        //pop出第一個元素,輸出:瘋狂Android講義
        System.out.println(stack.pop());

        //輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰]
        System.out.println(stack);
    }
}

以上就是List集合類的程式設計應用場景。我們來梳理一下思路

1. java提供的List就是一個"線性表介面",ArrayList(基於陣列的線性表)、LinkedList(基於鏈的線性表)是線性表的兩種典型實現
2. Queue代表了佇列,Deque代表了雙端佇列(既可以作為佇列使用、也可以作為棧使用)
3. 因為陣列以一塊連續記憶體來儲存所有的陣列元素,所以陣列在隨機訪問時效能最好。所以的內部以陣列作為底層實現的集合在隨機訪問時效能最好。
4. 內部以連結串列作為底層實現的集合在執行插入、刪除操作時有很好的效能
5. 進行迭代操作時,以連結串列作為底層實現的集合比以陣列作為底層實現的集合效能好

我們之前說過,Collection介面繼承了Iterable介面,也就是說,我們以上學習到的所有的Collection集合類都具有"可遍歷性"

Iterable介面也是java集合框架的成員,它隱藏了各種Collection實現類的底層細節,嚮應用程式提供了遍歷Collection集合元素的統一程式設計介面:

1) boolean hasNext(): 是否還有下一個未遍歷過的元素
2) Object next(): 返回集合裡的下一個元素
3) void remove(): 刪除集合裡上一次next方法返回的元素

iterator實現遍歷:

import java.util.*;

public class IteratorTest
{
    public static void main(String[] args) 
    {
        //建立一個集合
        Collection books = new HashSet();
        books.add("輕量級Java EE企業應用實戰");
        books.add("瘋狂Java講義");
        books.add("瘋狂Android講義");


        //獲取books集合對應的迭代器
        Iterator it = books.iterator();
        while(it.hasNext())
        {
            //it.next()方法返回的資料型別是Object型別,
            //需要強制型別轉換
            String book = (String)it.next();
            System.out.println(book);
            if (book.equals("瘋狂Java講義"))
            {
                //從集合中刪除上一次next方法返回的元素
                it.remove();
            }
            //對book變數賦值,不會改變集合元素本身
            book = "測試字串";    
        }
        System.out.println(books);
    }
}

從程式碼可以看出,iterator必須依附於Collection物件,若有一個iterator物件,必然有一個與之關聯的Collection物件。

除了可以使用iterator介面迭代訪問Collection集合裡的元素之外,使用java5提供的foreach迴圈迭代訪問集合元素更加便捷

foreach實現遍歷:

import java.util.*;

public class ForeachTest
{
    public static void main(String[] args) 
    {
        //建立一個集合
        Collection books = new HashSet();
        books.add(new String("輕量級Java EE企業應用實戰"));
        books.add(new String("瘋狂Java講義"));
        books.add(new String("瘋狂Android講義"));

        for (Object obj : books)
        {
            //此處的book變數也不是集合元素本身
            String book = (String)obj;
            System.out.println(book);
            if (book.equals("瘋狂Android講義"))
            {
                //下面程式碼會引發ConcurrentModificationException異常
                //books.remove(book);      
            }
        }
        System.out.println(books);
    }
}

除了Collection固有的iterator()方法,List還額外提供了一個listIterator()方法,該方法返回一個ListIterator物件,ListIterator介面繼承了Iterator介面,提供了專門操作List的方法。ListIterator介面在Iterator介面的繼承上增加了如下方法:

1) boolean hasPrevious(): 返回該迭代器關聯的集合是否還有上一個元素
2) Object previous(): 返回該迭代器的上一個元素(向前迭代)
3) void add(): 在指定位置插入一個元素

ListIterator實現遍歷:

import java.util.*;

public class ListIteratorTest
{
    public static void main(String[] args) 
    {
        String[] books = {
            "瘋狂Java講義",
            "輕量級Java EE企業應用實戰"
        };
        List bookList = new ArrayList();
        for (int i = 0; i < books.length ; i++ )
        {
            bookList.add(books[i]);
        }
        ListIterator lit = bookList.listIterator();
        while (lit.hasNext())
        {
            System.out.println(lit.next());
            lit.add("-------分隔符-------");
        }
        System.out.println("=======下面開始反向迭代=======");
        while(lit.hasPrevious())
        {
            System.out.println(lit.previous());
        }
    }
}

 

0x4: Map

HashMap、Hashtable

import java.util.*;

class A
{
    int count;
    public A(int count)
    {
        this.count = count;
    }
    //根據count的值來判斷兩個物件是否相等。
    public boolean equals(Object obj)
    {
        if (obj == this)
            return true;
        if (obj!=null &&
            obj.getClass()==A.class)
        {
            A a = (A)obj;
            return this.count == a.count;
        }
        return false;
    }
    //根據count來計算hashCode值。
    public int hashCode()
    {
        return this.count;
    }
}
class B
{
    //重寫equals()方法,B物件與任何物件通過equals()方法比較都相等
    public boolean equals(Object obj)
    {
        return true;
    }
}
public class HashtableTest
{
    public static void main(String[] args) 
    {
        Hashtable ht = new Hashtable();
        ht.put(new A(60000) , "瘋狂Java講義");
        ht.put(new A(87563) , "輕量級Java EE企業應用實戰");
        ht.put(new A(1232) , new B());
        System.out.println(ht);

        //只要兩個物件通過equals比較返回true,
        //Hashtable就認為它們是相等的value。
        //由於Hashtable中有一個B物件,
        //它與任何物件通過equals比較都相等,所以下面輸出true。
        System.out.println(ht.containsValue("測試字串"));  ////只要兩個A物件的count相等,它們通過equals比較返回true,且hashCode相等
        //Hashtable即認為它們是相同的key,所以下面輸出true。
        System.out.println(ht.containsKey(new A(87563)));   ////下面語句可以刪除最後一個key-value對
        ht.remove(new A(1232));    ////通過返回Hashtable的所有key組成的Set集合,
        //從而遍歷Hashtable每個key-value對
        for (Object key : ht.keySet())
        {
            System.out.print(key + "---->");
            System.out.print(ht.get(key) + "\n");
        }
    }
}

當使用自定義類作為HashMap、Hashtable的key時,如果重寫該類的equals(Object obj)和hashCode()方法,則應該保證兩個方法的判斷標準一致--當兩個key通過equals()方法比較返回true時,兩個key的hashCode()的返回值也應該相同

LinkedHashMap

import java.util.*;

public class LinkedHashMapTest
{
    public static void main(String[] args) 
    {
        LinkedHashMap scores = new LinkedHashMap();
        scores.put("語文" , 80);
        scores.put("英文" , 82);
        scores.put("數學" , 76);
        //遍歷scores裡的所有的key-value對
        for (Object key : scores.keySet())
        {
            System.out.println(key + "------>" + scores.get(key));
        }
    }
}

Properties

import java.util.*;
import java.io.*;

public class PropertiesTest
{
    public static void main(String[] args) throws Exception
    {
        Properties props = new Properties();
        //向Properties中增加屬性
        props.setProperty("username" , "yeeku");
        props.setProperty("password" , "123456");

        //將Properties中的key-value對儲存到a.ini檔案中
        props.store(new FileOutputStream("a.ini"), "comment line");   ////新建一個Properties物件
        Properties props2 = new Properties();
        //向Properties中增加屬性
        props2.setProperty("gender" , "male");

        //將a.ini檔案中的key-value對追加到props2中
        props2.load(new FileInputStream("a.ini") );    //
        System.out.println(props2);
    }
}

Properties還可以把key-value對以XML檔案的形式儲存起來,也可以從XML檔案中載入key-value對

TreeMap

import java.util.*;

class R implements Comparable
{
    int count;
    public R(int count)
    {
        this.count = count;
    }
    public String toString()
    {
        return "R[count:" + count + "]";
    }
    //根據count來判斷兩個物件是否相等。
    public boolean equals(Object obj)
    {
        if (this == obj)
            return true;
        if (obj!=null
            && obj.getClass()==R.class)
        {
            R r = (R)obj;
            return r.count == this.count;
        }
        return false;
    }
    //根據count屬性值來判斷兩個物件的大小。
    public int compareTo(Object obj)
    {
        R r = (R)obj;
        return count > r.count ? 1 :
            count < r.count ? -1 : 0;
    }
}
public class TreeMapTest
{
    public static void main(String[] args) 
    {
        TreeMap tm = new TreeMap();
        tm.put(new R(3) , "輕量級Java EE企業應用實戰");
        tm.put(new R(-5) , "瘋狂Java講義");
        tm.put(new R(9) , "瘋狂Android講義");

        System.out.println(tm);

        //返回該TreeMap的第一個Entry物件
        System.out.println(tm.firstEntry());

        //返回該TreeMap的最後一個key值
        System.out.println(tm.lastKey());

        //返回該TreeMap的比new R(2)大的最小key值。
        System.out.println(tm.higherKey(new R(2)));

        //返回該TreeMap的比new R(2)小的最大的key-value對。
        System.out.println(tm.lowerEntry(new R(2)));

        //返回該TreeMap的子TreeMap
        System.out.println(tm.subMap(new R(-1) , new R(4)));
    }
}

從程式碼中可以看出,類似於TreeSet中判斷兩個元素是否相等的標準,TreeMap中判斷兩個key相等的標準是: 

1) 兩個key通過compareTo()方法返回0
2) equals()放回true

我們在重寫這兩個方法的時候一定要保證它們的邏輯關係一致。

再次強調一下:

Set和Map的關係十分密切,java原始碼就是先實現了HashMap、TreeMap等集合,然後通過包裝一個所有的value都為null的Map集合實現了Set集合類

WeakHashMap

import java.util.*;

public class WeakHashMapTest
{
    public static void main(String[] args) 
    {
        WeakHashMap whm = new WeakHashMap();
        //將WeakHashMap中新增三個key-value對,
        //三個key都是匿名字串物件(沒有其他引用)
        whm.put(new String("語文") , new String("良好"));
        whm.put(new String("數學") , new String("及格"));
        whm.put(new String("英文") , new String("中等"));

        //將WeakHashMap中新增一個key-value對,
        //該key是一個系統快取的字串物件。"java"是一個常量字串強引用
        whm.put("java" , new String("中等"));
        //輸出whm物件,將看到4個key-value對。
        System.out.println(whm);
        //通知系統立即進行垃圾回收
        System.gc();
        System.runFinalization();
        //通常情況下,將只看到一個key-value對。
        System.out.println(whm);
    }
}

如果需要使用WeakHashMap的key來保留物件的弱引用,則不要讓key所引用的物件具有任何強引用,否則將失去使用WeakHashMap的意義

IdentityHashMap

import java.util.*;

public class IdentityHashMapTest
{
    public static void main(String[] args) 
    {
        IdentityHashMap ihm = new IdentityHashMap();
        //下面兩行程式碼將會向IdentityHashMap物件中新增兩個key-value對
        ihm.put(new String("語文") , 89);
        ihm.put(new String("語文") , 78);

        //下面兩行程式碼只會向IdentityHashMap物件中新增一個key-value對
        ihm.put("java" , 93);
        ihm.put("java" , 98);
        System.out.println(ihm);
    }
} 

EnumMap

import java.util.*;

enum Season
{
    SPRING,SUMMER,FALL,WINTER
}
public class EnumMapTest
{
    public static void main(String[] args) 
    {
        //建立一個EnumMap物件,該EnumMap的所有key
        //必須是Season列舉類的列舉值
        EnumMap enumMap = new EnumMap(Season.class);
        enumMap.put(Season.SUMMER , "夏日炎炎");
        enumMap.put(Season.SPRING , "春暖花開");
        System.out.println(enumMap);
    }
}

與建立普通Map有所區別的是,建立EnumMap是必須指定一個列舉類,從而將該EnumMap和指定列舉類關聯起來

以上就是Map集合類的程式設計應用場景。我們來梳理一下思路

1) HashMap和Hashtable的效率大致相同,因為它們的實現機制幾乎完全一樣。但HashMap通常比Hashtable要快一點,因為Hashtable需要額外的執行緒同步控制
2) TreeMap通常比HashMap、Hashtable要慢(尤其是在插入、刪除key-value對時更慢),因為TreeMap底層採用紅黑樹來管理key-value對
3) 使用TreeMap的一個好處就是: TreeMap中的key-value對總是處於有序狀態,無須專門進行排序操作

 

Copyright (c) 2014 LittleHann All rights reserved

 

 

 

 

 

相關文章