Java集合Set與Collections案例詳解
集合進階(Set集合、Collections)
一、Set系列集合
1.1 認識Set集合的特點
Set集合是屬于Collection體系下的另一個分支,它的特點如下圖所示

下面我們用代碼簡單演示一下,每一種Set集合的特點。
public class SetTest1 {
public static void main(String[] args) {
// 創(chuàng)建一個Set集合的對象
/**
* HashSet的集合, 是一行經(jīng)典代碼(常用) 特點: 無序不重復 無索引
* 針對無序不是每次都是隨機的, 第一次無序排好之后, 以后都是這個順序(面試題)
*/
// Set<Integer> set = new HashSet<>();
// 特點: 有序、無索引、不重復
// Set<Integer> set = new LinkedHashSet<>();
// 可排序(默認升序)、無索引、不重復
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集合底層原理
接下來,為了讓同學們更加透徹的理解HashSet為什么可以去重,我們來看一下它的底層原理。
HashSet集合底層是基于哈希表實現(xiàn)的,所以在正式了解HashSet集合的底層原理前,我們需要先搞清楚一個前置知識:哈希值!:

演示哈希值的相同與不相同
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("寶強", 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集合的底層原理
- 基于哈希表實現(xiàn)。
- 哈希表是一種增刪改查數(shù)據(jù),性能都較好的數(shù)據(jù)結(jié)構(gòu)。
哈希表根據(jù)JDK版本的不同,也是有點區(qū)別的
- JDK8以前:哈希表 = 數(shù)組+鏈表
- JDK8以后:哈希表 = 數(shù)組+鏈表+紅黑樹

我們發(fā)現(xiàn)往HashSet集合中存儲元素時,底層調(diào)用了元素的兩個方法:一個是hashCode方法獲取元素的hashCode值(哈希值);另一個是調(diào)用了元素的equals方法,用來比較新添加的元素和集合中已有的元素是否相同。
- 只有新添加元素的hashCode值和集合中以后元素的hashCode值相同、新添加的元素調(diào)用equals方法和集合中已有元素比較結(jié)果為true, 才認為元素重復。
- 如果hashCode值相同,equals比較不同,則以鏈表的形式連接在數(shù)組的同一個索引為位置(如上圖所示)
在JDK8開始后,為了提高性能,當鏈表的長度超過8時,就會把鏈表轉(zhuǎn)換為紅黑樹,如下圖所示:

總結(jié):
1. 什么是哈希值?對象的哈希值有什么特點?
所謂的哈希值就是JDK根據(jù)對象的地址或者屬性值算出來的int類型整數(shù)。
特點:
同一個對象多次調(diào)用hashCode()方法的哈希值相同
不同對象調(diào)用的hashCode()方法,哈希值不同,但是可以根據(jù)子類重寫hashCode()方法讓其相同。
2. HashSet集合的底層原理是什么樣的?
基于哈希表實現(xiàn)的。
JDK8之前的,哈希表:底層使用數(shù)組+鏈表組成
JDK8開始后,哈希表:底層采用數(shù)組+鏈表+紅黑樹組成。
3. HashSet集合利用哈希表操作數(shù)據(jù)的詳細流程是咋回事?
HashSet底層采用了哈希表數(shù)據(jù)結(jié)構(gòu)
哈希表又叫做散列表,哈希表底層是一個數(shù)組,這個數(shù)組中每一個元素是一個單向鏈表,每個單向鏈表都有一個獨一無二的hash值,代表數(shù)組的下標。在某個單向鏈表中的每一個節(jié)點上的hash值是相同的。hash值實際上是key調(diào)用hashCode方法,再通過"hash function"轉(zhuǎn)換成的值。
如何向哈希表中添加元素?
先調(diào)用被存儲的key的hashCode方法,經(jīng)過某個算法得出hash值,如果在這個哈希表中不存在這個hash值,則直接加入元素。如果該hash值已經(jīng)存在,繼續(xù)調(diào)用Key之間的equals方法,如果equals方法返回false,則將該元素添加。如果equals方法返回true,則放棄添加該元素
HashSet初始化容量是16,默認加載因子是0.75
4. 哈希表的詳細流程(面試題)
①.創(chuàng)建一個默認長度16,默認加載因為0.75的數(shù)組,數(shù)組名table
②.根據(jù)元素的哈希值跟數(shù)組的長度計算出應(yīng)存入的位置
③.判斷當前位置是否為null,如果是null直接存入,如果位置不為null,表示有元素,則調(diào)用equals方法比較屬性值,如果一樣,則不存,如果不一樣,則存入數(shù)組。
④.當數(shù)組存滿到16*0.75=12時,就自動擴容,每次擴容原先的兩倍
1.3 HashSet去重原理
前面我們學習了HashSet存儲元素的原理,依賴于兩個方法:一個是hashCode方法用來確定在底層數(shù)組中存儲的位置,另一個是用equals方法判斷新添加的元素是否和集合中已有的元素相同。
要想保證在HashSet集合中沒有重復元素,我們需要重寫元素類的hashCode和equals方法。
比如以下面的Student類為例,假設(shè)把Student類的對象作為HashSet集合的元素,想要讓學生的姓名和年齡相同,就認為元素重復。
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()方法自己補上..
// 按快捷鍵生成hashCode和equals方法
// alt+insert 選擇 hashCode and equals
// 只要兩個對象的內(nèi)容一樣就會返回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;
}
?
// 只要兩個對象內(nèi)容一樣, 返回的哈希值就是一樣的
@Override
public int hashCode() {
// 根據(jù)姓名 年齡 身高計算哈希值
return Objects.hash(name, age, height);
}
}接著,寫一個測試類,往HashSet集合中存儲Student對象。
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)存了兩個蜘蛛精,當時實際打印出來只有一個,而且是無序的。
Student{name='牛魔王', age=48, height=169.6}
Student{name='至尊寶', age=20, height=169.6}
Student{name='蜘蛛精', age=23, height=169.6}
總結(jié):
如果希望Set集合認為2個內(nèi)容相同的對象是重復的應(yīng)該怎么辦?
需要重寫元素類的hashCode和equals方法。
1.4 LinkedHashSet底層原理
接下來,我們再學習一個HashSet的子類LinkedHashSet類。LinkedHashSet它底層采用的是也是哈希表結(jié)構(gòu),只不過額外新增了一個雙向鏈表來維護元素的存取順序。如下下圖所示:

每次添加元素,就和上一個元素用雙向鏈表連接一下。第一個添加的元素是雙向鏈表的頭節(jié)點,最后一個添加的元素是雙向鏈表的尾節(jié)點。
把上個案例中的集合改成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集合的特點和原理是怎么樣的?
特點: 有序、不重復、無索引
底層原理: 基于哈希表,使用鏈表記錄添加順序。
到此這篇關(guān)于Java集合Set與Collections案例詳解的文章就介紹到這了,更多相關(guān)Java集合Set與Collections內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java集合框架實戰(zhàn)HashMap與HashSet的使用案例
- Java中Set集合遍歷的四種方法實現(xiàn)
- Java合并集合幾種常見方式總結(jié)(List、Set、Map)
- Java位集合之BitMap、BitSet和布隆過濾器示例解析
- Java集合ArrayList、LinkedList、HashMap、HashSet最大容量
- Java中Set集合轉(zhuǎn)為List集合常見的兩種方式
- Java中的HashSet集合解析
- Java中的HashSet、LinkedHashSet集合解析
- Java后臺通過Collections獲取list集合中最大數(shù),最小數(shù)代碼
- Java使用Collections工具類對List集合進行排序
相關(guān)文章
啟動Tomcat報錯Unsupported major.minor version xxx的解決方法
這篇文章主要為大家詳細介紹了啟動Tomcat報錯Unsupported major.minor version xxx的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11
微服務(wù)springcloud 03.Eureka實現(xiàn)高可用的過程
這篇文章主要介紹了微服務(wù)springcloud 03.Eureka實現(xiàn)高可用的相關(guān)資料,本文給大家介紹的非常詳細,需要的朋友可以參考下2024-07-07
基于SpringBoot的Dubbo泛化調(diào)用的實現(xiàn)代碼
這篇文章主要介紹了基于SpringBoot的Dubbo泛化調(diào)用的實現(xiàn),從泛化調(diào)用實現(xiàn)的過程來看,我們可以對自己提供所有服務(wù)進行測試,不需要引入調(diào)用的接口,減少代碼的侵入,需要的朋友可以參考下2022-04-04
mybatis的ParamNameResolver參數(shù)名稱解析
這篇文章主要為大家介紹了mybatis的ParamNameResolver參數(shù)名稱解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08
SpringBoot一個接口多個實現(xiàn)類的調(diào)用方式總結(jié)
這篇文章主要介紹了SpringBoot一個接口多個實現(xiàn)類的調(diào)用方式,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2024-01-01

