All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.sirix.index.art.AdaptiveRadixTree Maven / Gradle / Ivy

package io.sirix.index.art;

import java.util.*;

/**
 * An Adaptive Radix trie based {@link NavigableMap} implementation.
 * The map is sorted according to the {@linkplain BinaryComparable} provided at map
 * creation time.
 *
 * 

This implementation provides log(k) time cost for the * {@code containsKey}, {@code get}, {@code put} and {@code remove} * operations where k is the length of the key. * Algorithms are adaptations of those as described in the * paper * "The Adaptive Radix Tree: ARTful Indexing for Main-Memory Databases" * by Dr. Viktor Leis. * *

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.) * *

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 iterators 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. * *

Note that 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 {@code 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. * *

Note that null keys are not permitted. * *

All {@code Map.Entry} pairs returned by methods in this class * and its views represent snapshots of mappings at the time they were * produced. They do not support the {@code Entry.setValue} * method. (Note however that it is possible to change mappings in the * associated map using {@code put}.) * * @param the type of keys maintained by this map * @param the type of mapped values * @author Rohan Suri * @see NavigableMap * @see BinaryComparable */ public class AdaptiveRadixTree extends AbstractMap implements NavigableMap { private final BinaryComparable binaryComparable; private transient EntrySet entrySet; private transient NavigableMap descendingMap; private transient KeySet navigableKeySet; private transient Collection values; private transient int size = 0; /** * The number of structural modifications to the tree. * To be touched where ever size changes. */ private transient int modCount = 0; int getModCount() { return modCount; } // TODO: offer a bulk create constructor public AdaptiveRadixTree(BinaryComparable binaryComparable) { Objects.requireNonNull(binaryComparable, "Specifying a BinaryComparable is necessary"); this.binaryComparable = binaryComparable; } private Node root; public V put(K key, V value) { if (key == null) { throw new NullPointerException(); } byte[] bytes = binaryComparable.get(key); if (root == null) { // create leaf node and set root to that root = new LeafNode<>(bytes, key, value); size = 1; modCount++; return null; } return put(bytes, key, value); } // note: taken from TreeMap @Override public boolean containsKey(Object key) { return getEntry(key) != null; } // note: taken from TreeMap // why doesn't TreeMap use AbstractMap's provided impl? // the only difference is default impl requires an iterator to be created, // but it ultimately uses the successor calls to iterate. @Override public boolean containsValue(Object value) { for (LeafNode e = getFirstEntry(); e != null; e = successor(e)) { if (valEquals(value, e.getValue())) return true; } return false; } // Note: taken from TreeMap public Entry pollFirstEntry() { LeafNode p = getFirstEntry(); Entry result = exportEntry(p); if (p != null) deleteEntry(p); return result; } // Note: taken from TreeMap public Entry pollLastEntry() { LeafNode p = getLastEntry(); Entry result = exportEntry(p); if (p != null) deleteEntry(p); return result; } @Override public void clear() { size = 0; root = null; modCount++; } @Override public Set> entrySet() { EntrySet es = entrySet; return (es != null) ? es : (entrySet = new EntrySet<>(this)); } @Override public Collection values() { Collection c = values; return (c != null) ? c : (values = new Values<>(this)); } @Override public V get(Object key) { LeafNode entry = getEntry(key); return (entry == null ? null : entry.getValue()); } /** * Returns this map's entry for the given key, or {@code null} if the map * does not contain an entry for the key. * * @return this map's entry for the given key, or {@code null} if the map * does not contain an entry for the key * @throws ClassCastException if the specified key cannot be compared * with the keys currently in the map * @throws NullPointerException if the specified key is null */ LeafNode getEntry(Object key) { if (key == null) throw new NullPointerException(); if (root == null) { // empty tree return null; } K k = (K) key; byte[] bytes = binaryComparable.get(k); return getEntry(root, bytes); } @Override public V remove(Object key) { LeafNode p = getEntry(key); if (p == null) return null; V oldValue = p.getValue(); deleteEntry(p); return oldValue; } /* given node only has one child and has a parent. we eliminate this node and pull up it's only child, linking it with the parent. transform: parent --> partial key to this node --> partialKey to only child to: parent --> same partial key to this node, but now directly to only child also update child's compressed path updated to: this node's compressed path + partialKey to child + child's own compressed path */ private void pathCompressOnlyChild(Node4 toCompress) { Node onlyChild = toCompress.getChildren()[0]; updateCompressedPathOfOnlyChild(toCompress, onlyChild); replace(toCompress.uplinkKey(), toCompress.parent(), onlyChild); } /* updates given node's only child's compressed path to: given node's compressed path + partialKey to child + child's own compressed path */ static void updateCompressedPathOfOnlyChild(Node4 toCompress, Node onlyChild) { assert onlyChild != null; if (!(onlyChild instanceof LeafNode)) { byte partialKeyToOnlyChild = toCompress.getOnlyChildKey();// toCompress.getKeys()[0]; // R InnerNode oc = (InnerNode) onlyChild; // update nextNode's compressed path with toCompress int toCopy = Math.min(InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT, toCompress.prefixLen + 1); int leftForMe = InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT - toCopy; int iHave = Math.min(InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT, oc.prefixLen); // make space System.arraycopy(oc.prefixKeys, 0, oc.prefixKeys, toCopy, Math.min(leftForMe, iHave)); int toCopyFromToCompress = Math.min(InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT, toCompress.prefixLen); System.arraycopy(toCompress.prefixKeys, 0, oc.prefixKeys, 0, toCopyFromToCompress); if (toCopyFromToCompress < InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT) { // we got space left for the partialKey to only child oc.prefixKeys[toCopyFromToCompress] = partialKeyToOnlyChild; } oc.prefixLen += toCompress.prefixLen + 1; } } private LeafNode getEntry(Node node, byte[] key) { int depth = 0; boolean skippedPrefix = false; while (true) { if (node instanceof LeafNode) { LeafNode leaf = (LeafNode) node; byte[] leafBytes = leaf.getKeyBytes(); int startFrom = skippedPrefix ? 0 : depth; if (Arrays.equals(leafBytes, startFrom, leafBytes.length, key, startFrom, key.length)) { return leaf; } return null; } InnerNode innerNode = (InnerNode) node; if (key.length < depth + innerNode.prefixLen) { return null; } if (innerNode.prefixLen <= InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT) { // match pessimistic compressed path completely for (int i = 0; i < innerNode.prefixLen; i++) { if (innerNode.prefixKeys[i] != key[depth + i]) return null; } } else { // else take optimistic jump skippedPrefix = true; } // took pessimistic match or optimistic jump, continue search depth = depth + innerNode.prefixLen; Node nextNode; if (depth == key.length) { nextNode = innerNode.getLeaf(); if (!skippedPrefix) { return (LeafNode) nextNode; } } else { nextNode = innerNode.findChild(key[depth]); depth++; } if (nextNode == null) { return null; } // set fields for next iteration node = nextNode; } } // is compressed path equal/more/lesser (0, 1, -1) than key static int comparePessimisticCompressedPath(InnerNode node, byte[] key, int depth) { byte[] prefix = node.prefixKeys; int upperLimitForPessimisticMatch = Math.min(InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT, node.prefixLen); // limit key because if key length greater than compressed path // and all byte comparisons are same, then also we consider // compressed path == key length return compare(prefix, 0, upperLimitForPessimisticMatch, key, depth, Math.min(depth + upperLimitForPessimisticMatch, key.length)); } private static int compareOptimisticCompressedPath(InnerNode node, byte[] key, int depth) { int result = comparePessimisticCompressedPath(node, key, depth); if (result != 0 || node.prefixLen <= InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT) { return result; } // expand optimistic path and compare byte[] leafBytes = getFirstEntry(node).getKeyBytes(); // limit key because if key length greater than compressed path // and all byte comparisons are same, then also we consider // compressed path == key length return compare(leafBytes, depth + InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT, depth + node.prefixLen, key, depth + InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT, Math.min(depth + node.prefixLen, key.length)); } void replace(int depth, byte[] key, InnerNode prevDepth, Node replaceWith) { if (prevDepth == null) { assert depth == 0; root = replaceWith; Node.replaceUplink(null, root); } else { assert depth > 0; prevDepth.replace(key[depth - 1], replaceWith); } } // replace down link private void replace(byte partialKey, InnerNode prevDepth, Node replaceWith) { if (prevDepth == null) { root = replaceWith; Node.replaceUplink(null, root); } else { prevDepth.replace(partialKey, replaceWith); } } private V put(byte[] keyBytes, K key, V value) { int depth = 0; InnerNode prevDepth = null; Node node = root; while (true) { if (node instanceof LeafNode) { LeafNode leaf = (LeafNode) node; Node pathCompressedNode = lazyExpansion(leaf, keyBytes, key, value, depth); if (pathCompressedNode == node) { // key already exists V oldValue = leaf.getValue(); leaf.setValue(value); return oldValue; } // we have to replace the prevDepth's child pointer to this new node replace(depth, keyBytes, prevDepth, pathCompressedNode); size++; modCount++; return null; } // compare with compressed path InnerNode innerNode = (InnerNode) node; int newDepth = matchCompressedPath(innerNode, keyBytes, key, value, depth, prevDepth); if (newDepth == -1) { // matchCompressedPath already inserted the leaf node for us size++; modCount++; return null; } if (keyBytes.length == newDepth) { LeafNode leaf = (LeafNode) innerNode.getLeaf(); V oldValue = leaf.getValue(); leaf.setValue(value); return oldValue; } // we're now at line 26 in paper byte partialKey = keyBytes[newDepth]; Node child = innerNode.findChild(partialKey); if (child != null) { // set fields for next iteration prevDepth = innerNode; depth = newDepth + 1; node = child; continue; } // add this key as child Node leaf = new LeafNode<>(keyBytes, key, value); if (innerNode.isFull()) { innerNode = innerNode.grow(); replace(depth, keyBytes, prevDepth, innerNode); } innerNode.addChild(partialKey, leaf); size++; modCount++; return null; } } /* we reached a lazy expanded leaf node, we have to expand it now. but how much should we expand? since we reached depth X, it means till now both leaf node and new node have same bytes. now what has been stored lazily is leaf node's key(depth, end). that's the part over which we need to compute the longest common prefix. that's the part we can path compress. */ private static Node lazyExpansion(LeafNode leaf, byte[] keyBytes, K key, V value, int depth) { // find LCP int lcp = 0; byte[] leafKey = leaf.getKeyBytes(); // loadKey in paper int end = Math.min(leafKey.length, keyBytes.length); for (; depth < end && leafKey[depth] == keyBytes[depth]; depth++, lcp++) { } if (depth == keyBytes.length && depth == leafKey.length) { // we're referring to a key that already exists, replace value and return current return leaf; } // create new node with LCP Node4 pathCompressedNode = new Node4(); pathCompressedNode.prefixLen = lcp; int pessimisticLcp = Math.min(lcp, InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT); System.arraycopy(keyBytes, depth - lcp, pathCompressedNode.prefixKeys, 0, pessimisticLcp); // add new key and old leaf as children LeafNode newLeaf = new LeafNode<>(keyBytes, key, value); if (depth == keyBytes.length) { // barca to be inserted, barcalona already exists // set barca's parent to be this path compressed node // setup uplink whenever we set downlink pathCompressedNode.setLeaf(newLeaf); pathCompressedNode.addChild(leafKey[depth], leaf); // l } else if (depth == leafKey.length) { // barcalona to be inserted, barca already exists pathCompressedNode.setLeaf(leaf); pathCompressedNode.addChild(keyBytes[depth], newLeaf); // l } else { pathCompressedNode.addChild(leafKey[depth], leaf); pathCompressedNode.addChild(keyBytes[depth], newLeaf); } return pathCompressedNode; } static void removeOptimisticLCPFromCompressedPath(InnerNode node, int depth, int lcp, byte[] leafBytes) { // lcp cannot be equal to node.prefixLen // it has to be less, else it'd mean the compressed path matches completely assert lcp < node.prefixLen && lcp >= InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT : lcp; // since there's more compressed path left // we need to "bring up" more of it what we can take node.prefixLen = node.prefixLen - lcp - 1; int end = Math.min(InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT, node.prefixLen); System.arraycopy(leafBytes, depth + 1, node.prefixKeys, 0, end); } static void removePessimisticLCPFromCompressedPath(InnerNode node, int depth, int lcp) { // lcp cannot be equal to Math.min(InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT, node.prefixLen) // it has to be less, else it'd mean the compressed path matches completely assert lcp < Math.min(InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT, node.prefixLen); if (node.prefixLen <= InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT) { node.prefixLen = node.prefixLen - lcp - 1; System.arraycopy(node.prefixKeys, lcp + 1, node.prefixKeys, 0, node.prefixLen); } else { // since there's more compressed path left // we need to "bring up" more of it what we can take node.prefixLen = node.prefixLen - lcp - 1; byte[] leafBytes = getFirstEntry(node).getKeyBytes(); int end = Math.min(InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT, node.prefixLen); System.arraycopy(leafBytes, depth + 1, node.prefixKeys, 0, end); } } /* 1) pessimistic path matched entirely case 1: key has nothing left (can't happen, else they'd be prefixes and our key transformations must ensure that it is not possible) case 2: prefixLen <= InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT we're done here, we can do a findChild for next partial key (caller's depth + lcp + 1) case 3: prefixLen is more i.e. an optimistic path is left to match. traverse down and get leaf to match remaining optimistic prefix path. case 3a: optimistic path matches, we can do findChild for next partial key case 3b: have to split 2) pessimistic path did not match, we have to split */ private int matchCompressedPath(InnerNode node, byte[] keyBytes, K key, V value, int depth, InnerNode prevDepth) { int lcp = 0; int end = Math.min(keyBytes.length - depth, Math.min(node.prefixLen, InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT)); // match pessimistic compressed path for (; lcp < end && keyBytes[depth] == node.prefixKeys[lcp]; lcp++, depth++) ; if (lcp == node.prefixLen) { if (depth == keyBytes.length && !node.hasLeaf()) { // key ended, it means it is a prefix LeafNode leafNode = new LeafNode<>(keyBytes, key, value); node.setLeaf(leafNode); return -1; } else { return depth; } } InnerNode newNode; if (lcp == InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT) { // match remaining optimistic path byte[] leafBytes = getFirstEntry(node).getKeyBytes(); int leftToMatch = node.prefixLen - InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT; end = Math.min(keyBytes.length, depth + leftToMatch); /* match remaining optimistic path if we match entirely we return with new depth and caller can proceed with findChild (depth + lcp + 1) if we don't match entirely, then we split */ for (; depth < end && keyBytes[depth] == leafBytes[depth]; depth++, lcp++) ; if (lcp == node.prefixLen) { if (depth == keyBytes.length && !node.hasLeaf()) { // key ended, it means it is a prefix LeafNode leafNode = new LeafNode<>(keyBytes, key, value); node.setLeaf(leafNode); return -1; } else { // matched entirely, but key is left return depth; } } else { newNode = branchOutOptimistic(node, keyBytes, key, value, lcp, depth, leafBytes); } } else { newNode = branchOutPessimistic(node, keyBytes, key, value, lcp, depth); } // replace "this" node with newNode // initialDepth can be zero even if prefixLen is not zero. // the root node could have a prefix too, for example after insertions of // BAR, BAZ? prefix would be BA kept in the root node itself replace(depth - lcp, keyBytes, prevDepth, newNode); return -1; // we've already inserted the leaf node, caller needs to do nothing more } // called when lcp has become more than InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT static InnerNode branchOutOptimistic(InnerNode node, byte[] keyBytes, K key, V value, int lcp, int depth, byte[] leafBytes) { // prefix doesn't match entirely, we have to branch assert lcp < node.prefixLen && lcp >= InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT : lcp + ", " + node.prefixLen; int initialDepth = depth - lcp; LeafNode leafNode = new LeafNode<>(keyBytes, key, value); // new node with updated prefix len, compressed path Node4 branchOut = new Node4(); branchOut.prefixLen = lcp; // note: depth is the updated depth (initialDepth = depth - lcp) System.arraycopy(keyBytes, initialDepth, branchOut.prefixKeys, 0, InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT); if (depth == keyBytes.length) { branchOut.setLeaf(leafNode); } else { branchOut.addChild(keyBytes[depth], leafNode); } branchOut.addChild(leafBytes[depth], node); // reusing "this" node // remove lcp common prefix key from "this" node removeOptimisticLCPFromCompressedPath(node, depth, lcp, leafBytes); return branchOut; } static InnerNode branchOutPessimistic(InnerNode node, byte[] keyBytes, K key, V value, int lcp, int depth) { // pessimistic prefix doesn't match entirely, we have to branch // BAR, BAZ inserted, now inserting BOZ assert lcp < node.prefixLen && lcp < InnerNode.PESSIMISTIC_PATH_COMPRESSION_LIMIT; int initialDepth = depth - lcp; // create new lazy leaf node for unmatched key? LeafNode leafNode = new LeafNode<>(keyBytes, key, value); // new node with updated prefix len, compressed path Node4 branchOut = new Node4(); branchOut.prefixLen = lcp; // note: depth is the updated depth (initialDepth = depth - lcp) System.arraycopy(keyBytes, initialDepth, branchOut.prefixKeys, 0, lcp); if (depth == keyBytes.length) { // key ended it means it is a prefix branchOut.setLeaf(leafNode); } else { branchOut.addChild(keyBytes[depth], leafNode); } branchOut.addChild(node.prefixKeys[lcp], node); // reusing "this" node // remove lcp common prefix key from "this" node removePessimisticLCPFromCompressedPath(node, depth, lcp); return branchOut; } /* Returns null if the ART is empty */ LeafNode getFirstEntry() { if (isEmpty()) { return null; } return getFirstEntry(root); } private static LeafNode getFirstEntry(Node startFrom) { Node node = startFrom; Node next = node.firstOrLeaf(); while (next != null) { node = next; next = node.firstOrLeaf(); } return (LeafNode) node; } /* Returns null if the ART is empty */ LeafNode getLastEntry() { if (isEmpty()) { return null; } return getLastEntry(root); } private static LeafNode getLastEntry(Node startFrom) { Node node = startFrom; Node next = node.last(); while (next != null) { node = next; next = node.last(); } return (LeafNode) node; } @Override public Entry lowerEntry(K key) { return exportEntry(getLowerEntry(key)); } @Override public K lowerKey(K key) { return keyOrNull(getLowerEntry(key)); } @Override public Entry floorEntry(K key) { return exportEntry(getFloorEntry(key)); } @Override public K floorKey(K key) { return keyOrNull(getFloorEntry(key)); } LeafNode getLowerEntry(K k) { return getLowerOrFloorEntry(true, k); } LeafNode getLowerEntry(byte[] k) { if (isEmpty()) { return null; } return getLowerOrFloorEntry(true, k); } LeafNode getFloorEntry(K k) { return getLowerOrFloorEntry(false, k); } LeafNode getFloorEntry(byte[] k) { if (isEmpty()) { return null; } return getLowerOrFloorEntry(false, k); } // note: caller needs to check if map is empty private LeafNode getLowerOrFloorEntry(boolean lower, byte[] key) { int depth = 0; Node node = root; while (true) { if (node instanceof LeafNode) { // binary comparable comparison LeafNode leafNode = (LeafNode) node; byte[] leafKey = leafNode.getKeyBytes(); if (compare(key, depth, key.length, leafKey, depth, leafKey.length) >= (lower ? 1 : 0)) { return leafNode; } return predecessor(leafNode); } InnerNode innerNode = (InnerNode) node; // compare compressed path int compare = compareOptimisticCompressedPath((InnerNode) node, key, depth); if (compare < 0) { // lesser return getLastEntry(node); } else if (compare > 0) { // greater, that means all children of this node will be greater than key return predecessor(node); } // compressed path matches completely depth += innerNode.prefixLen; if (depth == key.length) { if (!lower && innerNode.hasLeaf()) { return (LeafNode) innerNode.getLeaf(); } return predecessor(innerNode); } Node child = innerNode.floor(key[depth]); if (child == null) { return leafOrPredecessor(innerNode); } else if (child.uplinkKey() != key[depth]) { return getLastEntry(child); } depth++; node = child; } } private LeafNode getLowerOrFloorEntry(boolean lower, K k) { if (isEmpty()) { return null; } byte[] key = binaryComparable.get(k); return getLowerOrFloorEntry(lower, key); } private LeafNode leafOrPredecessor(InnerNode innerNode) { if (innerNode.hasLeaf()) { return (LeafNode) innerNode.getLeaf(); } return predecessor(innerNode); } @Override public Entry ceilingEntry(K key) { return exportEntry(getCeilingEntry(key)); } int compare(K k1, byte[] k2Bytes) { byte[] k1Bytes = binaryComparable.get(k1); return compare(k1Bytes, 0, k1Bytes.length, k2Bytes, 0, k2Bytes.length); } // 0 if a == b // -1 if a < b // 1 if a > b // note: aFrom, bFrom are exclusive bounds static int compare(byte[] a, int aFrom, int aTo, byte[] b, int bFrom, int bTo) { int i = aFrom, j = bFrom; for (; i < aTo && j < bTo && a[i] == b[j]; i++, j++) ; if (i == aTo && j == bTo) { return 0; } else if (i == aTo) { return -1; } else if (j == bTo) { return 1; } else { return BinaryComparableUtils.unsigned(a[i]) < BinaryComparableUtils.unsigned(b[j]) ? -1 : 1; } } @Override public K ceilingKey(K key) { return keyOrNull(getCeilingEntry(key)); } /** * Return key for entry, or null if null * Note: taken from TreeMap */ static K keyOrNull(Entry e) { return (e == null) ? null : e.getKey(); } LeafNode getHigherEntry(K k) { return getHigherOrCeilEntry(false, k); } LeafNode getHigherEntry(byte[] key) { if (isEmpty()) { return null; } return getHigherOrCeilEntry(false, key); } LeafNode getCeilingEntry(K k) { return getHigherOrCeilEntry(true, k); } LeafNode getCeilingEntry(byte[] key) { if (isEmpty()) { return null; } return getHigherOrCeilEntry(true, key); } /* On level X match compressed path of "this" node if matches, then take follow-on pointer and continue matching if it doesn't, see if compressed path greater/smaller than key if greater, return the first node of the level i.e. call first on this node and return. if lesser, go one level up (using parent link) and find the next partialKey greater than the uplinking partialKey on level X-1. if you got one, simply take the first child nodes at each down level and return the leaf (left most traversal) if not, then we got to go on level X-2 and find the next greater and keep going level ups until we either find a next greater partialKey or we find root (which will have parent null and hence search ends). What if all compressed paths matched, then when taking the next follow-on pointer, we reach a leafNode? or a null? if leafNode then it means, until now the leafNode has the same prefix as the provided key. if leafNode >= given key, then return leafNode if leafNode < given key, then take leafNode's parent uplink and find next greater partialKey than the uplinking partialKey on level leaf-1. if you reach a null, then it means key doesn't exist, but before taking this previous partialKey, the entire path did exist. Hence, we come up a level from where we got the null. Find the next higher partialKey than which we took for null (no uplink from the null node, so we do it before the recursive call itself). so it seems the uplinking traversal is same in all cases */ // note: caller needs to check if map is empty private LeafNode getHigherOrCeilEntry(boolean ceil, byte[] key) { int depth = 0; Node node = root; while (true) { if (node instanceof LeafNode) { // binary comparable comparison LeafNode leafNode = (LeafNode) node; byte[] leafKey = leafNode.getKeyBytes(); if (compare(key, depth, key.length, leafKey, depth, leafKey.length) < (ceil ? 1 : 0)) { return leafNode; } return successor(leafNode); } InnerNode innerNode = (InnerNode) node; // compare compressed path int compare = compareOptimisticCompressedPath(innerNode, key, depth); if (compare > 0) { // greater return getFirstEntry(node); } else if (compare < 0) { // lesser, that means all children of this node will be lesser than key return successor(node); } // compressed path matches completely depth += innerNode.prefixLen; if (depth == key.length) { // if ceil is true, then we are allowed to return the prefix ending here (leaf of this node) // if ceil is false, then we need something higher and not the prefix, hence we start traversal // from first() return ceil ? getFirstEntry(innerNode) : getFirstEntry(innerNode.first()); } Node child = innerNode.ceil(key[depth]); if (child == null) { // on this level, no child is greater or equal return successor(node); } else if (child.uplinkKey() != key[depth]) { // ceil returned a greater child return getFirstEntry(child); } depth++; node = child; } } private LeafNode getHigherOrCeilEntry(boolean ceil, K k) { if (isEmpty()) { return null; } byte[] key = binaryComparable.get(k); return getHigherOrCeilEntry(ceil, key); } @Override public Entry higherEntry(K key) { return exportEntry(getHigherEntry(key)); } @Override public K higherKey(K key) { return keyOrNull(getHigherOrCeilEntry(false, key)); } @Override public Entry firstEntry() { // we need a snapshot (i.e. immutable entry) as per NavigableMap's docs // also see Doug Lea's reply: // http://jsr166-concurrency.10961.n7.nabble.com/Immutable-Entry-objects-in-j-u-TreeMap-td3384.html // but why do we need a snapshot? return exportEntry(getFirstEntry()); } /** * Return SimpleImmutableEntry for entry, or null if null
* Note: taken from TreeMap */ static Entry exportEntry(Entry e) { return (e == null) ? null : new SimpleImmutableEntry<>(e); } @Override public Entry lastEntry() { return exportEntry(getLastEntry()); } @Override public NavigableMap descendingMap() { NavigableMap km = descendingMap; return (km != null) ? km : (descendingMap = new DescendingSubMap<>(this, true, null, true, true, null, true)); } @Override public NavigableSet navigableKeySet() { KeySet nks = navigableKeySet; return (nks != null) ? nks : (navigableKeySet = new KeySet<>(this)); } @Override public Set keySet() { return navigableKeySet(); } @Override public NavigableSet descendingKeySet() { return descendingMap().navigableKeySet(); } @Override public NavigableMap subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { return new AscendingSubMap<>(this, false, fromKey, fromInclusive, false, toKey, toInclusive); } @Override public NavigableMap headMap(K toKey, boolean inclusive) { return new AscendingSubMap<>(this, true, null, true, false, toKey, inclusive); } @Override public NavigableMap tailMap(K fromKey, boolean inclusive) { return new AscendingSubMap<>(this, false, fromKey, inclusive, true, null, true); } // QUES: why does comparator return ? super K? @Override public Comparator comparator() { return null; } public BinaryComparable binaryComparable() { return binaryComparable; } @Override public SortedMap subMap(K fromKey, K toKey) { return subMap(fromKey, true, toKey, false); } @Override public SortedMap headMap(K toKey) { return headMap(toKey, false); } @Override public SortedMap tailMap(K fromKey) { return tailMap(fromKey, true); } @Override public K firstKey() { return key(getFirstEntry()); } /** * Returns the key corresponding to the specified Entry. * * @throws NoSuchElementException if the Entry is null * Note: taken from TreeMap */ static K key(Entry e) { if (e == null) throw new NoSuchElementException(); return e.getKey(); } @Override public K lastKey() { return key(getLastEntry()); } @Override public int size() { return size; } static LeafNode successor(Node node) { InnerNode uplink; while ((uplink = node.parent()) != null) { if (uplink.getLeaf() == node) { // we surely have a first node return getFirstEntry(uplink.first()); } Node greater = uplink.greater(node.uplinkKey()); if (greater != null) { return getFirstEntry(greater); } node = uplink; } return null; } static LeafNode predecessor(Node node) { InnerNode uplink; while ((uplink = node.parent()) != null) { if (uplink.getLeaf() == node) { // least node, go up node = uplink; continue; } Node lesser = uplink.lesser(node.uplinkKey()); if (lesser != null) { return getLastEntry(lesser); } else if (uplink.hasLeaf()) { return (LeafNode) uplink.getLeaf(); } node = uplink; } return null; } // leaf should not be null // neither should tree be empty when calling this void deleteEntry(LeafNode leaf) { size--; modCount++; InnerNode parent = leaf.parent(); if (parent == null) { // means root == leaf root = null; return; } if (parent.getLeaf() == leaf) { parent.removeLeaf(); } else { parent.removeChild(leaf.uplinkKey()); } if (parent.shouldShrink()) { InnerNode newParent = parent.shrink(); // newParent should have copied the uplink to same grandParent of oldParent InnerNode grandParent = newParent.parent(); replace(newParent.uplinkKey(), grandParent, newParent); } else if (parent.size() == 1 && !parent.hasLeaf()) { pathCompressOnlyChild((Node4) parent); } else if (parent.size() == 0) { assert parent.hasLeaf(); replace(parent.uplinkKey(), parent.parent(), parent.getLeaf()); } } /** * Test two values for equality. Differs from o1.equals(o2) only in * that it copes with {@code null} o1 properly. * Note: Taken from TreeMap */ static boolean valEquals(Object o1, Object o2) { return Objects.equals(o1, o2); } Iterator> entryIterator() { return new EntryIterator<>(this, getFirstEntry()); } Iterator valueIterator() { return new ValueIterator<>(this, getFirstEntry()); } Iterator keyIterator() { return new KeyIterator<>(this, getFirstEntry()); } Iterator descendingKeyIterator() { return new DescendingKeyIterator<>(this, getLastEntry()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy