您的位置:首页 > 其它

源码分析-TreeMap

2016-12-22 15:01 447 查看

TreeMap概述

首先TreeMap是实现了NavigableMap和SortMap的Map,从实现的角度说是红黑树。

红黑树

红黑树是平衡搜索树的一种,也是使用最多的一种树,其特点

对于所有的动态集合操作都可以保证以最坏O(lgn)的时间复杂度来运行。

每个节点都包含一个储存位来表示节点的颜色,非红即黑。

通过对任何一条从根节点到叶子节点的简单路径上各个节点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,而是近似平衡的。

红黑性质:

每个节点或是红色或是黑色

根节点是黑色

每个叶节点是黑色

如果一个节点是红色,则它的两个子节点都是红色

对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。

从某节点出发到达一个叶节点的任意一条简单路径上的黑色节点数称为黑高。红黑树的黑高就是其根的黑高。

一棵有n个内部节点的红黑树的高度至多为2lg(n+1)。

相对于其他平衡树,因为他的插入和删除操作只要最多3次左旋或者右旋就可以完成,因此相对于AVL树它的插入和删除效率更高,但由于其不是高度平衡树,其查询效率略低于AVL树。

当然红黑树插入和删除操作是比较复杂的。这里不详细描述等到代码里再说。

静态域和静态方法

静态域:

private static final boolean RED   = false;
private static final boolean BLACK = true;


表示红黑节点颜色的bool值。这种用法类似于C++中的宏定义

private static final Object UNBOUNDED = new Object();


表示对于subMap不可访问的区域。



大部分就是获得或者设置节点的域没什么好说的,这里重点说几个有意思的。

computeRedLevel用途不明;

后续会介绍到。

exportEntry用于返回Entry,但是并不返回TreeMap的节点,而是返回AbstractMap.SimpleImmutableEntry类型。

static <K,V> Map.Entry<K,V> exportEntry(TreeMap.Entry<K,V> e) {
return (e == null) ? null :
new AbstractMap.SimpleImmutableEntry<>(e);
}


ExportEntry返回一个AbstractMap.SimpleImmutableEntry<>(e)。这很有意思。这个AbstractMap.SimpleImmutableEntry<>(e)是给静态内部类,所以可以单独初始化。

前驱和后继

当然前驱和后继的算法是相同的,这里只看一个。

static <K,V> Entry<K,V> predecessor(Entry<K,V> t) {
if (t == null)
return null;
else if (t.left != null) {
Entry<K,V> p = t.left;
while (p.right != null)
p = p.right;
return p;
} else {
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
while (p != null && ch == p.left) {
ch = p;
p = p.parent;
}
return p;
}
}


先判断t是否为空,如果为空则找t节点的做节点,如果左节点存在则很显然其左节点就是前驱节点,如果左节点为空,则需要向上溯源。直到当前节点是其父节点的右节点,则父节点就是前驱节点。当然这段程序是针对BST来说的。所以所有的BST都可以用类似的方法实现。



内部域很简单,主要就是root,size,和comparator。其他的内容是由于需要返回视图而设置的。意义也很明显

内部类

Treemap的内部类和之前说HashMap的内部类是类似的,大部分都是为了实现map需要返回视图的要求,或者迭代器的要求来实现的 。

Entry

K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;
boolean color = BLACK;


其他部分的内容并没有什么特别需要说明的,只看下equals和hashCode

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());
}

static final boolean valEquals(Object o1, Object o2) {
return (o1==null ? o2==null : o1.equals(o2));
}


equals先做类型检查,然后仅仅考虑key和value是否相同,然后调用的静态valEquals屏蔽了null和object的区别。

key、value和Entry视图类

类似于HashMap,这几个类都是private的内部类,然后通过一个public方法返回set或者collections类型的视图。通常来说这几个内部类都不实现方法,而是通过委托的形式将操作交由TreeMap来实现。这里不再赘述。



Submap和NavigableSubMap



严格的说这两大类也是视图类,但是这两个类使HashMap所没有的,而且这两大类内部实现形式类似这里放在一起说。这两者并非简单的将方法委托给TreeMap来实现,而相反Treemap返回视图的方法反而是通过构造这几个内部类来实现的。

SubMap

是实现了SortMap接口的并拓展了AbstractMap的实现,但是SubMap没有实现任何方法全部都是抛出异常,我这里还不是太明白具体为什么要加一个这样的类。唯一实现的方法是readResolve。仅仅是返回一个新的AscendingSubMap。我也没有发现到底在哪里用到。对于这个部分代码我还是比较疑惑的。submap包括四个域,类似于NavigableSubMap。

NavigableSubMap

NavigableSubMap的域

final TreeMap<K,V> m;
final K lo, hi;
final boolean fromStart, toEnd;
final boolean loInclusive, hiInclusive;


m很显然是所支持的TreeMap,剩下6个参数。成2组,

如果fromStart或者toEnd为true,则直到m的开始或者结尾,这样另外的参数就没有意义了。

如果这两个参数为false,则lo和hi分别表示首尾的范围,而loInclusive和hiInclusive表示是否包括此范围。

构造器

NavigableSubMap(TreeMap<K,V> m,
boolean fromStart, K lo, boolean loInclusive,
boolean toEnd,     K hi, boolean hiInclusive) {
if (!fromStart && !toEnd) {
if (m.compare(lo, hi) > 0)
throw new IllegalArgumentException("fromKey > toKey");
} else {
if (!fromStart) // type check
m.compare(lo, lo);
if (!toEnd)
m.compare(hi, hi);
}

this.m = m;
this.fromStart = fromStart;
this.lo = lo;
this.loInclusive = loInclusive;
this.toEnd = toEnd;
this.hi = hi;
this.hiInclusive = hiInclusive;
}


如果fromStart和toEnd为假则检测范围,如果任一为假则检测类型。这里的类型使用的方法是用比较子比较其自身。这是个很好的方法,因为TreeMap只关心Map内的元素可否互相比较。

NavigableSubMap方法比较多。大概的分可以分为几大类:

工具方法:判断key是否在范围内;

绝对方法:将相对操作表示为绝对操作;通常返回一个Entry作为标识位

抽象方法:就是NavigableSubMap的sub方法。

公用方法:基本上实现了map的公用方法

视图方面:

视图方面的东西比较多也比较杂。主要就是keyset、EntrySet和NavigableMap以及迭代器的类。东西比较多也比较杂。没有仔细看但总体来说并不难。包括域、方法和类。

NavigableSubMap的子类

AscendingSubMap和DescendingSubMap是继承了NavigableSubMap的子类,基本上就是对某些方法的六个域进行调整和简化。实现上委托给NavigableSubMap的构造器。

主要方法

get

get的方法比较简单因为是BST所以直接比较key的大小然后搜索就行

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


都是使用getEntry的方法找到Entry。getEntry对于带比较器的方法还有一个getEntryUsingComparator不过两者比较类,这里只看getEntry了,就是简单的比较key直到p为空为止。如果找到则返回。没有则返回null。

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();
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;
}


put

public V put(K key, V value) {
Entry<K,V> t = 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<K,V> parent;
// 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();
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;
}


首先对于空集合特殊处理,将根元素定义为当前插入元素。

然后根据是否有comparator来分类处理。不过内容是类似的

定义两个迭代变量,一个cmp比较的结果,一个parent是假设的目前父节点。

让parent分别指向当前的t,然后根据key来找是否Map里面已经有了Key,如有则直接设置其值就可以,并返回就好。

如果没有则说明需要跌倒直到当前的t为空。则其parent为叶节点或者其对应key的位置为空的只有一个子节点的节点。

则在parent的子节点上构造一个新的Entry。并根据cmp设置值。

然后运行fixAfterInsertion来维持红黑树的不变性条件。这个fixAfterInsertion函数比较复杂。最好还是通过算法导论里的说明来解决。

private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;

while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry<K,V> y = rightOf(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 == rightOf(parentOf(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;
}


通过代码来分析太麻烦了。直接从结果上来说,这里实际上分为3种情况。对于任意情况旋转次数绝不会超过3次。我们直接从图上来理解效率更高:

情况1:X的叔叔节点是红色的



情况2:X的叔叔节点y是黑色的,而且X是一个右子节点



情况3:X的叔叔节点y是黑色的,而且X是一个左子节点



remove

public V remove(Object key) {
Entry<K,V> p = getEntry(key);
if (p == null)
return null;

V oldValue = p.value;
deleteEntry(p);
return oldValue;
}


删除先找到是否有这个key如果没有则直接返回null,如果是保留旧值用于返回。然后调用deleteEntry删除这个键值对,再返回;

private void deleteEntry(Entry<K,V> p) {
modCount++;
size--;

// If strictly internal, copy successor's element to p and then make p
// point to successor.
if (p.left != null && p.right != null) {
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children

// Start fixup at replacement node, if it exists.
Entry<K,V> replacement = (p.left != null ? p.left : p.right);

if (replacement != null) {
// Link replacement to parent
replacement.parent = p.parent;
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left  = replacement;
else
p.parent.right = replacement;

// Null out links so they are OK to use by fixAfterDeletion.
p.left = p.right = p.parent = null;

// Fix replacement
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
root = null;
} else { //  No children. Use self as phantom replacement and unlink.
if (p.color == BLACK)
fixAfterDeletion(p);

if (p.parent != null) {
if (p == p.parent.left)
p.parent.left = null;
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}


如果p是有两个子节点的叶节点,则直接将用其后继替换p就行。并使得p指向其后继

使得replacement的指向p的子节点,左节点优先。

如果replacement非空则说明p有子节点,用replacement替换p。并使得p的所有指针指向null,如果p的颜色为黑则进行修复。

如果replacement为空,则分两种情况讨论,一种是其父节点为null说明这个树只有一个节点,并且被删除了,则使得root指向空,另一种是replacement为叶节点然后如果p是黑色的,然后修复就好,使得其父节点的对应子节点指向空就行。

private void fixAfterDeletion(Entry<K,V> x) {
while (x != root && colorOf(x) == BLACK) {
if (x == leftOf(parentOf(x))) {
Entry<K,V> sib = rightOf(parentOf(x));

if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateLeft(parentOf(x));
sib = rightOf(parentOf(x));
}

if (colorOf(leftOf(sib))  == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(rightOf(sib)) == BLACK) {
setColor(leftOf(sib), BLACK);
setColor(sib, RED);
rotateRight(sib);
sib = rightOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(rightOf(sib), BLACK);
rotateLeft(parentOf(x));
x = root;
}
} else { // symmetric
Entry<K,V> sib = leftOf(parentOf(x));

if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateRight(parentOf(x));
sib = leftOf(parentOf(x));
}

if (colorOf(rightOf(sib)) == BLACK &&
colorOf(leftOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(leftOf(sib)) == BLACK) {
setColor(rightOf(sib), BLACK);
setColor(sib, RED);
rotateLeft(sib);
sib = leftOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(leftOf(sib), BLACK);
rotateRight(parentOf(x));
x = root;
}
}
}

setColor(x, BLACK);
}


同样修复代码也是异常复杂。不过和之前增加的修复代码一样可以通过图解来解释更为简洁。

情况一:x的兄弟节点w是红色的;



情况二:x的兄弟节点w是黑色的,而且w的两个子结点都是黑色的



情况三:x的兄弟节点w是黑色的,而且w的左子结点是红色的,w的右子节点是黑色的



情况四:x的兄弟节点w是黑色的,而且w的右子节点是红色的



HashMap和HashTable的区别

HashMap是比较新的类,HashTable是比较老的类,现在基本已经不用了。

HashMap支持null为key,HashTable不支持

HashMap支持containsKey和containsValue,而HashTable支持contains;

HashMap不是同步的,HashTable是同步的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息