1. 程式人生 > 實用技巧 >Java進階--Set介面

Java進階--Set介面

Set介面


java.util.Set介面和java.util.List介面一樣,同樣繼承自Collection介面,它與Collection介面中的方法基本一致,並沒有對Collection介面進行功能上的擴充,只是比Collection介面更加嚴格了。與List介面不同的是,Set介面中元素無序,並且都會以某種規則保證存入的元素不出現重複。


Set集合有多個子類,這裡我們介紹其中的java.util.HashSetjava.util.LinkedHashSet這兩個集合。

tips:Set集合取出元素的方式可以採用:迭代器、增強for。

1.1 HashSet集合

1.1.1 HashSet集合介紹


java.util.HashSetSet介面的一個實現類,它所儲存的元素是不可重複的並且元素都是無序的(即存取順序不一致)。java.util.HashSet底層的實現其實是一個java.util.HashMap支援,由於我們暫時還未學習,先做了解。


HashSet是根據物件的雜湊值來確定元素在集合中的儲存位置,因此具有良好的存取和查詢效能。保證元素唯一性的方式依賴於:hashCodeequals方法。


我們先來使用一下Set集合儲存,看下現象,再進行原理的講解:

package 集合和泛型.Set;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/*
    HashSet的特點:
        1. 不允許儲存重複的元素
        2. 沒有索引,沒有帶索引的方法,也不能使用普通的for迴圈遍歷
        3. 是一個無序的集合,儲存元素和取出元素的順序有可能一致
        4. 底層是一個Hash表結構: 查詢速度非常快。
 */
public class Demo01HashSet {
    public static void main(String[] args) {
        Set<Integer> set = new HashSet<>();
        set.add(1);
        set.add(2);
        // set.add(2);
        set.add(3);
        set.add(4);

        // 使用迭代器變數
        Iterator it = set.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
        System.out.println("---------------");
        for(Integer s: set){
            System.out.println(s);
        }

    }
}

1.1.2  HashSet集合儲存資料的結構(雜湊表)


什麼是雜湊表呢?


JDK1.8之前,雜湊表底層採用陣列+連結串列實現,即使用連結串列處理衝突,同一hash值的連結串列都儲存在一個連結串列裡。但是當位於一個桶中的元素較多,即hash值相等的元素較多時,通過key值依次查詢的效率較低。
JDK1.8中,雜湊表儲存採用陣列+連結串列+紅黑樹實現,當連結串列長度超過閾值(8)時,將連結串列轉換為紅黑樹,這樣大大減少了查詢時間。
特點: 查詢速度快。
簡單的來說,雜湊表是由陣列+連結串列+紅黑樹(JDK1.8增加了紅黑樹部分)實現的,如下圖所示。

儲存流程圖:
                


總而言之,JDK1.8引入紅黑樹大程度優化了HashMap的效能,那麼對於我們來講保證HashSet集合元素的唯一,其實就是根據物件的hashCode和equals方法來決定的。如果我們往集合中存放自定義的物件,那麼保證其唯一,就必須複寫hashCode和equals方法建立屬於當前物件的比較方式。

package 集合和泛型.Set;

import java.util.HashSet;

/*
    Set集合不允許儲存重複元素的原理
 */
public class Demo02HashSetSavaString {
    public static void main(String[] args) {

        HashSet<String> set = new HashSet<>();
        String s1 = new String("abc");
        String s2 = new String("abc");
        String s3 = new String("重地");
        String s4 = new String("通話");
        set.add(s1);
        set.add(s2);
        set.add(s3);
        set.add(s4);
        set.add("abc");
        System.out.println(set);
    }
}


1.1.3  HashSet儲存自定義型別元素


給HashSet中存放自定義型別元素時,需要重寫物件中的hashCode和equals方法,建立自己的比較方式,才能保證HashSet集合中的物件唯一


建立自定義Student類

package 集合和泛型.Set;

import java.util.HashSet;
import java.util.Objects;

/*
    HashSet儲存自定義型別的元素

   Set集合報錯元素唯一
        儲存的元素(String,Integer,.... Student,Person ),必須重寫hashCode方法和equals方法
  要求:
    同名,同年齡的人視為一個人
 */
class Student{
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        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 "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;
        Student student = (Student) o;
        return getAge() == student.getAge() &&
                Objects.equals(getName(), student.getName());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getName(), getAge());
    }
}
public class Demo03HashSetSavePerson {
    public static void main(String[] args) {

        // 建立HashSet集合儲存Student
        HashSet<Student> set = new HashSet<>();
        Student stu1 = new Student("張三", 18);
        Student stu2 = new Student("張三", 18);
        Student stu3 = new Student("張三", 18);
        set.add(stu1);
        set.add(stu2);
        set.add(stu3);
        System.out.println(set);        // 沒重寫equals和hashCode方法,重複的值也存入了
        System.out.println(stu1.hashCode());
        System.out.println(stu2.hashCode());
        System.out.println(stu1==stu2);
        System.out.println(stu1.equals(stu2));
        // 沒重寫
        //[Student{name='張三', age=18}, Student{name='張三', age=18}, Student{name='張三', age=18}]
        //764977973
        //381259350
        //false
        //false
        //重寫後
        //[Student{name='張三', age=18}]
        //24022538
        //24022538
        //false
        //true   
    }
}

1.2 LinkedHashSet


我們知道HashSet保證元素唯一,可是元素存放進去是沒有順序的,那麼我們要保證有序,怎麼辦呢?


在HashSet下面有一個子類java.util.LinkedHashSet,它是連結串列和雜湊表組合的一個數據儲存結構。


演示程式碼如下:

package 集合和泛型.Set;

import java.util.HashSet;
import java.util.LinkedHashSet;

/*
    java.util.LinkedHashSet集合, extends HashSet集合
    LinkedHashSet集合的特點:
        底層是一個雜湊表(陣列+連結串列+紅黑樹)+ 連結串列,多了一條連結串列(記錄元素儲存順序),保證資料的有序
 */
public class Demo04LinkedHashSet {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("www");
        set.add("abc");
        set.add("abc");
        set.add("java");
        System.out.println(set);  //[abc, java, www] 無序,不允許重複

        LinkedHashSet<String> linkset = new LinkedHashSet<>();
        linkset.add("www");
        linkset.add("abc");
        linkset.add("abc");
        linkset.add("java");
        System.out.println(linkset); //[www, abc, java] 有序,不允許重複,先存入先取出

    }
}

1.3 可變引數


JDK1.5之後,如果我們定義一個方法需要接受多個引數,並且多個引數型別一致,我們可以對其簡化成如下格式:

修飾符 返回值型別 方法名(引數型別... 形參名){  }


其實這個書寫完全等價與

修飾符 返回值型別 方法名(引數型別[] 形參名){  }


只是後面這種定義,在呼叫時必須傳遞陣列,而前者可以直接傳遞資料即可。


JDK1.5以後。出現了簡化操作。... 用在引數上,稱之為可變引數。


同樣是代表陣列,但是在呼叫這個帶有可變引數的方法時,不用建立陣列(這就是簡單之處),直接將陣列中的元素作為實際引數進行傳遞,其實編譯成的class檔案,將這些元素先封裝到一個數組中,在進行傳遞。這些動作都在編譯.class檔案時,自動完成了。


程式碼演示:

public class ChangeArgs {
    public static void main(String[] args) {
        int[] arr = { 1, 4, 62, 431, 2 };
        int sum = getSum(arr);
        System.out.println(sum);
        //  6  7  2 12 2121
        // 求 這幾個元素和 6  7  2 12 2121
        int sum2 = getSum(6, 7, 2, 12, 2121);
        System.out.println(sum2);
    }

    /*
     * 完成陣列  所有元素的求和 原始寫法
     
      public static int getSum(int[] arr){
        int sum = 0;
        for(int a : arr){
            sum += a;
        }
        
        return sum;
      }
    */
    //可變引數寫法
    public static int getSum(int... arr) {
        int sum = 0;
        for (int a : arr) {
            sum += a;
        }
        return sum;
    }
}

tips: 上述add方法在同一個類中,只能存在一個。因為會發生呼叫的不確定性
注意:如果在方法書寫時,這個方法擁有多引數,引數中包含可變引數,可變引數一定要寫在引數列表的末尾位置。