JAVA基礎再回首(十七)——Set集合、增強for、HashSet類、LinkedHashSet類、TreeSet類、二叉樹、Comparator 排序
版權聲明:轉載必須註明本文轉自程序員杜鵬程的博客:http://blog.csdn.net/m366917
前面我們學習了集合框架的List集合,根據前面我畫的集合框架的圖,可以看到還有Set集合和Map集合我們沒有學習,這篇我們就來學習Set集合。
- Set
- 元素是無序(存儲順序和取出順序不一致),元素是唯一的,不可重複的
我們來看一下API
我們寫一個簡單的Demo看看它的元素是不是無序和唯一的
public class SetDemo {
public static void main(String[] args) {
// 創建集合對象
Set<String> set = new HashSet<String>();
// 創建並添加元素
set.add("hello");
set.add("java");
set.add("world");
set.add("java");
set.add("world");
// 增強for遍歷
for (String s : set) {
System.out.println(s);
}
//迭代器遍歷
Iterator it = set.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
我們先來看結果:
在這裏想給大家說一個要注意的地方
雖然Set集合的元素無序,但是,作爲集合來說,它肯定有它自己的存儲順序,而你的順序恰好和它的存儲順序一致,這代表不了有序,你可以多存儲一些數據,就能看到效果。
上面我們使用了兩種遍歷的方式,用了增強的for循環和迭代器來遍歷,前面我們學List集合的時候,我們還用了普通的for循環,但是在這裏不能用普通的for循環了,迭代器遍歷我們前面都學過,那我在下面就簡單的提一下增強的for循環的簡單使用。
增強for
增強for:是for循環的一種。
格式:
for(元素數據類型 變量 : 數組或者Collection集合) {
使用變量即可,該變量就是元素
}
好處:簡化了數組和集合的遍歷。
弊端: 增強for的目標不能爲null。
如何解決呢?對增強for的目標先進行不爲null的判斷,然後在使用。
HashSet類
什麼是HashSet?我們先來了解他的概述
- HashSet類概述
- 不保證 set 的迭代順序
- 特別是它不保證該順序恆久不變。
- HashSet如何保證元素唯一性
- 底層數據結構是哈希表(元素是鏈表的數組)
- 哈希表依賴於哈希值存儲
- 添加功能底層依賴兩個方法:
- int hashCode()
- boolean equals(Object obj)
public class HashSetDemo {
public static void main(String[] args) {
// 創建集合對象
HashSet<String> hs = new HashSet<String>();
// 創建並添加元素
hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("world");
// 遍歷集合(這裏可以用增強for也可以用迭代器,但是增強for比較簡單點)
for (String s : hs) {
System.out.println(s);
}
}
}
輸出結果:
爲什麼存儲字符串的時候,字符串內容相同的只存儲了一個呢?
我們可以查看add方法的源碼,就可以知道這個方法底層依賴 兩個方法:hashCode()和equals()。
- 步驟:
- 首先比較哈希值
- 如果相同,繼續走,比較地址值或者走equals()
- 如果不同,就直接添加到集合中
- 按照方法的步驟來說:
- 先看hashCode()值是否相同
- 相同:繼續走equals()方法
- 返回true: 說明元素重複,就不添加
- 返回false:說明元素不重複,就添加到集合
- 不同:就直接把元素添加到集合
- 如果類沒有重寫這兩個方法,默認使用的Object()。一般來說不同相同。
- 而String類重寫了hashCode()和equals()方法,所以,它就可以把內容相同的字符串去掉。只留下一個。
存儲自定義對象
我們存儲自定義對象,並保證元素的唯一性,
要求:如果兩個對象的成員變量值都相同,則爲同一個元素。
public class HashSetDemo {
public static void main(String[] args) {
// 創建集合對象
HashSet<Student> hs = new HashSet<Student>();
// 創建學生對象
Student s1 = new Student("朱婷", 22);
Student s2 = new Student("惠若琪", 22);
Student s3 = new Student("徐雲麗", 21);
Student s4 = new Student("朱婷", 22);
Student s5 = new Student("郎平", 55);
Student s6 = new Student("郎平", 57);
// 添加元素
hs.add(s1);
hs.add(s2);
hs.add(s3);
hs.add(s4);
hs.add(s5);
hs.add(s6);
// 遍歷集合
for (Student s : hs) {
System.out.println(s.getName() + "---" + s.getAge());
}
}
}
/**
* 存儲對象
*/
public class Student {
private String name;
private int age;
public Student() {
super();
}
public Student(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;
}
}
輸出結果:
![]()
我們看輸出結果,很明顯不符合我的要求。
因爲我們知道HashSet底層依賴的是hashCode()和equals()方法。
而這兩個方法我們在學生類中沒有重寫,所以,默認使用的是Object類。
這個時候,他們的哈希值是不會一樣的,根本就不會繼續判斷,執行了添加操作。
所以我們要在Student類中重寫hashCode()和equals()這兩個方法
這兩個方法都是重寫的,不用自己去寫
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
然後我們運行程序,會出現以下結果,你會發現,符合了我們的要求
LinkedHashSet類
- 概述
- 底層數據結構由哈希表和鏈表組成
- 由鏈表保證元素有序(存儲和取出是一致)
- 由哈希表保證元素唯一性
我們做個小練習來了解它
public class LinkedHashSetDemo {
public static void main(String[] args) {
// 創建集合對象
LinkedHashSet<String> hs = new LinkedHashSet<String>();
// 創建並添加元素
hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("world");
hs.add("java");
// 遍歷
for (String s : hs) {
System.out.println(s);
}
}
}
輸出結果:
![]()
LinkedHashSet類很好理解,下面我們來繼續學習TreeSet類
TreeSet類
- 概述
- 使用元素的自然順序對元素進行排序
- 或者根據創建 set 時提供的 Comparator 進行排序
- 所以排序有兩種方式
- 自然排序
- 比較器排序
- 所以排序有兩種方式
- 具體取決於使用的構造方法。
我們先創建集合對象,運用自然排序的方式來寫Demo
public class TreeSetDemo {
public static void main(String[] args) {
// 創建集合對象
// 自然順序進行排序
TreeSet<Integer> ts = new TreeSet<Integer>();
// 創建元素並添加
// 20,18,23,22,17,24,19,18,24
ts.add(20);
ts.add(18);
ts.add(23);
ts.add(22);
ts.add(17);
ts.add(24);
ts.add(19);
ts.add(18);
ts.add(24);
// 遍歷
for (Integer i : ts) {
System.out.println(i);
}
}
}
輸出結果:
![]()
我們可以看到它使用元素的自然順序對元素進行了排序
- TreeSet是如何保證元素的排序和唯一性的
- 底層數據結構是紅黑樹(紅黑樹是一種自平衡的二叉樹)
下面我們就來學習一下二叉樹
二叉樹
那麼二叉樹是怎麼把元素存進去,又是怎麼取出來的呢?
如果你弄懂了這個問題,那麼你就明白了二叉樹了
我們先來了解元素是如何存儲進去的?
第一個元素存儲的時候,直接作爲根節點存儲。
從第二個元素開始,每個元素從根節點開始比較
比根節點元素大,就放在右邊
比根節點元素小,就放在左邊
相等的話就忽略。我們以上面的存儲的元素20,18,23,22,17,24,19,18,24來畫一個圖幫助大家理解
元素是如何取出來的呢?
從根節點開始,按照左、中、右的原則依次取出元素即可。
Comparator 排序
上面我們學習了自然排序,接下來我們來學習比較器排序。
/*
* 需求:請按照姓名的長度排序
*/
public class TreeSetDemo {
public static void main(String[] args) {
// 創建集合對象
TreeSet<Student> ts = new TreeSet<Student>();
// 創建元素
Student s1 = new Student("朱婷", 22);
Student s2 = new Student("惠若琪", 22);
Student s3 = new Student("徐雲麗", 21);
Student s4 = new Student("朱婷婷", 22);
Student s5 = new Student("郎平", 55);
Student s6 = new Student("林丹", 34);
Student s7 = new Student("李宗偉", 33);
Student s8 = new Student("阿杜", 23);
// 添加元素
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
ts.add(s7);
ts.add(s8);
// 遍歷
for (Student s : ts) {
System.out.println(s.getName() + "---" + s.getAge());
}
}
}
/*
* 如果一個類的元素要想能夠進行自然排序,就必須實現自然排序接口
*/
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student() {
super();
}
public Student(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 int compareTo(Student s) {
// 主要條件 姓名的長度
int num = this.name.length() - s.name.length();
// 姓名的長度相同,不代表姓名的內容相同
int num2 = num == 0 ? this.name.compareTo(s.name) : num;
// 姓名的長度和內容相同,不代表年齡相同,所以還得繼續判斷年齡
int num3 = num2 == 0 ? this.age - s.age : num2;
return num3;
}
}
輸出結果:
![]()
我們就總結了以下幾點內容
- TreeSet集合保證元素排序和唯一性的原理
- 唯一性:是根據比較的返回是否是0來決定。
- 排序:
- A:自然排序(元素具備比較性)
- 讓元素所屬的類實現自然排序接口 Comparable
- B:比較器排序(集合具備比較性)
- 讓集合的構造方法接收一個比較器接口的子類對象 Comparator
- A:自然排序(元素具備比較性)
最後,我們在做一個聯繫題吧
獲取10個1至20的隨機數,要求隨機數不能重複。
我們先來分析一下:
- A:創建隨機數對象
- B:創建一個HashSet集合
- C:判斷集合的長度是不是小於10
- 是:就創建一個隨機數添加
- 否:不理它
- D:遍歷HashSet集合
知道了思路,那就開始寫吧!
public class HashSetDemo {
public static void main(String[] args) {
// 創建隨機數對象
Random r = new Random();
// 創建一個Set集合
HashSet<Integer> ts = new HashSet<Integer>();
// 判斷集合的長度是不是小於10
while (ts.size() < 10) {
int num = r.nextInt(20) + 1;
ts.add(num);
}
// 遍歷Set集合
for (Integer i : ts) {
System.out.println(i);
}
}
}
運行程序,每次都會得到10個1-20以內的隨機數
好了,這篇我們講了Set集合和它的子類,沒有掌握的,再好好理解一下,下篇我們學習Map集合。
歡迎有興趣的同學加我朋友的QQ羣:點擊直接加羣555974449 請備註:java基礎再回首我們一起來玩吧。