1. 程式人生 > >27-集合--Set及其子類(HashSet+LinkedHashSet+TreeSet)+二叉樹+Comparable+Comparator+雜湊表+HashSet儲存自定義物件+判斷元素唯一的方式

27-集合--Set及其子類(HashSet+LinkedHashSet+TreeSet)+二叉樹+Comparable+Comparator+雜湊表+HashSet儲存自定義物件+判斷元素唯一的方式

一、Set

1、Set:元素不可以重複,是無序的(存入和取出的順序不一致)

2、Set介面中的方法和Collection中的方法一致

3、Set集合的元素取出方式只有一種:迭代器iterator()

        Set set = new HashSet();
        Iterator it = set.iterator();
        while (it.hasNext()) {
            it.next();
        }

4、Set介面有兩個子類

(1)HashSet:內部資料結構是雜湊表(雜湊表底層還是陣列),是不同步的

(2)TreeSet:內部資料結構是二叉樹,是不同步的(可以對Set集合中的元素排序。存取順序不一致,但有指定順序)

注:LinkedHashSet:元素唯一(雜湊表),元素有序(連結串列)

二、HashSet

1、HashSet由雜湊表(實際上是一個HashMap例項)支援,它不保證迭代順序(無序)。因為底層的儲存方式是通過演算法來完成的

2、Set進行迭代所需的時間與HashSet例項的大小(元素的數量)和底層HashMap例項(桶的數量)的“容量”的和成比例。因此,如果迭代效能很重要,則不要將初始容量設定的太高(或將載入因子設定的太低)

3、HashSet用來保證元素唯一(重複的元素存不進去)。需要唯一才使用HashSet,不需要唯一應該選擇ArrayList

注:需要唯一就用Set,無所謂就用List(List使用較多)。List和Set最大的區別在於元素是否唯一

4、HashSet判斷元素唯一,需要覆蓋hashCode()和equals()方法,建立自己的判斷唯一的依據

5、無論是contains()還是remove(),關鍵問題是判斷元素是否相同,即容器中是否有和該元素相同的元素。而容器判斷相同的依據,根據容器的資料結構而定

(1)ArrayList:判斷equals()

(2)HashSet:判斷hashCode() + equals()    (雜湊表中必須首先判斷hashCode())

6、建構函式

(1)HashSet():構造一個新的空set,其底層HashMap例項的預設初始容量是16,載入因子是0.75

(2)HashSet(Collection<? extends E> c):構造一個包含指定collection中的元素的新set

(3)HashSet(int initialCapacity):構造一個新的空set,其底層HashMap例項具有指定的初始容量和預設的載入因子(0.75)

(4)HashSet(int initialCapacity, float loadFactory):構造一個新的空set,其底層HashMap例項具有指定的初始容量和指定的載入因子

三、LinkedHashSet

1、LinkedHashSet:具有可預知迭代順序的Set介面的雜湊表和連結列表實現

2、唯一+有序。LinkedHashSet可以讓客戶免遭未指定的、由HashSet提供的通常雜亂無章的排序工作,而又不致引起與TreeSet關聯的成本增加。使用它可以生成一個與原來順序相同的set副本,並且與原set的實現無關,然後返回由此副本決定了順序的結果(客戶通常期望內容返回的順序與它們出現的順序相同)

四、TreeSet

1、TreeSet:基於TreeMap的NavigableSet實現。使用元素的自然順序對元素進行排序(Comparable),或者根據建立set時提供的Comparator進行排序,具體取決於使用的構造方法

2、TreeSet可以對集合中的元素進行排序,是不同步的

3、TreeSet集合是Set集合的子類,也可以保證元素的唯一性。TreeSet判斷元素唯一的方式:根據比較方法(compareTo()或compare())的返回結果是否為0。為0,就是相同元素,不儲存

注:TreeSet集合和hashCode()、equals()沒有關係

4、TreeSet無序(存入和取出順序不同),但有自己的排序方式。TreeSet對元素進行排序的方式(兩種):

(1)讓元素自身具備比較功能。元素需要實現Comparable介面,覆蓋CompareTo()方法 -- 元素的自然排序

(2)讓集合自身具備比較功能。定義一個類,實現Comparator介面,覆蓋compare()方法。將該類物件(介面Comparator的子類物件)作為引數傳遞給TreeSet集合的建構函式 -- 比較器

注:TreeSet集合中的元素要按照指定方式排序,需要進行比較,必須要讓元素自身或者集合自身具備比較功能。在實際開發中,比較器(Comparator)比較常用。因為它能避免一些弊端和不足,eg:Person本身具備比較功能A,但不是想要的,此時就必須使用比較器定義另一個比較功能B

例:字串本身實現按元素的字典順序排序(Java寫好的,無法修改),若現在需要按字串長度排序,就只能使用Comparator比較器

public class Test {

    public static void main(String[] args) {

        TreeSet ts = new TreeSet(new ComparatorByLength());
        ts.add("aaaaa");
        ts.add("zz");
        ts.add("nbaq");
        ts.add("cba");
        ts.add("abc");
        System.out.println(ts); //[zz, abc, cba, nbaq, aaaaa]

        for (Iterator it = ts.iterator(); it.hasNext(); ) {
            System.out.println(it.next());
        }

    }

}

/**
 * 自定義類,實現Comparator介面,覆蓋compare()方法
 */
class ComparatorByLength implements Comparator {
    @Override
    public int compare(Object obj1, Object obj2) {
        //......此處省略強轉前的健壯性判斷
        String s1 = (String) obj1;
        String s2 = (String) obj2;

        int temp = s1.length() - s2.length();
        //如果長度相同,再按元素的字典順序排序
        return temp == 0 ? s1.compareTo(s2) : temp;
        //如果只按照字串長度比較,則長度相同的後面元素將會被捨棄
//        return temp;  //ts的值為:[zz, cba, nbaq, aaaaa]
    }
}

5、建構函式

(1)TreeSet():構造一個新的空set,該set根據其元素的自然順序進行排序。插入該set的所有元素都必須實現Comparable介面。另外,所有這些元素都必須是可互相比較的:對於set中的任意兩個元素e1和e2,執行e1.compareTo(e2);都不得丟擲ClassCastException

(2)TreeSet(Collection<? extends E> c):構造一個包含指定collection元素的新TreeSet,它按照其元素的自然順序進行排序。插入該set的所有元素都必須實現Comparable介面。另外,所有這些元素都必須是可互相比較的:對於set中的任意兩個元素e1和e2,執行e1.compareTo(e2);都不得丟擲ClassCastException

(3)TreeSet(Comparator<? super E> comparator):構造一個新的空TreeSet,它根據指定比較器進行排序。插入到該set的所有元素都必須能夠由指定比較器進行相互比較:對於set中的任意兩個元素e1和e2,執行comparator.compare(e1, e2);都不得丟擲ClassCastException

(4)TreeSet(SortedSet<E> s):構造一個與指定有序set具有相同對映關係和相同排序的新TreeSet

五、二叉樹(紅黑樹)

1、二叉樹是一種資料結構,持有左、右、父三個索引

2、用二叉樹可以完成排序,並能確定元素的位置。查詢順序:左 - 中 - 右,左邊小,右邊大,相同的不儲存

3、在儲存後一個元素時,已有的元素是有序的。對已有的有序元素進行折半(折半查詢/二分法查詢),再確定新元素的位置,效率較高

4、用二叉樹實現TreeSet 正序/倒序 存取(怎麼存入就怎麼取出,或者怎麼存入就倒序取出)

class ComparatorByName implements Comparator {
    /**
     * 用二叉樹實現TreeSet 正序/倒序 存取
     * @param obj1
     * @param obj2
     * @return
     */
    @Override
    public int compare(Object obj1, Object obj2) {
        //......此處省略強轉前的健壯性判斷
        Person p1 = (Person) obj1;
        Person p2 = (Person) obj2;

        //二叉樹只看正數、負數和零
        //正序:存入和取出順序一致
        return 1;
        //倒序:存入和取出順序相反
//        return -1;
        //若 return 0; ,說明後面新增的元素都和第一個元素相同,全部捨棄
    }

}

六、java.lang.Comparable

1、interface Comparable<T>:此介面強行對實現它的每個類的物件進行整體排序。這種排序被稱為類的自然排序,類的compareTo()方法被稱為它的自然比較方法

2、實現此介面的物件列表(和陣列)可以通過Collections.sort()(和Arrays.sort())進行自動排序。實現此介面的物件可以用作有序對映中的鍵或有序集合中的元素,無需指定比較器

3、建議:最好使自然排序和equals()一致。即 (x.compareTo(y) == 0) == (x.equals(y))    ???

4、方法

(1)int compareTo(T o):比較此物件與指定物件的順序。如果該物件小於、等於、大於指定物件,則分別返回負整數、零、正整數

5、TreeSet集合用Comparable(自然排序)判斷元素唯一的方式:根據比較方法compareTo()返回結果是否為0。為0,就是相同元素,不儲存

/**
 * 實現了Comparable介面的Person類
 */
class Person implements Comparable {

    private String name;

    private int age;

    //...... 省略 構造方法 和 get、set方法

    /**
     * 覆蓋 Comparable 介面的比較方法 compareTo()
     * 下面方法的排序方式:按年齡從小到大排序,如果年齡相同,按名稱的字典順序從前到後
     * 
     * 問題:如果要從大到小排序,如何修改?
     * 逆序。即 改變 this 和 p 兩個物件的位置(互換)
     * 
     * @param obj
     * @return
     */
    @Override
    public int compareTo(Object obj) {
        //凡是引用資料型別強轉之前,都要進行健壯性判斷,否則會發生ClassCastException
        if (!(obj instanceof Person)) {
            throw new ClassCastException();
        }

        Person p = (Person) obj;
        //age是int型別,可以相減
        int temp = this.age - p.age;
        //如果age相同,再比較name
        //String類的預設比較方法compareTo(),按照元素的字典順序排序 -- 字串本身就實現了Comparable介面
        return temp == 0 ? this.name.compareTo(p.name) : temp;
    }

}

七、java.util.Comparator

1、interface Comparator<T>:強行對某個物件collection進行整體排序的比較函式。可以將Comparator傳遞給sort()方法(如Collections.sort()或Arrays.sort()),從而允許在排序順序上實現精確控制。還可以使用Comparator來控制某些資料結構(如有序set或有序對映)的順序,或者為那些沒有自然順序的物件collection提供排序

2、當且僅當對於一組元素S中的每個e1和e2而言,c.compare(e1, e2) == 0與e1.equals(e2)具有相等的布林值時,Comparator c 強行對S進行的排序才叫做與equals一致的排序

3、通常,讓Comparator實現java.io.Serializable。因為它們在可序列化的資料結構(TreeSet、TreeMap)中可用作排序方法。為了成功地序列化資料結構,Comparator(如果已提供)必須實現Serializable

4、方法

(1)int compare(T o1, T o2):比較用來排序的兩個引數。根據第一個引數小於、等於、大於第二個引數分別返回負整數、零、正整數

(2)boolean equals(Object obj):指示某個其他物件是否“等於”此Comparator。此方法必須遵守Object.equals(Object)的常規協定。此外,僅當指定的物件也是一個Comparator,並且強行實施與此Comparator相同的排序時,此方法才返回true(判斷比較器是否相同)

注:此方法是抽象的,但不重寫Object.equals(Object)方法總是安全的(預設繼承Object,用覆寫的equals()方法即可)。然而,在某些情況下,重寫此方法可以允許程式確定兩個不同的Comparator是否強行實施了相同的排序,從而提高效能

5、建立集合物件時就應該具備的功能,使用建構函式(物件是在新增的時候進行比較的,所以在新增元素之前容器就要具備比較性)

/**
 * 定義一個類,實現Comparator
 */
class ComparatorByName implements Comparator {
    /**
     * 覆蓋compare()方法。按Person的 name + age 排序
     * @param obj1
     * @param obj2
     * @return
     */
    @Override
    public int compare(Object obj1, Object obj2) {
        //......此處省略強轉前的健壯性判斷
        Person p1 = (Person) obj1;
        Person p2 = (Person) obj2;

        //此處不能直接用 物件.屬性 呼叫。因為屬性私有,需要使用get()方法獲取屬性值
        int temp = p1.getName().compareTo(p2.getName());
        return temp == 0 ? p1.getAge() - p2.getAge() : temp;
    }

}

public class TreeSetDemo{
    public static void main(String[] args) {
        //將Comparator介面的子類物件(ComparatorByName)作為引數傳遞給TreeSet的建構函式
        TreeSet ts = new TreeSet(new ComparatorByName());
        ts.add("xxx");
        //......
    }
}

八、雜湊表

1、雜湊是一種演算法,這種演算法對陣列進行了優化。雜湊演算法算出的值儲存起來形成雜湊表,雜湊表中全是陣列,該表的特點是:有對應關係

2、雜湊演算法:根據元素自身的特點,對元素進行運算,獲取其在陣列中的位置。雜湊演算法對查詢進行了優化,效能穩定且高效(查詢一個元素,先根據雜湊演算法算出該元素在陣列中應該儲存的位置,然後判斷陣列中該位置的元素是否是要查詢的元素 -- 因為存入時也是按照雜湊演算法計算出來的位置存的,如果查詢的元素存在,一定在計算出來的位置上)

3、每個物件都有自己的雜湊值(即每個物件在記憶體中的位置),都是通過hashCode()計算出來的。hashCode()是用來計算物件雜湊值的方法

4、雜湊演算法的儲存過程

(1)根據雜湊演算法計算元素abc在陣列中的位置a

(2)判斷陣列中位置a處是否有元素。如果沒有,直接儲存;如果有,進行步驟(3)

(3)希判斷位置a處的元素是否是abc。如果是,不儲存;如果不是,用哈衝突的解決方式

5、雜湊表如何確定元素是重複的

(1)判斷雜湊值(hashCode())。雜湊值相同時,才會有第二次判斷。如果雜湊值不同,不需要判斷equals()

(2)判斷內容(equals())

6、雜湊衝突:兩個物件不同,但雜湊值相同。解決方式

(1)順延。即將陣列延長

(2)串聯。基於此位置繼續計算,算出一個位置

7、雜湊演算法的好處和弊端:

(1)好處:提高查詢效率

(2)弊端:不能重複

九、HashSet儲存自定義物件

1、HashSet中儲存的自定義物件,如果要保證儲存的自定義物件唯一,或者要進行contains()、remove()等操作,最關鍵的是判斷元素是否相同,要覆蓋自定義物件的hashCode()和equals()方法,建立自己的判斷相同的依據(最好同時覆蓋自定義物件的toString()方法)

注:ArrayList要覆蓋自定義物件的equals()方法,HashSet要覆蓋自定義物件的hashCode()和equals()方法

十、判斷元素唯一的方式

1、ArrayList:equals()

2、HashSet:hashCode() + equals()

3、TreeSet:compareTo() 或 compare()