Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3585e69
fix version
mr-bat Jun 27, 2019
b958c01
rename pop to poll
mr-bat Jun 27, 2019
043b37f
fix prev and next updating
mr-bat Jun 27, 2019
15870ed
revert: rename pop to poll
mr-bat Jun 27, 2019
2b96804
add peekLastNode and popBack
mr-bat Jun 27, 2019
c750fc0
use lastNode for caching and add peek and pop from back
mr-bat Jun 27, 2019
b0ef65c
add poll and peek for general use
mr-bat Jun 27, 2019
04ef414
fix peekLastValue
mr-bat Jun 27, 2019
8894389
add clone and equals to CircularFifoQueue
mr-bat Jun 29, 2019
6e581ab
Revert "add poll and peek for general use"
mr-bat Jun 29, 2019
40b152b
add SimpleCloneable and SimpleComparableCloneable for testing
mr-bat Jun 29, 2019
fb0a2b0
return null Iterator if tree is empty
mr-bat Jun 29, 2019
e9edd6e
add equals() and clone()
mr-bat Jun 29, 2019
0bb816e
return removed value after operation
mr-bat Jun 29, 2019
f5876fe
fix nested try/catch
mr-bat Jul 21, 2019
60fbe89
improve bplusTree.equals() test
mr-bat Jul 21, 2019
ea9c00d
run tearDown after each iteration
mr-bat Jul 21, 2019
bd87406
improve isInRange
mr-bat Jul 21, 2019
2c35da3
add benchmark for add in a row
mr-bat Jul 21, 2019
4d7e85d
improve tests
mr-bat Jul 24, 2019
b261c86
fix peekBackKey and make BplusTreeIterator public
mr-bat Jul 24, 2019
e1fe6d7
comment currently unnecessary lines
mr-bat Jul 24, 2019
dc98296
disable cache and add bottomUpRemoveFrom
mr-bat Jul 24, 2019
58783c4
tune capacity and disable capacity based tests
mr-bat Jul 27, 2019
197c2b8
add an optimized version of add
mr-bat Jul 27, 2019
32be9fa
add BTree photo in README.md
mr-bat Oct 18, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added BTree.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/18082c8908db4692bd702aa448734b33)](https://www.codacy.com/app/abateni/BPlusTree?utm_source=github.com&utm_medium=referral&utm_content=mr-bat/BPlusTree&utm_campaign=Badge_Grade)

Implementing BPlusTree with Java.

![Sample BPlus Tree](https://github.com/mr-bat/BPlusTree/blob/master/BTree.png?raw=true)
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<packaging>jar</packaging>
<groupId>io.uten</groupId>
<artifactId>BPlusTree</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.0.0</version>
<properties>
<jmh.version>1.21</jmh.version>
<maven-build-helper-plugin.version>1.12</maven-build-helper-plugin.version>
Expand Down
171 changes: 145 additions & 26 deletions src/main/java/bplustree/BplusTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,55 @@
import com.google.common.annotations.Beta;

public class BplusTree<Key extends Comparable<Key>, Value> {
private BplusTreeNode<Key, Value> _root = new BplusTreeLeafNode<Key, Value>(null, null, null, this);
private BplusTreeLeafNode recentlyUsed;
private BplusTreeNode<Key, Value> _root = new BplusTreeLeafNode<>(null, null, null, this);
private BplusTreeLeafNode<Key, Value> recentlyUsed;
private BplusTreeLeafNode<Key, Value> lastNode = (BplusTreeLeafNode) _root;
private int hit = 0, miss = 0;
private boolean cacheDisabled = false;
private boolean cacheDisabled;

@Override
public boolean equals(Object obj) {
if (!(obj instanceof BplusTree))
return false;

BplusTreeLeafNode.BplusTreeIterator here = peekLast();
BplusTreeLeafNode.BplusTreeIterator there = ((BplusTree) obj).peekLast();

if (here == null || there == null)
return here == there;

while (true) {
if (!here.getKey().equals(there.getKey()))
return false;
if (!here.getValue().equals(there.getValue()))
return false;
if (here.hasNext() != there.hasNext())
return false;

if (here.hasNext()) {
here.goToNext();
there.goToNext();
}
else
break;
}

return true;
}

@Override
public BplusTree<Key, Value> clone() throws CloneNotSupportedException{
BplusTree cloned = new BplusTree();
BplusTreeCloner treeCloner = new BplusTreeCloner<Key, Value>(cloned);

cloned._root = treeCloner.clone(this._root);
cloned.recentlyUsed = (BplusTreeLeafNode) treeCloner.clone(recentlyUsed);
cloned.lastNode = (BplusTreeLeafNode) treeCloner.clone(lastNode);
cloned.hit = this.hit;
cloned.miss = this.miss;
cloned.cacheDisabled = this.cacheDisabled;
return cloned;
}

public BplusTree() {
this(false);
Expand All @@ -20,11 +65,11 @@ public boolean cacheEnabled() {
return !cacheDisabled;
}

public BplusTreeBranchNode getRecentNode() {
public BplusTreeBranchNode<Key, Value> getRecentNode() {
if (cacheDisabled)
return null;
if (recentlyUsed != null && recentlyUsed.getParent() != null)
return recentlyUsed.getParent().getParent();
if (recentlyUsed != null)
return recentlyUsed.getParent();
return null;
}
public int getHit() {
Expand All @@ -48,43 +93,90 @@ public boolean isEmpty() {
return _root.isEmpty();
}

public void add(Key key, Value value) throws BTreeException {
if (getRecentNode() != null && getRecentNode().isInRange(key)) {
public void addWithCacheLastRecent(Key key, Value value) throws BTreeException {
if (recentlyUsed != null && recentlyUsed.isInRange(key)) {
++hit;
getRecentNode().add(key, value);
} else {
recentlyUsed.add(key, value);
if (_root.getParent() != null)
_root = _root.getParent();
if (lastNode.getNext() != null)
lastNode = lastNode.getNext();
} else
add(key, value);
}
public void add(Key key, Value value) throws BTreeException {
// if (!cacheDisabled && lastNode.isInRange(key)) {
// ++hit;
// lastNode.add(key, value);
// } else
// if (getRecentNode() != null && getRecentNode().isInRange(key)) {
// ++hit;
// getRecentNode().add(key, value);
// } else
{
_root.add(key, value);
++miss;
}

if (_root.getParent() != null)
_root = _root.getParent();
}
public void remove(Key key) throws BTreeException {
if (getRecentNode() != null && getRecentNode().isInRange(key)) {
getRecentNode().remove(key);
++hit;
} else {
_root.remove(key);
if (lastNode.getNext() != null)
lastNode = lastNode.getNext();
}
public Value remove(Key key) throws BTreeException {
Value result;
// if (!cacheDisabled && lastNode.isInRange(key)) {
// result = lastNode.remove(key);
// ++hit;
// } else
// if (getRecentNode() != null && getRecentNode().isInRange(key)) {
// result = getRecentNode().remove(key);
// ++hit;
// } else
{
result = _root.remove(key);
++miss;
}

if (_root.isEmpty())
if (_root.isEmpty()) {
_root = new BplusTreeLeafNode<Key, Value>(null, null, null, this);
lastNode = (BplusTreeLeafNode) _root;
}
else if (lastNode.isEmpty())
lastNode = lastNode.getPrev();

return result;
}
// TODO: CHECK Last Node
public void removeFrom(Key key) throws BTreeException {
_root.removeFrom(key);
if (!cacheDisabled) {
lastNode.bottomUpRemoveFrom(key);
} else
_root.removeFrom(key);

if (_root.isEmpty()) {
_root = new BplusTreeLeafNode<Key, Value>(null, null, null, this);
recentlyUsed = null;
_root = new BplusTreeLeafNode<>(null, null, null, this);
lastNode = (BplusTreeLeafNode<Key, Value>) _root;
// recentlyUsed = null;
}
else if (lastNode.isEmpty() || lastNode.peekKey().compareTo(key) == 0) {
lastNode = lastNode.getPrev();
} else if (lastNode.peekKey().compareTo(key) > 0)
lastNode = _root.peekLastNode();

// if (recentlyUsed != null && recentlyUsed.peekKey().compareTo(key) > -1)
// recentlyUsed = lastNode;
}
public Value find(Key key) throws BTreeException {
if (getRecentNode() != null && getRecentNode().isInRange(key)) {
++hit;
return (Value) getRecentNode().find(key);
} else {
// if (!cacheDisabled && lastNode.isInRange(key)) {
// ++hit;
// return lastNode.find(key);
// } else
// if (getRecentNode() != null && getRecentNode().isInRange(key)) {
// ++hit;
// return getRecentNode().find(key);
// } else
{
++miss;
return _root.find(key);
}
Expand All @@ -98,12 +190,39 @@ public Value peekValue() {
public Key peekKey() {
return _root.peekKey();
}
public Value peekLastValue() {
if (lastNode == null)
throw new RuntimeException("lastNode can not be null");
return lastNode.peekBackValue();
}
public Key peekLastKey() {
if (lastNode == null)
throw new RuntimeException("lastNode can not be null");
return lastNode.peekBackKey();
}
public Value pop() throws BTreeException {
Value poppedVal = _root.pop();
if (_root.isEmpty())
if (_root.isEmpty()) {
_root = new BplusTreeLeafNode<Key, Value>(null, null, null, this);
lastNode = (BplusTreeLeafNode) _root;
// recentlyUsed = lastNode;
}

return poppedVal;
}
public Value popBack() throws BTreeException {
if (lastNode == null)
throw new RuntimeException("lastNode can not be null");

Value poppedVal = lastNode.popBack();

if (_root.isEmpty()) {
_root = new BplusTreeLeafNode<Key, Value>(null, null, null, this);
lastNode = (BplusTreeLeafNode) _root;
}
else if (lastNode.isEmpty())
lastNode = lastNode.getPrev();

return poppedVal;
}
}
18 changes: 14 additions & 4 deletions src/main/java/bplustree/BplusTreeBranchNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import static utility.Utils.searchRightmostKey;

class BplusTreeBranchNode<Key extends Comparable, Value> extends BplusTreeNode<Key, Value>{
private CircularFifoQueue<BplusTreeNode<Key, Value>> children;
protected CircularFifoQueue<BplusTreeNode<Key, Value>> children;

public BplusTreeBranchNode(BplusTreeBranchNode parent) {
this(new CircularFifoQueue<>(CAPACITY), new CircularFifoQueue<>(CAPACITY), parent);
Expand Down Expand Up @@ -116,14 +116,14 @@ public void add(Key key, Value value) throws BTreeException {
}

@Override
public void remove(Key key) throws BTreeException {
public Value remove(Key key) throws BTreeException {
if (key == null) {
throw new BTreeException("Can't search on null Value");
}
int idx = searchRightmostKey(keys, key, keys.size());

idx = idx < 0 ? -(idx + 1) : idx;
children.get(idx).remove(key);
return children.get(idx).remove(key);
}

@Override
Expand Down Expand Up @@ -164,6 +164,11 @@ public BplusTreeLeafNode.BplusTreeIterator peekLast() {
return children.peekBack().peekLast();
}

@Override
public BplusTreeLeafNode peekLastNode() {
return children.peekBack().peekLastNode();
}

@Override
public Key peekKey() {
return children.get(0).peekKey();
Expand All @@ -176,6 +181,11 @@ public Value peekValue() {

@Override
public Value pop() throws BTreeException {
return children.get(0).pop();
return children.peekFront().pop();
}

@Override
public Value popBack() throws BTreeException {
return children.peekBack().popBack();
}
}
105 changes: 105 additions & 0 deletions src/main/java/bplustree/BplusTreeCloner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package bplustree;

import utility.CircularFifoQueue;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class BplusTreeCloner<K extends Comparable, V> {
private BplusTree tree;
private Map<BplusTreeNode, BplusTreeNode> nodeMap = new HashMap<>();

BplusTreeCloner(BplusTree tree) {
this.tree = tree;
}

public BplusTreeNode clone(BplusTreeNode node) throws CloneNotSupportedException {
if (node == null)
return null;
if (node instanceof BplusTreeLeafNode)
return clone((BplusTreeLeafNode) node);
if (node instanceof BplusTreeBranchNode)
return clone((BplusTreeBranchNode) node);

throw new IllegalArgumentException("node is in illegal state");
}

private BplusTreeLeafNode clone(BplusTreeLeafNode node) throws CloneNotSupportedException {
if (node == null)
return null;
if (nodeMap.containsKey(node))
return (BplusTreeLeafNode) nodeMap.get(node);

BplusTreeLeafNode cloned = new BplusTreeLeafNode(null, null, null, tree);
nodeMap.put(node, cloned);

cloneBplusTreeNodeInternals(node, cloned);
cloned.leaves = node.leaves.clone();


if (node.next != null)
cloned.next = clone(node.next);
if (node.prev != null)
cloned.prev = clone(node.prev);

return cloned;
}

private BplusTreeBranchNode clone(BplusTreeBranchNode node) throws CloneNotSupportedException {
if (node == null)
return null;
if (nodeMap.containsKey(node))
return (BplusTreeBranchNode) nodeMap.get(node);

BplusTreeBranchNode cloned = new BplusTreeBranchNode(null);
nodeMap.put(node, cloned);

cloneBplusTreeNodeInternals(node, cloned);
cloned.children = node.children.clone(this);

return cloned;
}

private void cloneBplusTreeNodeInternals(BplusTreeNode base, BplusTreeNode cloned) throws CloneNotSupportedException {
cloned.parent = clone(base.parent);
cloned.keys = base.keys.clone();
if (base.LeftRangeKey instanceof Cloneable)
try {
cloned.LeftRangeKey = (Comparable) base.LeftRangeKey.getClass().getMethod("clone").invoke(base.LeftRangeKey);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
cloned.LeftRangeKey = base.LeftRangeKey;
throw new CloneNotSupportedException("Key is not cloneable");
}
else
cloned.LeftRangeKey = base.LeftRangeKey;
}

private Object getFieldOfCircularFifoQueue(CircularFifoQueue queue, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Field field = queue.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(queue);
}

private Object[] getArrayFieldOfCircularFifoQueue(CircularFifoQueue queue) throws NoSuchFieldException, IllegalAccessException {
Field field = queue.getClass().getDeclaredField("elements");
field.setAccessible(true);
return (Object[]) field.get(queue);
}

private<T> void setFieldOfCircularFifoQueue(CircularFifoQueue queue, String fieldName, T value) throws NoSuchFieldException, IllegalAccessException {
Field field = queue.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(queue, value);
}

private<T> void copyCircularFifoQueueField(CircularFifoQueue from, CircularFifoQueue to, String fieldName) throws NoSuchFieldException, IllegalAccessException {
T value = (T)getFieldOfCircularFifoQueue(from, fieldName);
Field field = to.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(to, value);
}
}
Loading