最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java集合Set與Collections案例詳解

 更新時(shí)間:2026年05月13日 11:06:53   作者:Kiling_0704  
文章介紹了Set集合的特點(diǎn)和幾種常見的Set集合,包括HashSet和LinkedHashSet,重點(diǎn)講解了HashSet的底層原理,包括哈希值的計(jì)算、數(shù)組+鏈表/紅黑樹的實(shí)現(xiàn)方式、equals和hashCode方法的作用,感興趣的朋友一起看看吧

集合進(jìn)階(Set集合、Collections)

一、Set系列集合

1.1 認(rèn)識(shí)Set集合的特點(diǎn)

Set集合是屬于Collection體系下的另一個(gè)分支,它的特點(diǎn)如下圖所示

下面我們用代碼簡單演示一下,每一種Set集合的特點(diǎn)。

public class SetTest1 {
    public static void main(String[] args) {
        // 創(chuàng)建一個(gè)Set集合的對(duì)象
        /**
         * HashSet的集合, 是一行經(jīng)典代碼(常用) 特點(diǎn): 無序不重復(fù) 無索引
         * 針對(duì)無序不是每次都是隨機(jī)的, 第一次無序排好之后, 以后都是這個(gè)順序(面試題)
         */
        // Set<Integer> set = new HashSet<>();
        // 特點(diǎn): 有序、無索引、不重復(fù)
        // Set<Integer> set = new LinkedHashSet<>();
        // 可排序(默認(rèn)升序)、無索引、不重復(fù)
        Set<Integer> set = new TreeSet<>();
        set.add(666);
        set.add(555);
        set.add(555);
        set.add(888);
        set.add(888);
        set.add(777);
        set.add(777);
        System.out.println(set);
    }
}

1.2 HashSet集合底層原理

接下來,為了讓同學(xué)們更加透徹的理解HashSet為什么可以去重,我們來看一下它的底層原理。

HashSet集合底層是基于哈希表實(shí)現(xiàn)的,所以在正式了解HashSet集合的底層原理前,我們需要先搞清楚一個(gè)前置知識(shí):哈希值!:

演示哈希值的相同與不相同

public class Student {
    private String name;
    private int age;
    private double height;
?
    public Student() {
    }
?
    public Student(String name, int age, double height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }
?
    // 自己提供Getter 和 Setter方法 以及toString方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Double.compare(height, student.height) == 0 && Objects.equals(name, student.name);
    }
?
    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}
?
public class SetTest2 {
    public static void main(String[] args) {
        // 哈希值不相同
        Student s1 = new Student("柳巖", 18, 163);
        Student s2 = new Student("寶強(qiáng)", 18, 163);
        System.out.println(s1.hashCode());
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
?
        System.out.println("----------------------");
?
        // 哈希值相同
        String str1 = new String("abc");
        String str2 = new String("acD");
        System.out.println(str1.hashCode());
        System.out.println(str2.hashCode());
    }
}

HashSet集合的底層原理

  • 基于哈希表實(shí)現(xiàn)。
  • 哈希表是一種增刪改查數(shù)據(jù),性能都較好的數(shù)據(jù)結(jié)構(gòu)。

哈希表根據(jù)JDK版本的不同,也是有點(diǎn)區(qū)別的

  • JDK8以前:哈希表 = 數(shù)組+鏈表
  • JDK8以后:哈希表 = 數(shù)組+鏈表+紅黑樹

我們發(fā)現(xiàn)往HashSet集合中存儲(chǔ)元素時(shí),底層調(diào)用了元素的兩個(gè)方法:一個(gè)是hashCode方法獲取元素的hashCode值(哈希值);另一個(gè)是調(diào)用了元素的equals方法,用來比較新添加的元素和集合中已有的元素是否相同。

  • 只有新添加元素的hashCode值和集合中以后元素的hashCode值相同、新添加的元素調(diào)用equals方法和集合中已有元素比較結(jié)果為true, 才認(rèn)為元素重復(fù)。
  • 如果hashCode值相同,equals比較不同,則以鏈表的形式連接在數(shù)組的同一個(gè)索引為位置(如上圖所示)

在JDK8開始后,為了提高性能,當(dāng)鏈表的長度超過8時(shí),就會(huì)把鏈表轉(zhuǎn)換為紅黑樹,如下圖所示:

總結(jié):
1. 什么是哈希值?對(duì)象的哈希值有什么特點(diǎn)?
    所謂的哈希值就是JDK根據(jù)對(duì)象的地址或者屬性值算出來的int類型整數(shù)。
特點(diǎn):
    同一個(gè)對(duì)象多次調(diào)用hashCode()方法的哈希值相同
    不同對(duì)象調(diào)用的hashCode()方法,哈希值不同,但是可以根據(jù)子類重寫hashCode()方法讓其相同。
2. HashSet集合的底層原理是什么樣的? 
    基于哈希表實(shí)現(xiàn)的。
        JDK8之前的,哈希表:底層使用數(shù)組+鏈表組成
        JDK8開始后,哈希表:底層采用數(shù)組+鏈表+紅黑樹組成。
3. HashSet集合利用哈希表操作數(shù)據(jù)的詳細(xì)流程是咋回事?
    HashSet底層采用了哈希表數(shù)據(jù)結(jié)構(gòu)
    哈希表又叫做散列表,哈希表底層是一個(gè)數(shù)組,這個(gè)數(shù)組中每一個(gè)元素是一個(gè)單向鏈表,每個(gè)單向鏈表都有一個(gè)獨(dú)一無二的hash值,代表數(shù)組的下標(biāo)。在某個(gè)單向鏈表中的每一個(gè)節(jié)點(diǎn)上的hash值是相同的。hash值實(shí)際上是key調(diào)用hashCode方法,再通過"hash function"轉(zhuǎn)換成的值。
如何向哈希表中添加元素?
    先調(diào)用被存儲(chǔ)的key的hashCode方法,經(jīng)過某個(gè)算法得出hash值,如果在這個(gè)哈希表中不存在這個(gè)hash值,則直接加入元素。如果該hash值已經(jīng)存在,繼續(xù)調(diào)用Key之間的equals方法,如果equals方法返回false,則將該元素添加。如果equals方法返回true,則放棄添加該元素
    HashSet初始化容量是16,默認(rèn)加載因子是0.75
4. 哈希表的詳細(xì)流程(面試題)
    ①.創(chuàng)建一個(gè)默認(rèn)長度16,默認(rèn)加載因?yàn)?.75的數(shù)組,數(shù)組名table
    ②.根據(jù)元素的哈希值跟數(shù)組的長度計(jì)算出應(yīng)存入的位置
    ③.判斷當(dāng)前位置是否為null,如果是null直接存入,如果位置不為null,表示有元素,則調(diào)用equals方法比較屬性值,如果一樣,則不存,如果不一樣,則存入數(shù)組。
    ④.當(dāng)數(shù)組存滿到16*0.75=12時(shí),就自動(dòng)擴(kuò)容,每次擴(kuò)容原先的兩倍

1.3 HashSet去重原理

前面我們學(xué)習(xí)了HashSet存儲(chǔ)元素的原理,依賴于兩個(gè)方法:一個(gè)是hashCode方法用來確定在底層數(shù)組中存儲(chǔ)的位置,另一個(gè)是用equals方法判斷新添加的元素是否和集合中已有的元素相同。

要想保證在HashSet集合中沒有重復(fù)元素,我們需要重寫元素類的hashCode和equals方法。

比如以下面的Student類為例,假設(shè)把Student類的對(duì)象作為HashSet集合的元素,想要讓學(xué)生的姓名和年齡相同,就認(rèn)為元素重復(fù)。

public class Student{
    private String name; //姓名
    private int age; //年齡
    private double height; //身高
    //無參數(shù)構(gòu)造方法
    public Student(){}
    //全參數(shù)構(gòu)造方法
    public Student(String name, int age, double height){
        this.name=name;
        this.age=age;
        this.height=height;
    }
    //...get、set、toString()方法自己補(bǔ)上..
    // 按快捷鍵生成hashCode和equals方法
    // alt+insert 選擇 hashCode and equals
    // 只要兩個(gè)對(duì)象的內(nèi)容一樣就會(huì)返回true
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
?
        Student student = (Student) o;
?
        if (age != student.age) return false;
        if (Double.compare(student.height, height) != 0) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }
?
    // 只要兩個(gè)對(duì)象內(nèi)容一樣, 返回的哈希值就是一樣的
    @Override
    public int hashCode() {
        // 根據(jù)姓名  年齡  身高計(jì)算哈希值
        return Objects.hash(name, age, height);
    }
}

接著,寫一個(gè)測試類,往HashSet集合中存儲(chǔ)Student對(duì)象。

public class SetTest3 {
    public static void main(String[] args) {
        Set<Student> students = new HashSet<>();
        Student s1 = new Student("至尊寶",20, 169.6);
        Student s2 = new Student("蜘蛛精",23, 169.6);
        Student s3 = new Student("蜘蛛精",23, 169.6);
        Student s4 = new Student("牛魔王",48, 169.6);
        System.out.println(s2.hashCode());
        System.out.println(s3.hashCode());
?
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);
?
        for(Student s : students){
            System.out.println(s);
        }
    }
}

打印結(jié)果如下,我們發(fā)現(xiàn)存了兩個(gè)蜘蛛精,當(dāng)時(shí)實(shí)際打印出來只有一個(gè),而且是無序的。

Student{name='牛魔王', age=48, height=169.6}
Student{name='至尊寶', age=20, height=169.6}
Student{name='蜘蛛精', age=23, height=169.6}

總結(jié):
如果希望Set集合認(rèn)為2個(gè)內(nèi)容相同的對(duì)象是重復(fù)的應(yīng)該怎么辦?
    需要重寫元素類的hashCode和equals方法。

1.4 LinkedHashSet底層原理

接下來,我們?cè)賹W(xué)習(xí)一個(gè)HashSet的子類LinkedHashSet類。LinkedHashSet它底層采用的是也是哈希表結(jié)構(gòu),只不過額外新增了一個(gè)雙向鏈表來維護(hù)元素的存取順序。如下下圖所示:

每次添加元素,就和上一個(gè)元素用雙向鏈表連接一下。第一個(gè)添加的元素是雙向鏈表的頭節(jié)點(diǎn),最后一個(gè)添加的元素是雙向鏈表的尾節(jié)點(diǎn)。

把上個(gè)案例中的集合改成LinkedList集合,我們觀察效果怎樣

public class SetTest4 {
    public static void main(String[] args) {
        Set<Student> students = new LinkedHashSet<>();
        Student s1 = new Student("至尊寶",20, 169.6);
        Student s2 = new Student("蜘蛛精",23, 169.6);
        Student s3 = new Student("蜘蛛精",23, 169.6);
        Student s4 = new Student("牛魔王",48, 169.6);
?
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);
?
        for(Student s : students){
            System.out.println(s);
        }
    }
}

打印結(jié)果如下

Student{name='至尊寶', age=20, height=169.6}
Student{name='蜘蛛精', age=23, height=169.6}
Student{name='牛魔王', age=48, height=169.6}

總結(jié):
LinkedHashSet集合的特點(diǎn)和原理是怎么樣的?
    特點(diǎn): 有序、不重復(fù)、無索引
    底層原理: 基于哈希表,使用鏈表記錄添加順序。

到此這篇關(guān)于Java集合Set與Collections案例詳解的文章就介紹到這了,更多相關(guān)Java集合Set與Collections內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 啟動(dòng)Tomcat報(bào)錯(cuò)Unsupported major.minor version xxx的解決方法

    啟動(dòng)Tomcat報(bào)錯(cuò)Unsupported major.minor version xxx的解決方法

    這篇文章主要為大家詳細(xì)介紹了啟動(dòng)Tomcat報(bào)錯(cuò)Unsupported major.minor version xxx的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • java8 計(jì)算時(shí)間差的方法示例

    java8 計(jì)算時(shí)間差的方法示例

    這篇文章主要介紹了java8 計(jì)算時(shí)間差的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • 微服務(wù)springcloud 03.Eureka實(shí)現(xiàn)高可用的過程

    微服務(wù)springcloud 03.Eureka實(shí)現(xiàn)高可用的過程

    這篇文章主要介紹了微服務(wù)springcloud 03.Eureka實(shí)現(xiàn)高可用的相關(guān)資料,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2024-07-07
  • 詳解Java讀取本地文件并顯示在JSP文件中

    詳解Java讀取本地文件并顯示在JSP文件中

    這篇文章主要介紹了詳解Java讀取本地文件并顯示在JSP文件中的相關(guān)資料,這里提供實(shí)例幫助大家實(shí)現(xiàn)這樣的功能,希望能幫助到大家,需要的朋友可以參考下
    2017-08-08
  • 淺談hashmap為什么查詢時(shí)間復(fù)雜度為O(1)

    淺談hashmap為什么查詢時(shí)間復(fù)雜度為O(1)

    這篇文章主要介紹了hashmap為什么查詢時(shí)間復(fù)雜度為O(1),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • 全面理解java中的異常處理機(jī)制

    全面理解java中的異常處理機(jī)制

    下面小編就為大家?guī)硪黄胬斫鈐ava中的異常處理機(jī)制。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-06-06
  • 基于SpringBoot的Dubbo泛化調(diào)用的實(shí)現(xiàn)代碼

    基于SpringBoot的Dubbo泛化調(diào)用的實(shí)現(xiàn)代碼

    這篇文章主要介紹了基于SpringBoot的Dubbo泛化調(diào)用的實(shí)現(xiàn),從泛化調(diào)用實(shí)現(xiàn)的過程來看,我們可以對(duì)自己提供所有服務(wù)進(jìn)行測試,不需要引入調(diào)用的接口,減少代碼的侵入,需要的朋友可以參考下
    2022-04-04
  • mybatis的ParamNameResolver參數(shù)名稱解析

    mybatis的ParamNameResolver參數(shù)名稱解析

    這篇文章主要為大家介紹了mybatis的ParamNameResolver參數(shù)名稱解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • SpringBoot一個(gè)接口多個(gè)實(shí)現(xiàn)類的調(diào)用方式總結(jié)

    SpringBoot一個(gè)接口多個(gè)實(shí)現(xiàn)類的調(diào)用方式總結(jié)

    這篇文章主要介紹了SpringBoot一個(gè)接口多個(gè)實(shí)現(xiàn)類的調(diào)用方式,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2024-01-01
  • 全面解析Java中的注解與注釋

    全面解析Java中的注解與注釋

    這篇文章主要介紹了Java中的注解與注釋,簡單來說注解以@符號(hào)開頭而注釋被包含在/***/符號(hào)中,各自具體的作用則來看本文詳解,需要的朋友可以參考下
    2016-05-05

最新評(píng)論

旬阳县| 祁阳县| 大洼县| 尖扎县| 华容县| 双辽市| 大邑县| 富平县| 泗洪县| 平舆县| 三穗县| 定远县| 响水县| 玉山县| 舞阳县| 辉县市| 嫩江县| 牙克石市| 磐石市| 肇东市| 周口市| 龙海市| 福安市| 阿拉善左旗| 山东| 兴义市| 涡阳县| 太谷县| 礼泉县| 阳谷县| 高邑县| 济宁市| 繁峙县| 远安县| 雷山县| 永吉县| 固始县| 三台县| 佳木斯市| 隆化县| 临湘市|