zoukankan      html  css  js  c++  java
  • java集合之TreeSet

    TreeSet类:位于java.util包下


    特点:

     1)底层数据结构是红黑树,即平衡二叉树,有序(这里的有序不是list的有序概念),实现非同步,内部功能实现依赖于TreeMap的方法。

     2)该类返回的元素顺序并非是集合添加元素的顺序,而是按照某个排序算法排列的。该算法有两种情况,由创建TreeSet实例时所用的构造函数决定使用哪种顺序。


    TreeSet存储元素自然排序和唯一的图解:


    主要讲一下怎样实现有序的问题:

    1 TreeSet实现有序之自然排序:

    1)一般系统类:

    自然排序:表示添加对象实现java.lang包中定义的Comparable接口。很多情况下类实例的排序方法应该是较为直观合理的。比如,数字包装器类(Integer等)、String类、Date类的自然排序是从最低到最高的。

    TreeSet<Integer> set = new TreeSet<Integer>();

    set.add(new Integer(100));   //等价于set.add(100);

    set.add(new Integer(70));

    set.add(new Integer(89));

    for(Integer i:set){

      System.out.println(i);

    }

    输出:70

         89

         100

    为什么会按照这种排序方式呢?

        这是因为Integer类实现了Comparable接口,因此返回的数据按升序排列。

    Comparable接口只定义了一个方法:public int compareTo(Object o):如果调用该方法的对象小于比较对象,那么返回负值,如果大于,则返回正值,如果相等则返回0。

    Integer类中实现compareTo方法的源码:

    public int compareTo(Integer anotherInteger) {
      return compare(this.value, anotherInteger.value);   //调用的compare方法判断
    }

    public static int compare(int x, int y) {
      return (x < y) ? -1 : ((x == y) ? 0 : 1);    //如果x>y返回1,x=y返回0,x<y返回-1
    }

    2)自定义类:

    java中的系统类都需要有意义的、直观的排序,因此都实现了Comparable接口,但是如何处理Student这类自定义类?

      如果想要排序Student,只能在Student中实现Comparable接口,指定一种自然排序,那么treeSet就能对Student排序了

    Student类:

    package com.hzw.set;

    public class Student implements Comparable{
      private String name;
      private int age;
      @Override
      public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
      }
      public Student(String name, int age) {
        super();
        this.name = name;
        this.age = age;
      }
      public String getName() {
        return name;
      }
      public int getAge() {
        return age;
      }

      @Override
      public int compareTo(Object o) {
        if(o != null && o instanceof Student){
          Student another = (Student)o;
          int first = this.age-another.age;
          return first==0?this.name.compareTo(another.name):first; //Student首先按照年龄排序,如果年龄相同再按照姓名排序
        }
        return 0;
        //或者
        // if(o==null) return 0;
        // if(!(o instanceof Student)) return 0;
        // Student ano = (Student)o;
        // int result = 0;
        // return (result=age-ano.age)==0?name.compareTo(ano.name):result;
      }
    }

    package com.hzw.set;
    import java.util.TreeMap;
    import java.util.TreeSet;
    public class TreeSetDemo {
      public static void main(String[] args) {
        TreeSet<Student> set1 = new TreeSet<Student>();
        Student stu1 = new Student("wen",12);
        Student stu2 = new Student("di",15);
        Student stu3 = new Student("wen",12);
        Student stu4 = new Student("yy",14);
        Student stu5 = new Student("we",12);
        Student stu6 = new Student("cc",11);
        Student stu7 = new Student("aw",10);


        set1.add(stu1);
        set1.add(stu2);
        set1.add(stu3);
        set1.add(stu4);
        set1.add(stu5);
        set1.add(stu6);
        set1.add(stu7);
        //set1.add(null);
        for(Student stu:set1){
          System.out.println(stu.getName()+"-----------"+stu.getAge());
        }
      }
    }

    输出结果:

    aw-----------10
    cc-----------11
    we-----------12
    wen-----------12
    yy-----------14
    di-----------15

    补充:在讲前面几个集合时,他们基本都可以存储null,但是在TreeSet实现自然排序的,也就是本例中,是不能存储null的,会出现空指针异常:

    Exception in thread "main" java.lang.NullPointerException
      at java.util.TreeMap.put(Unknown Source)
      at java.util.TreeSet.add(Unknown Source)
      at com.hzw.set.TreeSetDemo.main(TreeSetDemo.java:24)

    如果可以解释这个现象只能通过源码来解决:

    此处是TreeSet存储数据的源码:

    public boolean add(E e) {
      return m.put(e, PRESENT)==null;  //底层操作依赖于Map集合,即包装了的TreeMap集合
    }

    通过查看TreeMap的put添加元素的源码:就是一颗二叉树

    public V put(K key, V value) {
      Entry<K,V> t = root;   
      if (t == null) {
        compare(key, key); 

        root = new Entry<>(key, value, null);    //树显然非空,pass 
        size = 1;
        modCount++;
        return null;
      }
      int cmp;
      Entry<K,V> parent;
      // split comparator and comparable paths
      Comparator<? super K> cpr = comparator;
      if (cpr != null) {
        do {
          parent = t;
          cmp = cpr.compare(key, t.key);         //此处是比较器实现需要考虑的,咱们此时是自然排序,所以pass
          if (cmp < 0)
            t = t.left;
          else if (cmp > 0)
            t = t.right;
          else
            return t.setValue(value);
        } while (t != null);
      }
      else {    //重点分析代码
        if (key == null)       //这就解释了为什么不能存null值
          throw new NullPointerException();
        @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
        do {
          parent = t;       
          cmp = k.compareTo(t.key);    //这里解释平衡二叉树存取元素的重点,也就是原理

          if (cmp < 0)                            //如果待插入元素小于根节点,则放在根的左子树上,大于放在右子树
            t = t.left;
          else if (cmp > 0)
            t = t.right;
          else
            return t.setValue(value);  //如果等于,则不存储
        } while (t != null);
      }
      Entry<K,V> e = new Entry<>(key, value, parent);
      if (cmp < 0)
        parent.left = e;
      else
        parent.right = e;
      fixAfterInsertion(e);
      size++;
      modCount++;
      return null;
    }


     2 TreeSet实现有序之比较器排序:

     TreeSet实现比较器排序的方式有两种:匿名内部类方式和外部类方式

     2.1 首先介绍下比较器接口:Comparator

     该接口有两个方法:

      int compare(T o1,To2):如果o1>o2,返回正值;o1<o2,返回负值;o1=o2,返回0;

      boolean equals(Object obj):该方法与Object中的equals方法一致

    2.2 存储比较器排序方式存储java系统类实例元素

     1)比较器的实现方式:匿名内部类方式

    package com.hzw.set;
    import java.util.Comparator;
    import java.util.TreeSet;
    public class TreeSetDemo2 {
      public static void main(String[] args) {
        TreeSet<Integer> set1 = new TreeSet<Integer>(new Comparator<Integer>(){
        @Override
        public int compare(Integer o1, Integer o2) {
          return o2-o1;
        }});
        set1.add(100);
        set1.add(89);
        set1.add(102);
        set1.add(79);
        set1.add(99);
        set1.add(89);

        for(Integer i:set1){
          System.out.println(i);
        }
      }
    }

    输出结果是:

    102
    100
    99
    89
    79

     2)比较器的实现方式:外部类方式

    外部类:MyComparator 

    package com.hzw.set;
    import java.util.Comparator;
    public class MyComparator implements Comparator<Integer> {
      @Override
      public int compare(Integer o1, Integer o2) {
        return o2-o1;
      }
      @Override
      public boolean equals(Object obj) {
        return super.equals(obj);
      }
    }

    package com.hzw.set;
    import java.util.TreeSet;

    public class TreeSetDemo3 {
      public static void main(String[] args) {
        MyComparator mc = new MyComparator();
        TreeSet<Integer> set1 = new TreeSet<Integer>(mc);
        set1.add(100);
        set1.add(89);
        set1.add(102);
        set1.add(79);
        set1.add(99);
        set1.add(89);
        for(Integer i:set1){
          System.out.println(i);
        }
      }
    }

    输出结果:

    102
    100
    99
    89
    79

    2.3 存储自定义类的实例:其实都是一样的,不讲了吧。。。。

  • 相关阅读:
    冒泡排序
    位运算查缺补漏
    单元测试Junit5+Mockito3+Assertj
    插入排序
    Java异常处理
    Redis数据结构-list
    Redis数据结构-hash
    Redis全局命令
    对各向异性高光的理解
    OpenGL投影矩阵(Projection Matrix)构造方法
  • 原文地址:https://www.cnblogs.com/zwbg/p/5913195.html
Copyright © 2011-2022 走看看