org.apache.hadoop.hbase.util.AvlUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hbase-common Show documentation
Show all versions of hbase-common Show documentation
Common functionality for HBase
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.util;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.yetus.audience.InterfaceStability;
/**
* Helper class that allows to create and manipulate an AvlTree. The main utility is in cases where
* over time we have a lot of add/remove of the same object, and we want to avoid all the
* allocations/deallocations of the "node" objects that the java containers will create.
*/
@InterfaceAudience.Private
@InterfaceStability.Evolving
public final class AvlUtil {
private AvlUtil() {
}
/**
* This class represent a node that will be used in an AvlTree. Instead of creating another object
* for the tree node, like the TreeMap and the other java contains, here the node can be extended
* and the content can be embedded directly in the node itself. This is useful in cases where over
* time we have a lot of add/remove of the same object.
*/
@InterfaceAudience.Private
public static abstract class AvlNode {
protected TNode avlLeft;
protected TNode avlRight;
protected int avlHeight;
public abstract int compareTo(TNode other);
}
/**
* This class extends the AvlNode and adds two links that will be used in conjunction with the
* AvlIterableList class. This is useful in situations where your node must be in a map to have a
* quick lookup by key, but it also require to be in something like a list/queue. This is useful
* in cases where over time we have a lot of add/remove of the same object.
*/
@InterfaceAudience.Private
public static abstract class AvlLinkedNode extends AvlNode {
protected TNode iterNext = null;
protected TNode iterPrev = null;
}
@InterfaceAudience.Private
public interface AvlInsertOrReplace {
TNode insert(Object searchKey);
TNode replace(Object searchKey, TNode prevNode);
}
/**
* The AvlTree allows to lookup an object using a custom key. e.g. the java Map allows only to
* lookup by key using the Comparator specified in the constructor. In this case you can pass a
* specific comparator for every needs.
*/
@InterfaceAudience.Private
public static interface AvlKeyComparator {
int compareKey(TNode node, Object key);
}
/**
* Visitor that allows to traverse a set of AvlNodes. If you don't like the callback style of the
* visitor you can always use the AvlTreeIterator.
*/
@InterfaceAudience.Private
public static interface AvlNodeVisitor {
/**
* Visitor
* @param node the node that we are currently visiting
* @return false to stop the iteration. true to continue.
*/
boolean visitNode(TNode node);
}
/**
* Helper class that allows to create and manipulate an AVL Tree
*/
@InterfaceAudience.Private
public static class AvlTree {
/**
* Return the node that matches the specified key or null in case of node not found.
* @param root the current root of the tree
* @param key the key for the node we are trying to find
* @param keyComparator the comparator to use to match node and key
* @return the node that matches the specified key or null in case of node not found.
*/
public static TNode get(TNode root, final Object key,
final AvlKeyComparator keyComparator) {
while (root != null) {
int cmp = keyComparator.compareKey(root, key);
if (cmp > 0) {
root = (TNode) root.avlLeft;
} else if (cmp < 0) {
root = (TNode) root.avlRight;
} else {
return (TNode) root;
}
}
return null;
}
/**
* Return the first node of the tree.
* @param root the current root of the tree
* @return the first (min) node of the tree
*/
public static TNode getFirst(TNode root) {
if (root != null) {
while (root.avlLeft != null) {
root = (TNode) root.avlLeft;
}
}
return root;
}
/**
* Return the last node of the tree.
* @param root the current root of the tree
* @return the last (max) node of the tree
*/
public static TNode getLast(TNode root) {
if (root != null) {
while (root.avlRight != null) {
root = (TNode) root.avlRight;
}
}
return root;
}
/**
* Insert a node into the tree. It uses the AvlNode.compareTo() for ordering. NOTE: The node
* must not be already in the tree.
* @param root the current root of the tree
* @param node the node to insert
* @return the new root of the tree
*/
public static TNode insert(TNode root, TNode node) {
if (root == null) return node;
int cmp = node.compareTo(root);
assert cmp != 0 : "node already inserted: " + root;
if (cmp < 0) {
root.avlLeft = insert(root.avlLeft, node);
} else {
root.avlRight = insert(root.avlRight, node);
}
return balance(root);
}
/**
* Insert a node into the tree. This is useful when you want to create a new node or replace the
* content depending if the node already exists or not. Using AvlInsertOrReplace class you can
* return the node to add/replace.
* @param root the current root of the tree
* @param key the key for the node we are trying to insert
* @param keyComparator the comparator to use to match node and key
* @param insertOrReplace the class to use to insert or replace the node
* @return the new root of the tree
*/
public static TNode insert(TNode root, Object key,
final AvlKeyComparator keyComparator,
final AvlInsertOrReplace insertOrReplace) {
if (root == null) {
return insertOrReplace.insert(key);
}
int cmp = keyComparator.compareKey(root, key);
if (cmp < 0) {
root.avlLeft = insert((TNode) root.avlLeft, key, keyComparator, insertOrReplace);
} else if (cmp > 0) {
root.avlRight = insert((TNode) root.avlRight, key, keyComparator, insertOrReplace);
} else {
TNode left = (TNode) root.avlLeft;
TNode right = (TNode) root.avlRight;
root = insertOrReplace.replace(key, root);
root.avlLeft = left;
root.avlRight = right;
return root;
}
return balance(root);
}
private static TNode removeMin(TNode p) {
if (p.avlLeft == null) return (TNode) p.avlRight;
p.avlLeft = removeMin(p.avlLeft);
return balance(p);
}
/**
* Removes the node matching the specified key from the tree
* @param root the current root of the tree
* @param key the key for the node we are trying to find
* @param keyComparator the comparator to use to match node and key
* @return the new root of the tree
*/
public static TNode remove(TNode root, Object key,
final AvlKeyComparator keyComparator) {
return remove(root, key, keyComparator, null);
}
/**
* Removes the node matching the specified key from the tree
* @param root the current root of the tree
* @param key the key for the node we are trying to find
* @param keyComparator the comparator to use to match node and key
* @param removed will be set to true if the node was found and removed, otherwise false
* @return the new root of the tree
*/
public static TNode remove(TNode root, Object key,
final AvlKeyComparator keyComparator, final AtomicBoolean removed) {
if (root == null) return null;
int cmp = keyComparator.compareKey(root, key);
if (cmp == 0) {
if (removed != null) removed.set(true);
TNode q = (TNode) root.avlLeft;
TNode r = (TNode) root.avlRight;
if (r == null) return q;
TNode min = getFirst(r);
min.avlRight = removeMin(r);
min.avlLeft = q;
return balance(min);
} else if (cmp > 0) {
root.avlLeft = remove((TNode) root.avlLeft, key, keyComparator);
} else /* if (cmp < 0) */ {
root.avlRight = remove((TNode) root.avlRight, key, keyComparator);
}
return balance(root);
}
/**
* Visit each node of the tree
* @param root the current root of the tree
* @param visitor the AvlNodeVisitor instance
*/
public static void visit(final TNode root,
final AvlNodeVisitor visitor) {
if (root == null) return;
final AvlTreeIterator iterator = new AvlTreeIterator<>(root);
boolean visitNext = true;
while (visitNext && iterator.hasNext()) {
visitNext = visitor.visitNode(iterator.next());
}
}
private static TNode balance(TNode p) {
fixHeight(p);
int balance = balanceFactor(p);
if (balance == 2) {
if (balanceFactor(p.avlRight) < 0) {
p.avlRight = rotateRight(p.avlRight);
}
return rotateLeft(p);
} else if (balance == -2) {
if (balanceFactor(p.avlLeft) > 0) {
p.avlLeft = rotateLeft(p.avlLeft);
}
return rotateRight(p);
}
return p;
}
private static TNode rotateRight(TNode p) {
TNode q = (TNode) p.avlLeft;
p.avlLeft = q.avlRight;
q.avlRight = p;
fixHeight(p);
fixHeight(q);
return q;
}
private static TNode rotateLeft(TNode q) {
TNode p = (TNode) q.avlRight;
q.avlRight = p.avlLeft;
p.avlLeft = q;
fixHeight(q);
fixHeight(p);
return p;
}
private static void fixHeight(TNode node) {
final int heightLeft = height(node.avlLeft);
final int heightRight = height(node.avlRight);
node.avlHeight = 1 + Math.max(heightLeft, heightRight);
}
private static int height(TNode node) {
return node != null ? node.avlHeight : 0;
}
private static int balanceFactor(TNode node) {
return height(node.avlRight) - height(node.avlLeft);
}
}
/**
* Iterator for the AvlTree
*/
@InterfaceAudience.Private
public static class AvlTreeIterator implements Iterator {
private final Object[] stack = new Object[64];
private TNode current = null;
private int height = 0;
public AvlTreeIterator() {
}
/**
* Create the iterator starting from the first (min) node of the tree
* @param root the current root of the tree
*/
public AvlTreeIterator(final TNode root) {
seekFirst(root);
}
/**
* Create the iterator starting from the specified key
* @param root the current root of the tree
* @param key the key for the node we are trying to find
* @param keyComparator the comparator to use to match node and key
*/
public AvlTreeIterator(final TNode root, final Object key,
final AvlKeyComparator keyComparator) {
seekTo(root, key, keyComparator);
}
@Override
public boolean hasNext() {
return current != null;
}
@Override
public TNode next() {
final TNode node = this.current;
seekNext();
return node;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Reset the iterator, and seeks to the first (min) node of the tree
* @param root the current root of the tree
*/
public void seekFirst(final TNode root) {
current = root;
height = 0;
if (root != null) {
while (current.avlLeft != null) {
stack[height++] = current;
current = (TNode) current.avlLeft;
}
}
}
/**
* Reset the iterator, and seeks to the specified key
* @param root the current root of the tree
* @param key the key for the node we are trying to find
* @param keyComparator the comparator to use to match node and key
*/
public void seekTo(final TNode root, final Object key,
final AvlKeyComparator keyComparator) {
current = null;
height = 0;
TNode node = root;
while (node != null) {
if (keyComparator.compareKey(node, key) >= 0) {
if (node.avlLeft != null) {
stack[height++] = node;
node = (TNode) node.avlLeft;
} else {
current = node;
return;
}
} else {
if (node.avlRight != null) {
stack[height++] = node;
node = (TNode) node.avlRight;
} else {
if (height > 0) {
TNode parent = (TNode) stack[--height];
while (node == parent.avlRight) {
if (height == 0) {
current = null;
return;
}
node = parent;
parent = (TNode) stack[--height];
}
current = parent;
return;
}
current = null;
return;
}
}
}
}
private void seekNext() {
if (current == null) return;
if (current.avlRight != null) {
stack[height++] = current;
current = (TNode) current.avlRight;
while (current.avlLeft != null) {
stack[height++] = current;
current = (TNode) current.avlLeft;
}
} else {
TNode node;
do {
if (height == 0) {
current = null;
return;
}
node = current;
current = (TNode) stack[--height];
} while (current.avlRight == node);
}
}
}
/**
* Helper class that allows to create and manipulate a linked list of AvlLinkedNodes
*/
@InterfaceAudience.Private
public static class AvlIterableList {
/**
* Return the successor of the current node
* @param node the current node
* @return the successor of the current node
*/
public static TNode readNext(TNode node) {
return (TNode) node.iterNext;
}
/**
* Return the predecessor of the current node
* @param node the current node
* @return the predecessor of the current node
*/
public static TNode readPrev(TNode node) {
return (TNode) node.iterPrev;
}
/**
* Prepend a node to the tree
* @param head the head of the linked list
* @param node the node to add to the front of the list
* @return the new head of the list
*/
public static TNode prepend(TNode head, TNode node) {
assert !isLinked(node) : node + " is already linked";
if (head != null) {
TNode tail = (TNode) head.iterPrev;
tail.iterNext = node;
head.iterPrev = node;
node.iterNext = head;
node.iterPrev = tail;
} else {
node.iterNext = node;
node.iterPrev = node;
}
return node;
}
/**
* Append a node to the tree
* @param head the head of the linked list
* @param node the node to add to the tail of the list
* @return the new head of the list
*/
public static TNode append(TNode head, TNode node) {
assert !isLinked(node) : node + " is already linked";
if (head != null) {
TNode tail = (TNode) head.iterPrev;
tail.iterNext = node;
node.iterNext = head;
node.iterPrev = tail;
head.iterPrev = node;
return head;
}
node.iterNext = node;
node.iterPrev = node;
return node;
}
/**
* Append a list of nodes to the tree
* @param head the head of the current linked list
* @param otherHead the head of the list to append to the current list
* @return the new head of the current list
*/
public static TNode appendList(TNode head, TNode otherHead) {
if (head == null) return otherHead;
if (otherHead == null) return head;
TNode tail = (TNode) head.iterPrev;
TNode otherTail = (TNode) otherHead.iterPrev;
tail.iterNext = otherHead;
otherHead.iterPrev = tail;
otherTail.iterNext = head;
head.iterPrev = otherTail;
return head;
}
/**
* Remove a node from the tree
* @param head the head of the linked list
* @param node the node to remove from the list
* @return the new head of the list
*/
public static TNode remove(TNode head, TNode node) {
assert isLinked(node) : node + " is not linked";
if (node != node.iterNext) {
node.iterPrev.iterNext = node.iterNext;
node.iterNext.iterPrev = node.iterPrev;
head = (head == node) ? (TNode) node.iterNext : head;
} else {
head = null;
}
node.iterNext = null;
node.iterPrev = null;
return head;
}
/**
* Prepend a node to the tree before a specific node
* @param head the head of the linked list
* @param base the node which we want to add the {@code node} before it
* @param node the node which we want to add it before the {@code base} node
*/
public static TNode prepend(TNode head, TNode base, TNode node) {
assert !isLinked(node) : node + " is already linked";
node.iterNext = base;
node.iterPrev = base.iterPrev;
base.iterPrev.iterNext = node;
base.iterPrev = node;
return head == base ? node : head;
}
/** Return true if the node is linked to a list, false otherwise */
public static boolean isLinked(TNode node) {
return node.iterPrev != null && node.iterNext != null;
}
}
}