【第17天】Java集合(四)---Sorted介面實現的TreeSet集合及單值型別集合總結
1 TreeSet簡介
作為SortedSet介面的一個實現類,特點是有序、儲存元素唯一的。底層實現的資料結構為二叉樹。這裡的二叉樹是自平衡二叉樹,集合裡面的根元素,一日是根,不代表永遠是根,底層會自動的進行旋轉修復。
2 基本用法與特點
-
建立一個TreeSet集合物件(暫用兩種方式)
與前面學到的集合相同,TreeSet集合物件建立也依據不同的版本有不同的方法。參照前面的兩篇文章。
但在TreeSet類中,依據傳入的不同引數,初始化實現的構造方法也不同。HashSet使用其建立初始空間和載入因子,這裡根據傳入的引數定義比較規則。重點說其中兩種構造方法。TreeSet() 構造一個新的空TreeSet,該TreeSet根據其元素的自然順序進行排序。 TreeSet(Comparator<? super E> comparator) 構造一個新的空TreeSet,它根據指定比較器進行排序。
-
新增元素到TreeSet
set.add(Object obj); Collections.addAll(set, Object obj1,Object obj2....); set1.addAll(集合型別的引用);
-
得到TreeSet集合的大小
set.size();
-
判斷集合裡是否包含指定元素
set.contains(Object obj);
-
指定元素進行刪除
set.remove(Object obj);
注意,TreeSet中的刪除、判斷是否包含指定元素底層依賴的依據是實現介面Comparable中的compareTo(),當方法返回為0,即相當於equals()返回的true。
與HashSet不同,TreeSet插入元素時的判斷標準其實只需實現Comparable介面的compareTo()方法就可以。不需重寫equals()和hashCode()方法。這個方法返回值不能為0,依賴compareTo操作集合的方法均不得使用。需要遍歷獲取值進行判斷是否存在或者刪除。
-
得到集合裡的第一個元素並且刪除(poll:投出)
set.pollFirst()
-
得到集合裡最後一個元素並且刪除
set.pollLast();
-
得到集合裡的第一個元素
set.first()
-
得到集合裡最後一個元素
set.last();
-
遍歷
//遍歷 //foreach for(Integer x : set){ System.out.println(x); } //迭代器 for(Iterator<Integer> i = set.iterator(); i.hasNext(); ){ Integer num = i.next(); System.out.println(num); }
與前面HashSet一樣,因為HashSet集合是無序的,在遍歷時不能根據for + 下標進行順序遍歷,只能根據迭代器進行遍歷。foreach底層也是依據迭代器遍歷。另外TreeSet也缺少依據下標的get()和remove()。
-
倒序輸出
TreeSet<E> reSet = (TreeSet)set.descendingSet();
-
-
例題:
//將TreeSet中的元素倒序輸出
import java.util.*;
public class Example{
public static void main(String[] args){
TreeSet<Integer> set = new TreeSet<>();
Collections.addAll(set,50,80,70,40,25,88);
System.out.println(set);//--->[25, 40, 50, 70, 80, 88]
ArrayList<Integer> list = new ArrayList<>();
while(set.size() != 0){
//獲取最後一個元素並且刪除
list.add(set.pollLast());
}
System.out.println(list);//--->[88, 80, 70, 50, 40, 25]
}
}
因TreeSet同樣是無序的,所以它也不具有以下標為引數列表的方法,與HashSet相同。
3 制定單值比較規則
3.1 自然排序(compareTo(Object obj))
//依照球號從小到大排序(基本資料型別)
import java.util.*;
public class Test1{
public static void main(String[] args){
TreeSet<Ball> set = new TreeSet<>();
Ball b1 = new Ball(3,'紅');
Ball b2 = new Ball(5,'藍');
Ball b3 = new Ball(1,'黃');
Collections.addAll(set,b1,b2,b3);
System.out.println(set);//--->[1號球:黃,3號球:紅色,5號球:藍色]
}
}
//需實現Comparable<泛型>類中的int compareTo(泛型)方法
class Ball implements Comparable<Ball>{
int number;
char color;
public Ball(int number,char color){
this.number = number;
this.color = color;
}
@Override
public int compareTo(Ball b){
//新元素(呼叫):this.number
//舊元素(傳入):b.number
return this.number - b.number;
}
@Override
public String toString(){
return number + "號球:" + color + "色";
}
}
//按名字升序排序(引用資料型別)
import java.util.*;
public class Test2{
public static void main(String[] args){
TreeSet<Student> set = new TreeSet<>();
Student stu1 = new Student("Bob",30);
Student stu2 = new Student("Cindy",18);
Student stu3 = new Student("Ella",18);
Student stu4 = new Student("Zella",18);
Collections.addAll(set,stu1,stu2, stu3, stu4);
System.out.println(set);//--->[Bob:30, Cindy:18, Ella:18, Zella:18]
}
}
class Student implements Comparable<Student>{
String name;
int age;
public Student(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return name + ":" + age;
}
@Override
public int compareTo(Student stu){
return this.name.compareTo(stu.name);
}
}
當compareTo方法返回0時,不儲存;當compareTo方法返回正數時,存入二叉樹的右子樹;當compareTo方法返回負數時,存入二叉樹的左子樹。
-
新元素(呼叫):this . xxx
-
舊元素(傳入):obj . xxx
-
升序:
- 基本資料型別:返回“新元素 - 舊元素”,當值為正,將新(較大的)元素右置
- 引用資料型別:新元素.compareTo(舊元素)
-
降序:
- 基本資料型別:返回“舊元素 - 新元素”,當值為正,將新(較小的)元素右置
- 引用資料型別:舊元素.compareTo(新元素)
3.2 定製排序(定義比較器類)
3.2.1 普通類內定義
//按名字升序排序
import java.util.*;
public class Test{
public static void main(String[] args){
MyComparator mc = new MyComparator();
TreeSet<Student> set = new TreeSet<>(mc);
Student stu1 = new Student("Bob",30);
Student stu2 = new Student("Cindy",18);
Student stu3 = new Student("Ella",18);
Student stu4 = new Student("Zella",18);
Collections.addAll(set,stu1,stu2, stu3, stu4);
System.out.println(set);//--->[Bob:30, Cindy:18, Ella:18, Zella:18]
}
}
class Student{
String name;
int age;
public Student(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return name + ":" + age;
}
}
class MyComparator implements Comparator<Student>{
//引數列表傳入的方式:(新元素, 舊元素)
@Override
public int compare(Student s1, Student s2){
return s1.compareTo(s2);
}
}
-
新元素(呼叫):compare()引數列表中第一個引數
-
舊元素(傳入):compare()引數列表中第二個引數
-
升序:
- 新元素.compareTo(舊元素)
-
降序:
- 舊元素.compareTo(新元素)
3.2.2 單例類定義(更常用)
注意單例時實現的類和方法與類內定義的都不一樣//按名字升序排序(單例定義)
import java.util.*;
public class TestSingleTask{
public static void main(String[] args){
TreeSet<Student> set = new TreeSet<>(MyComparator.getMyComparator());
Student stu1 = new Student("Bob",30);
Student stu2 = new Student("Cindy",18);
Student stu3 = new Student("Ella",18);
Student stu4 = new Student("Zella",18);
Collections.addAll(set,stu1,stu2, stu3, stu4);
System.out.println(set);//--->[Bob:30, Cindy:18, Ella:18, Zella:18]
}
}
class Student{
String name;
int age;
public Student(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return name + ":" + age;
}
}
class MyComparator implements Comparator<Student>{
//私有化構造方法
private MyComparator(){}
//私有並靜態實現一個本類引用
private static MyComparator myComparator = new MyComparator();
//供外界呼叫的get方法
public static MyComparator getMyComparator(){
return myComparator;
}
//引數列表傳入的方式:(新元素, 舊元素)
@Override
public int compare(Student s1, Student s2){
return s1.compareTo(s2);
}
}
3.2.3 String的中文排序
String的compareTo()預設按首字母排序,若要為中文元素排序,需要匯入"java.text.*"包使用元素的第一個字的首字母排序,否則直接按照Unicode編碼排序。String的中文排序(升序排列):
//首先導包
import java.text*;
//定義Collator的引用,並在例項中傳入中文
Collator instance = Collator.getInstance(Locale.CHINA);
//使用引用呼叫compare方法,引數列表中第一位是新元素,第二位是舊元素
return instance.compare(s1.name, s2.name);
若改為降序,將compare()的兩個引數調換位置即可。
4 多個比較規則時的情況
想按多個屬性綜合排序時,優先比較什麼屬性,就優先描述什麼屬性不同。其他定義方式與單值相同。不論是定製排序還是自然排序,寫法相同,都是根據條件寫出if的幾個判斷以及完全相同的元素是否允許儲存(此時返回定值1)。
import java.util.*;
public class Test{
/**
兩個學生優先按照分數進行升序排序
如果分數一樣的話 按照年齡降序排序
如果分數和年齡都一樣的話 那麼按照姓名升序排序
如果所有屬性都一樣 那麼也要儲存
*/
public static void main(String[] args){
TreeSet<Student> set = new TreeSet<>();
Student s1 = new Student("Andy",20,45);
Student s2 = new Student("Lee",22,99);
Student s3 = new Student("Andy",20,45);
Student s4 = new Student("Jack",18,99);
Collections.addAll(set,s1,s2,s3,s4);
System.out.println(set);//s1 s3 s2 s4
}
}
class Student implements Comparable<Student>{
String name;
int age;
int score;
public Student(String name,int age,int score){
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString(){
return name + "[" + age + "," + score + "]";
}
@Override
public int compareTo(Student stu){
//新元素
Student s1 = this;
//老元素
Student s2 = stu;
/*
//分數相等,否則按分數升序排列
if(s1.score == s2.score){
//年齡相等,否則按年齡降序排列
if(s1.age == s2.age){
//姓名相同,否則按姓名(英文)首字母升序排列
if(s1.name.equals(s2.name)){
//若分數、年齡、姓名都一致,返回大於0,新元素在右子樹儲存
return 1;
}else{
return s1.name.compareTo(s2.name);
}
}else{
return s2.age - s1.age;
}
}else{
return s1.score - s2.score;
}
*/
//直接考慮對立面的條件,上面的分析可以簡化為:
//按分數升序排列
if(s1.score != s2.score) return s1.score - s2.score;
//若分數一樣,按年齡降序排列
if(s1.age != s2.age) return s2.age - s1.age;
//若分數、年齡都一樣,按姓名(英文)首字母升序排列
if(!(s1.name.equals(s2.name))) return s1.name.compareTo(s2.name);
//都一樣,也要儲存記錄
return 1;
}
}
5 增刪改查需要注意
5.1 當比較方法的返回值無法得到0
在重寫compareTo()時,儘量保證這個方法有機會返回0,否則集合無法保證其元素唯一性,集合也不能呼叫方法指定元素進行刪除(remove(Object obj)),也無法使用集合呼叫方法查詢元素是否包含某個值(contains(Object obj)),無法使用get(Object obj)。(參照物件.compareTo(元素) == 0時,contains才會為true或remove才會刪除這個元素)
當compareTo()一定沒有機會返回0時,如果想要刪除集合裡面的某個元素,要使用迭代器的刪除方法:迭代器引用.remove(),其底層不遵循compareTo()的比較規則,只與當前游標位置有關,所以可直接刪除元素。
要查詢元素是否包含某個值時,也要使用迭代器,取出這個元素值之後再進行比對。
如下面的例子:
import java.util.*;
public class Example{
public static void main(String[] args){
TreeSet<Teacher> set = new TreeSet<>();
Teacher tea = new Teacher("Tom",35);
set.add(tea);
set.add(tea);//底層執行tea.compareTo(tea),返回1,加入右子樹
set.add(tea);//底層執行tea.compareTo(tea),返回1,加入右子樹
/**執行結果
tea[Tom,35]
tea[Tom,35]
tea[Tom,35]
*/
set.remove(tea);//參照物件.compareTo(元素) == 0? 等於0就刪除
System.out.println(set);//--->[Tom:35, Tom:35, Tom:35]
for(Iterator<Teacher> car = set.iterator();car.hasNext();){
//取出元素進行比對
Teacher t = car.next();
if(t.name.equals("Tom")){
car.remove();
}
}
System.out.println(set);//[]
}
}
class Teacher implements Comparable<Teacher>{
String name;
int age;
public Teacher(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return name + ":" + age;
}
@Override
public int compareTo(Teacher tea){
//所有的新元素都被認為比集合裡的舊元素大,新元素置於右子樹
return 1;
}
}
5.2 遍歷增刪元素時
- 與之前集合相同,刪除元素時使用迭代器的刪除方法;增加元素時要使用一個新的TreeSet集合接收元素,並在遍歷結束之後使用TreeSet的addAll方法將這個集合新增到原集合中。否則會報CME異常。
import java.util.*;
public class Test{
public static void main(String[] args){
TreeSet<Integer> set = new TreeSet<>();
Collections.addAll(set,45,66,72,38,90,59);
//刪除集合裡面所有不及格的分數
for(Iterator<Integer> i = set.iterator();car.hasNext();){
Integer score = i.next();
if(score < 60){
i.remove();
}
}
System.out.println(set);//--->[66,72,90]
}
}
- 與HashSet集合不得直接操作已加入集合的物件中已參與生成雜湊特徵碼的屬性類似,修改元素時,不要直接修改已被新增進集合的物件的參與排序(compareTo或compare方法)的屬性值。否則將會造成修改後的元素無法刪除的問題,且再新增同樣的元素,不會被認為是同一個物件。(雖在樹的一個節點改變了其屬性,節點卻不發生移動,使得刪除時根據改變之後的路徑搜尋這個節點無法使結果等於0;且再新增時這個改變後的應該對應的正確節點仍為空,故可以新增)
- 如果一定要修改,需要先獲取物件,刪除這個物件後,將物件進行修改後再添加回集合中。
import java.util.*;
public class Test{
public static void main(String[] args){
TreeSet<Teacher> set = new TreeSet<>();
Teacher t1 = new Teacher("Sam",32);
Teacher t2 = new Teacher("Andy",23);
set.add(t1);
set.add(t2);
/**
t1[Sam,32]
t2[Andy,33]
*/
t2.age += 10;
set.remove(t2);
//本應該是升序排列,Andy元素在原節點增長了10,沒有移動出現混亂
System.out.println(set);