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);//--->[Andy:33, Sam:32]
set.add(t2);
System.out.println(set);//--->[Andy:33, Sam:32, Andy:33]
}
}
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 this.age - tea.age;
}
}
6 单值类型集合总结
6.1 Collection(单值类型统一的父接口)
-
父接口:JCF
-
子接口:List、Set
-
共有的方法:add()、addAll()、size()、contains(Object obj)、iterator()、remove(Object obj)、clear()、get(Object obj)
-
都可以使用foreach、迭代器进行循环遍历
-
共有的特性:
当使用迭代器遍历集合的过程中,不允许对集合的整体进行添加/删除操作 否则都会触发CME异常,如果一定要遍历的过程中删除的话,使用迭代器的删除方法。若要增加,定义新的集合,遍历时添加,遍历结束后将新的集合addAll进旧集合。 -
善用contains(Object obj)、remove(Object obj)并设置相关的equals()、hashCode()或compareTo()/compare()来简化代码,但注意remove(Object obj)不能在foreach和迭代器遍历中使用。
6.2 List(有序,不唯一)
-
父接口:Collection
-
主要实现类:
- ArrayList(数组空间大小),使用数组实现
- LinkedList(),链表实现
- Vector(数组空间大小),数组实现
- Stack(),栈实现
-
特有的方法:get(int 下标)、remove(int 下标)、for + 下标
-
基本用法和特点:
向List集合里面添加元素: list.add(Object 元素) -> 因为元素不唯一,所以不作比较,直接加 从List集合删除元素: list.remove(int 下标) -> 直接删 list.remove(Object 元素)-> 将传入的元素作为参照物,调用本类定义的equals()挨个元素传入比较,为true删除(若本类未定义,使用Object类的equals()比较地址进行删除) 判断List集合里是否出现指定的元素: list.contains(Object 元素) -> 将传入的元素作为参照物,调用本类定义的equals()挨个元素传入比较,为true表示存在,方法返回true(若本类未定义,使用Object类的equals()比较地址) 当一个对象已添加进集合后,修改某个对象(自定义引用数据类型)的属性,直接修改。如下面的例子:
import java.util.*;
public class Example{
public static void main(String args[]){
Student s1 = new Student("xx", "123");
ArrayList<Student> sList = new ArrayList<>();
sList.add(s1);
sList.add(s1);
sList.add(s1);
System.out.println(sList);//--->[xx:123, xx:123, xx:123]
s1.name = "xx2";
System.out.println(sList);//--->[xx2:123, xx2:123, xx2:123]
}
}
class Student{
String name;
String tel;
public Student(String name, String tel){
this.name = name;
this.tel = tel;
}
@Override
public String toString(){
return name + ":" + tel;
}
}
-
ArrayList和LinkedList之间的区别?
-
ArrayList和LinkedList底层采用的数据结构不同 导致优劣势不同
-
ArrayList:底层基于数组实现,
优点:随机访问,遍历查找的效率高(可以指定下标对数组进行查找或依据下标遍历查找)
缺点:增删元素效率低(1.创建新数组对象 2.复制老数组元素 3.改变引用指向 4.回收老数组 ) -
LinkedList:底层基于双向循环链表实现,
优点:增删元素效率高(直接通过改变地址指向进行增删)
缺点:不能随机访问,遍历查找效率低(每次遍历查找都需要从头找起)
-
在开发的时候,尽量避免使用LinkedList的get(int 下标)方法
ArrayList更适合访问、读取数据,LinkedList适合增删数据。
- ArrayList和Vector之间的区别?
-
同步线程不同
Vector同一时间允许单个线程进行访问,效率较低,但不会出现并发错误;
ArrayList同一时间允许多个线程访问,效率较高,但可能出现并发错误。 -
扩容机制不同
ArrayList和Vector的初始长度都是10,但扩容机制不同,
Vector分构造方法,(Vector(10) -> 2倍扩容 10->20->40->80…;Vector(10,3)->定长扩容 10->13 ->16->19…);
ArrayList分版本(JDK6.0之前 x * 3 / 2 + 1;JDK7.0之后 x + (x >> 1))。 -
出现的版本不同
Vector出现于JDK1.0;
ArrayList出现于JDK1.2。
-
JDK5.0开始,集合的工具类(Collections)里面提供的一个方法(synchronizedList)可以将线程不安全的ArrayList对象变成线程安全的集合对象,于是Vector渐渐被淘汰。
6.3 Set(无序,唯一)
-
父接口:Collection
-
子接口:SortedSet
-
实现类:HashSet(分组组数,加载因子),基于哈希表实现,默认参数为16、0.75。
-
特有的方法:无
-
基本用法及特点:
向Set集合中添加元素: set.add(Object 元素) -> 需逐步验证hashCode()、 ==、 equals()来确定唯一性 从Set集合中删除元素时: set.remove(Object 元素) -> 需以这个元素根据hashCode()、==、equals()逐个判断是否有这样的元素 判断Set集合里是否出现指定元素: set.contains(Object 元素) -> 需以这个元素根据hashCode()、==、equals()逐个判断是否有这样的元素 当一个对象已经添加进HashSet集合之后,不要随意的修改。 如果一定要修改那些用于生成哈希码的属性值,需先删除、再修改和添加。
-
ArrayList、LinkedList、HashSet集合分别使用什么应用场景?
- ArrayList:按照添加顺序排序,对元素没有唯一要求,查找元素频时
- LinkedList:对元素没有唯一要求,添加/删除元素频繁时
- HashSet:当对元素有唯一要求,但是对排序无要求时
6.4 SortedSet(有序,唯一)
-
父接口:Set
-
实现类:TreeSet(比较器对象),比较器对象可选,基于二叉树实现。
-
特有的方法:first()、last()、pollFirst()、pollLast()
-
特点:
向TreeSet集合中添加元素时,需在比较器或实现的比较方法中验证唯一性并确定添加后的位置: set.add(元素) ->implements Comparable -> compareTo(XXX) ->implements Comparator -> compare(XXX,YYY) 从TreeSet集合里删除元素时,需根据比较器或实现的比较方法中验证唯一性并确定添加后的位置: set.remove(Object 元素) -> implements Comparable -> compareTo(XXX) -> implements Comparator -> compare(XXX,YYY) 判断TreeSet集合里面是否出现指定元素,同样: set.contains(Object 元素) -> implements Comparable -> compareTo(XXX) -> implements Comparator -> compare(XXX,YYY) *:当一个对象已经添加进TreeSet集合后,不要随意的修改那些参与排序的属性值,如果一定要修改,先删除这个元素对象,然后修改对象,再添加回去。
6.5 所有单值集合都有的方法
add(obj)、remove(obj)、size()、iterator()、contains(obj)、clear()、addAll()(取两个集合的并集,如果是唯一性的单值集合,将合并重复的元素)、removeAll()、retainAll()(取两个集合交集)