TreeSet的理解和用法

Posted qq511430890

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TreeSet的理解和用法相关的知识,希望对你有一定的参考价值。

treeset 底层用treemap实现,实现了sortedset接口。红黑树实现,不允许重复。可以自然和定制排序。
用到的例子:
1,ijkplayer中treeset存入mp4,flv。判断后缀。
总结:
1、不能有重复的元素; 
2、具有排序功能; 
3、TreeSet中的元素必须实现Comparable接口并重写compareTo()方法,TreeSet判断元素是否重复 、以及确定元素的顺序 靠的都是这个方法; 
①对于java类库中定义的类,TreeSet可以直接对其进行存储,如String,Integer等,因为这些类已经实现了Comparable接口); 
②对于自定义类,如果不做适当的处理,TreeSet中只能存储一个该类型的对象实例,否则无法判断是否重复。 
4、依赖TreeMap。 
5、相对HashSet,TreeSet的优势是有序,劣势是相对读取慢。根据不同的场景选择不同的集合。
 
Set<String> treeset = new TreeSet<String>();
treeset.add("treeset1");
treeset.add("treeset2");
treeset.add("treeset3");
treeset.add("treeset4");
treeset.add("treeset10");
treeset.add("treeset9");
String stree = "treeset2";
treeset.add(stree);
treeset.add("tree9");
 
// 测试,执行添加了8次,但是treeset.add(stree);  重复,上述元素打印出来有  7 个。
 
System.out.println(treeset.size());
Iterator<String> treesetIt = treeset.iterator();
while(treesetIt.hasNext())
    //  System.out.println(listIt.next());
    System.out.println(treesetIt.next());
打印结果:(按照字符串规则,字典查找法上升排序)
  7 (元素个数)
    tree9
    treeset1
    treeset10
    treeset2
    treeset3
    treeset4
    treeset9
 
如果treeset存的是整形,那么排列是:
Set<Integer> treeset1 = new TreeSet<>();
treeset1.add(1);
treeset1.add(2);
treeset1.add(3);
treeset1.add(4);
treeset1.add(10);
treeset1.add(9);
int stree22 = 22;
treeset1.add(stree22);
treeset1.add(9);
打印:
   7(元素)
    1    2   3    4    9   10    22
 
 
如果存的是一个bean对象,例如Student
Set<Student> studentSet = new TreeSet<>();
studentSet.add(new Student("张三1",18,99));
studentSet.add(new Student("张三2",17,100));
studentSet.add(new Student("张三3",22,60));
studentSet.add(new Student("张三4",43,69));
studentSet.add(new Student("张三4",20,92));
 
System.out.println(studentSet.size());
//使用迭代器遍历Set集合
Iterator<Student> setIt = studentSet.iterator();
while(setIt.hasNext())
    System.out.println(setIt.next());
 
结果:抛出异常
     Caused by: java.lang.ClassCastException: com.leesoo.lee.myplayer.ui.Student cannot be cast to java.lang.Comparable
违背了第三点:student类没有实现Comparable接口,不能存进treeset,无法判断重复。
 
如何解决:
如何指定比较的规则,需要在自定义类(Student)中实现Comparable接口,并重写接口中的compareTo方法
public class Student implements Comparable<Student>   //这个《student》要加,跟下面比较对应     private String name;     private int age;     ... @Override
public int compareTo(@NonNull Student o)     //    return 0;                //当compareTo方法返回0的时候集合中只有一个元素         return 1;                //当compareTo方法返回正数的时候集合会怎么存就怎么取      //   return -1;                //当compareTo方法返回负数的时候集合会倒序存储    
为什么返回0,只会存一个元素,返回-1会倒序存储,返回1会怎么存就怎么取呢?原因在于TreeSet底层其实是一个二叉树机构,且每插入一个新元素(第一个除外)都会调用compareTo()方法去和上一个插入的元素作比较,并按二叉树的结构进行排列。
  1. 如果将compareTo()返回值写死为0,元素值每次比较,都认为是相同的元素,这时就不再向TreeSet中插入除第一个外的新元素。所以TreeSet中就只存在插入的第一个元素。
  2. 如果将compareTo()返回值写死为1,元素值每次比较,都认为新插入的元素比上一个元素大,于是二叉树存储时,会存在根的右侧,读取时就是正序排列的。
  3. 如果将compareTo()返回值写死为-1,元素值每次比较,都认为新插入的元素比上一个元素小,于是二叉树存储时,会存在根的左侧,读取时就是倒序序排列的。
 
 
最终:student类添加到treeset时候,比较方法是   1 ,那么
插进去时候是   123456   打印出来也是123456
如果compareTo方法返回负数,插进去时候是   123456   打印出来就是654321
 
 
对java类库的String 字符串重新排序,不按照字典了,按照长度来排列。
 
只需要在新建treeset 时候,构造函数传入自定义的排序规则即可。
技术图片
 
//定义一个类,实现Comparator接口,并重写compare()方法, class CompareByLength implements Comparator<String>
    @Override     public int compare(String s1, String s2)         //按照字符串的长度比较         int num = s1.length() - s2.length();        //长度为主要条件         return num == 0 ? s1.compareTo(s2) : num;    //内容为次要条件    
 
 
 
原来:
增加自定义排序后:
I/System.out: [t9, tree9, treeset2, treeset3, treeset4, treeset9, treeset10, treeset21, treeset1111]
 
 
比较方法对比:
 
第一种是添加自定义对象元素时候,自定义类要实现comparable<E>  接口,添加后,要重写
compareTo() 方法,这个方法传进一个对象类进来。
implements Comparable<Student>
public int compareTo(@NonNull Student o)
第二种是添加一个排序规则类,实现Comparator<E>接口,并重写compare()方法,这个方法传进2个字符串,来比较。
implements Comparator<String>
public int compare(String s1, String s2)    
 
 
想法1: Student元素在treeset中 按照分数排列。
student实现 Comparable<Student> 接口,重新 compareTo 方法。
返回这个即可:
public int compareTo(@NonNull Student student)
   return this.score -student.score;
  //  return 0;
 
上面不能这么写,表面是看起来是按照student的分数进行比较存放,大于当前放右边,小于放左边。
实际上上面有个缺陷。如果2个学生 分数一样。 那么treeset 就会认为这个元素对象一样,就不会存进集合里面!!!!
java集合的工具类Collections中提供了两种排序的方法,分别是:
  1. Collections.sort(List list)
  2. Collections.sort(List list,Comparator c)
第一种称为自然排序,参与排序的对象需实现comparable接口,重写其compareTo()方法,方法体中实现对象的比较大小规则
 
注意注意!!!Collections  只能对LIST 进行排序,并不能对set排序。 看它传入的对象参数是list。
 
解决1:
改用ArrayList 来存储.
private static ArrayList<Student> studentSet = new ArrayList<>();
studentSet.add(new Student("张三1", 18, 99));
studentSet.add(new Student("张三2", 17, 100));
studentSet.add(new Student("张三3", 22, 60));
studentSet.add(new Student("张三4", 43, 69));
studentSet.add(new Student("张三4", 20, 92));
studentSet.add(new Student("张三5", 17, 92));
 
// 这个类对集合ArrayList进行排序
Collections.sort(studentSet);
// 打印遍历集合
Iterator<Student> setIt1 = studentSet.iterator();
while (setIt1.hasNext())
    System.out.println(setIt1.next());
打印结果:自然排序按分数升序排序后:  (因为student类实现了分数比较的方法)
Studentname=‘张三3‘, age=22, score=60
Studentname=‘张三4‘, age=43, score=69
Studentname=‘张三4‘, age=20, score=92
Studentname=‘张三5‘, age=17, score=92
Studentname=‘张三1‘, age=18, score=99
Studentname=‘张三2‘, age=17, score=100
 
改用自定义排序: 需要实现Comparator<E>接口,这个可以匿名实现。
自定义排序比第一种好,解耦强,如果更改排序规则只需要传进不同的比较器即可。不用修改类的比较接口。
Collections.sort(studentSet);// 不传参数这样表示默认排序,用student类的自然排序
Collections.sort(studentSet, new Comparator<Student>()  //传参数这样表示定制排序,用这个定制的规则来排序。
 
定制排序:按年龄排序:
     Collections.sort(studentSet, new Comparator<Student>()
          @Override
          public int compare(Student o1, Student o2)
//              if (o1 instanceof Student && o2 instanceof Student)
//                  Student e1 = (Student) o1;
//                  Student e2 = (Student) o2;
//                  return e1.getAge() - e2.getAge();
//             
//              throw new ClassCastException("不能转换为Student类型");
              return o2.getAge() - o1.getAge();
         
      );
传进一个比较器,获取2个student的对象,然后获取这个对象的age属性,对这个属性进行排序。
最终打印:
Studentname=‘张三4‘, age=43, score=69
Studentname=‘张三3‘, age=22, score=60
Studentname=‘张三4‘, age=20, score=92
Studentname=‘张三1‘, age=18, score=99
Studentname=‘张三2‘, age=17, score=100
Studentname=‘张三5‘, age=17, score=92
 
技术图片
if (o2.getAge() == o1.getAge())
    return o1.getScore() - o2.getScore();
else
    return o2.getAge() - o1.getAge();
 
打印: 最后2个92,100的分数就换过来了
Studentname=‘张三4‘, age=43, score=69
Studentname=‘张三3‘, age=22, score=60
Studentname=‘张三4‘, age=20, score=92
Studentname=‘张三1‘, age=18, score=99
Studentname=‘张三5‘, age=17, score=92
Studentname=‘张三2‘, age=17, score=100
 
关于自定义类实现compare接口,放进treeset。
 
treeset怎么判定2个自定义的类 是否相等?
自定义类要实现2个接口,一个是equal,一个是hashcode 。通过这2个判定2个对象是否重复。
 
1 HashSet的作用就是去除重复的对象,而TreeSet的主要作用就是排序compareTo(obj) 方法
2 HashSet是由哈希算法来实现的,集合存储时先判断其hashCode()值一样吗,不一样直接存  若一样再调用equals方法进行比较   所以不仅要重写hashCode方法 还要重写 equals方法
 
所以要排序就要用treeset,自定义类实现compare to 方法。
要去重复要用到hashset,自定义类要实现hashcode和equals方法。
 
1.HashSet的a.基本数据类型和b.引用类型数据
[Student [name=张三, age=18], Student [name=张三, age=18], Student [name=李四, age=19]
 
可以看出,俩张三都被添加进去了,但是重复的我们并不想添加进去,为什么会被添加进去呢
这时因为在创建student的时候,我们给每个对象都在堆内存中开辟了一片空间,虽然俩对象中的元素相等,但是他们的地址是不一样的,所以系统认为他们是不同的,所以被添加进去了
如果我们要让系统把student中重复的去掉,我们只能从底层去操作,因为HashSet是系统内部根据比较Hash值来判断的,然后在调用equals方法来比较的,所以我们要重写Object类中的hashCode和equals方法
因为我们要对Student类的对象排序,自然是Student类的对象来调用,所以在Student类中进行方法的重写
 
 
 
关于自定义类student,可以重写它的hashcode和equals。 compareto方法最好不写,用比较器来写。因为compareto 一般装在treeset集合。 如果student要装在hashset  和treeset。 外部写比较器传入比较好。这样即使student类被装到很多个集合中,并且排序的条件不同,我们也可以为每一个集合创建一个特有的比较器来比较
 
 
 
 
 

 

以上是关于TreeSet的理解和用法的主要内容,如果未能解决你的问题,请参考以下文章

Java中的NavigableSet,SortedSet和TreeSet之间的区别

Set集合接口-HashSet_TreeSet理解

TreeSet和TreeMap

TreeSet简单介绍与使用方法HashSet和TreeSet有什么区别面试题

Java中TreeMap和TreeSet的底层实现

Java 数据结构TreeMap和TreeSet的介绍