更好的使用Java集合(一)

    Array(循環數組)與Linked(鏈表)前端

    經常使用的集合類型
java

經常使用集合類型
描述
ArrayList
一種能夠動態增加和縮減的索引序列
LinkedList
一種能夠在任何位置進行高效地插入和刪除操做的有序序列
ArrayDeque
一種循環數組實現的雙端隊列
HashSet
一種沒有重複元素的無序集合
TreeSet
一種有序集
EnumSet
一種包含枚舉類型值的集
LinkedHashSet
一種能夠記住元素插入次序的集
PriorityQueue
一種容許高效刪除最小元素的集合
HashMap
一種存儲健值關聯的數據結構
TreeMap
一種健值有序排列的映射表
LinkedHashMap
一種能夠記住健值項添加次序的映射表
WeakHashMap
一種其值無用武之地後能夠被垃圾回收器回收的映射表
IdentityHashMap 一種用==,而不使用equals比較健值的映射

    對於數組和ArrayList都有一個重大的缺陷,就是從中間位置刪除一個元素要付出很大的代價,其緣由是數組中處於被刪除元素以後的全部元素都要向數組前端移動。在數組中間的位置上插入一個元素也是如此。數組

    爲了瞭解這個缺陷,看一段代碼:安全

    @Test
    public void ArrayListAddAndRemoveTest() {
        ArrayList<Integer> arrayList = new ArrayList<Integer>();

        // 向ArrayList中添加10萬個整形。
        for (int i = 0; i < 100000; i++) {
            arrayList.add(i);
        }
    }

    

    向一個ArrayList集合中add了10萬個整形,花費了0.011秒。接下來測試一下以前所說的,從中間位置刪除一個元素要付出很大的代價,其緣由是數組中處於被刪除元素以後的全部元素都要向數組前端移動。數據結構

    @Test
    public void ArrayListAddAndRemoveTest() {
        ArrayList<Integer> arrayList = new ArrayList<Integer>();

        // 向ArrayList中添加10萬個整形。
        for (int i = 0; i < 100000; i++) {
            arrayList.add(i);
        }

        // 從頭開始移除ArrayList集合中的元素,被刪除元素以後的全部元素都要向數組前端移動。
        for (int i = 0; i < 100000; i++) {
            arrayList.remove(0);
        }
    }

    

    從頭開始移除ArrayList集合中的元素,執行10萬次,將ArrayList中的對象都刪除掉,花費了1.045秒。
測試

    接下來,咱們很是熟悉的數據結構——鏈表就解決了這個問題。數組在連續的存儲位置上存放對象引用,但鏈表卻將每一個對象存放在獨立的節點中。在Java語言中,全部鏈表實際上都是雙向鏈接的。優化

@Test
    public void LinkedListAddAndRemoveTest() {
        LinkedList<Integer> linkedList = new LinkedList<Integer>();

        // 向LinkedList集合中添加10萬個整形。
        for (int i = 0; i < 100000; i++) {
            linkedList.add(i);
        }
        
        // 從頭開始移除LinkedList集合中的元素。
        for (int i = 0; i < 100000; i++) {
            linkedList.remove(0);
        }
    }

    

    結果是使人欣慰的,在鏈表中間刪除一個元素是一個很輕鬆的操做,簡單來講就是隻須要對被刪除的元素的前一個元素的next指針更新,對被刪除元素的下一個元素的pervious指針更新便可。spa

    可是鏈表不支持快速地隨機訪問。若是要查看鏈表中第n個元素,就必須從頭開始,越過n-1個元素。沒有捷徑可走。鑑於這個緣由,在程序須要採用整數索引訪問元素時,一般不選用鏈表(儘管對鏈表作了優化,若是  索引大於size()/2就從列表尾端開始搜索)。.net

    繼續測試一下,看看鏈表是否不擅長快速地隨機訪問。線程

    @Test
    public void LinkedListGetTest() {
        LinkedList<Integer> linkedList = new LinkedList<Integer>();

        // 向LinkedList集合中添加10萬個整形。
        for (int i = 0; i < 100000; i++) {
            linkedList.add(i);
        }
        
        // 經過索引隨機快速訪問LinkedList集合。
        for (int i = 0; i < 100000; i++) {
            linkedList.get(i);
        }
    }

    

    花費了3.6秒左右來作這件事,看來鏈表確實是不太擅長快速地隨機訪問。若用迭代器順序訪問或者使用ArrayList來作隨機訪問會更好些嗎?繼續測試:

    @Test
    public void LinkedListGetByIteratorTest() {
        LinkedList<Integer> linkedList = new LinkedList<Integer>();

        // 向LinkedList集合中添加10萬個整形。
        for (int i = 0; i < 100000; i++) {
            linkedList.add(i);
        }

        // 使用迭代器按順序得到鏈表中的值。
        Iterator<Integer> iterator = linkedList.iterator();
        while (iterator.hasNext()) {
            iterator.next();
        }
    }

    

    能夠看到按照順序使用迭代器或者鏈表中的數據是很是快的,但不能進行隨機訪問。接下來試試ArrayList的隨機訪問速度。

    @Test
    public void ArrayListGetTest() {
        ArrayList<Integer> arrayList = new ArrayList<Integer>();

        // 向ArrayList集合中添加10萬個整形。
        for (int i = 0; i < 100000; i++) {
            arrayList.add(i);
        }
        
        // 經過索引隨機快速訪問ArrayList集合。
        for (int i = 0; i < 100000; i++) {
            arrayList.get(i);
        }
    }

   

    到這裏,已經測試完畢。

    若是鏈表中只有不多幾個元素,就徹底沒有必要爲get方法和set方法的開銷而煩惱。可是,有沒有必要優先考慮使用鏈表呢?其實,使用鏈表的惟一理由就是儘量地減小在列表中間插入或者刪除元素所付出的代價。若是列表只有少數幾個元素,就徹底可使用ArrayList(數組列表)。

    建議避免使用以證書索引表示鏈表中位置的全部方法,如LinkedList.get(n)。若是須要對集合進行隨機訪問,就使用數組或者ArrayList就好。

    附加一個使用LinkedList的例子。

package cn.net.bysoft.test;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import org.junit.Test;

public class LinkedListTest {

    @Test
    public void Test() {
        List<String> a = new LinkedList<String>();
        a.add("Amy");
        a.add("Carl");
        a.add("Erica");

        List<String> b = new LinkedList<String>();
        b.add("Bob");
        b.add("Doug");
        b.add("Frances");
        b.add("Gloria");

        // 將b集合的內容合併到a中。
        ListIterator<String> aIter = a.listIterator();
        Iterator<String> bIter = b.iterator();

        // 移動b的迭代器,同時移動a的迭代其,將b的內容添加到a中。
        while (bIter.hasNext()) {
            if (aIter.hasNext())
                aIter.next();
            aIter.add(bIter.next());
        }

        System.out.println("將b集合的內容合併到a中。");
        System.out.println(a);

        // 得到一個迭代其
        bIter = b.iterator();
        // 從b中隔一個一刪除內容。
        while (bIter.hasNext()) {
            bIter.next();
            if (bIter.hasNext()) {
                bIter.next();
                bIter.remove();
            }
        }

        System.out.println("從b中隔一個一刪除內容。");
        System.out.println(b);

        // 批量刪除a中與b相同的內容。
        a.removeAll(b);

        System.out.println("批量刪除a中與b相同的內容。");
        System.out.println(a);
    }
    
    /*
     * output:
     * [Amy, Bob, Carl, Doug, Erica, Frances, Gloria]
     * [Bob, Frances]
     * [Amy, Carl, Doug, Erica, Gloria]
     * */
}

    上面的代碼用迭代器遍歷了LinkedList集合,集合有兩種訪問元素的協議,一個是迭代器,另外一個就是用get和set方法隨機地訪問每一個元素。後者不適用於鏈表,可是對數組卻頗有用。集合類庫提供了一種你們最常使用的ArrayList類,它實現List接口封裝了一個動態再分配的對象數組。

    動態數組還有一個Vector類,這個類的全部方法都是同步的,也就是說是線程安全的。可是,若是由一個線程訪問Vector,代碼要在同步操做上耗費大量時間。所以,在不須要同步的時候使用ArrayList更好些。

相關文章
相關標籤/搜索