您的位置:首页 > 编程语言 > Java开发

JDK之TreeMap源码解析

2017-03-01 09:55 295 查看
刚入java不久的程序猿,对于简单的使用已毫不满足,最终为了一探究竟,翻开了JDK的源码,以下观点为自己的理解及看了多篇博客的总结,欢迎各位大神指出不对的地方,当然也欢迎和我一样刚学的同学,一起加油努力吧~~

TreeMap简述

TreeMap看名字就知道是Map家族的成员,通过key,value键值对的方式存储元素,相对于其他map,TreeMap是有序的,TreeMap的底层是由红黑树实现的,藉由LZ算法不精,红黑树相对也比较复杂,所以参考了很多资料,浅谈一下源码的剖析

TreeMap源码解析

在看源码之前,首先先大致说下树的概念。刚刚上面也说了TreeMap底层是由红黑树实现的,所以这里我们得对红黑树有个大致印象再看源码。树无非就是由根生枝生叶的一个结构,就像java里的类也是有根Object一层一层下来的,这就是普通的树,这里我们把根叫做根节点,这里有一个规则就是根节点数据会大于左子节点,小于右子节点,但是由于会一边倒,所以有了后期优化的平衡二叉树和红黑树等等,这里我们着重介绍下红黑树。

有了树的概念,下面来简单讲一下红黑树是什么,由于根节点为黑色,所以其子节点为红色,这个红色节点的子节点为黑色,依次延伸,红黑树会保持两边平衡,从任意一节点到叶子所有路径都会包含相同数量的黑色节点,红黑树会通过左旋右旋着色来重新进行结构的调整保持平衡。

简单介绍后,我们来深入一下源码

public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable{
...
}


好了简单说一下,继承AbstractMap,实现了3个接口,第一个接口过会着重讲下,后两个接口应该都不陌生了,克隆方法与序列化,下面专门把那个比较陌生的接口拿出来看看到底是干什么的,NavigableMap这个接口继承了SortedMap,所以我们先看下SortedMap接口里定义了哪些方法

public interface SortedMap<K,V> extends Map<K,V> {
/**
* 根据key进行排序的方法
*/
Comparator<? super K> comparator();

/**
* 将fromKey到toKey进行排序
*/
SortedMap<K,V> subMap(K fromKey, K toKey);

/**
* 将小于toKey的key进行排序
*/
SortedMap<K,V> headMap(K toKey);

/**
* 将大于fromKey的key进行排序
*/
SortedMap<K,V> tailMap(K fromKey);

/**
* 返回第一个key值
*/
K firstKey();

/**
* 返回最后一个key值
*/
K lastKey();

/**
* 获得所有的key
*/
Set<K> keySet();

/**
* 获得所有的value
*/
Collection<V> values();

/**
* 将key,value转换为Set类型
*/
Set<Map.Entry<K, V>> entrySet();
}


好了,看了上面的接口,我们来看下NavigableMap这个接口里新增的方法

public interface NavigableMap<K,V> extends SortedMap<K,V> {
/**
* 返回最大key值且key值小于参数key的entry
*/
Map.Entry<K,V> lowerEntry(K key);

/**
* 返回最大key值且key值小于参数key
*/
K lowerKey(K key);

/**
* 返回最大key值且key值 <= 参数key的entry
*/
Map.Entry<K,V> floorEntry(K key);

/**
* 返回最大key值且key值 <= 参数key的key值
*/
K floorKey(K key);

/**
* 返回最小key值且key值 >= 参数key的entry
*/
Map.Entry<K,V> ceilingEntry(K key);

/**
* 返回最小key值且key值 >= 参数key的key值
*/
K ceilingKey(K key);

/**
* 返回最小key值且key值 > 参数key的entry
*/
Map.Entry<K,V> higherEntry(K key);

/**
* 返回最小key值且key值 > 参数key的key值
*/
K higherKey(K key);

/**
* 返回最小key值的entry
*/
Map.Entry<K,V> firstEntry();

/**
* 返回最大key值的entry
*/
Map.Entry<K,V> lastEntry();

/**
* 移除最小key值的entry
*/
Map.Entry<K,V> pollFirstEntry();

/**
* 移除最大key值的entry
*/
Map.Entry<K,V> pollLastEntry();

/**
* 返回一个顺序反转后的Map
*/
NavigableMap<K,V> descendingMap();

/**
* 返回包含所有key的set集合,正常排序
*/
NavigableSet<K> navigableKeySet();

/**
* 返回包含所有key的set集合,顺序为反转排序
*/
NavigableSet<K> descendingKeySet();

/**
* 返回key值在fromKey到toKey之间的Map
*/
NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
K toKey,   boolean toInclusive);

/**
* 返回key值 <= toKey的Map
*/
NavigableMap<K,V> headMap(K toKey, boolean inclusive);

/**
* 返回key值 >= fromKey的Map
*/
NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);

/**
* 等同于subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)
*/
SortedMap<K,V> subMap(K fromKey, K toKey);

/**
* 等同于headMap(K toKey, boolean inclusive)
*/
SortedMap<K,V> headMap(K toKey);

/**
* 等同于tailMap(K fromKey, boolean inclusive)
*/
SortedMap<K,V> tailMap(K fromKey);
}


看了一下接口的方法,感觉用处很相似,返回的都是key值排序后其中的key或entry,这边也没太多说的,知道每个方法干什么的就行,下面开始正式的看TreeMap的源码,首先我们来了解一下很重要的一个内部类

static final class Entry<K,V> implements Map.Entry<K,V> {
//key值
K key;
//value值
V value;
//左子节点
Entry<K,V> left = null;
//右子节点
Entry<K,V> right = null;
//父节点
Entry<K,V> parent;
//节点颜色,黑色
boolean color = BLACK;

/**
* 创建新的entry,并赋予初始值
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}

/**
* 返回entry的key值
*/
public K getKey() {
return key;
}

/**
* 返回value值
*/
public V getValue() {
return value;
}

/**
* 设置value值
*/
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}

/**
* 判断节点是否相等
*/
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;

return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
}

//计算哈希值的方法
public int hashCode() {
int keyHash = (key==null ? 0 : key.hashCode());
int valueHash = (value==null ? 0 : value.hashCode());
return keyHash ^ valueHash;
}

//toString打印方法
public String toString() {
return key + "=" + value;
}
}


上面的entry很重要,毕竟是由一个个节点构成的,所以我们要了解它的组成,下面看一下TreeMap的全局变量

/**
* 比较器,用来对key值进行比较排序,如为null,则采用默认的
*/
private final Comparator<? super K> comparator;

/**
* 用来存放entry的
*/
private transient Entry<K,V> root = null;

/**
* entry的数量
*/
private transient int size = 0;

/**
* 修改次数
*/
private transient int modCount = 0;


TreeMap的全局变量就这些,也都比较简单,comparator一个用来排序的,TreeMap之所以有序就是这个的作用了,其他的都比较简单,下面我们继续看构造函数

/**
* TreeMap无参构造,使用默认的比较
*/
public TreeMap() {
comparator = null;
}

/**
* 参数为comparator,用给出的比较器进行排序
*/
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}

/**
* 将整个map放入TreeMap中
*/
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}

/**
* 创建一个新的TreeMap,包含传入参数map,排序方法相同
*/
public TreeMap(SortedMap<K, ? extends V> m) {
comparator = m.comparator();
try {
buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
}

/**
* 将整个map放入TreeMap中
*/
public void putAll(Map<? extends K, ? extends V> map) {
//map大小
int mapSize = map.size();
//判断TreeMap无entry,传入参数元素不为0并且传入后map是SortedMap子类
if (size==0 && mapSize!=0 && map instanceof SortedMap) {
//获得其比较器
Comparator c = ((SortedMap)map).comparator();
//条件比较器地址相同或者值相同
if (c == comparator || (c != null && c.equals(comparator))) {
++modCount;
try {
buildFromSorted(mapSize, map.entrySet().iterator(),
null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
return;
}
}
//调用父类putAll方法
super.putAll(map);
}


上面是TreeMap的几个构造方法,开篇说了由于红黑树相对比较复杂,所以这里我就挑一些常用的方法进行学习理解,如果有感兴趣的同学可以翻开源码深钻一下,应该可以收获很多,下面我们来看看一些常用的方法。

/**
* 返回键值对个数
*/
public int size() {
return size;
}

/**
* 根据key获取value
*/
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}

/**
* 根据key获取value具体实现
*/
final Entry<K,V> getEntry(Object key) {
// 比较器存在时,调用getEntryUsingComparator方法
if (comparator != null)
return getEntryUsingComparator(key);
//key为空,抛出异常
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
//获取根节点
Entry<K,V> p = root;
//遍历节点
while (p != null) {
//把遍历出的节点key与key进行比较
int cmp = k.compareTo(p.key);
//key小于当前节点key时,p左子节点移动到p位置
if (cmp < 0)
p = p.left;
//key大于当前节点key时,p右子节点移动到p位置
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}

/**
* 比较器存在时,调用的方法
*/
final Entry<K,V> getEntryUsingComparator(Object key) {
K k = (K) key;
Comparator<? super K> cpr = comparator;
//比较器不为空时进入
if (cpr != null) {
//获取根节点
Entry<K,V> p = root;
//根节点不为空时进入
while (p != null) {
//比较两个key返回结果
int cmp = cpr.compare(k, p.key);
//结果小于0,p移动到左子节点
if (cmp < 0)
p = p.left;
//结果大于0,p移动到右子节点
else if (cmp > 0)
p = p.right;
else
return p;
}
}
return null;
}


上面介绍了get的时候,TreeMap内部是如何去操作的,接下来看看put

/**
* key,value的形式向TreeMap中放值
*/
public V put(K key, V value) {
//根节点
Entry<K,V> t = root;
//根节点为空时进入
if (t == null) {
//比较key值,这里注释为类型检测
compare(key, key); // type (and possibly null) check

//新建节点,并修改size
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
//比较后的返回值
int cmp;
//父节点
Entry<K,V> parent;
//比较器存在与不存在时分别做处理
Comparator<? super K> cpr = comparator;
if (cpr != null) {
//比较器存在时,遍历根节点
do {
//更改父节点
parent = t;
//将需要put的key与遍历出节点的key做比较
cmp = cpr.compare(key, t.key);
//当参数key大于节点key时,t的左子节点代替t节点
if (cmp < 0)
t = t.left;
//当参数key小于节点key时,t的右子节点代替t节点
else if (cmp > 0)
t = t.right;
else
//赋值并返回旧的value值
return t.setValue(value);
} while (t != null);
}
else {
//参数key为空抛出异常
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
Entry<K,V> e = new Entry<>(key, value, parent);
//如新增节点key小于parent的key时,作为parent的左子节点插入,否则作为右子节点插入
if (cmp < 0)
parent.left = e;
else
parent.right = e;
//对整体的树形结构进行调整,保持平衡
fixAfterInsertion(e);
size++;
modCount++;
return null;
}

/** 调整整个树的整体结构 */
private void fixAfterInsertion(Entry<K,V> x) {
//新增节点颜色为红色
x.color = RED;

//循环,条件为x不为空,x不是父节点,x父节点颜色为红色,满足后跳出循环
while (x != null && x != root && x.parent.color == RED) {
/**
* parentOf(x)父节点
* leftOf(parentOf(parentOf(x)))父节点的父节点的左子节点
* 相等时进入
*/
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//获取父节点的父节点的右子节点
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
//如果当y节点为红色
if (colorOf(y) == RED) {
//设置父节点颜色为黑色
setColor(parentOf(x), BLACK);
//设置y节点为黑色
setColor(y, BLACK);
//设置父节点的父节点的颜色为红色
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
//当x为其父节点的右子节点
if (x == rightOf(parentOf(x))) {
//将x的父节点作为x
x = parentOf(x);
//左旋
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
//右旋
rotateRight(parentOf(parentOf(x)));
}
} else {
//下面和上面差不多
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
//根节点为黑色
root.color = BLACK;
}

/** 右旋方法 */
private void rotateLeft(Entry<K,V> p) {
if (p != null) {
//获取p的右子节点
Entry<K,V> r = p.right;
//将r的左子节点设置为p的右子节点
p.right = r.left;
//r的左子节点不为空,则将p设置为r左子节点的父节点
if (r.left != null)
r.left.parent = p;
//将p的父节点设置为r的父节点
r.parent = p.parent;
//p的父节点不为空时,将r设置为根节点,否则将r设为p的父节点的子节点
if (p.parent == null)
root = r;
else if (p.parent.left == p)
p.parent.left = r;
else
p.parent.right = r;
r.left = p;
p.parent = r;
}
}

/** 右旋方法 */
private void rotateRight(Entry<K,V> p) {
if (p != null) {
Entry<K,V> l = p.left;
p.left = l.right;
if (l.right != null) l.right.parent = p;
l.parent = p.parent;
if (p.parent == null)
root = l;
else if (p.parent.right == p)
p.parent.right = l;
else p.parent.left = l;
l.right = p;
p.parent = l;
}
}


在这里将右旋方法贴出,与左旋类似就不详细说明了,相信大家应该都能看懂。到这里基本上最常用的方法基本已经介绍完了,对于算法不好的LZ表示头已经快绕晕了,下面列几个简单点也能用到的方法结束TreeMap的解析

/**
* 判断是否包含此key
*/
public boolean containsKey(Object key) {
return getEntry(key) != null;
}

/**
* 判断是否包含此value
*/
public boolean containsValue(Object value) {
for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
if (valEquals(value, e.value))
return true;
return false;
}

/**
* 根据key值删除entry
*/
public V remove(Object key) {
//获取entry
Entry<K,V> p = getEntry(key);
if (p == null)
return null;

V oldValue = p.value;
//删除entry
deleteEntry(p);
return oldValue;
}

/**
* 清空TreeMap
*/
public void clear() {
modCount++;
size = 0;
root = null;
}


好了,没有太深入的了解TreeMap,但是主要分析了几个常用方法的实现原理,如果有感兴趣想深入的同学,可以自己看看源码,相信对算法有兴趣的朋友应该还是会喜欢的
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jdk java 源码