Java集合

上篇博文我們介紹了Java I/O流的使用方法,這篇文章我們介紹Java的集合,Java集合是日常開發中使用最多的Java模塊了,我們經常使用List、Map集合,但是我們有沒有了解過Java其他集合的使用方法和使用場景,下面我們就係統的介紹下Java的集合框架。

Java集合大致可以分爲Set、List、Queue和Map四種,其中Set代表無序、不可重複的集合;List代表有序、重複的集合;而Map代表具有映射關係的集合,Java 5 又增加了Queue體系集合,代表一種隊列集合實現。

Java的集合類主要由兩個接口派生而出:Collection和Map;Collection和Map是Java集合框架的根接口。Collection接口的結構如下所示(虛線框表示接口,實線框表示類):

list.png

Map實現類用於保存具有映射關係的數據。Map保存的每項數據都是key-value對,也就是由key和value兩個值組成。Map裏的key是不可重複的。Map接口的結構如下圖所示:

Map.png

一、Collection接口簡介

Collection接口是Set,Queue,List的父接口。Collection接口中定義了多種方法可供其子類進行實現,以實現數據操作。我們經常使用的方法有add(Object)、remove(Object)、size()、iterator();需要特別說明的是iterator()這個方法,該方法用於遍歷集合中的元素,該方法的返回值是Iterator<E>,其中類Iterator是Collection接口的父接口。類Iterator提供了兩個方法hasNext()判斷集合是否有元素可以循環;next()獲取迭代的下一個元素。

Set集合與Collection集合基本相同,沒有提供任何額外的方法,Set集合不允許包含相同的元素,如果試圖把兩個相同的元素加入同一個Set集合中,則添加操作失敗,add()方法返回false,且新元素不會被加入。

List集合代表一個元素有序、可重複的集合,集合中每個元素都有其對應的順序索引。List集合允許使用重複元素,可以通過索引來訪問指定位置的集合元素 。List集合默認按元素的添加順序設置元素的索引,索引下標從0開始,List作爲Collection接口的子接口,可以使用Collection接口裏的全部方法。由於List是有序集合,因此List集合裏增加了一些根據索引來操作集合元素的方法。

Queue用戶模擬隊列這種數據結構,隊列通常是指“先進先出”(FIFO,first-in-first-out)的容器。隊列的頭部是在隊列中存放時間最長的元素,隊列的尾部是保存在隊列中存放時間最短的元素。新元素插入(offer)到隊列的尾部,訪問元素(poll)操作會返回隊列頭部的元素。通常,隊列不允許隨機訪問隊列中的元素。

二、Map接口簡介

Map用於保存具有映射關係的數據,因此Map集合裏保存着兩組數,一組值用戶保存Map裏的key,另一組值用戶保存Map裏的value,key和value都可以是任何引用類型的數據。Map的key不允許重複;如果把Map裏的所有key放在一起看,它們就是一個Set集合(所有的key沒有順序,key與key之間不能重複),實際上Map也確實包含了一個keySet()方法,用戶返回Map裏所有key組成的Set集合。Map中還包括一個內部類Entry,該類封裝了一個key-value對,我們可以使用Entry中的getKey()方法和getValue方法,分別獲取鍵值對的key和value。

三、List集合詳解

List是有序可重複的集合,List根據集合中對象的equles()方法判斷對象是否相同,equles方法繼承自Object對象,我們也可以根據需要重寫equles()方法;List集合在使用remove(obj)方法刪除元素時,就是比較obj和集合中的元素是否相等,如果相等則刪除元素,示例代碼如下:

/**
 * 集合元素,重寫equles方法
 * 如果兩個對象的name屬性值相同則表示兩個對象相同
 */
public class Person {

    private String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if(this == obj) {
            return true;
        } else if(obj == null) {
            return false;
        } else if(this.getClass() != obj.getClass()) {
            return false;
        } else {
            Person compare = (Person) obj;
            if(this.name == compare.name) {
                return true;
            } else {
                return false;
            }
        }
    }
}

/**
 * 驗證List
 */
public class TestList {

    public static void main(String[] args) {
        List<Person> list = new ArrayList<Person>();
        Person person = new Person("Tom");
        Person person1 = new Person("Tom");

        list.add(person);

        System.out.println("刪除前List中元素個數:" + list.size());

        // 刪除元素時,使用不同的對象,但是對象中的name屬性相同
        // 在根據equles()方法做比較時,返回true,List就會認爲這兩個對象是相同的對象,所以會將集合中的元素刪除
        list.remove(person1);

        System.out.println("刪除前List中元素個數:" + list.size());
    }
}

執行結果:

刪除前List中元素個數:1
刪除前List中元素個數:0

結果說明:List集合中判斷元素是否相同的原則是,當兩個對象根據equles()方法判斷返回值爲true,那麼這兩個對象就是相同的。

下面介紹List的幾個具體實現:

ArrayList和Vector是List類的兩個典型實現,ArrayList和Vector類都是基於數組實現的List類,ArrayList和Vector類封裝了一個動態的、允許再分配的Object[]數組。ArrayList或Vector對象使用initalCapacity參數來設置該數組的長度,當向ArrayList或Vector中添加元素超過了該數組的長度時,它們的initalCapacity會自動增加。ArrayList不是線程安全的,Vector是線程安全的,所以Vector的性能比ArrayList差。Stack是Vector的子類,用戶模擬“棧”這種數據結構,“棧”通常是指“後進先出”(LIFO)的容器。最後“push”進棧的元素,將被最先“pop”出棧。

LinkedList類是List接口的實現類——這意味着它是一個List集合,可以根據索引來隨機訪問集合中的元素。除此之外,LinkedList還實現了Deque接口,可以被當作成雙端隊列來使用,因此既可以被當成“棧"來使用,也可以當成隊列來使用。LinkedList的實現機制與ArrayList完全不同。ArrayList內部是以數組的形式來保存集合中的元素的,因此隨機訪問集合元素時有較好的性能;而LinkedList內部以鏈表的形式來保存集合中的元素,因此隨機訪問集合元素時性能較差,但在插入、刪除元素時性能比較出色。由於LinkedList雙端隊列的特性,所以新增了一些方法,如:addFirst(E e)、addLast(E e)、getFirst(E e)、getLast(E e)等。

四、Set集合詳解

HashSet是Set接口的典型實現,實現了Set接口中的所有方法,並沒有添加額外的方法,大多數時候使用Set集合時就是使用這個實現類。HashSet按Hash算法來存儲集合中的元素。因此具有很好的存取和查找性能。HashSet不能保證元素的排列順序,順序可能與添加順序不同,順序也有可能發生變化,HashSet集合元素值可以是null。HashSet集合判斷兩個元素相等的標準是兩個對象通過equals()方法比較相等,並且兩個對象的hashCode()方法返回值也相等。即:如果兩個對象equles()方法返回true且haseCode()方法返回值相同,則認爲是相同的元素,不能重複添加。具體的判定規則如下:

  1. 如果有兩個元素通過equal()方法比較返回false,且它們的hashCode()方法返回不相等,HashSet將會把它們存儲在不同的位置。

  2. 如果有兩個元素通過equal()方法比較返回true,但它們的hashCode()方法返回不相等,HashSet將會把它們存儲在不同的位置

  3. 如果兩個對象通過equals()方法比較不相等,hashCode()方法比較相等,HashSet將會把它們存儲在相同的位置,在這個位置以鏈表式結構來保存多個對象。這是因爲當向HashSet集合中存入一個元素時,HashSet會調用對象的hashCode()方法來得到對象的hashCode值,然後根據該hashCode值來決定該對象存儲在HashSet中存儲位置。

  4. 如果有兩個元素通過equal()方法比較返回true,但它們的hashCode()方法返回true,HashSet將會報錯。

LinkedHashSet是HashSet對的子類,也是根據元素的hashCode值來決定元素的存儲位置,同時使用鏈表維護元素的次序,使得元素是以插入的順序來保存的。當遍歷LinkedHashSet集合裏的元素時,LinkedHashSet將會按元素的添加順序來訪問集合裏的元素。但是由於要維護元素的插入順序,在性能上略低與HashSet,但在迭代訪問Set裏的全部元素時有很好的性能。(HashSet中的元素時無序的,LinkedHashSet中的元素是按照存放順序有序存放的)

TreeSet是SortedSet接口的實現類,正如SortedSet名字所暗示的,TreeSet可以確保集合元素處於排序狀態。TreeSet中所謂的有序,不同於LinkedHaseSet所講的插入順序,而是通過集合中元素的屬性進行排序方式來實現的。TreeSet支持兩種排序方法:自然排序和定製排序。在默認情況下,TreeSet採用自然排序。自然排序就是通過實現Comparable接口,TreeSet會調用集合中元素所屬類的compareTo(Object obj)方法來比較元素之間的大小關係,然後將集合元素按升序排列,即把通過compareTo(Object obj)方法比較後比較大的的往後排。這種方式就是自然排序。測試類分別如下:

/**
 * Set集合中不能放置相同的元素,set根據對象的equles方法和hashcode方法判斷兩個
 * 對象是否相同,如果equles方法返回true且hashcode方法返回的值相同,則判斷爲兩個
 * 元素相同,不能重複放入
 * 日常使用時,最好equles返回true時,hashCode返回的值也相同,這樣可以提高HashSet保存效率
 */
public class TestHashSet {

    /**
     * HashObj1中的equals方法返回爲true
     * HashObj1中的hashCode方法返回爲1
     * 則在做保存操作時,將會判定兩個元素相同,只會保存成功一個元素
     */
    @Test
    public void setFail() {
        HashObj1 hashObj1 = new HashObj1();
        hashObj1.setAge("12");
        hashObj1.setName("tom");

        HashObj1 hashObj2 = new HashObj1();
        hashObj2.setAge("12");
        hashObj2.setName("tom");

        Set<HashObj1> set = new HashSet<HashObj1>();
        set.add(hashObj1);
        set.add(hashObj2);

        System.out.println(set.size());  // 運行結果爲1
    }

    /**
     * HashObj1中的equals方法返回爲false
     * HashObj1中的hashCode方法返回爲1
     * 則在做保存操作時,將會判定兩個元素不相同,兩個元素全都保存成功
     */
    @Test
    public void setSuccess() {
        HashObj1 hashObj1 = new HashObj1();
        hashObj1.setAge("12");
        hashObj1.setName("tom");

        HashObj1 hashObj2 = new HashObj1();
        hashObj2.setAge("12");
        hashObj2.setName("Lee");

        Set<HashObj1> set = new HashSet<HashObj1>();
        set.add(hashObj1);
        set.add(hashObj2);

        System.out.println(set.size());  // 運行結果爲2
    }

    /**
     * HashObj2中的equals方法返回爲true
     * HashObj2中的hashCode方法返回不同的值
     * 則在做保存操作時,將會判定兩個元素不相同,兩個元素全都保存成功
     */
    @Test
    public void testSucc() {
        HashObj2 hashObj1 = new HashObj2();
        hashObj1.setAge("12");
        hashObj1.setName("tom");

        HashObj2 hashObj2 = new HashObj2();
        hashObj2.setAge("12");
        hashObj2.setName("tom");

        Set<HashObj2> set = new HashSet<HashObj2>();
        set.add(hashObj1);
        set.add(hashObj2);

        System.out.println(set.size());  // 運行結果爲2
    }
}
/**
 * TreeSet實現了SortedSet接口,SortedSet接口實現了Set接口
 * TreeSet集合中的元素不能重複,判斷重複的方法和HashSet不同(根據compareTo方法的返回值判斷)
 * TreeSet集合中的元素處於排序狀態
 * TreeSet中的元素可分爲自然排序和自定義排序(默認使用自然排序)
 * 自然排序的元素必須實現Comparable接口(基本數據類型和字符串默認實現了Comparable接口)
 */
public class TestTreeSet {

    /**
     * 測試基本數據類型排序
     */
    @Test
    public void testBasicType() {
        SortedSet<Integer> set = new TreeSet<Integer>();
        set.add(3);
        set.add(1);
        set.add(1);
        set.add(5);
        set.add(0);

        // 打印結果爲0 1 3 5
        for(Integer ele : set) {
            System.out.print(ele + " ");
        }
    }

    /**
     * 根據自然排序保存元素(保存的元素需實現Comparable接口)
     */
    @Test
    public void testDefaultSort() {
        TreeSetObj treeSetObj1 = new TreeSetObj();
        treeSetObj1.setAge(18);
        treeSetObj1.setName("tom");

        TreeSetObj treeSetObj2 = new TreeSetObj();
        treeSetObj2.setAge(11);
        treeSetObj2.setName("lee");

        TreeSetObj treeSetObj3 = new TreeSetObj();
        treeSetObj3.setAge(12);
        treeSetObj3.setName("jimmy");

        // kobe和jimmy age的值相同根據compareTo方法判斷相同,所有不會重複保存
        TreeSetObj treeSetObj4 = new TreeSetObj();
        treeSetObj4.setAge(12);
        treeSetObj4.setName("kobe");

        // jimmy[20]和jimmy[12]根據equles方法和hashCode方法判斷相同但是
        // jimmy[20]和jimmy[12]的age值相同根據compareTo方法判斷不同同,所以會保存
        TreeSetObj treeSetObj5 = new TreeSetObj();
        treeSetObj5.setAge(20);
        treeSetObj5.setName("jimmy");


        SortedSet<TreeSetObj> set = new TreeSet<TreeSetObj>();
        set.add(treeSetObj1);
        set.add(treeSetObj2);
        set.add(treeSetObj3);
        set.add(treeSetObj4);
        set.add(treeSetObj5);

        // 執行結果 lee jimmy tom jimmy
        for(TreeSetObj obj : set) {
            System.out.println(obj.getAge() + "==" + obj.getName());
        }
    }

    /**
     * 根據定製排序保存元素
     */
    @Test
    public void testDefineSet() {
        Comparator<TreeSetObj1> comparator = new Comparator<TreeSetObj1>() {
            @Override
            public int compare(TreeSetObj1 obj1, TreeSetObj1 obj2) {
                if(obj1.getAge() > obj2.getAge()) {
                    return 1;
                } else if(obj1.getAge() == obj2.getAge()) {
                    return 0;
                } else {
                    return -1;
                }
            }
        };

        TreeSetObj1 treeSetObj1 = new TreeSetObj1();
        treeSetObj1.setAge(18);
        treeSetObj1.setName("tom");

        TreeSetObj1 treeSetObj2 = new TreeSetObj1();
        treeSetObj2.setAge(11);
        treeSetObj2.setName("lee");

        TreeSetObj1 treeSetObj3 = new TreeSetObj1();
        treeSetObj3.setAge(12);
        treeSetObj3.setName("jimmy");

        SortedSet<TreeSetObj1> set = new TreeSet<TreeSetObj1>(comparator);
        set.add(treeSetObj1);
        set.add(treeSetObj2);
        set.add(treeSetObj3);

        for(TreeSetObj1 obj : set) {
            System.out.println(obj.getAge());
        }
    }
}

五、Map詳解

HashMap 是一個散列表,它存儲的內容是鍵值對(key-value)映射。HashMap和Hashtable都是Map接口的經典實現類,Hashtable是一個線程安全的Map實現,但HashMap是線程不安全的實現,所以HashMap比Hashtable的性能好一些;Hashtable不允許使用null作爲key和value,如果試圖把null值放進Hashtable中,將會引發NullPointerException異常;但是HashMap可以使用null作爲key或value。因爲Map的key就是一個HashSet集合,所有判斷key是否相等和Set的判斷方法相同,即:兩個key通過equals()方法比較返回true,兩個key的hashCode值也相等,則認爲兩個key是相等的。判斷兩個value相等的標準是:只要兩個對象通過equals()方法比較返回true即可。因爲Map的key就是一個HashSet集合,所以HashMap中key所組成的集合元素不能重複,value所組成的集合元素可以重複。

HashMap有一個LinkedHashMap子類;LinkedHashMap使用雙向鏈表來維護key-value對的次序。LinkedHashMap需要維護元素的插入順序,因此性能略低於HashMap的性能;但是因爲它以鏈表來維護內部順序,所以在迭代訪問Map裏的全部元素時有較好的性能。迭代輸出LinkedHashMap的元素時,將會按照添加key-value對的順序輸出。

一般的應用場景,儘可能多考慮使用HashMap,因爲其爲快速查詢設計的。
如果需要特定的排序時,考慮使用TreeMap。
如果需要維護插入順序的,考慮使用LinkedHashMap。



由於篇幅有限文章中的代碼無法一一列出,如需獲取全部的源碼信息,可關注微信公衆號 布衣暖,回覆 java基礎 獲取全部的源碼信息

buyinuan.jpg


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