org.zoodb.index.critbit.CritBit64COW Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of phtree Show documentation
Show all versions of phtree Show documentation
The PH-Tree is a multi-dimensional index
/*
* Copyright 2009-2017 Tilmann Zaeschke. All rights reserved.
*
* This file is part of TinSpin.
*
* Licensed 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.zoodb.index.critbit;
/**
* This version of CritBit uses Copy-On-Write to create new versions of the tree
* following each write operation.
*
*/
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* A concurrency enabled version of the CritBit64 tree.
* The implementation uses copy-on-write concurrency, therefore locking occurs only during updates,
* read-access is never locked.
*
* Currently write/update access is limited to one thread at a time.
* Read access guarantees full snapshot consistency for all read access including iterators.
*
* Version 1.3.4
* - CB64COW is now a sub-class of CB64
*
* Version 1.3.3
* - Fixed 64COW iterators returning empty Entry for queries that shouldn't return anything.
*
* Version 1.3.2
* - Improved mask checking in QueryWithMask
*
* Version 1.3.1
* - Fixed issue #3 where iterators won't work with 'null' as values.
*
* Version 1.0: initial implementation
*
* @author bvancea
*
* @param value type
*/
public class CritBit64COW extends CritBit64 implements Iterable {
private final Lock writeLock = new ReentrantLock();
private CritBit64COW() {
//private
super();
}
/**
* Create a 1D crit-bit tree with 64 bit key length.
* @return a 1D crit-bit tree
* @param value type
*/
public static CritBit64COW create() {
return new CritBit64COW();
}
public CritBit64COW copy() {
CritBit64COW critBitCopy = create();
critBitCopy.info = this.info.copy();
return critBitCopy;
}
/**
* Add a key value pair to the tree or replace the value if the key already exists.
* @param key key
* @param val value
* @return The previous value or {@code null} if there was no previous value
*/
@Override
public V put(long key, V val) {
try {
writeLock.lock();
AtomicInfo newInfo = this.info.copy();
V ret = doPut(key, val, newInfo);
this.info = newInfo;
return ret;
} finally {
writeLock.unlock();;
}
}
private V doPut(long key, V val, AtomicInfo newInfo) {
AtomicInfo currentInfo = this.info;
int size = currentInfo.size;
if (size == 0) {
newInfo.rootKey = key;
newInfo.rootVal = val;
newInfo.size++;
return null;
}
if (size == 1) {
Node n2 = createNode(key, val, info.rootKey, info.rootVal);
if (n2 == null) {
V prev = currentInfo.rootVal;
newInfo.rootVal = val;
return prev;
}
newInfo.root = n2;
newInfo.rootKey = extractPrefix(key, n2.posDiff - 1);
newInfo.rootVal = null;
newInfo.size++;
return null;
}
Node n = newInfo.root;
int parentPosDiff = -1;
long prefix = newInfo.rootKey;
Node parent = null;
boolean isCurrentChildLo = false;
while (true) {
//perform copy on write
n = new Node(n);
if (parent != null) {
if (isCurrentChildLo) {
parent.lo = n;
} else {
parent.hi = n;
}
} else {
newInfo.root = n;
}
//insertion
if (parentPosDiff + 1 != n.posDiff) {
//split in prefix?
int posDiff = compare(key, prefix);
if (posDiff < n.posDiff && posDiff != -1) {
Node newSub;
long subPrefix = extractPrefix(prefix, posDiff - 1);
if (BitTools.getBit(key, posDiff)) {
newSub = new Node(prefix, null, key, val, posDiff);
newSub.lo = n;
} else {
newSub = new Node(key, val, prefix, null, posDiff);
newSub.hi = n;
}
if (parent == null) {
newInfo.rootKey = subPrefix;
newInfo.root = newSub;
} else if (isCurrentChildLo) {
parent.loPost = subPrefix;
parent.lo = newSub;
} else {
parent.hiPost = subPrefix;
parent.hi = newSub;
}
newInfo.size++;
return null;
}
}
//prefix matches, so now we check sub-nodes and postfixes
if (BitTools.getBit(key, n.posDiff)) {
if (n.hi != null) {
prefix = n.hiPost;
parent = n;
n = n.hi;
isCurrentChildLo = false;
} else {
Node n2 = createNode(key, val, n.hiPost, n.hiVal);
if (n2 == null) {
V prev = n.hiVal;
n.hiVal = val;
return prev;
}
n.hi = n2;
n.hiPost = extractPrefix(key, n2.posDiff - 1);
n.hiVal = null;
newInfo.size++;
return null;
}
} else {
if (n.lo != null) {
prefix = n.loPost;
parent = n;
n = n.lo;
isCurrentChildLo = true;
} else {
Node n2 = createNode(key, val, n.loPost, n.loVal);
if (n2 == null) {
V prev = n.loVal;
n.loVal = val;
return prev;
}
n.lo = n2;
n.loPost = extractPrefix(key, n2.posDiff - 1);
n.loVal = null;
newInfo.size++;
return null;
}
}
parentPosDiff = n.posDiff;
}
}
@Override
public void printTree() {
System.out.println("Tree: \n" + toString());
}
@Override
public String toString() {
AtomicInfo info = this.info;
if (info.size == 0) {
return "- -";
}
if (info.root == null) {
return "-" + BitTools.toBinary(info.rootKey, 64) + " v=" + info.rootVal;
}
Node n = info.root;
StringBuilder s = new StringBuilder();
printNode(n, s, "", 0, info.rootKey);
return s.toString();
}
private void printNode(Node n, StringBuilder s, String level, int currentDepth, long infix) {
char NL = '\n';
if (currentDepth != n.posDiff) {
s.append(level + "n: " + currentDepth + "/" + n.posDiff + " " +
BitTools.toBinary(infix, 64) + NL);
} else {
s.append(level + "n: " + currentDepth + "/" + n.posDiff + " i=0" + NL);
}
if (n.lo != null) {
printNode(n.lo, s, level + "-", n.posDiff+1, n.loPost);
} else {
s.append(level + " " + BitTools.toBinary(n.loPost, 64) + " v=" + n.loVal + NL);
}
if (n.hi != null) {
printNode(n.hi, s, level + "-", n.posDiff+1, n.hiPost);
} else {
s.append(level + " " + BitTools.toBinary(n.hiPost,64) + " v=" + n.hiVal + NL);
}
}
@Override
public boolean checkTree() {
AtomicInfo info = this.info;
if (info.root == null) {
if (info.size > 1) {
System.err.println("root node = null AND size = " + info.size);
return false;
}
return true;
}
return checkNode(info.root, 0, info.rootKey);
}
private boolean checkNode(Node n, int firstBitOfNode, long prefix) {
//check prefix
if (n.posDiff < firstBitOfNode) {
System.err.println("prefix with len=0 detected!");
return false;
}
if (n.lo != null) {
if (!doesPrefixMatch(n.posDiff-1, n.loPost, prefix)) {
System.err.println("lo: prefix mismatch");
return false;
}
checkNode(n.lo, n.posDiff+1, n.loPost);
}
if (n.hi != null) {
if (!doesPrefixMatch(n.posDiff-1, n.hiPost, prefix)) {
System.err.println("hi: prefix mismatch");
return false;
}
checkNode(n.hi, n.posDiff+1, n.hiPost);
}
return true;
}
private Node createNode(long k1, V val1, long k2, V val2) {
int posDiff = compare(k1, k2);
if (posDiff == -1) {
return null;
}
if (BitTools.getBit(k2, posDiff)) {
return new Node(k1, val1, k2, val2, posDiff);
} else {
return new Node(k2, val2, k1, val1, posDiff);
}
}
/**
*
* @param v
* @param endPos last bit of prefix, counting starts with 0 for 1st bit
* @return The prefix.
*/
private static long extractPrefix(long v, int endPos) {
long inf = v;
//avoid shifting by 64 bit which means 0 shifting in Java!
if (endPos < 63) {
inf &= ~((-1L) >>> (1+endPos)); // & 0x3f == %64
}
return inf;
}
/**
*
* @param v
* @return True if the prefix matches the value or if no prefix is defined
*/
private boolean doesPrefixMatch(int posDiff, long v, long prefix) {
if (posDiff > 0) {
return (v ^ prefix) >>> (64-posDiff) == 0;
}
return true;
}
/**
* Compares two values.
* @param v1
* @param v2
* @return Position of the differing bit, or -1 if both values are equal
*/
private static int compare(long v1, long v2) {
return (v1 == v2) ? -1 : Long.numberOfLeadingZeros(v1 ^ v2);
}
/**
* Get the size of the tree.
* @return the number of keys in the tree
*/
@Override
public int size() {
return info.size;
}
/**
* Check whether a given key exists in the tree.
* @param key key
* @return {@code true} if the key exists otherwise {@code false}
*/
@Override
public boolean contains(long key) {
AtomicInfo info = this.info;
int size = info.size;
if (size == 0) {
return false;
}
if (size == 1) {
return key == info.rootKey;
}
Node n = info.root;
long prefix = info.rootKey;
while (doesPrefixMatch(n.posDiff, key, prefix)) {
//prefix matches, so now we check sub-nodes and postfixes
if (BitTools.getBit(key, n.posDiff)) {
if (n.hi != null) {
prefix = n.hiPost;
n = n.hi;
continue;
}
return key == n.hiPost;
} else {
if (n.lo != null) {
prefix = n.loPost;
n = n.lo;
continue;
}
return key == n.loPost;
}
}
return false;
}
/**
* Get the value for a given key.
* @param key key
* @return the values associated with {@code key} or {@code null} if the key does not exist.
*/
@Override
public V get(long key) {
AtomicInfo info = this.info;
if (info.size == 0) {
return null;
}
if (info.size == 1) {
return (key == info.rootKey) ? info.rootVal : null;
}
Node n = info.root;
long prefix = info.rootKey;
while (doesPrefixMatch(n.posDiff, key, prefix)) {
//prefix matches, so now we check sub-nodes and postfixes
if (BitTools.getBit(key, n.posDiff)) {
if (n.hi != null) {
prefix = n.hiPost;
n = n.hi;
continue;
}
return (key == n.hiPost) ? n.hiVal : null;
} else {
if (n.lo != null) {
prefix = n.loPost;
n = n.lo;
continue;
}
return (key == n.loPost) ? n.loVal : null;
}
}
return null;
}
/**
* Remove a key and its value
* @param key key
* @return The value of the key of {@code null} if the value was not found.
*/
@Override
public V remove(long key) {
try {
writeLock.lock();
if (contains(key)) {
AtomicInfo newInfo = info.copy();
V ret = doRemove(key, newInfo);
this.info = newInfo;
return ret;
} else {
return null;
}
} finally {
writeLock.unlock();
}
}
private V doRemove(long key, AtomicInfo newInfo) {
AtomicInfo info = this.info;
if (info.size == 0) {
return null;
}
if (info.size == 1) {
if (key == info.rootKey) {
newInfo.size--;
newInfo.rootKey = 0;
V prev = info.rootVal;
info.rootVal = null;
return prev;
}
return null;
}
Node n = info.root;
Node parent = null;
boolean isParentHigh = false;
long prefix = info.rootKey;
while (doesPrefixMatch(n.posDiff, key, prefix)) {
//perform copy on write
n = new Node(n);
if (parent != null) {
if (!isParentHigh) {
parent.lo = n;
} else {
parent.hi = n;
}
} else {
newInfo.root = n;
}
//prefix matches, so now we check sub-nodes and postfixes
if (BitTools.getBit(key, n.posDiff)) {
if (n.hi != null) {
isParentHigh = true;
prefix = n.hiPost;
parent = n;
n = n.hi;
continue;
} else {
if (key != n.hiPost) {
return null;
}
//match! --> delete node
//replace data in parent node
updateParentAfterRemove(newInfo, parent, n.loPost, n.loVal, n.lo, isParentHigh);
return n.hiVal;
}
} else {
if (n.lo != null) {
isParentHigh = false;
prefix = n.loPost;
parent = n;
n = n.lo;
continue;
} else {
if (key != n.loPost) {
return null;
}
//match! --> delete node
//replace data in parent node
//for new prefixes...
updateParentAfterRemove(newInfo, parent, n.hiPost, n.hiVal, n.hi, isParentHigh);
return n.loVal;
}
}
}
return null;
}
private void updateParentAfterRemove(AtomicInfo newInfo, Node parent, long newPost,
V newVal, Node newSub, boolean isParentHigh) {
newPost = (newSub == null) ? newPost : extractPrefix(newPost, newSub.posDiff-1);
if (parent == null) {
newInfo.rootKey = newPost;
newInfo.rootVal = newVal;
newInfo.root = newSub;
} else if (isParentHigh) {
parent.hiPost = newPost;
parent.hiVal = newVal;
parent.hi = newSub;
} else {
parent.loPost = newPost;
parent.loVal = newVal;
parent.lo = newSub;
}
newInfo.size--;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy