1. 程式人生 > >Java-Collection集合和Map集合總結

Java-Collection集合和Map集合總結

本文歡迎轉載,轉載前請聯絡作者,經允許後方可轉載。轉載後請註明出處,謝謝! http://blog.csdn.net/colton_null 作者:喝酒不騎馬 Colton_Null from CSDN

如果一個程式只有包含固定數量的且其生命週期都是已知的物件,那麼這是一個非常簡單的程式。

一、引言

我們在程式設計的時候,有時候在程式執行中需要根據當時的情況去建立物件,在此之前可能不知道物件的數量或者確切的型別。例如,我們在根據某些條件去查詢資料庫後,返回的條數不一定。那麼這個時候,就需要用到一個東西(我們暫且稱之為容器),用它來裝載數量不固定的、型別不固定的物件。

陣列的存在,可以解決其中一部分問題。陣列是儲存一組物件最有效的方式,如果你想儲存一組基本資料型別,官方也是推薦使用這個方式去儲存資料。但是資料的弊端是,它具有固定的大小。也就是說,在我們編寫程式碼建立陣列的時候,必須要給定資料的大小。給定後,這個資料的大小就不能被改變了。所以它麻煩在於我們有時候事先不知道需要存放都少物件(例如前面起到的查庫,返回的條數不確定),而且一旦陣列大小定小了,陣列的存放個數還會受到限制。

於是,Java為我們提供了一套完整且好用的容器,來解決上述問題,我們稱之為“集合”。

二、集合簡介

集合類存放在java.util包中。主要的型別一共有四種:List(列表)、Set(集)、Queue(佇列)、Map(對映)。其中主要有兩大介面,分別是Collection和Map。其中List、Set、Queue實現了Collection介面。

偷一張圖哈
Java集合框架圖
這張圖可以說非常經典了。

Tips:實線邊框的是實現類,折線邊框的是抽象類,而點線邊框的是介面。帶有空心箭頭的點線表示一個特定的類實現了一個介面,實心箭頭表示某個類可以生成箭頭所指向類的物件。比如,Collection可以生成Iterator。

圖中標粗的類(HashSet、ArrayList、LinkedList、HashMap)是比較常用的集合。

那麼這些集合(容器)都是幹什麼用的呢?他們各自又有什麼特性呢?

三、集合基本概念

首先在搞清楚常用集合之前,先說說有關集合的基本概念。

前面我們說到,在程式執行過程中,對於不確定數量、不確定型別的物件的儲存,我們可以用集合來解決。也就是說,我們可以通過用這些集合,來方便的儲存我們想要的物件。當然了,集合對物件的儲存,並不是真正把物件塞到容器裡來,那是通過什麼方式儲存物件的呢?這個我們後面再談。

根據兩大集合介面Collection和Map,我們可以把集合也分為兩種不同的概念來探討。

1.Collection。

它是一個序列,可以想象學校裡學生們站排這個場景。每個學生就是一個物件,這個排就是一個Collection集合。不同的實現類,對於它所儲存的物件的規則要求也不同。List、Queue須按照插入的順序儲存元素,而Set則不能有重複元素。這裡簡單說一下Queue,它實際上是對佇列這種資料結構的一種實現,典型特性就是先進先出(FIFO)。Queue介面提供了offer(Object e)、peek()、poll()等方法對有關佇列概念的操作進行實現。

2.Map。

它是一組成對的“鍵值對”物件,可以通過鍵來查詢值。這就像班級的座位表,任課老師來班級上課,通過座位表上“第三排第五座”就能找到對應的學生“馬叨叨”。最神奇的是,我們不光可以用類似“第三排第五座”這樣的文字(String)來尋找值,我們也可用通過物件來尋找值。也就是說,Map中的鍵值都可以是物件。我們稱這種對應關係為“對映表”。

四、常用集合總結

1.List

a.ArrayList

它在隨機訪問元素,即用角標查詢資料的時候比較快,但是在元素的插入和刪除操作上速度慢一些。

b.LinkedList

而LinkedList在元素插入刪除操作上速度較快,但是在隨機訪問的操作上相對較慢。

對此,我編寫了一段程式來測試這個說法。測試思路:分別用插入十萬條資料到ArrayList和LinkedList中,以及十萬條資料的隨機讀取,計算每次任務執行的時間差。

public class ListDemo {
    // 列表長度定義
    private static final int LIST_SIZE = 100000;

    public static void main(String[] args) {
        List arrayList = new ArrayList(LIST_SIZE);
        List linkedList = new LinkedList();

        // 測試arraylist增加資料時間
        long timestart1 = System.currentTimeMillis();
        System.out.println(timestart1 + ":arrayList開始插入資料");
        for(int i = 0; i < LIST_SIZE; i++) {
            // 每次都將隨機數插入到列表的第0位
            arrayList.add(0, Math.random());
        }
        long timeend1 = System.currentTimeMillis();
        System.out.println(timeend1 + ":arrayList結束插入資料");
        System.out.println("時間差(ms):" + (timeend1 - timestart1) + "\n");

        // 測試linkedlist增加資料時間
        long timestart2 = System.currentTimeMillis();
        System.out.println(timestart2 + ":linkedlist開始插入資料");
        for(int i = 0; i < LIST_SIZE; i++) {
            // 每次都將隨機數插入到列表的第0位
            linkedList.add(0, Math.random());
        }
        long timeend2 = System.currentTimeMillis();
        System.out.println(timeend2 + ":linkedlist結束插入資料");
        System.out.println("時間差(ms):" + (timeend2 - timestart2) + "\n");


        // 測試arrayList讀取資料時間
        long timestart3 = System.currentTimeMillis();
        System.out.println(timestart3 + ":arraylist開始讀取資料");
        for(int i = 0; i < LIST_SIZE; i++) {
            arrayList.get((int) Math.random() * LIST_SIZE);
        }
        long timeend3 = System.currentTimeMillis();
        System.out.println(timeend3 + ":arraylist結束讀取資料");
        System.out.println("時間差(ms):" + (timeend3 - timestart3) + "\n");

        // 測試linkedList讀取資料時間
        long timestart4 = System.currentTimeMillis();
        System.out.println(timestart4 + ":linkedlist開始讀取資料");
        for(int i = 0; i < LIST_SIZE; i++) {
            linkedList.get((int) Math.random() * LIST_SIZE);
        }
        long timeend4 = System.currentTimeMillis();
        System.out.println(timeend4 + ":linkedlist結束讀取資料");
        System.out.println("時間差(ms):" + (timeend4 - timestart4) + "\n");
    }
}

最後的測試結果是如下圖所示:
這裡寫圖片描述
從圖中我們可以看出,在元素插入的效率上,LinkedList要遠快於ArrayList;在元素隨機讀取的效率上,ArrayList偏快一些。

那麼為什麼會這樣呢?

實際上,ArrayList是實現了基於動態陣列的資料結構,而LinkedList基於連結串列的資料結構。對於隨機訪問get和set,ArrayList用陣列角標定位元素,而LinkedList則要移動指標才能定位元素。 前者的時間複雜度為O(1),後者為O(n)。

但對於插入和刪除操作add和remove,LinkedList就佔有一定的優勢了,因為ArrayList要移動資料,而LinkedList修改指標指向就ok了。所以前者的增刪操作時間複雜度為O(n),後者為O(1)。

所以,我們要根據不同的場景,選擇合適的List來完成我們的需要。當我們需要頻繁隨機讀取資料時,首選ArrayList。當我們需要在列表中間頻繁插入、移除資料時,首選LinkedList。

另外,LinkedList還添加了可以使其用作棧、佇列或雙向佇列的方法。比如getFirst()、element()、peek()等。這些方法在Java API中都有詳細的介紹。

2.Set

Set具有和Collection完全一樣的介面,不像List。Set和List最大的區別在於,Set是唯一的、無序的。“唯一的”表示Set不儲存重複的原色,“無序的”表示Set中元素的儲存是沒有順序的,不過如果真想要保持元素的順序可以用TreeSet。所以對於Set,最常被使用的簡單應用就是測試歸屬性,即判斷某個元素是不是在Set集合裡。

對於Set,一般最常使用的就是HashSet,它專門對快速查詢操作進行了優化。因為它的底層資料結構為散列表(即雜湊表),有關雜湊的內容,我會在另外一篇文章中詳細介紹。

3.Queue

通常情況下,LinkedList可以被用作Queue的一種實現,因為它實現了Queue的介面。不過還有一個類,叫PriorityQueue,它是一個比較標準的佇列實現類,但不是絕對標準。因為PriorityQueue儲存佇列元素的順序並不是按加入佇列的順序,而是按佇列元素的大小進行重新排序。因此當呼叫peek()、pull()方法來取出佇列中的元素時,並不是取出最先進入佇列的元素,而是取出佇列中最小的元素。這其實違背了先進先出(FIFO)規則。

4.Map

最後,終於到大家都熟悉的Map了。

a.HashMap

HashMap是最為常見的Map了,一般初學者學習的時候都是從HashMap開始,然後有些人就一直使用HashMap不知道其它MapleStory了……HashMap的是根據鍵的HashCode值來儲存資料,根據鍵可以直接獲取它的值,訪問速度極快,但資料的儲存是無序的。其底層資料結構在Java 1.8後為陣列+連結串列+紅黑樹(之前是資料+連結串列,1.8後對結構進行了優化,在連結串列數量大於8後,用紅黑樹儲存資料,體現就是查詢速度更快了,有關這塊的內容我也會用單獨的篇幅來跟大家一起探討)。

HashMap允許key和value都為null。

線上程安全方面,HashMap是執行緒不安全的。如果需要執行緒安全的Map,可以使用ConcurrentHashMap。

b.LinkedHashMap

LinkedHashMap儲存了元素插入Map時的順序。通常情況下,LinkedHashMap的遍歷要慢於HashMap。當然也有特殊情況,比如在HashMap容量巨大但是儲存資料較少的時候,遍歷會慢於LinkedHashMap。因為LinkedHashMap遍歷只和資料數量有關,與容量無關;而HashMap的遍歷和它的容量有關。

c.TreeMap

TreeMap實現了SortMap介面,所以它可以根據鍵進行排序。它預設的排序規則是升序排序,當然開發者也可以指定排序比較器,修改排序規則。

有人可能會提到HashTable。HashTable與HashMap類似,不過它現在已經過時了,變成了遺留類而已。要說它的區別就是,它不允許key和value為null。另外它是個執行緒安全的Map。不過現在如果想要HashMap執行緒安全的話,建議使用ConcurrentHashMap。因為HashTable已經被淘汰了,當資料增加到一定程度的時候,效率太低。

—————手動分割線—————

最後說一下前面丟出的一個問題。之前我們提到,集合對物件的儲存,並不是把物件塞到容器中,那它是通過什麼方式儲存物件呢?實際上,集合中存放的是物件的引用,學過C語言同學可能會更好理解,這個引用可以理解為就是記錄物件存放地址的指標。根據引用,我們就能找到物件在記憶體中的存放位置,從而獲得物件。而並非在記憶體中把物件實際裝載到某個容器中。

五、總結

  1. Map儲存的是相關聯的鍵值對,而Collection儲存的是單一的元素。
  2. List和陣列的區別在於,List能夠自動擴充容量。
  3. 對於Collection集合,需要唯一性我們要想到Set,根據排序與否選擇TreeSet和HashSet。如果不需要唯一性則要想要List,如果查詢多則使用ArrayList,增刪多聚選擇LinkedList。
  4. 對於Queue、Stack等資料結構的實現,用LinkedList即可。
  5. 對於Map集合。一般使用HashMap,如果需要排序則使用TreeMap,如果需要保持元素插入順序則使用LinkedHashMap,如果需要執行緒安全的Map則使用ConcurrentHashMap。
  6. 過時的類就不要使用了,比如HashTable、Vector、Stack等。

站在前人的肩膀上前行,感謝以下部落格及文獻的支援。