首页 > 技术文章 > java集合之TreeSet

zwbg 2016-09-27 16:04 原文

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 存储自定义类的实例:其实都是一样的,不讲了吧。。。。

 

推荐阅读