1 前言
本人使用的是jdk1.8版本。
2 Map集合继承结构
3 底层原理
TreeMap底层使用红黑树的结构进行数据的增删改查,红黑树是一种自平衡的二叉查找树,想了解红黑树推荐看看这篇博文:30张图带你彻底理解红黑树。学过数据结构的都知道二叉查找树是一种有序树,即进行中序遍历可以得到一组有序序列,所以TreeMap也是有序的Map集合。
在红黑树的加持下,TreeMap的众多方法,如:containsKey、get、put和reomve,都能保证log(n)的时间复杂度,这个效率可以说是相当高了。
/**
* A Red-Black tree based {@link NavigableMap} implementation.
* The map is sorted according to the {@linkplain Comparable natural
* ordering} of its keys, or by a {@link Comparator} provided at map
* creation time, depending on which constructor is used.
*
* <p>This implementation provides guaranteed log(n) time cost for the
* {@code containsKey}, {@code get}, {@code put} and {@code remove}
* operations. Algorithms are adaptations of those in Cormen, Leiserson, and
* Rivest's <em>Introduction to Algorithms</em>.
*/
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable{
private final Comparator<? super K> comparator; // 可以在构造器方法中传入实例
private transient Entry<K,V> root; // 根节点
......
}
4 有序性如何保证
TreeMap使用两种方法来保证有序性:Comparator和Comparable。
4.1 Comparator
你可以把它看做一个比较器,它的compare方法可以比较两个传入类型的对象,至于比较的规则是你实现这个接口后自己重写。从上面的代码看到,TreeMap中有一个全局comparator属性,你可以在构造其中传入自己的实现类。后面再put、get时就会调用comparator的compare方法来比较你的key和已存在的key,以此来决定存或取的位置。
public interface Comparator<T> {
int compare(T o1, T o2);
......
}
4.2 Comparable
TreeMap规定,put、get和remove等方法传入的参数key必须是实现了Comparable接口的,否则在调用这些方法的时候会抛出类型转换的异常,因为要调用compareTo方法就必须把key强制转换成Comparable类型,即:(Comparable<? super K>)key。
所以这种比较方式就是:key1.compareTo(key2)。
public interface Comparable<T> {
public int compareTo(T o);
......
}
4.3 两种比较策略?
当然,TreeMap中不可能同时使用两种策略,而是先使用comparator属性去比较,若comparator为空(没有在构造方法中传入实例)则采用Comparable策略。代码如下:
@SuppressWarnings("unchecked")
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
5 方法介绍
5.1 put()
就是按排序二叉树的方式插入节点,插入key比遍历节点的key小就往左边,大就往右,相等就替换。在插入结束后会对整个红黑树进行调整,已使它达到基本平衡,保证操作效率。
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // 若两种比较策略都没有使用,则抛出异常
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) { // 使用comparator策略
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);
} // 使用comparable策略
else {
if (key == 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;
}
5.2 get()
与put一样的道理,查找key比遍历节点的key小,就往左边找,否则往右边,相等直接返回。同样要判断两种比较策略。
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
5.3 remove()
remove也是一样的道理,先找到,后移除,不过移除后要对红黑树进行调整。