Java中TreeMap的内部工作原理

1. 概述

TreeMap是Java中提供的一种Map实现方式,它的底层实现是基于红黑树(Red-Black Tree)。TreeMap在存储大量数据时,能够保证查询和插入操作都能够在O(log n)时间内完成,具有较高的效率。

2. 红黑树

2.1 红黑树是什么

红黑树,是一种自平衡的二叉查找树,它在每个节点上增加一个存储位表示节点的颜色,可以是红色或黑色。通过对任何一条路径上的颜色进行限制,红黑树保证最长路径不超过最短路径的两倍,因此它是一种平衡树。红黑树的高度近似log N,因此它保证了基本的查找、插入和删除操作的时间复杂度均为O(log N)

2.2 红黑树节点的规则

红黑树是一种特殊的二叉查找树,它的每个节点都必须满足以下规则:

每个节点要么是红色,要么是黑色。

根节点是黑色的。

每个叶子节点(NIL节点,空节点)都是黑色的。

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

从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点。

2.3 红黑树插入操作的规则

在红黑树中插入一个节点,需要遵循以下规则:

按照二叉查找树的方式插入节点,并将节点设置为红色。

如果插入节点的父节点是黑色的,则不需要进行任何操作,红黑树的性质没有被破坏。

如果插入节点的父节点是红色的,则要进行调整操作,确保红黑树的性质不被破坏。

3. TreeMap的内部实现

3.1 TreeMap的概述

在Java中,Map是一个非常常用的接口,TreeMap是Map接口的一种实现方式。TreeMap继承自AbstractMap抽象类,它使用红黑树作为内部数据结构来存储键值对。由于红黑树是一种自平衡二叉查找树,因此相比于其它Map实现,TreeMap有着更好的查找、删除、插入性能。

3.2 TreeMap的属性及方法

在了解TreeMap内部实现之前,先来介绍下它的一些属性及方法:

root: TreeMap的红黑树的根节点

Comparator: TreeMap中key对象之间比较器,用于对key进行排序

size: TreeMap中存储的键值对数量

get(Object key):根据key获取对应的value

put(K key, V value):插入键值对,如果已有对应的key则覆盖原有的value

remove(Object key):删除键值对,如果不存在对应的key则返回null

clear():清空TreeMap,将所有节点设置为null

entrySet():返回TreeMap中存储的键值对集合

3.3 TreeMap的实现

TreeMap的内部实现,使用一个红黑树来维护存储的键值对。TreeMap中每个节点维护一个键值对,其中键是节点的关键字。TreeMap使用键所属的类的自然排序规则或专门提供的比较器来对键进行排序。

具体实现请参考以下代码:

/**

* A Red-Black tree based NavigableMap implementation.

*

* The Map is sorted according to the {@linkplain Comparable natural

* ordering} of its keys, or by a {@link Comparator} is provided at

* map creation time, depending on which constructor is used.

*

* This implementation provides guaranteed log(n) time cost for the

* containsKey, get, put and

* remove operations. Algorithms are adaptations of those in

* Cormen, Leiserson, and Rivest's Introduction to Algorithms.

*

* Note that the ordering maintained by a tree map, like any sorted map, and whether or not

* an explicit comparator is provided, must be consistent with equals if this sorted map

* is to correctly implement the Map interface. (See {@code Comparable} or

* {@code Comparator} for a precise definition of consistent with equals.) This is so because

* the Map interface is defined in terms of the {@code equals} operation, but a sorted

* map performs all key comparisons using its {@code compareTo} (or compare) method, so two keys

* that are deemed equal by this method are, from the standpoint of the sorted map, equal. The behavior

* of a sorted map is well-defined even if its ordering is inconsistent with equals; it

* just fails to obey the general contract of the Map interface.

*

* Note that this implementation is not synchronized.

* If multiple threads access a map concurrently, and at least one of the threads modifies the map

* structurally, it must be synchronized externally. (A structural modification is any operation

* that adds or deletes one or more mappings; merely changing the value associated with an existing key

* is not a structural modification.) This is typically accomplished by synchronizing on some object

* that naturally encapsulates the map. If no such object exists, the map should be "wrapped" using the

* {@link Collections#synchronizedSortedMap Collections.synchronizedSortedMap} method. This is best

* done at creation time, to prevent accidental unsynchronized access to the map:

*

*

* SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));

*

* The iterators returned by the {@code iterator} method of the collections returned by all of

* this class's "collection view methods" are fail-fast: if the map is structurally modified

* at any time after the iterator is created, in any way except through the iterator's own

* {@code remove} method, the iterator will throw a {@link ConcurrentModificationException}.

* Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather

* than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

* The fail-fast behavior of an iterator cannot be guaranteed as it is, generally

* speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent

* modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort

* basis. Therefore, it would be wrong to write a program that depended on this exception for its

* correctness: the fail-fast behavior of iterators should be used only to detect bugs.

*

* @param the type of keys maintained by this map

* @param the type of mapped values

* @since 1.2

*/

public class TreeMap extends AbstractMap

implements NavigableMap, Cloneable, java.io.Serializable {

/**

* 根节点

*/

private transient Entry root;

/**

* 比较器,用于对键进行排序

*/

private transient Comparator comparator;

/**

* TreeMap中存储数据的条数

*/

private transient int size = 0;

public V put(K key, V value) {

// 按照二叉查找树的方式插入节点,并将节点设置为红色

Entry t = root;

if (t == null) {

compare(key, key); // 验证key是否可以比较

root = new Entry<>(key, value, null);

size = 1;

modCount++;

return null;

}

int cmp;

Entry parent;

Comparator 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

// 如果已有对应的key则覆盖原有的value

return t.setValue(value);

} while (t != null);

} else {

if (key == null)

throw new NullPointerException();

Comparable k = (Comparable) 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;

}

/**

* 调整新插入节点后的红黑树

*/

private void fixAfterInsertion(Entry x) {

x.color = RED;

while (x != null && x != root && x.parent.color == RED) {

if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {

Entry 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 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;

}

// more methods...

}

在TreeMap的实现中,调用put()方法时,会按照二叉查找树的方式插入节点,并将节点设置为红色。如果被插入节点的父节点是黑色的,则不需要进行任何操作,此时红黑树的性质没有被破坏。如果被插入节点的父节点是红色的,则会进行调整操作,确保红黑树的性质不被破坏。

4. 总结

TreeMap是Java中一种非常有用的Map实现方式,它使用红黑树作为内部数据结构来存储键值对,并能够保证查询和插入操作都能够在O(log n)时间内完成。在使用TreeMap时,需要注意自定义比较器,保证键对象可以进行比较。此外,由于红黑树的性质,插入、删除、查找操作的效率都很高,因此在对大量数据进行存储和查询时,TreeMap是一个非常好的选择。

后端开发标签