这篇博客记录了学习TreeMap时关于put()方法和遍历TreeMap的解析,方便巩固学习,仅作参考。
TreeMap treeMap = new TreeMap<>() ;
String str1 = "12";
String str2 = "123";
String str3 = "1234";
treeMap.put(str1, "1");
treeMap.put(str2, "2");
treeMap.put(str3, "3");
System.out.println("TreeMap not set iterator");
Iterator> iterator = treeMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
System.out.println("[" + entry.getKey() + " ," + entry.getValue() + "]");
}
控制台打印结果:
TreeMap not set iterator
[12 ,1]
[123 ,2]
[1234 ,3]
在解析源码之前,粗略解释一下Comparable接口实现:
实现了Comparable接口的类的实例之间是可以相互比较的。在实现Comparable时,会将实例的一个特征或多个特征作为重要判定依据,按照某种方法得出的值(这里假设为compareVal),实例间做相互比较。如果实例A的compareVal 大于 实例B的compareVal, A.compareTo(B)返回 1, B.compareTo(A)返回 -1,则对实例的排序是升序的。如果实例A的compareVal 大于 实例B的compareVal, A.compareTo(B)返回 -1, B.compareTo(A)返回 1,则对实例的排序是降序的。
举一个简单的例子:
Person类
public class Person implements Comparable{
int age;
String name;
...
// 按照age来比较(升序)
public int compareTo(Person person) {
return this.age - person.age;
}
/**
* // 按照age来比较 (降序)
* public int compareTo(Person person) {
* return person.age - this.age;
* }
*/
}
TreeMap put(K key, V value)源码解析
public V put(K key, V value) {
Entry t = root;
// 第一次向容器中放入元素(k/v)时,创建根节点(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 parent;
// 默认的比较器(comparator)是空的,在创建TreeMap容器时传入一个比较器。
// 所以按照上面代码创建TreeMap容器,比较器是空的。
// 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();
// 如果没有设定比较器,就使用Key对应类的比较方式。所以,下面这行代码就是得到
// key所实现的比较方式。key先跟父节点(最开始时父节点就是根节点root)的key比较,小于则
// 继续跟父节点的左子节点比较,大于则跟父节点的右子节点,以此类推,直到找到与e的key相
// 等的节点或者父节点的左子节点或者右子节点为null。
// 当找到与e的key相等的节点时,替换该节点的value,当父节点的
// 左子节点(右子节点)为null时,就创建一个节点作为父节点的左子节点(右子节点)
@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 e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
TreeMap遍历,源码解析:
Iterator> iterator = treeMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
}
treeMap.entrySet().iterator()内部源码调用顺序:
-> TreeMap.entrySet() 返回一个EntrySet的集合
-> new EntrySet()
-> EntrySet.iterator() 返回一个基于EntrySet集合的迭代器EntryIterator
-> new EntryIterator()
-> TreeMap.getFirstEntry() 得到整个树的最左边的节点
iterator.next()内部源码调用顺序:
-> EntryIterator.next()
-> PrivateEntryIterator.nextEntry()
-> successor(e) 整个迭代器最主要的迭代代码在successor(e)中
// 得到Entry的set集合
public Set> entrySet() {
EntrySet es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}
// TreeMap的内部类EntrySet
class EntrySet extends AbstractSet> {
public Iterator> iterator() {
return new EntryIterator(getFirstEntry());
}
}
// TreeMap内部类 EntryIterator (迭代器)
// EntryIterator 继承PrivateEntryIterator类
final class EntryIterator extends PrivateEntryIterator> {
EntryIterator(Entry first) {
super(first);
}
public Map.Entry next() {
return nextEntry();
}
}
// PrivateEntryIterator
abstract class PrivateEntryIterator implements Iterator {
Entry next;
Entry lastReturned;
int expectedModCount;
PrivateEntryIterator(Entry first) {
expectedModCount = modCount;
lastReturned = null;
next = first; // 初始化EntryIterator时,next指向整个树的最左边的叶子节点
}
public final boolean hasNext() {
return next != null; // 当没有节点时,迭代终止。
}
final Entry nextEntry() {
Entry e = next; // 第一次调用这个方法时,e指向整个树的最左边的叶子节点
if (e == null)
throw new NoSuchElementException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// 整个迭代器最主要的迭代代码在successor(e)中,successor方法返回下一个节点next,下一次遍
// 历时,next又用作参数e,作为基础节点来寻找下一个返回节点。
next = successor(e);
lastReturned = e;
return e; // 执行successor方法后返回e。
}
}
final Entry getFirstEntry() {
Entry p = root;
if (p != null)
while (p.left != null)
p = p.left;
return p;
}
TreeMap的getFirstEntry()方法,找到整个树的最左边的叶子节点,参考下图:即节点 D
A
B C
D E F G
static TreeMap.Entry successor(Entry t) {
// 第一次调用时 t 指向整个树的最左边的叶子节点 D(如上图)
if (t == null)
return null;
// 如果节点t有右节点t_right,但是t_right 没有左节点,就返回t_right(即t的右节点)
// 如果节点t有右节点t_right,并且t_right有左节点t_right_left,就以t_right为父节点,
// 返回其下最左边的子节点。
else if (t.right != null) {
Entry p = t.right;
while (p.left != null)
p = p.left;
return p;
} else {
// 节点t没有右节点
// 并且t不是一个右子节点,就返回t的父节点
// 如果节点t是一个右节点,就返回t的父节点的父节点
Entry p = t.parent;
Entry ch = t;
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p; //注意这里返回的节点P用作下一次迭代的基础,而不是iterator.next()的返回值
// iterator.next()返回值是节点 t
}
}
以上图所示的树结构图为例,以iterator.next()的调用顺序为例:
调用前next指的是调用前next指向的节点
调用后next指的是调用后next指向的节点
次数 | 调用前next | 调用后next | 返回节点 | 分析 |
1 | D | B | D | D节点没有右节点,但是D节点不是一个右节点,所以调用后next()方法后,next指向D的父节点B。 |
2 | B | E | B | B节点有右节点E,但是E节点没有左节点,所以调用后next()方法后,next指向B的右节点E。 |
3 | E | A | E | E没有右节点,并且B节点是一个右节点,所以调用后next()方法后,next指向E的父节点B的父节点A。 |
依次类推,整个树的遍历结果是DBEAFCG。
根据TreeMap的遍历代码可以看出遍历的顺序是左节点 -> 父节点 -> 右节点,是中序遍历。
自定义TreeMap的比较器
通过自定义比较器来控制整个树的节点顺序。这里为了偷懒,采用与String排序相反的方式。
TreeMap treeMapWithIterator = new TreeMap<>((x, y)->{
return y.compareTo(x);
});
treeMapWithIterator.put(str1, "1");
treeMapWithIterator.put(str2, "2");
treeMapWithIterator.put(str3, "3");
System.out.println("降序:");
// TreeMap是用中序遍历
Iterator> iterator2 = treeMapWithIterator.entrySet().iterator();
while (iterator2.hasNext()) {
Map.Entry entry = (Map.Entry) iterator2.next();
System.out.println("[" + entry.getKey() + " ," + entry.getValue() + "]");
}
下面是没有自定义比较器的测试,默认使用String实例的比较方式:
TreeMap treeMap = new TreeMap<>() ;
String str1 = "12";
String str2 = "123";
String str3 = "1234";
treeMap.put(str1, "1");
treeMap.put(str2, "2");
treeMap.put(str3, "3");
System.out.println("升序:");
Iterator> iterator = treeMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
System.out.println("[" + entry.getKey() + " ," + entry.getValue() + "]");
}
看看两者的输出结果对比:
升序
[12 ,1]
[123 ,2]
[1234 ,3]
降序:
[1234 ,3]
[123 ,2]
[12 ,1]
在构建TreeMap时如果没设定比较器,那么往TreeMap中放入元素时就会使用Key对应的类实现的比较方式。
如果既没有设定比较器,Key对应的类也未实现Comparable接口,那么就会出现ClassCastException
参考put()方法的这行代码: Comparable super K> k = (Comparable super K>) key
仅作参考,有疏漏或者不正确的地方欢迎指出,相互学习。