Set集合之TreeSet

今天跟大家来浅谈一下TreeSet

    HashSet的底层是HashMap,而HashMap的数据结构是哈希(散列)表: 是一个数组,里面存储的是链表
    TreeSet的底层是TreeMap,而TreeMap的数据结构是红黑数,是一个自平衡的二叉树!

1.TreeSet有两种排序方式:
        默认排序:自然顺序进行排序。
                      注意: 如果使用的是自然排序,那么添加到集合的该元素,必须实现java.lang.Comparable接口,
                                    否则会出现java.lang.ClassCastException
                  有的类,已经实现了Comparable接口比如String: 这也是为什么添加String,他没有出现ClassCastException异常,

       比较器排序: java.util.Comparator接口

2.到底使用哪种,取决于TreeSet的构造方法
        如果是无参构造:是使用默认排序:   TreeSet() 
        如果是有参构造:是使用比较器排序: TreeSet(Collection<? extends E> c) 

       都和根节点比较:
       小的往左放
       大的往右放
       相等不搭理,就相当于是去除重复,这里是把最后添加的那个相等的给去掉!!

其实使用哪种排序方式都行,最重要的是你怎么给他定义排序规则!!

案例一:

public class Demo2 {

	public static void main(String[] args) {
		TreeSet<String> set1 = new TreeSet<String>();
		set1.add("3");
		set1.add("7");
		set1.add("2");
		set1.add("5");
		
		System.out.println(set1);
	}
}

最终输出结果是:[2, 3, 5, 7]
为什么会这样输出呢?,我们上面说过,TreeSet有两种排序方式,如果是无参构造,说明是自然排序
说明存储的元素必须实现 java.lang.comparable接口,否则会出现 ClassCastException 类型转换异常!

java.lang.comparable接口中只有一个方法
 int compareTo(T o):比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,
                                         则分别返回负整数、零或正整数。

接下来我们看看,String类是否实现了这个这个接口

其实所谓的自然排序,其实也是制定好了排序规则!
 

接下来我们用TreeSet存储一个没有实现java.lang.Comparable的接口,看会发生什么?

class Student{
	String name;
	int age;
	
	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
}

public class Test2 {

	public static void main(String[] args) {
		
		//创建集合
		TreeSet<Student1> set = new TreeSet<Student1>();
		
		//创建元素
		Student1 s1 = new Student1("胡歌", 20);
		Student1 s2 = new Student1("苏炳添", 20);
		
		//添加元素到集合
		set.add(s1);
		set.add(s2);
		
		System.out.println(set);
	}
}

结果控制台报错:

这是怎么回事呢?
一:我们跟进TreeSet.add() 源码

 public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable{

 private transient NavigableMap<E,Object> m;

 private static final Object PRESENT = new Object();

 public TreeSet() {
        this(new TreeMap<E,Object>());
 }

  TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
 }
    
 public boolean add(E e) {
        return m.put(e, PRESENT)==null;
 }

}

二:这是我从TreeSet源码中抽取出来的,由上面可知,在创建TreeSet 的时候,无参构造会调用有参构造并且传递了一个TreeMap,然后给本类的成员变量 m 赋值 ,由此可见 NavigableMap m = new TreeMap(); 
所以说TreeSet的add() , 本质上是调用的 TreeMap的put() 方法

三:然后我们跟进TreeMap的put() 源码 

 public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            //①
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            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);
                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)
                throw new NullPointerException();
            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;
    }

在①处会执行compare() 方法,该方法的源码

 final int compare(Object k1, Object k2) {
        return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
            : comparator.compare((K)k1, (K)k2);
}

它会将k1强制转换为Comparable接口,这里的k1就是Student类,由于Student类并不是Comparable的实现类,所以会产生
类转换异常!!

那么这里只需将Student 类实现java.lang.Comparable接口即可,并重写compareTo() 方法,在里面自定义排序规则

注意事项
那是不是只要实现了java.lang.Comparable接口的类就全部可以往TreeSet里面放呢?见如下案例
我们知道,String类,Integer类都实现了java.lang.Comparable接口,我们做个测试
 


public class Test3 {

	public static void main(String[] args) {
		
		TreeSet set = new TreeSet();
		set.add("1");//添加String类型
		set.add(2);//添加整型
		
		System.out.println(set);
	}
}

还是发生这个错误,这是为什么呢?不是都已经实现了java.lang.Comparable接口了吗?

这是因为在整数2的时候,他会调用comparaTo() 方法去和字符串 “1” 比较,而Integer类的compareTo()

他需要的是一个整型,而你给他传了个字符串!,所以会发生异常!
因为只有相同类的两个实例才会比较大小!

结论:向TreeSet中添加的应该是同一个类的对象,否则也会发生ClassCastException!!

 

接下来说一下比较器排序:java.util.comparator接口,使用它做一个案例
需求是这样的,只要对象的成员变量值,相同我们就认为是重复的!!
 

public class Demo1 {
	
	public static void main(String[] args) {
		
		//无参构造是自然排序
		//TreeSet<Student> set = new TreeSet<Student>();
		TreeSet<Student1> set = new TreeSet<Student1>(new Comparator<Student1>() {

			@Override
			public int compare(Student1 o1, Student1 o2) {
				//1.先比年龄
				int num = o1.getAge() - o2.getAge();
				//2.年龄相同,比名字长度
				int num2 = num == 0 ? o1.getName().length() - o2.getName().length() : num;
				//3.名字长度相同,按名字首字母排序, 英文是 "z".compara("y") 是正整数, 但 "张".compare("羊") 是负整数!! 
				//就是如果比的是英文首字母: 那么就是升序, 中文就是降序!
				int num3 = num2 == 0 ? o1.getName().compareTo(o2.getName()) : num2;
				return num3;
			}
		});
		
		
		Student1 s1 = new Student1("张三",18);
		Student1 s2 = new Student1("李四",19);
		Student1 s3 = new Student1("王五",18);
		Student1 s4 = new Student1("赵六",20);
		Student1 s5 = new Student1("王五",18);
		Student1 s6 = new Student1("王五",21);
		
		set.add(s1);
		set.add(s2);
		set.add(s3);
		set.add(s4);
		set.add(s5);
		set.add(s6);
		
		/**
		 * 需求:如果成员变量值相同,就说明重复!,
		 * 	   1. 并且先按年龄降序排!
		 * 	   2.年龄相同再按姓名长度排!
		 *     3.姓名长度相同再按姓名内容排!
		 */
		
		System.out.println(set);
	}
}

 

TreeSet判断元素是否重复的标准:
是看compare()方法是不是返回0,如果是返回0,后面这个元素过滤!

举个栗子:

最后:两种排序的区别是
自然排序(元素具备比较性) 
比较器排序(集合具备比较性)

举个栗子:
像我们大一刚进教室(容器),我们都是按从矮到高坐,这就是元素具备比较性
然而到了考试的时候,教室里哪里贴了你的考号,你就坐哪,这就是集合具备比较性(在容器中,已经定义好了规则)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值