TreeMap排序规则

时间:2025-04-04 07:26:57
public static void main(String[] args) { Map<String,String> map = new TreeMap<String,String>(); ("1", "A"); ("2", "C"); ("3", "K"); ("4", "D"); ("5", "B"); ("6", "L"); System.out.println(map); } 运行结果: {1=A, 2=C, 3=K, 4=D, 5=B, 6=L} key和value调换位置 public class TestDemo { public static void main(String[] args) { Map<String,String> map = new TreeMap<String,String>(); ("A", "1"); ("C", "2"); ("K", "3"); ("D", "4"); ("B", "5"); ("L", "6"); System.out.println(map); } } 运行结果: {A=1, B=5, C=2, D=4, K=3, L=6} 我们发现它是根据key进行排序的 下面看一个自定义对象作为key的例子 class Person{ public String name; public int age; public Person(String name,int age){ this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class TestDemo { public static void main(String[] args) { Map<Person,String> map = new TreeMap<Person,String>(); (new Person("张三",20), "1"); (new Person("李四",18), "2"); (new Person("王五",18), "3"); (new Person("赵六",18), "4"); (new Person("钱七",26), "5"); (new Person("周八",33), "6"); System.out.println(map); } } 我们发现哇哇的报错 Exception in thread "main" : cannot be cast to at (:1290) at (:538) at (:16) 看到Comparable接口我们就瞬间明白了,String为什么能排序,因为它是Comparable的子类,而TreeMap中的排序就是依赖于Comparable接口的compareTo方法得以实现的,我们自定义的对象不是Comparable的子类,不是Comparable接口子类就不能排序? 看一下源码:[TreeMap存储机制] public V put(K key, V value) { Entry<K,V> t = root; //根节点,新建一个根节点 return if (t == null) { //比较器关键部分,下面贴出compare方法 compare(key, key); // type (and possibly null) check //代码解析在下面 原理:红黑树 root = new Entry<>(key, value, null); //TreeMap长度,调用()直接返回size size = 1; modCount++; return null; } int cmp; Entry<K,V> parent; // 我们使用的是Comparable接口,条件不成立,进入else Comparator<? super K> cpr = comparator; if (cpr != null) { do { parent = t; //这里的比较方法会导致复写的类的所有属性必须参与排序操作,否则会被认为是同一个key,直接被setValue(value),下面会说到这个问题 cmp = (key, ); if (cmp < 0) t = ; else if (cmp > 0) t = ; else return (value); } while (t != null); } else { //我们发现key不能为null,而hashMap是可以的 if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = (); //新来的key小于当前节点key,将的地址给t,表示当前变量t表示了,在下一次循环中就是当前节点,一直到当前节点是个空位置 if (cmp < 0) t = ; //新来的key大于当前节点key,将的地址给t,表示当前变量t表示了,在下一次循环中就是当前节点,一直到当前节点是个空位置 else if (cmp > 0) t = ; else //等于,直接更新当前节点值 return (value); } while (t != null); } Entry<K,V> e = new Entry<>(key, value, parent); //为空位置赋值 if (cmp < 0) = e; else = e; //红黑树概念 //这个方法的代码就是红黑树平衡与旋转的处理,太多了,自己看 fixAfterInsertion(e); //计数 size++; modCount++; return null; } //关键问题 @SuppressWarnings("unchecked") final int compare(Object k1, Object k2) { //如果你传入自定义比较器,则调用自定义比较器,如果没传,它会认为你实现了Comparable接口,将你向上强制转换为Comparable类,所以Person由于没有实现Comparable接口就会转换错误,有人问了为什么k1和k2是同一个key?这样做没有什么特殊的目的,就是为了判断你是否实现了Comparable接口,否则无法排序啊 return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2) : ((K)k1, (K)k2); } 下面我们让Person实现Comparable接口,复写compareTo方法,看看效果 class Person implements Comparable<Person>{ public String name; public int age; public Person(String name,int age){ this.name = name; this.age = age; } @Override public int compareTo(Person o) { if( > this.age){ return 1; }else if( < this.age ){ return -1; }else{ return this.(); } } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class TestDemo { public static void main(String[] args) { Map<Person,String> map = new TreeMap<Person,String>(); (new Person("张三",20), "1"); (new Person("李四",18), "2"); (new Person("王五",18), "3"); (new Person("赵六",18), "4"); (new Person("钱七",26), "5"); (new Person("周八",33), "6"); System.out.println(map); } } 运行结果: { Person [name=周八, age=33]=6, Person [name=钱七, age=26]=5, Person [name=张三, age=20]=1, Person [name=李四, age=18]=4 } 我们发现元素少了,我在源码里面已经讲过原因了 就是这段 //这里的比较方法会导致复写的类的所有属性必须参与排序操作,否则如果前几个属性相同,最后一个不同,而最后一个没有参与排序,则会被认为是同一个key,直接被setValue(value),value是最新的一个元素的value,导致元素减少 cmp = (key, ); if (cmp < 0) t = ; else if (cmp > 0) t = ; else return (value); 下面我们让所有属性参与排序 我们把复写的compareTo方法修改如下: @Override public int compareTo(Person o) { if( > this.age){ return 1; }else if( < this.age ){ return -1; }else{ return (this.name); } } 运行结果: { Person [name=周八, age=33]=6, Person [name=钱七, age=26]=5, Person [name=张三, age=20]=1, Person [name=赵六, age=18]=4, Person [name=王五, age=18]=3, Person [name=李四, age=18]=2 } 另外hashMap有一点还要注意: 在我们使用自定义的对象作为key的时候,复写equals和hashCode方法一定要注意,比如你用对象作为key,对象里有三个属性,而你复写equals只使用了两个,但是实际上第三个属性的值是不同的,但是hashMap会通过equals和hashCode判断返回true和相同的hashCode,那么他们就会被当成一个key,就麻烦了,当然自定义对象很少用作key HashSet同样如此,凡是不能出现重复元素,或者重复key的集合,一定要注意复写equals和hashCode方法的正确性,所以一般别别复写就行了。 复写的话 new Person("张三",20) new Person("张三",20) 可以是同一个元素 不复写一定不是同一个元素 END