Java中的Set集合詳解

一、簡介

Set集合中的元素是無序的且不可重複, 如果試圖把兩個相同元素加入同一個Set集合中,則添加操作失敗,add()方法返回false,且新元素不會被加入。

二、HashSet類

HashSet底層數據結構是哈希表,因此具有很好的存取和查找性能。
哈希表:一個元素爲鏈表的數組,綜合了鏈表(存儲速度快)和數組(查詢速度快)的優點。
哈希表的存取原理:
1. 調用對象的hashCode()方法,獲得要存儲元素的哈希值。
2. 將哈希值與表的長度(即數組的長度)進行求餘運算得到一個整數值,該值就是新元素要存放的位置(即是索引值)。

  • 如果索引值對應的位置上沒有存儲任何元素,則直接將元素存儲到該位置上。
  • 如果索引值對應的位置上已經存儲了元素,則執行第3步。

3.遍歷該位置上的所有舊元素,依次比較每個舊元素的哈希值和新元素的哈希值是否相同。

  • 如果有哈希值相同的舊元素,則執行第4步。
  • 如果沒有哈希值相同的舊元素,則執行第5步。

4.比較新元素和舊元素的地址是否相同

如果地址值相同則用新的元素替換老的元素。停止比較。
如果地址值不同,則新元素調用equals方法與舊元素比較內容是否相同。

  • 如果返回true,用新的元素替換老的元素,停止比較。
  • 如果返回false,則回到第3步繼續遍歷下一個舊元素。

5.說明沒有重複,則將新元素存放到該位置上並讓新元素記住之前該位置的元素。

HashSet特點:

  • 無序
  • 集合中的元素值可以是null
  • hashSet不是同步的,如果多個線程同時訪問一個Set,只要有一個線程修改了Set中的值,就必須進行同步處理,通常通過同步封裝這個Set對象來完成同步,如果不存在這樣的對象,可以使用Collections.synchronizedSet()方法完成。

實體類:

public class Person {
    String name;
    int age;
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}

測試代碼:

@Test
public void testHashSet(){
    Person p1 = new Person("鍾梅", 25);
    Person p2 = new Person("王興", 34);
    Person p3 = new Person("張三", 18);
    Person p4 = new Person("李四", 21);
    Person p5 = new Person("李四", 21);
    HashSet<Person> hashSet = new HashSet<>();
    hashSet.add(p1);
    hashSet.add(p2);
    hashSet.add(p3);
    hashSet.add(p4);
    hashSet.add(p5);
    for (Person person : hashSet) {
        System.out.println(person.getName()+ "----------" + person.getAge());
    }
}

輸出結果:
在這裏插入圖片描述
由上可以看到,結果中出現重複元素。在實體類Person中重寫hashCode和equals方法:

//判斷判斷兩個對象是否相等,對象是否存在,對象的name和age是否相等
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person person = (Person) o;
    return age == person.age &&
            Objects.equals(name, person.name);
}

//返回對象的name和age的hash值
@Override
public int hashCode() {
    return Objects.hash(name, age);
}

重寫之後不是判斷兩個對象hashCode是否相等,而是判斷對象的name和age是否同時相等,如果同時相等則判斷爲同一對象,不能重複出現在集合中。
再次遍歷集合,運行結果:
在這裏插入圖片描述

可以看到重複的元素已經被覆蓋,保證了集合中元素的唯一性。

爲什麼不直接使用數組,而用HashSet呢?
因爲數組的索引是連續的而且數組的長度是固定的,無法自由增加數組的長度。而HashSet就不一樣了,HashCode表用每個元素的hashCode值來計算其存儲位置,從而可以自由增加HashCode的長度,並根據元素的hashCode值來訪問元素。而不用一個個遍歷索引去訪問,這就是它比數組快的原因。

LinkedHashSet類
LinkedHashSet集合也是根據元素的hashCode值來決定元素的存儲位置,但它同時使用鏈表維護元素的次序,這樣使得元素看起來是以插入的順序保存的,也就是說當遍歷集合LinkedHashSet集合裏的元素時,集合將會按元素的添加順序來訪問集合裏的元素。
輸出集合裏的元素時,元素順序總是與添加順序一致。但是LinkedHashSet依然是HashSet,因此它不允許集合重複。

三、TreeSet類

TreeSet可以確保集合元素處於排序狀態

內部存儲機制
TreeSet內部實現的是紅黑樹,默認整形排序爲從小到大。
在這裏插入圖片描述
與HashSet集合相比,TreeSet還提供了幾個額外方法:

//如果TreeSet採用了定製順序,則該方法返回定製排序所使用的Comparator,如果TreeSet採用自然排序,則返回null;
Comparator comparator();
//返回集合中的第一個元素;
Object first();
//返回集合中的最後一個元素;
Object last();
//返回指定元素之前的元素。
Object lower(Object e);
//返回指定元素之後的元素。
Object higher(Object e);
//返回此Set的子集合,含頭不含尾;
SortedSet subSet(Object fromElement,Object toElement);
//返回此Set的子集,由小於toElement的元素組成;
SortedSet headSet(Object toElement);
//返回此Set的子集,由大於fromElement的元素組成;
SortedSet tailSet(Object fromElement);

用法示例:

@Test
public void testTreeSet(){
    TreeSet<Integer> nums = new TreeSet<>();
    //向集合中添加元素
    nums.add(5);
    nums.add(2);
    nums.add(15);
    nums.add(-4);
    //輸出集合,可以看到元素已經處於排序狀態
    System.out.println(nums);//[-4, 2, 5, 15]
    System.out.println("集合中的第一個元素:"+nums.first());//集合中的第一個元素:-4
    System.out.println("集合中的最後一個元素:"+nums.last());//集合中的最後一個元素:15
    System.out.println("集合小於4的子集,不包含4:"+nums.headSet(4));//集合小於4的子集,不包含4:[-4, 2]
    System.out.println("集合大於5的子集:"+nums.tailSet(2));//集合大於5的子集:[2, 5, 15]
    System.out.println("集合中大於等於-3,小於4的子集:"+nums.subSet(-3,4));//集合中大於等於-3,小於4的子集:[2]
}

輸出結果:
在這裏插入圖片描述
從上面的運行結果可以看出輸出的集合已經按從小到大排好了,但是問題來了,只能從小到大排序嗎?如果是字符對象應按該怎樣的順序排序?如果是一個對象又按怎樣的順序排序呢?遵循怎樣的排序規則呢?

針對這個問題,TreeSet支持兩種排序方法:自然排序和定製排序,在默認情況下,採用的是自然排序。

自然排序
TreeSet會調用集合元素的compareTo(Objec obj)方法來比較元素之間的大小關係,然後將集合元素按升序排列,這就是自然排序。

拓展:
Java提供了一個Comparable接口,該接口裏定義了一個compareTo(Object obj)方法,該方法返回一個整數值,實現該接口的類必須實現該方法,實現了該接口的類必須實現該方法,實現接口的類就可以比較大小了。當調用一個一個對象調用該方法與另一個對象進行比較時, compareTo(Object obj)如果返回0表示兩個對象相等;如果返回正整數則表明obj1大於obj2,如果是負整數則相反。

案例:
實現存儲Person類的集合,排序方式,按年齡大小,如果年齡相等,則按name字符串長度,如果長度相等則比較字符。如果name和age都相等則視爲同一對象。

public class Person implements Comparable<Person>{
    String name;
    int age;
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

    @Override
    public int compareTo(Person o) {
        //比較age
        int num=this.age-o.age;
        //如果age相等則比較name長度
        int num1=num==0?this.name.length()-o.name.length():num;
        //如果前兩者都相等則比較name字符串
        int num2=num1==0?this.name.compareTo(o.name):num1;
        return num2;
    }
}

測試:

@Test
public void testTreeSet(){
    TreeSet<Person> tree = new TreeSet<>();
    //向集合中添加元素
    tree.add(new Person("孫悟空",16));
    tree.add(new Person("孫悟空",17));
    tree.add(new Person("孫悟空",16));
    tree.add(new Person("唐僧",16));
    tree.add(new Person("沙悟淨",23));
    tree.add(new Person("唐僧",30));
    //遍歷
    System.out.println(tree);
}

輸出:
在這裏插入圖片描述
從運行結果可以看到滿足定義的排序規則。
當把一個對象添加進集合時,集合調用該對象的CompareTo(Object obj)方法與容器中的其他對象比較大小,然後根據紅黑樹結構中找到它的存儲位置。如果兩個對象相等則新對象無法加入到集合中。

定製排序
TreeSet的自然排序是根據集合元素的大小,TreeSet將它們以升序排列。如果需要實現定製排序,例如降序排序,則可通過Comparator接口的幫助。該接口裏包含一個int compare(T o1,T o2)方法,用於比較o1和o2的大小。由於Comparator是一個函數式接口,因此還可以使用Lambda表達式來代替Comparator子類對象。

@Test
public void testTreeSet2(){
    TreeSet<Integer> nums = new TreeSet<>((a,b)->-(a-b));
    //向集合中添加元素
    nums.add(5);
    nums.add(2);
    nums.add(15);
    nums.add(-4);
    //輸出集合,可以看到元素已經處於排序狀態
    System.out.println(nums);//[15, 5, 2, -4]
}

以上就是我查看資料後的一些整理,有什麼問題請評論區留言。

轉載自: https://blog.csdn.net/mashaokang1314/article/details/83721792

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