DoubleOrderedMap 是 Commons Collections 中獨特的 Map 實作,內部維護兩棵紅黑樹:一棵按 Key 排序,一棵按 Value 排序,支援雙向高效查找、範圍操作與有序迭代。本文深度解析其核心資料結構、紅黑樹插入/刪除平衡邏輯、雙向索引同步機制與迭代器實作。
public final class DoubleOrderedMap extends AbstractMap {
private Node[] rootNode = new Node[] { null, null }; // [KEY_ROOT, VALUE_ROOT]
private static final int KEY = 0, VALUE = 1;
// Node 同時掛載於兩棵樹,含左右子節點、父節點、顏色陣列
private static final class Node implements Map.Entry, KeyValue {
private Comparable[] data; // [key, value]
private Node[] leftNode; // [keyLeft, valueLeft]
private Node[] rightNode; // [keyRight, valueRight]
private Node[] parentNode; // [keyParent, valueParent]
private boolean[] blackColor; // [keyBlack, valueBlack]
}
}
Node 同時存在於 Key 樹與 Value 樹,透過索引 0/1 區分。private Node lookup(Comparable data, int index) {
Node node = rootNode[index];
while (node != null) {
int cmp = compare(data, node.getData(index));
if (cmp == 0) return node;
node = (cmp < 0) ? node.getLeft(index) : node.getRight(index);
}
return null;
}
index=0 走 Key 樹,index=1 走 Value 樹。get(key)、remove(key) 使用 Key 樹;entrySetByValue() 相關操作使用 Value 樹。private void doRedBlackInsert(Node insertedNode, int index) {
makeRed(insertedNode, index);
while (insertedNode != rootNode[index] && isRed(getParent(insertedNode, index), index)) {
if (isLeftChild(getParent(insertedNode, index), index)) {
Node uncle = getRightChild(getGrandParent(insertedNode, index), index);
if (isRed(uncle, index)) { // Case 1: 紅叔叔
makeBlack(getParent(insertedNode, index), index);
makeBlack(uncle, index);
makeRed(getGrandParent(insertedNode, index), index);
insertedNode = getGrandParent(insertedNode, index);
} else { // Case 2/3: 黑叔叔
if (isRightChild(insertedNode, index)) { // LR
insertedNode = getParent(insertedNode, index);
rotateLeft(insertedNode, index);
}
makeBlack(getParent(insertedNode, index), index);
makeRed(getGrandParent(insertedNode, index), index);
rotateRight(getGrandParent(insertedNode, index), index);
}
} else { // 鏡像邏輯:父節點為右孩子
// ... 對稱處理 ...
}
}
makeBlack(rootNode[index], index);
}
put() 先插入 Key 樹,再呼叫 insertValue() 插入 Value 樹,各自獨立平衡。private void doRedBlackDelete(Node deletedNode) {
for (int index = KEY; index <= VALUE; index++) {
// 1. 兩個孩子皆存在 → 與後繼節點交換位置
// 2. 至多一個孩子 → 直接替換
// 3. 刪除節點為黑色 → doRedBlackDeleteFixup 修正
}
shrink(); // 更新 size/modCount
}
doRedBlackDeleteFixup 實作標準刪除修正四案例(兄弟紅/黑、姪子紅/黑),雙樹各自獨立執行。
private void swapPosition(Node x, Node y, int index) {
// 處理一般情況與父子關係特殊情況
// 交換父、左、右指標,修正子節點父指標
// 交換顏色
// 更新根節點引用
}
private abstract class DoubleOrderedMapIterator implements Iterator {
protected Node lastReturnedNode, nextNode;
private int iteratorType; // KEY 或 VALUE
public Object next() {
if (modifications != expectedModifications) throw new ConcurrentModificationException();
lastReturnedNode = nextNode;
nextNode = nextGreater(nextNode, iteratorType); // 中序後繼
return doGetNext();
}
public void remove() { doRedBlackDelete(lastReturnedNode); }
}
entrySet() 走 Key 樹(leastNode 取最左節點起始)。entrySetByValue() 走 Value 樹,支援按值排序迭代。modCount 檢測並發修改。public Object put(Object key, Object value) {
checkKeyAndValue(key, value); // 非空、可比較
Node node = rootNode[KEY];
if (node == null) { // 空樹
Node root = new Node((Comparable)key, (Comparable)value);
rootNode[KEY] = rootNode[VALUE] = root;
grow(); return null;
}
while (true) {
int cmp = compare((Comparable)key, node.getData(KEY));
if (cmp == 0) throw new IllegalArgumentException("Duplicate key");
if (cmp < 0) {
if (node.getLeft(KEY) != null) node = node.getLeft(KEY);
else { insertNewNode(node, key, value, true); break; }
} else {
if (node.getRight(KEY) != null) node = node.getRight(KEY);
else { insertNewNode(node, key, value, false); break; }
}
}
return null;
}
insertNewNode 建立節點,掛入 Key 樹並 doRedBlackInsert,再呼叫 insertValue 掛入 Value 樹並平衡。
| 操作 | 時間複雜度 | 說明 |
|---|---|---|
| get/containsKey/put/remove | O(log n) | 雙樹高度平衡 |
| 按 Key 範圍查詢 | O(log n + k) | 中序遍歷 |
| 按 Value 範圍查詢 | O(log n + k) | Value 樹同樣支援 |
| 記憶體開銷 | ~4x 標準 HashMap | 雙樹節點、指標、顏色陣列 |