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

java1.8 常用集合源码学习:TreeMap

2017-10-28 16:26 323 查看
1、api
基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
此实现为 containsKey、get、put 和 remove 操作提供受保证的
log(n) 时间开销。这些算法是 Cormen、Leiserson 和 Rivest 的 Introduction to Algorithms 中的算法的改编。
注意,如果要正确实现 Map 接口,则有序映射所保持的顺序(无论是否明确提供了比较器)都必须与
equals 一致。(关于与 equals 一致 的精确定义,请参阅 Comparable 或 Comparator)。这是因为 Map 接口是按照
equals 操作定义的,但有序映射使用它的 compareTo(或 compare)方法对所有键进行比较,因此从有序映射的观点来看,此方法认为相等的两个键就是相等的。即使排序与
equals 不一致,有序映射的行为仍然是 定义良好的,只不过没有遵守 Map 接口的常规协定。
注意,此实现不是同步的。如果多个线程同时访问一个映射,并且其中至少一个线程从结构上修改了该映射,则其必须 外部同步。(结构上的修改是指添加或删除一个或多个映射关系的操作;仅改变与现有键关联的值不是结构上的修改。)这一般是通过对自然封装该映射的对象执行同步操作来完成的。如果不存在这样的对象,则应该使用 Collections.synchronizedSortedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的不同步访问,如下所示:
SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
collection(由此类所有的“collection 视图方法”返回)的 iterator 方法返回的迭代器都是快速失败 的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器自身的 remove 方法,否则在其他任何时间以任何方式进行修改都将导致迭代器抛出 ConcurrentModificationException。因此,对于并发的修改,迭代器很快就完全失败,而不会冒着在将来不确定的时间发生不确定行为的风险。
注意,迭代器的快速失败行为无法得到保证,一般来说,当存在不同步的并发修改时,不可能作出任何肯定的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测
bug。
此类及其视图中的方法返回的所有 Map.Entry 对都表示生成它们时的映射关系的快照。它们不 支持 Entry.setValue 方法。(不过要注意的是,使用 put 更改相关映射中的映射关系是有可能的。)
此类是 Java
Collections Framework 的成员。

2、源码学习

比较器
private final
Comparator<?
super
K>
comparator;

红黑树的根节点
private transient
Entry<K,V>
root;

结构修改的次数
private transient int
modCount
=
0;

从已有map创建TreeMap,调用了putAll方法
public
TreeMap(Map<?
extends
K,
?
extends
V> m) {
comparator
=
null;
putAll(m);
}

用SortedMap创建TreeMap,调用了buildFromSorted方法
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) {
}
}

判断是否包含key,调用了getEntry方法
public boolean
containsKey(Object key) {
return
getEntry(key) !=
null;
}

取得指定key的value
final
Entry<K,V>
getEntry(Object key) {
// Offload comparator-based
version for sake of performance
//如果有比较器,则使用getEntryUsingComparator方法
if
(comparator
!=
null)
return
getEntryUsingComparator(key);
//不允许key为null值
if
(key ==
null)
throw new
NullPointerException();
@SuppressWarnings("unchecked")
Comparable<?
super
K> k = (Comparable<?
super
K>) key;
Entry<K,V>
p = root;
while
(p !=
null) {
//比较待查找key和当前节点p(第一次进入循环是当前节点为根节点)的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;
//如果两个key相等,则返回当前节点p
else
return
p;
}
//如果没找到返回null
return null;
}

这个方法是getEntry方法的带比较器的版本,实现基本是一样的,也是从根节点开始找
final
Entry<K,V>
getEntryUsingComparator(Object key) {
@SuppressWarnings("unchecked")
K
k = (K)
key;
Comparator<?
super
K> cpr =
comparator;
if
(cpr !=
null) {
Entry<K,V>
p = root;
while
(p !=
null) {
int
cmp = cpr.compare(k,
p.key);
if
(cmp <
0)
p = p.left;
else if
(cmp >
0)
p = p.right;
else
return
p;
}
}
return null;
}

查看map是否包含给定的value
public boolean
containsValue(Object value) {
//首先调用getFirstEntry取得红黑树最左侧的子节点,然后遍历时每次调用successor取得下一个元素
for
(Entry<K,V>
e = getFirstEntry();
e !=
null;
e =
successor(e))
//调用valEquals判断两个值是否相等
if
(valEquals(value,
e.value))
return true;
return false;
}

getFirstEntry方法取得整个树的最左侧的子节点
final
Entry<K,V>
getFirstEntry() {
Entry<K,V>
p = root;
if
(p !=
null)
while
(p.left
!=
null)
p = p.left;
return
p;
}

successor方法查找比给定的节点t大的节点
predecessor方法查找比给定的节点t小的节点,实现方式和successor非常类似,只是方向相反,不再赘述
static
<K,V>
TreeMap.Entry<K,V>
successor(Entry<K,V>
t) {
//如果t为null直接返回null
if
(t ==
null)
return null;
//如果t有右子树,则取得t的右子树的最左侧的子节点返回
else if
(t.right
!=
null) {
Entry<K,V>
p = t.right;
while
(p.left
!=
null)
p = p.left;
return
p;
//如果t没有右子树(即t是一个叶子节点),则判断t本身,如果t为它父亲的左节点,则返回t的父节点,如果t为它父亲的右节点,则往上推,直到他的某一层的祖先是其父亲的左子节点,返回这个祖先的父节点
}
else
{
Entry<K,V>
p = t.parent;
Entry<K,V>
ch = t;
while
(p !=
null
&& ch == p.right)
{
ch = p;
p = p.parent;
}
return
p;
}
}

valEquals方法用于判断两个给定对象是否相等(或者都为null)
static final boolean
valEquals(Object o1,
Object o2) {
return
(o1==null
? o2==null
: o1.equals(o2));
}

取得map的最后一个key,调用了getLastEntry方法和key方法
public
K
lastKey() {
return
key(getLastEntry());
}

getLastEntry方法取得了map的最右侧的子节点
final
Entry<K,V>
getLastEntry() {
Entry<K,V>
p = root;
if
(p !=
null)
while
(p.right
!=
null)
p = p.right;
return
p;
}

key方法取得了给定的键值对的键对象
static
<K>
K
key(Entry<K,?>
e) {
if
(e==null)
throw new
NoSuchElementException();
return
e.key;
}

将给定的map的所有键值对都put到当前TreeMap中,如果给定的map是SortedMap并且比较器和当前TreeMap相同,则调用buildFromSorted方法,否则直接调用super.putAll(遍历给定map,调用TreeMap的put方法)
public void
putAll(Map<?
extends
K,
?
extends
V> map) {
int
mapSize = map.size();
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;
}
}
super.putAll(map);
}

getCeilingEntry方法返回大于或等于给定key的键值对(优先返回等于的),
getFloorEntry方法返回小于或等于给定key的键值对(优先返回等于的),
getHigherEntry方法返回大于给定key的键值对,
getLowerEntry方法返回小于给定key的键值对,
后三个方法的实现方式和getCeilingEntry方法非常类似,只是方向相反(以及判断条件不同),不再赘述
final
Entry<K,V>
getCeilingEntry(K
key) {
//p为当前遍历节点,首先从根节点开始遍历
Entry<K,V>
p = root;
while
(p !=
null) {
//比较给定key和p的key
int
cmp = compare(key,
p.key);
//如果给定key比p的key小:如果p有左子节点则将p指向p的左子节点并继续遍历,如果p没有左子节点则返回p
if
(cmp <
0) {
if
(p.left
!=
null)
p = p.left;
else
return
p;
//如果给定key比p的key大
}
else if
(cmp >
0) {
//如果p有右子节点,则将p指向p的右子节点并继续遍历
if
(p.right
!=
null) {
p = p.right;
//如果p没有右子节点,则判断p的类型。如果p是它父亲的左子节点,则直接返回p的父亲;如果p是它父亲的右子节点,则往上推,直到他的某一层的祖先是其父亲的左子节点,返回这个祖先的父节点
//这里这样判断的原因是,如果p是左子节点,那么它的父节点一定大于给定的key,如果p是右子节点,那么他的父节点一定是小于给定的key,所以要一直往上推,直到他的某一个祖先是左节点,这时候这个祖先的父节点是大于给定的key的
}
else
{
Entry<K,V>
parent = p.parent;
Entry<K,V>
ch = p;
while
(parent !=
null
&& ch == parent.right)
{
ch = parent;
parent = parent.parent;
}
return
parent;
}
//如果给定key和p的key相等,则返回p
}
else
return
p;
}
return null;
}

将指定的键值对放入TreeMap中
public
V
put(K
key,
V
value) {
//首先需要遍历TreeMap,找到适当的插入位置,当前遍历节点t首先指向root,从根节点开始遍历
Entry<K,V>
t = root;
//如果map是空的,则创建一个新节点,并将其设置为根节点,返回null
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);
//如果待插入key比t的key小,则将t指向t的左节点
if
(cmp <
0)
t = t.left;
//如果待插入key比t的key大,则将t指向t的右节点
else if
(cmp >
0)
t = t.right;
//如果两个key相等,则直接修改t的value为方法传入的value,并返回旧value
else
return
t.setValue(value);
}
while
(t !=
null);
}
//如果没有比较器,则直接比较,逻辑和使用比较器一样
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);
}
//如果遍历结束仍无法在map中找到和待插入的key相同的key,则创建一个新的Entry
Entry<K,V>
e = new
Entry<>(key,
value,
parent);
//根据之前最后一次的比较结果,确定新的Entry节点应该插入的parent的左侧还是右侧
if
(cmp <
0)
parent.left
= e;
else
parent.right
= e;
//插入后,红黑树可能不再满足红黑树的条件,需要调用fixAfterInsertion方法使其重新成为一颗红黑树
fixAfterInsertion(e);
//更新size,modCount,并返回null
size++;
modCount++;
return null;
}

删除指定的key对于的键值对,首先调用getEntry方法找到要删除的键值对,然后调用deleteEntry方法将其删除
public
V
remove(Object key) {
Entry<K,V>
p = getEntry(key);
if
(p ==
null)
return null;

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

调用forEach的时候,实际也是从这个树的最小值开始,一直遍历到最大值
public void
forEach(BiConsumer<?
super
K,
?
super
V> action) {
Objects.requireNonNull(action);
int
expectedModCount =
modCount;
for
(Entry<K,
V> e = getFirstEntry();
e !=
null;
e =
successor(e)) {
action.accept(e.key,
e.value);

if
(expectedModCount !=
modCount) {
throw new
ConcurrentModificationException();
}
}
}

PrivateEntryIterator类是所有TreeMap的Iterator类的基类,实现了hasNext、nextEntry、prevEntry、remove等方法

NavigableSubMap作为一个比较重要的内部类,基本上所有子map的实现都是基于它
内部维护了一个TreeMap
final
TreeMap<K,V>
m;
子map的最低键lo、子map的最高键hi、子map是否从m的最小值开始(fromStart)、子map是否一直到m的结尾才结束(toEnd)、是否包含最小键lo(loInclusive)、是否包含最大键hi(hiInclusive)
final
K
lo,
hi;
final boolean
fromStart,
toEnd;
final boolean
loInclusive,
hiInclusive;
NavigableSubMap内部的所有方法都是基于上述这些实现的。

Entry类为内部使用的红黑树节点,包含:
K
key;
V
value;
Entry<K,V>
left;
Entry<K,V>
right;
Entry<K,V>
parent;
boolean
color
=
BLACK;

buildFromSorted方法是所有根据排序集合(包括反序列化TreeMap)创建新TreeMap的底层方法,这个方法比较抽象,如果不好理解,可以自己举个例子,比如使用一个总共4个节点的TreeMap,跟踪一下代码
private final
Entry<K,V>
buildFromSorted(int
level, int
lo, int
hi,
int
redLevel,
Iterator<?> it,
java.io.ObjectInputStream
str,
V
defaultVal)
throws
java.io.IOException,
ClassNotFoundException {

//这是一个递归的方法,总体思路是按照迭代器的顺序去构建整个map,迭代器已经排好序了,所以不需要关系排序,只需要按顺序将红黑树组成即可

//如果最高索引小于最低索引则返回null
if
(hi < lo)
return null;
//计算中间索引,他是最低索引加最高的值除以2
int
mid = (lo + hi) >>>
1;

Entry<K,V>
left = null;
//如果最低索引小于中间索引,继续递归调用buildFromSorted,传入参数的level+1(代表红黑树的层数),mid自减1(这样才可能最终退出递归)
if
(lo < mid)
left = buildFromSorted(level+1,
lo,
mid -
1,
redLevel,
it,
str,
defaultVal);
//最终取得了左叶节点后,根据迭代器或输入流来创建中间节点
// extract key
and/or value from iterator or stream
K
key;
V
value;
if
(it !=
null) {
if
(defaultVal==null)
{
Map.Entry<?,?>
entry = (Map.Entry<?,?>)it.next();
key = (K)entry.getKey();
value = (V)entry.getValue();
}
else
{
key = (K)it.next();
value = defaultVal;
}
}
else
{
// use stream
key = (K)
str.readObject();
value = (defaultVal
!= null
? defaultVal : (V)
str.readObject());
}

Entry<K,V>
middle = new
Entry<>(key,
value, null);
//如果当前层级应该是红色,则将刚创建的节点置为红色,redLevel是在外层方法中计算并传入的
// color nodes
in non-full bottommost level red
if
(level == redLevel)
middle.color
=
RED;
//如果有创建过左子节点,更新节点关系
if
(left !=
null) {
middle.left
= left;
left.parent
= middle;
}
//如果中间节点小于最高节点,则递归调用buildFromSorted方法创建右子节点,并更新关系
if
(mid < hi) {
Entry<K,V>
right = buildFromSorted(level+1,
mid+1,
hi,
redLevel,
it,
str,
defaultVal);
middle.right
= right;
right.parent
= middle;
}
//返回本次调用创建的中间节点(如果是最外层的调用,这个就是map的根节点)
return
middle;
}

这个方法在删除节点是调用
private void
deleteEntry(Entry<K,V>
p) {
modCount++;
size--;
//如果左右子树都存在,则找到他的后继节点(即比它大的节点中最小的那个节点,一般是他的右子树的最左侧子树),将后继节点的值赋给p,将p指向后继节点(即调换他们的位置)
// 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
//经过上一步以后,p目前一定是只有左节点或只有右节点或没有子节点,我们下一步需要将p去掉,将p的唯一子节点(如果有的话)放到p原来的位置上
// Start fixup at replacement node, if it exists.
Entry<K,V>
replacement = (p.left
!=
null
? p.left
: p.right);
//如果p有子节点,则设其子节点为replacement
if
(replacement !=
null) {
// Link replacement
to parent
//将replacement的父节点指向p的父节点
replacement.parent
= p.parent;
//将p的父节点的子节点指向replacement,到此,即完成了replacement对p的替换
if
(p.parent
==
null)
root
= replacement;
else if
(p == p.parent.left)
p.parent.left
= replacement;
else
p.parent.right
= replacement;
//完全删除p
// Null out links
so they are OK to use by fixAfterDeletion.
p.left
= p.right
= p.parent
=
null;
//如果删除的p是黑色,则需要调整红黑树
// Fix replacement
if
(p.color
==
BLACK)
fixAfterDeletion(replacement);
//如果p没有子节点并且p没有父节点,则说明p是根节点,简单重置根节点为null即可
}
else if
(p.parent
==
null) {
// return if we are the only node.
root
=
null;
//如果p没有子节点且p不是根节点,则可以直接删除,即去掉相关的指针,并且按需调整红黑树
}
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;
}
}
}

rotateLeft、rotateRight、fixAfterInsertion、fixAfterDeletion四个方法和hashmap中的方法基本一样,那几个方法在hashmap中已经看过,不再赘述

Spliterator相关的以后统一看
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: