01、自然顺序
默认情况下,TreeMap 是根据 key 的自然顺序排列的。比如说整数,就是升序,1、2、3、4、5。
TreeMap<Integer,String> mapInt = new TreeMap<>(); mapInt.put(3, "沉默王二"); mapInt.put(2, "沉默王二"); mapInt.put(1, "沉默王二"); mapInt.put(5, "沉默王二"); mapInt.put(4, "沉默王二"); System.out.println(mapInt);
输出结果如下所示:
{1=沉默王二, 2=沉默王二, 3=沉默王二, 4=沉默王二, 5=沉默王二}
1
TreeMap 是怎么做到的呢?想一探究竟,就得上源码了,来看 TreeMap 的 put() 方法(省去了一部分,版本为 JDK 14):
public V put(K key, V value) { TreeMap.Entry<K,V> t = root; int cmp; TreeMap.Entry<K,V> parent; // split comparator and comparable paths Comparator<? super K> cpr = comparator; if (cpr != null) { } else { @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); } return null; }
注意 cmp = k.compareTo(t.key) 这行代码,就是用来进行 key 的比较的,由于此时 key 是 int,所以就会调用 Integer 类的 compareTo() 方法进行比较。
public int compareTo(Integer anotherInteger) { return compare(this.value, anotherInteger.value); } public static int compare(int x, int y) { return (x < y) ? -1 : ((x == y) ? 0 : 1); }
那相应的,如果 key 是字符串的话,也就会调用 String 类的 compareTo() 方法进行比较。
public int compareTo(String anotherString) { byte v1[] = value; byte v2[] = anotherString.value; byte coder = coder(); if (coder == anotherString.coder()) { return coder == LATIN1 ? StringLatin1.compareTo(v1, v2) : StringUTF16.compareTo(v1, v2); } return coder == LATIN1 ? StringLatin1.compareToUTF16(v1, v2) : StringUTF16.compareToLatin1(v1, v2); }
由于内部是由字符串的字节数组的字符进行比较的,是不是听起来很绕?对,就是很绕,所以使用中文字符串作为 key 的话,看不出来效果。
TreeMap<String,String> mapString = new TreeMap<>(); mapString.put("c", "沉默王二"); mapString.put("b", "沉默王二"); mapString.put("a", "沉默王二"); mapString.put("e", "沉默王二"); mapString.put("d", "沉默王二"); System.out.println(mapString);
输出结果如下所示:
{a=沉默王二, b=沉默王二, c=沉默王二, d=沉默王二, e=沉默王二}
1
字母的升序,对吧?
02、自定义排序
如果自然顺序不满足,那就可以在声明 TreeMap 对象的时候指定排序规则。
TreeMap<Integer,String> mapIntReverse = new TreeMap<>(Comparator.reverseOrder());
mapIntReverse.put(3, "沉默王二");
mapIntReverse.put(2, "沉默王二");
mapIntReverse.put(1, "沉默王二");
mapIntReverse.put(5, "沉默王二");
mapIntReverse.put(4, "沉默王二");
System.out.println(mapIntReverse);
TreeMap 提供了可以指定排序规则的构造方法:
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
Comparator.reverseOrder() 返回的是 ReverseComparator 对象,就是用来反转顺序的,非常方便。
所以,输出结果如下所示:
{5=沉默王二, 4=沉默王二, 3=沉默王二, 2=沉默王二, 1=沉默王二}
1
HashMap 是无序的,插入的顺序随着元素的增加会不停地变动。但 TreeMap 能够至始至终按照指定的顺序排列,这对于需要自定义排序的场景,实在是太有用了!
03、排序的好处
既然 TreeMap 的元素是经过排序的,那找出最大的那个,最小的那个,或者找出所有大于或者小于某个值的键来说,就方便多了。
Integer highestKey = mapInt.lastKey(); Integer lowestKey = mapInt.firstKey(); Set<Integer> keysLessThan3 = mapInt.headMap(3).keySet(); Set<Integer> keysGreaterThanEqTo3 = mapInt.tailMap(3).keySet(); System.out.println(highestKey); System.out.println(lowestKey); System.out.println(keysLessThan3); System.out.println(keysGreaterThanEqTo3);
TreeMap 考虑得很周全,恰好就提供了 lastKey()、firstKey() 这样获取最后一个 key 和第一个 key 的方法。
headMap() 获取的是到指定 key 之前的 key;tailMap() 获取的是指定 key 之后的 key(包括指定 key)。
来看一下输出结果:
5
1
[1, 2]
[3, 4, 5]
04、如何选择 Map
在学习 TreeMap 之前,我们已经学习了 HashMap 和 LinkedHashMap ,那如何从它们三个中间选择呢?
HashMap、LinkedHashMap、TreeMap 都实现了 Map 接口,并提供了几乎相同的功能(增删改查)。它们之间最大的区别就在于元素的顺序:
HashMap 完全不保证元素的顺序,添加了新的元素,之前的顺序可能完全逆转。
LinkedHashMap 默认会保持元素的插入顺序。
TreeMap 默认会保持 key 的自然顺序(根据 compareTo() 方法)。
来个表格吧,一目了然。
谢谢大家,下期见,同学们。