From 1e8f54fa6e624595926f18c11ac5add78bcd1e34 Mon Sep 17 00:00:00 2001
From: Abtin Bateni
Date: Sun, 5 May 2019 10:56:10 +0430
Subject: [PATCH 01/12] Add removeFrom function
---
.../java/btree4j/utils/lang/ArrayUtils.java | 61 +++++++-----
src/test/java/btree4j/ArrayUtilsTest.java | 95 +++++++++++++++++++
2 files changed, 134 insertions(+), 22 deletions(-)
create mode 100644 src/test/java/btree4j/ArrayUtilsTest.java
diff --git a/src/main/java/btree4j/utils/lang/ArrayUtils.java b/src/main/java/btree4j/utils/lang/ArrayUtils.java
index a386c26..9796584 100644
--- a/src/main/java/btree4j/utils/lang/ArrayUtils.java
+++ b/src/main/java/btree4j/utils/lang/ArrayUtils.java
@@ -31,6 +31,7 @@
package btree4j.utils.lang;
import java.lang.reflect.Array;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Random;
@@ -64,11 +65,11 @@ public static int[] copy(final int[] original) {
*
*
*
- * ArrayUtils.getLength(null) = 0
- * ArrayUtils.getLength([]) = 0
- * ArrayUtils.getLength([null]) = 1
- * ArrayUtils.getLength([true, false]) = 2
- * ArrayUtils.getLength([1, 2, 3]) = 3
+ * ArrayUtils.getLength(null) = 0
+ * ArrayUtils.getLength([]) = 0
+ * ArrayUtils.getLength([null]) = 1
+ * ArrayUtils.getLength([true, false]) = 2
+ * ArrayUtils.getLength([1, 2, 3]) = 3
* ArrayUtils.getLength(["a", "b", "c"]) = 3
*
*
@@ -151,11 +152,11 @@ public static int indexOf(final byte[] array, final byte valueToFind, int startI
* since arrays indices are 0-based.
*
*
- * ArrayUtils.lastIndex(null) = -1
- * ArrayUtils.lastIndex([]) = -1
- * ArrayUtils.lastIndex([null]) = 0
- * ArrayUtils.lastIndex([true, false]) = 1
- * ArrayUtils.lastIndex([1, 2, 3]) = 2
+ * ArrayUtils.lastIndex(null) = -1
+ * ArrayUtils.lastIndex([]) = -1
+ * ArrayUtils.lastIndex([null]) = 0
+ * ArrayUtils.lastIndex([true, false]) = 1
+ * ArrayUtils.lastIndex([1, 2, 3]) = 2
* ArrayUtils.lastIndex(["a", "b", "c"]) = 2
*
*
@@ -186,10 +187,10 @@ public static int lastIndex(final Object array) {
*
*
*
- * ArrayUtils.insert(null, 0, null) = [null]
- * ArrayUtils.insert(null, 0, "a") = ["a"]
- * ArrayUtils.insert(["a"], 1, null) = ["a", null]
- * ArrayUtils.insert(["a"], 1, "b") = ["a", "b"]
+ * ArrayUtils.insert(null, 0, null) = [null]
+ * ArrayUtils.insert(null, 0, "a") = ["a"]
+ * ArrayUtils.insert(["a"], 1, null) = ["a", null]
+ * ArrayUtils.insert(["a"], 1, "b") = ["a", "b"]
* ArrayUtils.insert(["a", "b"], 3, "c") = ["a", "b", "c"]
*
*
@@ -300,21 +301,29 @@ public static int[] append(final int[] left, final int[] right) {
* @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index >=
* array.length), or if the array is null.
* @since 2.1
+ * @implNote Its dependance on removeFrom may slow down the function.
*/
@SuppressWarnings("unchecked")
public static T[] remove(final T[] array, final int index) {
+ int length = getLength(array);
+ Object result = Arrays.copyOf(removeFrom(array, index), length - 1);
+ if (index < length - 1) {
+ System.arraycopy(array, index + 1, result, index, length - index - 1);
+ }
+ return (T[]) result;
+ }
+
+ public static T[] removeFrom(final T[] array, final int index) {
int length = getLength(array);
if (index < 0 || index >= length) {
throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length);
}
- Object result = Array.newInstance(array.getClass().getComponentType(), length - 1);
+ Object result = Array.newInstance(array.getClass().getComponentType(), index);
System.arraycopy(array, 0, result, 0, index);
- if (index < length - 1) {
- System.arraycopy(array, index + 1, result, index, length - index - 1);
- }
return (T[]) result;
}
+
@SuppressWarnings("unchecked")
public static T[] remove(final T[] array, final int from, final int to) {
assert (to >= from) : to + " - " + from;
@@ -332,17 +341,25 @@ public static T[] remove(final T[] array, final int from, final int to) {
return (T[]) result;
}
+ /**
+ * @implNote Its dependance on removeFrom may slow down the function.
+ */
public static long[] remove(final long[] vals, final int idx) {
- long[] newVals = new long[vals.length - 1];
- if (idx > 0) {
- System.arraycopy(vals, 0, newVals, 0, idx);
- }
+ long[] newVals = Arrays.copyOf(removeFrom(vals, idx), vals.length - 1);
if (idx < newVals.length) {
System.arraycopy(vals, idx + 1, newVals, idx, newVals.length - idx);
}
return newVals;
}
+ public static long[] removeFrom(final long[] vals, final int idx) {
+ long[] newVals = new long[idx];
+ if (idx > 0) {
+ System.arraycopy(vals, 0, newVals, 0, idx);
+ }
+ return newVals;
+ }
+
public static long[] remove(final long[] vals, final int from, final int to) {
int remsize = to - from + 1;
long[] newVals = new long[vals.length - remsize];
diff --git a/src/test/java/btree4j/ArrayUtilsTest.java b/src/test/java/btree4j/ArrayUtilsTest.java
new file mode 100644
index 0000000..3b34d7a
--- /dev/null
+++ b/src/test/java/btree4j/ArrayUtilsTest.java
@@ -0,0 +1,95 @@
+package btree4j;
+
+import btree4j.utils.lang.ArrayUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+public class ArrayUtilsTest {
+ // --------------- remove
+ @Test
+ public void shouldRemoveFirstValue() {
+ Integer[] a = {1, 2, 3};
+ a = ArrayUtils.remove(a, 0);
+ Assert.assertArrayEquals(new Integer[]{2, 3}, a);
+ }
+
+ @Test
+ public void shouldRemoveLastValue() {
+ Integer[] a = {1, 2, 3};
+ a = ArrayUtils.remove(a, 2);
+ Assert.assertArrayEquals(new Integer[]{1, 2}, a);
+ }
+
+ @Test
+ public void shouldRemoveMiddleValue() {
+ Integer[] a = {1, 2, 3};
+ a = ArrayUtils.remove(a, 1);
+ Assert.assertArrayEquals(new Integer[]{1, 3}, a);
+ }
+
+ @Test
+ public void shouldRemoveFirstValuePrimitive() {
+ long[] a = {1, 2, 3};
+ a = ArrayUtils.remove(a, 0);
+ Assert.assertArrayEquals(new long[]{2, 3}, a);
+ }
+
+ @Test
+ public void shouldRemoveLastValuePrimitive() {
+ long[] a = {1, 2, 3};
+ a = ArrayUtils.remove(a, 2);
+ Assert.assertArrayEquals(new long[]{1, 2}, a);
+ }
+
+ @Test
+ public void shouldRemoveMiddleValuePrimitive() {
+ long[] a = {1, 2, 3};
+ a = ArrayUtils.remove(a, 1);
+ Assert.assertArrayEquals(new long[]{1, 3}, a);
+ }
+
+ // --------------- removeFrom
+ @Test
+ public void shouldRemoveFromFirstValue() {
+ Integer[] a = {1, 2, 3};
+ a = ArrayUtils.removeFrom(a, 0);
+ Assert.assertArrayEquals(new Integer[]{}, a);
+ }
+
+ @Test
+ public void shouldRemoveFromLastValue() {
+ Integer[] a = {1, 2, 3};
+ a = ArrayUtils.removeFrom(a, 2);
+ Assert.assertArrayEquals(new Integer[]{1, 2}, a);
+ }
+
+ @Test
+ public void shouldRemoveFromMiddleValue() {
+ Integer[] a = {1, 2, 3};
+ a = ArrayUtils.removeFrom(a, 1);
+ Assert.assertArrayEquals(new Integer[]{1}, a);
+ }
+
+ @Test
+ public void shouldRemoveFromFirstValuePrimitive() {
+ long[] a = {1, 2, 3};
+ a = ArrayUtils.removeFrom(a, 0);
+ Assert.assertArrayEquals(new long[]{}, a);
+ }
+
+ @Test
+ public void shouldRemoveFromLastValuePrimitive() {
+ long[] a = {1, 2, 3};
+ a = ArrayUtils.removeFrom(a, 2);
+ Assert.assertArrayEquals(new long[]{1, 2}, a);
+ }
+
+ @Test
+ public void shouldRemoveFromMiddleValuePrimitive() {
+ long[] a = {1, 2, 3};
+ a = ArrayUtils.removeFrom(a, 1);
+ Assert.assertArrayEquals(new long[]{1}, a);
+ }
+}
From a966126c5c552a0b1c35303e287335db9f98349b Mon Sep 17 00:00:00 2001
From: Abtin Bateni
Date: Sun, 5 May 2019 17:16:54 +0430
Subject: [PATCH 02/12] Add resetDataLength
---
src/main/java/btree4j/BTree.java | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/main/java/btree4j/BTree.java b/src/main/java/btree4j/BTree.java
index 1c6ba0f..83f832e 100644
--- a/src/main/java/btree4j/BTree.java
+++ b/src/main/java/btree4j/BTree.java
@@ -50,6 +50,7 @@
import javax.annotation.CheckForNull;
+import com.google.common.annotations.Beta;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -929,12 +930,12 @@ private void set(final Value[] values, final long[] ptrs) {
this.ptrs = ptrs;
this.ph.setValueCount((short) vlen);
if (vlen > 1) {
- final int prevPreixLen = ph.getPrefixLength();
+ final int prevPrefixLen = ph.getPrefixLength();
this.prefix = getPrefix(values[0], values[vlen - 1]);
final int prefixLen = prefix.getLength();
assert (prefixLen <= Short.MAX_VALUE) : prefixLen;
- if (prefixLen != prevPreixLen) {
- int diff = prefixLen - prevPreixLen;
+ if (prefixLen != prevPrefixLen) {
+ int diff = prefixLen - prevPrefixLen;
currentDataLen += diff;
ph.setPrefixLength((short) prefixLen);
}
@@ -1063,6 +1064,10 @@ private void write() throws IOException, BTreeException {
setDirty(false);
}
+ private void resetDataLength() {
+ currentDataLen = -1;
+ }
+
private int calculateDataLength() {
if (currentDataLen > 0) {
return currentDataLen;
From 55c9aa07965245db4401a52b23781f8ecb466c8c Mon Sep 17 00:00:00 2001
From: Abtin Bateni
Date: Sun, 5 May 2019 18:06:22 +0430
Subject: [PATCH 03/12] Add/refactor tests
---
src/test/java/btree4j/BTreeTest.java | 95 +++++++++++++++++++++-------
1 file changed, 72 insertions(+), 23 deletions(-)
diff --git a/src/test/java/btree4j/BTreeTest.java b/src/test/java/btree4j/BTreeTest.java
index 5b223fe..7c51974 100644
--- a/src/test/java/btree4j/BTreeTest.java
+++ b/src/test/java/btree4j/BTreeTest.java
@@ -28,11 +28,39 @@
import org.junit.Assert;
import org.junit.Test;
+import static btree4j.BTree.KEY_NOT_FOUND;
+
public class BTreeTest {
private static final boolean DEBUG = true;
+ final private int MAXN = 5000 * 1000;
+
+ @Test
+ public void shouldAdd5m() throws BTreeException {
+ File tmpDir = FileUtils.getTempDir();
+ Assert.assertTrue(tmpDir.exists());
+ File tmpFile = new File(tmpDir, "BTreeTest1.idx");
+ tmpFile.deleteOnExit();
+ if (tmpFile.exists()) {
+ Assert.assertTrue(tmpFile.delete());
+ }
+
+ BTree btree = new BTree(tmpFile);
+ btree.init(/* bulkload */ false);
+
+ for (int i = 0; i < MAXN; i++) {
+ Value k = new Value("k" + i);
+ btree.addValue(k, i);
+ }
+
+ for (int i = 0; i < MAXN; i++) {
+ Value k = new Value("k" + i);
+ long actual = btree.findValue(k);
+ Assert.assertEquals(i, actual);
+ }
+ }
@Test
- public void test() throws BTreeException {
+ public void shouldAddRemove5m() throws BTreeException {
File tmpDir = FileUtils.getTempDir();
Assert.assertTrue(tmpDir.exists());
File tmpFile = new File(tmpDir, "BTreeTest1.idx");
@@ -44,37 +72,58 @@ public void test() throws BTreeException {
BTree btree = new BTree(tmpFile);
btree.init(/* bulkload */ false);
- for (int i = 0; i < 1000; i++) {
+ for (int i = 0; i < MAXN; i++) {
+ Value k = new Value("k" + i);
+ btree.addValue(k, i);
+ }
+
+ for (int i = 0; i < MAXN; i++) {
Value k = new Value("k" + i);
- long v = i;
- btree.addValue(k, v);
+ btree.removeValue(k);
}
- for (int i = 0; i < 1000; i++) {
+ for (int i = 0; i < MAXN; i++) {
Value k = new Value("k" + i);
- long expected = i;
long actual = btree.findValue(k);
- Assert.assertEquals(expected, actual);
+ Assert.assertEquals(KEY_NOT_FOUND, actual);
+ }
+ }
+
+ @Test
+ public void shouldAddThenRemoveHalf5m() throws BTreeException {
+ File tmpDir = FileUtils.getTempDir();
+ Assert.assertTrue(tmpDir.exists());
+ File tmpFile = new File(tmpDir, "BTreeTest1.idx");
+ tmpFile.deleteOnExit();
+ if (tmpFile.exists()) {
+ Assert.assertTrue(tmpFile.delete());
}
- btree.search(new IndexConditionBW(new Value("k" + 900), new Value("k" + 910)),
- new BTreeCallback() {
+ BTree btree = new BTree(tmpFile);
+ btree.init(/* bulkload */ false);
- @Override
- public boolean indexInfo(Value value, long pointer) {
- //System.out.println(pointer);
- return true;
- }
+ for (int i = 0; i < MAXN; i++) {
+ Value k = new Value("k" + i);
+ btree.addValue(k, i);
+ }
- @Override
- public boolean indexInfo(Value key, byte[] value) {
- throw new UnsupportedOperationException();
- }
- });
+ for (int i = 0; i < MAXN/2; i++) {
+ Value k = new Value("k" + i);
+ btree.removeValue(k);
+ }
+
+ for (int i = 0; i < MAXN; i++) {
+ Value k = new Value("k" + i);
+ long actual = btree.findValue(k);
+ if (i < MAXN / 2)
+ Assert.assertEquals(KEY_NOT_FOUND, actual);
+ else
+ Assert.assertEquals(i, actual);
+ }
}
@Test
- public void test10m() throws BTreeException {
+ public void shouldAddRemoveRandom10m() throws BTreeException {
File tmpDir = FileUtils.getTempDir();
Assert.assertTrue(tmpDir.exists());
File indexFile = new File(tmpDir, "test10m.idx");
@@ -88,7 +137,7 @@ public void test10m() throws BTreeException {
final Map kv = new HashMap<>();
final Random rand = new Random();
- for (int i = 0; i < 10000000; i++) {
+ for (int i = 0; i < 2 * MAXN; i++) {
long nt = System.nanoTime(), val = rand.nextInt(Integer.MAX_VALUE); // FIXME val = rand.nextLong();
Value key = new Value(String.valueOf(nt) + val);
btree.addValue(key, val);
@@ -115,9 +164,9 @@ public void test10m() throws BTreeException {
Value k = e.getKey();
Long v = e.getValue();
long result = btree.findValue(k);
- Assert.assertNotEquals("key is not registered: " + k, BTree.KEY_NOT_FOUND, result);
+ Assert.assertNotEquals("key is not registered: " + k, KEY_NOT_FOUND, result);
Assert.assertEquals("Exexpected value '" + result + "' found for key: " + k,
- v.longValue(), result);
+ v.longValue(), result);
}
}
From 95ac30d7915924e3667585af6afc62354b63d48b Mon Sep 17 00:00:00 2001
From: Abtin Bateni
Date: Sat, 11 May 2019 16:57:18 +0430
Subject: [PATCH 04/12] Add removeFrom to BTree
---
src/main/java/btree4j/BTree.java | 55 ++++++++++--
.../java/btree4j/utils/lang/ArrayUtils.java | 5 ++
src/test/java/btree4j/BTreeTest.java | 88 +++++++++++++++++--
3 files changed, 135 insertions(+), 13 deletions(-)
diff --git a/src/main/java/btree4j/BTree.java b/src/main/java/btree4j/BTree.java
index 83f832e..40de363 100644
--- a/src/main/java/btree4j/BTree.java
+++ b/src/main/java/btree4j/BTree.java
@@ -71,6 +71,8 @@ public class BTree extends Paged {
}
public static final int KEY_NOT_FOUND = -1;
+ private static final int CHILD_NODE_CLEARED = 0;
+ private static final int CHILD_NODE_HAS_VALUE = 1;
private static final int LEAST_KEYS = 5;
private static final byte[] EmptyBytes = new byte[0];
@@ -239,6 +241,10 @@ public synchronized long[] removeValue(Value value, long pointer) throws BTreeEx
}
}
+ public synchronized void removeValueFrom(Value value) throws BTreeException {
+ _rootNode.removeFrom(value);
+ }
+
/**
* findValue finds a Value in the BTree and returns the associated pointer for it.
*
@@ -651,6 +657,40 @@ private int searchRightmostKey(final Value[] ary, final Value key, final int to)
return -(low + 1); // key not found.
}
+ /**
+ * remove all values greater than a threshold
+ * @return variable indicating if it should delete the child node as well
+ */
+ @Beta
+ int removeFrom(Value searchKey) throws BTreeException {
+ resetDataLength();
+ int leftIdx = searchLeftmostKey(keys, searchKey, keys.length);
+ switch (ph.getStatus()) {
+ case BRANCH:
+ leftIdx = (leftIdx < 0) ? -(leftIdx + 1) : leftIdx;
+ int childNodeStatus = getChildNode(leftIdx).removeFrom(searchKey);
+
+ leftIdx += (childNodeStatus == CHILD_NODE_CLEARED) ? 0 : 0;
+ if (leftIdx < keys.length && leftIdx + 1 < ptrs.length)
+ set(ArrayUtils.removeFrom(keys, leftIdx), ArrayUtils.removeFrom(ptrs, leftIdx + 1));
+ break;
+ case LEAF:
+ leftIdx = (leftIdx < 0) ? -(leftIdx + 1) : leftIdx;
+ if (leftIdx < keys.length && leftIdx < ptrs.length)
+ set(ArrayUtils.removeFrom(keys, leftIdx), ArrayUtils.removeFrom(ptrs, leftIdx));
+ break;
+ default:
+ throw new BTreeCorruptException(
+ "Invalid page type '" + ph.getStatus() + "' in removeValue");
+ }
+
+ if (getParent() == null)
+ while (_rootNode.ptrs.length == 1) {
+ _rootNode = _rootNode.getChildNode(0);
+ }
+ return leftIdx > 0 ? CHILD_NODE_HAS_VALUE : CHILD_NODE_CLEARED;
+ }
+
/** @return pointer of left-most matched item */
long removeValue(Value searchKey) throws IOException, BTreeException {
int leftIdx = searchLeftmostKey(keys, searchKey, keys.length);
@@ -1120,15 +1160,16 @@ private void decrDataLength(final Value value) {
}
/** find lest-most value which matches to the key */
- long findValue(Value serarchKey) throws BTreeException {
- if (serarchKey == null) {
+ long findValue(Value searchKey) throws BTreeException {
+ if (searchKey == null) {
throw new BTreeException("Can't search on null Value");
}
- int idx = searchLeftmostKey(keys, serarchKey, keys.length);
+ int idx = searchLeftmostKey(keys, searchKey, keys.length);
switch (ph.getStatus()) {
case BRANCH:
idx = idx < 0 ? -(idx + 1) : idx + 1;
- return getChildNode(idx).findValue(serarchKey);
+// if (idx >= keys.length) idx = keys.length - 1;
+ return getChildNode(idx).findValue(searchKey);
case LEAF:
if (idx < 0) {
return KEY_NOT_FOUND;
@@ -1139,7 +1180,7 @@ long findValue(Value serarchKey) throws BTreeException {
leftmostNode = getBTreeNode(root, leftmostNode._prev);
final Value[] lmKeys = leftmostNode.keys;
assert (lmKeys.length > 0);
- if (!lmKeys[0].equals(serarchKey)) {
+ if (!lmKeys[0].equals(searchKey)) {
break;
}
final int prevLookup = leftmostNode.ph.getLeftLookup();
@@ -1148,11 +1189,11 @@ long findValue(Value serarchKey) throws BTreeException {
}
}
final Value[] lmKeys = leftmostNode.keys;
- final int lmIdx = leftmostNode.searchLeftmostKey(lmKeys, serarchKey,
+ final int lmIdx = leftmostNode.searchLeftmostKey(lmKeys, searchKey,
lmKeys.length);
if (lmIdx < 0) {
throw new BTreeCorruptException(
- "Duplicated key was not found: " + serarchKey);
+ "Duplicated key was not found: " + searchKey);
}
final long[] leftmostPtrs = leftmostNode.ptrs;
return leftmostPtrs[lmIdx];
diff --git a/src/main/java/btree4j/utils/lang/ArrayUtils.java b/src/main/java/btree4j/utils/lang/ArrayUtils.java
index 9796584..994d8ae 100644
--- a/src/main/java/btree4j/utils/lang/ArrayUtils.java
+++ b/src/main/java/btree4j/utils/lang/ArrayUtils.java
@@ -501,6 +501,11 @@ public static T[] copyOf(U[] original, int newLength, Class extends T[]
return copy;
}
+ /**
+ * @param fromIndex Inclusive first index
+ * @param toIndex Non-inclusive last index
+ * @return returns either the found index or -1 * (the index of the first key greater than key + 1)
+ */
public static > int binarySearch(final T[] a, final int fromIndex,
final int toIndex, final T key) {
int low = fromIndex;
diff --git a/src/test/java/btree4j/BTreeTest.java b/src/test/java/btree4j/BTreeTest.java
index 7c51974..fd6e8cf 100644
--- a/src/test/java/btree4j/BTreeTest.java
+++ b/src/test/java/btree4j/BTreeTest.java
@@ -15,7 +15,6 @@
*/
package btree4j;
-import btree4j.indexer.BasicIndexQuery.IndexConditionBW;
import btree4j.utils.io.FileUtils;
import btree4j.utils.lang.PrintUtils;
@@ -32,10 +31,10 @@
public class BTreeTest {
private static final boolean DEBUG = true;
- final private int MAXN = 5000 * 1000;
+ final private int MAXN = 5000 * 100;
@Test
- public void shouldAdd5m() throws BTreeException {
+ public void shouldAdd() throws BTreeException {
File tmpDir = FileUtils.getTempDir();
Assert.assertTrue(tmpDir.exists());
File tmpFile = new File(tmpDir, "BTreeTest1.idx");
@@ -60,7 +59,7 @@ public void shouldAdd5m() throws BTreeException {
}
@Test
- public void shouldAddRemove5m() throws BTreeException {
+ public void shouldAddThenRemove() throws BTreeException {
File tmpDir = FileUtils.getTempDir();
Assert.assertTrue(tmpDir.exists());
File tmpFile = new File(tmpDir, "BTreeTest1.idx");
@@ -90,7 +89,84 @@ public void shouldAddRemove5m() throws BTreeException {
}
@Test
- public void shouldAddThenRemoveHalf5m() throws BTreeException {
+ public void shouldAddThenRemoveFrom() throws BTreeException {
+ File tmpDir = FileUtils.getTempDir();
+ Assert.assertTrue(tmpDir.exists());
+ File tmpFile = new File(tmpDir, "BTreeTest1.idx");
+ tmpFile.deleteOnExit();
+ if (tmpFile.exists()) {
+ Assert.assertTrue(tmpFile.delete());
+ }
+
+ BTree btree = new BTree(tmpFile);
+ btree.init(/* bulkload */ false);
+ btree.findValue(new Value("k"));
+
+ for (int i = 0; i < MAXN; i++) {
+ Value k = new Value("k" + i);
+ btree.addValue(k, i);
+ }
+
+ btree.removeValueFrom(new Value("k" + 0));
+
+ for (int i = 0; i < MAXN; i++) {
+ Value k = new Value("k" + i);
+ long actual = btree.findValue(k);
+ Assert.assertEquals(KEY_NOT_FOUND, actual);
+ }
+ }
+
+ private int getFirstDigit(int a) {
+ return Integer.parseInt(Integer.toString(a).substring(0, 1));
+ }
+ @Test
+ public void shouldAddThenRemoveFromHalf() throws BTreeException {
+ shouldAddThenRemoveFrom(MAXN / 2);
+ }
+ @Test
+ public void shouldAddThenRemoveFromOneTenth() throws BTreeException {
+ shouldAddThenRemoveFrom(MAXN / 10);
+ }
+ @Test
+ public void shouldAddThenRemoveFromAndClearChild() throws BTreeException {
+ shouldAddThenRemoveFrom(249905 < MAXN ? 249905 : 1);
+ }
+ @Test
+ public void shouldAddThenRemoveFromAndChangeRoot() throws BTreeException {
+ shouldAddThenRemoveFrom(433 < MAXN ? 433 : 1);
+ }
+
+ private void shouldAddThenRemoveFrom(int threshold) throws BTreeException {
+ File tmpDir = FileUtils.getTempDir();
+ Assert.assertTrue(tmpDir.exists());
+ File tmpFile = new File(tmpDir, "BTreeTest1.idx");
+ tmpFile.deleteOnExit();
+ if (tmpFile.exists()) {
+ Assert.assertTrue(tmpFile.delete());
+ }
+
+ BTree btree = new BTree(tmpFile);
+ btree.init(/* bulkload */ false);
+
+ for (int i = 0; i < MAXN; i++) {
+ Value k = new Value("k" + i);
+ btree.addValue(k, i);
+ }
+
+ btree.removeValueFrom(new Value("k" + threshold));
+
+ for (int i = 0; i < MAXN; i++) {
+ Value k = new Value("k" + i);
+ long actual = btree.findValue(k);
+ if (String.valueOf(i).compareTo(String.valueOf(threshold)) < 0)
+ Assert.assertEquals(i, actual);
+ else
+ Assert.assertEquals(KEY_NOT_FOUND, actual);
+ }
+ }
+
+ @Test
+ public void shouldAddThenRemoveHalf500k() throws BTreeException {
File tmpDir = FileUtils.getTempDir();
Assert.assertTrue(tmpDir.exists());
File tmpFile = new File(tmpDir, "BTreeTest1.idx");
@@ -123,7 +199,7 @@ public void shouldAddThenRemoveHalf5m() throws BTreeException {
}
@Test
- public void shouldAddRemoveRandom10m() throws BTreeException {
+ public void shouldAddRemoveRandom1m() throws BTreeException {
File tmpDir = FileUtils.getTempDir();
Assert.assertTrue(tmpDir.exists());
File indexFile = new File(tmpDir, "test10m.idx");
From 9a5366f8f88f60b8d51f30dffe9dda86bf1dcdd4 Mon Sep 17 00:00:00 2001
From: Abtin Bateni
Date: Sat, 18 May 2019 14:24:42 +0430
Subject: [PATCH 05/12] Refactor redundant code
---
src/main/java/btree4j/BTree.java | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/src/main/java/btree4j/BTree.java b/src/main/java/btree4j/BTree.java
index 40de363..ee7615b 100644
--- a/src/main/java/btree4j/BTree.java
+++ b/src/main/java/btree4j/BTree.java
@@ -71,8 +71,6 @@ public class BTree extends Paged {
}
public static final int KEY_NOT_FOUND = -1;
- private static final int CHILD_NODE_CLEARED = 0;
- private static final int CHILD_NODE_HAS_VALUE = 1;
private static final int LEAST_KEYS = 5;
private static final byte[] EmptyBytes = new byte[0];
@@ -662,15 +660,14 @@ private int searchRightmostKey(final Value[] ary, final Value key, final int to)
* @return variable indicating if it should delete the child node as well
*/
@Beta
- int removeFrom(Value searchKey) throws BTreeException {
+ void removeFrom(Value searchKey) throws BTreeException {
resetDataLength();
int leftIdx = searchLeftmostKey(keys, searchKey, keys.length);
switch (ph.getStatus()) {
case BRANCH:
leftIdx = (leftIdx < 0) ? -(leftIdx + 1) : leftIdx;
- int childNodeStatus = getChildNode(leftIdx).removeFrom(searchKey);
+ getChildNode(leftIdx).removeFrom(searchKey);
- leftIdx += (childNodeStatus == CHILD_NODE_CLEARED) ? 0 : 0;
if (leftIdx < keys.length && leftIdx + 1 < ptrs.length)
set(ArrayUtils.removeFrom(keys, leftIdx), ArrayUtils.removeFrom(ptrs, leftIdx + 1));
break;
@@ -688,7 +685,6 @@ int removeFrom(Value searchKey) throws BTreeException {
while (_rootNode.ptrs.length == 1) {
_rootNode = _rootNode.getChildNode(0);
}
- return leftIdx > 0 ? CHILD_NODE_HAS_VALUE : CHILD_NODE_CLEARED;
}
/** @return pointer of left-most matched item */
@@ -1168,7 +1164,6 @@ long findValue(Value searchKey) throws BTreeException {
switch (ph.getStatus()) {
case BRANCH:
idx = idx < 0 ? -(idx + 1) : idx + 1;
-// if (idx >= keys.length) idx = keys.length - 1;
return getChildNode(idx).findValue(searchKey);
case LEAF:
if (idx < 0) {
From 1d983d328f423cb274978fde9461c7009a264b52 Mon Sep 17 00:00:00 2001
From: Abtin Bateni
Date: Sat, 18 May 2019 15:23:56 +0430
Subject: [PATCH 06/12] Fix _next pointer of the last leaf
---
src/main/java/btree4j/BTree.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/java/btree4j/BTree.java b/src/main/java/btree4j/BTree.java
index ee7615b..448af26 100644
--- a/src/main/java/btree4j/BTree.java
+++ b/src/main/java/btree4j/BTree.java
@@ -675,6 +675,7 @@ void removeFrom(Value searchKey) throws BTreeException {
leftIdx = (leftIdx < 0) ? -(leftIdx + 1) : leftIdx;
if (leftIdx < keys.length && leftIdx < ptrs.length)
set(ArrayUtils.removeFrom(keys, leftIdx), ArrayUtils.removeFrom(ptrs, leftIdx));
+ this._next = -1;
break;
default:
throw new BTreeCorruptException(
From b756041e2b0fe8bf71effd2484b5fb0bfce1a8e9 Mon Sep 17 00:00:00 2001
From: Abtin Bateni
Date: Thu, 23 May 2019 14:34:13 +0430
Subject: [PATCH 07/12] Add peek and pop
---
src/main/java/btree4j/BTree.java | 109 +++++++++++++++++++++---
src/main/java/btree4j/BTreeKey.java | 19 +++++
src/test/java/btree4j/BTreeTest.java | 120 ++++++++++++++++++++++++++-
src/test/java/utility/Range.java | 18 ++++
src/test/java/utility/Utils.java | 14 ++++
5 files changed, 265 insertions(+), 15 deletions(-)
create mode 100644 src/main/java/btree4j/BTreeKey.java
create mode 100644 src/test/java/utility/Range.java
create mode 100644 src/test/java/utility/Utils.java
diff --git a/src/main/java/btree4j/BTree.java b/src/main/java/btree4j/BTree.java
index ee7615b..56ba2b7 100644
--- a/src/main/java/btree4j/BTree.java
+++ b/src/main/java/btree4j/BTree.java
@@ -39,21 +39,15 @@
import btree4j.utils.io.FastMultiByteArrayOutputStream;
import btree4j.utils.lang.ArrayUtils;
import btree4j.utils.lang.Primitives;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-import javax.annotation.CheckForNull;
-
import com.google.common.annotations.Beta;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import javax.annotation.CheckForNull;
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
/**
* BTree represents a Variable Magnitude Simple-Prefix B+Tree File.
*/
@@ -92,6 +86,7 @@ public class BTree extends Paged {
private BTreeRootInfo _rootInfo;
private BTreeNode _rootNode;
+ private BTreeNode _firstNode;
public BTree(File file) {
this(file, true);
@@ -163,6 +158,7 @@ public boolean open() throws BTreeException {
long p = _fileHeader.getRootPage();
this._rootInfo = new BTreeRootInfo(p);
this._rootNode = getBTreeNode(_rootInfo, p, null);
+ this._firstNode = this._rootNode;
return true;
} else {
return false;
@@ -191,6 +187,7 @@ public boolean create(boolean close) throws BTreeException {
if (close) {
close();
}
+ this._firstNode = this._rootNode;
return true;
}
return false;
@@ -200,6 +197,24 @@ protected final boolean isDuplicateAllowed() {
return _fileHeader._duplicateAllowed;
}
+ /**
+ * peeks first value in Btree.
+ * @return minimum value
+ */
+ public BTreeKey peekMinimum() throws BTreeException {
+ _firstNode = _firstNode.getFirstNode();
+ return _rootNode.getFirstNode().peekMinimum();
+ }
+
+ /**
+ * pops first value in Btree.
+ * @return minimum value
+ */
+ public synchronized BTreeKey popMinimum() throws BTreeException {
+ _firstNode = _firstNode.getFirstNode();
+ return _firstNode.popMinimum();
+ }
+
/**
* addValue adds a Value to the BTree and associates a pointer with it. The pointer can be used
* for referencing any type of data, it just so happens that Xindice uses it for referencing
@@ -554,6 +569,78 @@ private void setParent(BTreeNode node) {
}
}
+ private boolean isEmpty() {
+ return this.ptrs.length == 0;
+ }
+
+ private void removeSelf() throws BTreeException {
+ if (ph.getStatus() == BRANCH)
+ throw new RuntimeException("removeSelf is not implemented for Branch nodes.");
+
+ if (this._prev != -1) {
+ BTreeNode prev = getBTreeNode(root, this._prev);
+ prev._next = this._next;
+ }
+ if (this._next != -1) {
+ BTreeNode next = getBTreeNode(root, this._next);
+ next._prev = this._prev;
+ }
+
+ BTreeNode parent = this.getParent();
+ if (parent != null)
+ parent.removeChild(this.page.getPageNum());
+ }
+ private void removeChild(long pointer) throws BTreeException {
+ if (this.ph.getStatus() == LEAF) return;
+ for (int i = 0; i < ptrs.length; i++) {
+ if (ptrs[i] == pointer && keys.length != 0) {
+ decrDataLength(keys[i]);
+ set(ArrayUtils.remove(keys, i), ArrayUtils.remove(ptrs, i));
+ break;
+ }
+ }
+ if (this.ptrs.length == 1) {
+ BTreeNode parent = this.getParent();
+ if (parent != null)
+ parent.updateChild(this.page.getPageNum(), this.ptrs[0]);
+ else
+ _rootNode = getBTreeNode(_rootInfo, ptrs[0]);
+ }
+ }
+ private void updateChild(long currentPointer, long newPointer) throws BTreeException {
+ for (int i = 0; i < ptrs.length; i++) {
+ if (ptrs[i] == currentPointer) {
+ BTreeNode child = getBTreeNode(_rootInfo, newPointer, this);
+// child.setParent(this); // Previous line makes it redundant?
+ ptrs[i] = newPointer;
+ break;
+ }
+ }
+ }
+
+ private BTreeNode getFirstNode() throws BTreeException {
+ if (ph.getStatus() == BRANCH)
+ return getChildNode(0).getFirstNode();
+ if (this._prev != -1) // Very unlikely, mostly for robustness
+ return getBTreeNode(root, this._prev).getFirstNode();
+ if (this.ptrs.length == 0) // _prev links should be set correctly
+ return getBTreeNode(root, this._next).getFirstNode();
+ return this;
+ }
+
+ BTreeKey peekMinimum() {
+ return new BTreeKey(keys[0], ptrs[0]);
+ }
+
+ BTreeKey popMinimum() throws BTreeException {
+ BTreeKey result = new BTreeKey(keys[0], ptrs[0]);
+ set(ArrayUtils.remove(keys, 0), ArrayUtils.remove(ptrs, 0));
+ decrDataLength(result.getKey());
+ if (this.isEmpty())
+ removeSelf();
+ return result;
+ }
+
long addValue(Value value, long pointer) throws IOException, BTreeException {
if (value == null) {
throw new IllegalArgumentException("Can't add a null Value");
diff --git a/src/main/java/btree4j/BTreeKey.java b/src/main/java/btree4j/BTreeKey.java
new file mode 100644
index 0000000..b134f17
--- /dev/null
+++ b/src/main/java/btree4j/BTreeKey.java
@@ -0,0 +1,19 @@
+package btree4j;
+
+public class BTreeKey {
+ private Value key;
+ private Long pointer;
+
+ public BTreeKey(Value key, Long pointer) {
+ this.key = key;
+ this.pointer = pointer;
+ }
+
+ public Value getKey() {
+ return key;
+ }
+
+ public Long getPointer() {
+ return pointer;
+ }
+}
diff --git a/src/test/java/btree4j/BTreeTest.java b/src/test/java/btree4j/BTreeTest.java
index fd6e8cf..898d34e 100644
--- a/src/test/java/btree4j/BTreeTest.java
+++ b/src/test/java/btree4j/BTreeTest.java
@@ -17,6 +17,9 @@
import btree4j.utils.io.FileUtils;
import btree4j.utils.lang.PrintUtils;
+import org.junit.Assert;
+import org.junit.Test;
+import utility.Range;
import java.io.File;
import java.util.HashMap;
@@ -24,14 +27,95 @@
import java.util.Map.Entry;
import java.util.Random;
-import org.junit.Assert;
-import org.junit.Test;
-
import static btree4j.BTree.KEY_NOT_FOUND;
+import static utility.Utils.MAXN;
+import static utility.Utils.getRangeOfTen;
public class BTreeTest {
private static final boolean DEBUG = true;
- final private int MAXN = 5000 * 100;
+
+ @Test
+ public void shouldAddThenPeekMin() throws BTreeException {
+ File tmpDir = FileUtils.getTempDir();
+ Assert.assertTrue(tmpDir.exists());
+ File tmpFile = new File(tmpDir, "BTreeTest1.idx");
+ tmpFile.deleteOnExit();
+ if (tmpFile.exists()) {
+ Assert.assertTrue(tmpFile.delete());
+ }
+
+ BTree btree = new BTree(tmpFile);
+ btree.init(/* bulkload */ false);
+
+ Range range = getRangeOfTen(MAXN);
+ BTreeKey keyValue;
+ for (int i = range.getMax() - 1; i >= range.getMin(); i--) {
+ Value k = new Value("k" + i);
+ btree.addValue(k, i);
+ keyValue = btree.peekMinimum();
+ Assert.assertEquals(keyValue.getPointer().longValue(), i);
+ Assert.assertTrue(keyValue.getKey().equals(k));
+ }
+ }
+ @Test
+ public void shouldAddThenPeekAndPopMin() throws BTreeException {
+ File tmpDir = FileUtils.getTempDir();
+ Assert.assertTrue(tmpDir.exists());
+ File tmpFile = new File(tmpDir, "BTreeTest1.idx");
+ tmpFile.deleteOnExit();
+ if (tmpFile.exists()) {
+ Assert.assertTrue(tmpFile.delete());
+ }
+
+ BTree btree = new BTree(tmpFile);
+ btree.init(/* bulkload */ false);
+
+ Range range = getRangeOfTen(MAXN);
+ BTreeKey peekKey, popKey;
+ for (int i = range.getMin(); i <= range.getMax(); i++) {
+ Value k = new Value("k" + i);
+ btree.addValue(k, i);
+ }
+
+ for (int i = range.getMin(); i <= range.getMax(); i++) {
+ Value k = new Value("k" + i);
+ peekKey = btree.peekMinimum();
+ Assert.assertEquals(peekKey.getPointer().longValue(), i);
+ Assert.assertTrue(peekKey.getKey().equals(k));
+ popKey = btree.popMinimum();
+ Assert.assertEquals(peekKey.getKey(), popKey.getKey());
+ Assert.assertEquals(peekKey.getPointer(), popKey.getPointer());
+ }
+ }
+ @Test
+ public void shouldAddInReverseThenPeekAndPopMin() throws BTreeException {
+ File tmpDir = FileUtils.getTempDir();
+ Assert.assertTrue(tmpDir.exists());
+ File tmpFile = new File(tmpDir, "BTreeTest1.idx");
+ tmpFile.deleteOnExit();
+ if (tmpFile.exists()) {
+ Assert.assertTrue(tmpFile.delete());
+ }
+
+ BTree btree = new BTree(tmpFile);
+ btree.init(/* bulkload */ false);
+
+ Range range = getRangeOfTen(MAXN);
+ BTreeKey peekKey, popKey;
+ for (int i = range.getMax(); i >= range.getMin(); i--) {
+ Value k = new Value("k" + i);
+ btree.addValue(k, i);
+ }
+ for (int i = range.getMin(); i <= range.getMax(); i++) {
+ Value k = new Value("k" + i);
+ peekKey = btree.peekMinimum();
+ Assert.assertEquals(peekKey.getPointer().longValue(), i);
+ Assert.assertTrue(peekKey.getKey().equals(k));
+ popKey = btree.popMinimum();
+ Assert.assertEquals(peekKey.getKey(), popKey.getKey());
+ Assert.assertEquals(peekKey.getPointer(), popKey.getPointer());
+ }
+ }
@Test
public void shouldAdd() throws BTreeException {
@@ -57,6 +141,34 @@ public void shouldAdd() throws BTreeException {
Assert.assertEquals(i, actual);
}
}
+ @Test
+ public void shouldAddRemovePeekAndPopMin() throws BTreeException {
+ File tmpDir = FileUtils.getTempDir();
+ Assert.assertTrue(tmpDir.exists());
+ File tmpFile = new File(tmpDir, "BTreeTest1.idx");
+ tmpFile.deleteOnExit();
+ if (tmpFile.exists()) {
+ Assert.assertTrue(tmpFile.delete());
+ }
+
+ BTree btree = new BTree(tmpFile);
+ btree.init(/* bulkload */ false);
+
+ Range range = new Range(1, 3);
+ BTreeKey peekKey, popKey;
+ for (int i = range.getMin(); i <= range.getMax(); i++) {
+ Value k = new Value("k" + i);
+ btree.addValue(k, i);
+ }
+
+ btree.removeValue(new Value("k" + 1));
+ peekKey = btree.peekMinimum();
+ Assert.assertEquals(peekKey.getPointer().longValue(), 2);
+ Assert.assertTrue(peekKey.getKey().equals(new Value("k" + 2)));
+ popKey = btree.popMinimum();
+ Assert.assertEquals(peekKey.getKey(), popKey.getKey());
+ Assert.assertEquals(peekKey.getPointer(), popKey.getPointer());
+ }
@Test
public void shouldAddThenRemove() throws BTreeException {
diff --git a/src/test/java/utility/Range.java b/src/test/java/utility/Range.java
new file mode 100644
index 0000000..3450377
--- /dev/null
+++ b/src/test/java/utility/Range.java
@@ -0,0 +1,18 @@
+package utility;
+
+public class Range {
+ private int min, max;
+
+ public Range(int min, int max) {
+ this.min = min;
+ this.max = max;
+ }
+
+ public int getMin() {
+ return min;
+ }
+
+ public int getMax() {
+ return max;
+ }
+}
diff --git a/src/test/java/utility/Utils.java b/src/test/java/utility/Utils.java
new file mode 100644
index 0000000..81da1b2
--- /dev/null
+++ b/src/test/java/utility/Utils.java
@@ -0,0 +1,14 @@
+package utility;
+
+public class Utils {
+ public static final int MAXN = 5000 * 100;
+
+ public static Range getRangeOfTen(int x) {
+ if (x <= 0) throw new RuntimeException("x should be positive.");
+
+ int MAXNLength = String.valueOf(x).length();
+ int max = (int)Math.pow(10, MAXNLength) - 1;
+ int min = (int)Math.pow(10, MAXNLength - 1);
+ return new Range(min, max);
+ }
+}
From fd974be037af106856bf65d1b124472ba5dffd51 Mon Sep 17 00:00:00 2001
From: Abtin Bateni
Date: Fri, 24 May 2019 00:37:44 +0430
Subject: [PATCH 08/12] Change remove to its previous implementation, use JDK's
copyOf for removeFrom
---
src/main/java/btree4j/BTree.java | 22 ++++-------
.../java/btree4j/utils/lang/ArrayUtils.java | 37 +++++++------------
src/test/java/btree4j/ArrayUtilsTest.java | 16 ++++----
3 files changed, 28 insertions(+), 47 deletions(-)
diff --git a/src/main/java/btree4j/BTree.java b/src/main/java/btree4j/BTree.java
index 448af26..3896f75 100644
--- a/src/main/java/btree4j/BTree.java
+++ b/src/main/java/btree4j/BTree.java
@@ -39,21 +39,15 @@
import btree4j.utils.io.FastMultiByteArrayOutputStream;
import btree4j.utils.lang.ArrayUtils;
import btree4j.utils.lang.Primitives;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-import javax.annotation.CheckForNull;
-
import com.google.common.annotations.Beta;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import javax.annotation.CheckForNull;
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
/**
* BTree represents a Variable Magnitude Simple-Prefix B+Tree File.
*/
@@ -669,12 +663,12 @@ void removeFrom(Value searchKey) throws BTreeException {
getChildNode(leftIdx).removeFrom(searchKey);
if (leftIdx < keys.length && leftIdx + 1 < ptrs.length)
- set(ArrayUtils.removeFrom(keys, leftIdx), ArrayUtils.removeFrom(ptrs, leftIdx + 1));
+ set(ArrayUtils.copyOf(keys, leftIdx), ArrayUtils.copyOf(ptrs, leftIdx + 1));
break;
case LEAF:
leftIdx = (leftIdx < 0) ? -(leftIdx + 1) : leftIdx;
if (leftIdx < keys.length && leftIdx < ptrs.length)
- set(ArrayUtils.removeFrom(keys, leftIdx), ArrayUtils.removeFrom(ptrs, leftIdx));
+ set(ArrayUtils.copyOf(keys, leftIdx), ArrayUtils.copyOf(ptrs, leftIdx));
this._next = -1;
break;
default:
@@ -682,7 +676,7 @@ void removeFrom(Value searchKey) throws BTreeException {
"Invalid page type '" + ph.getStatus() + "' in removeValue");
}
- if (getParent() == null)
+ if (getParent() == null)
while (_rootNode.ptrs.length == 1) {
_rootNode = _rootNode.getChildNode(0);
}
diff --git a/src/main/java/btree4j/utils/lang/ArrayUtils.java b/src/main/java/btree4j/utils/lang/ArrayUtils.java
index 994d8ae..f1b2567 100644
--- a/src/main/java/btree4j/utils/lang/ArrayUtils.java
+++ b/src/main/java/btree4j/utils/lang/ArrayUtils.java
@@ -301,29 +301,21 @@ public static int[] append(final int[] left, final int[] right) {
* @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index >=
* array.length), or if the array is null.
* @since 2.1
- * @implNote Its dependance on removeFrom may slow down the function.
*/
@SuppressWarnings("unchecked")
public static T[] remove(final T[] array, final int index) {
- int length = getLength(array);
- Object result = Arrays.copyOf(removeFrom(array, index), length - 1);
- if (index < length - 1) {
- System.arraycopy(array, index + 1, result, index, length - index - 1);
- }
- return (T[]) result;
- }
-
- public static T[] removeFrom(final T[] array, final int index) {
int length = getLength(array);
if (index < 0 || index >= length) {
throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + length);
}
- Object result = Array.newInstance(array.getClass().getComponentType(), index);
+ Object result = Array.newInstance(array.getClass().getComponentType(), length - 1);
System.arraycopy(array, 0, result, 0, index);
+ if (index < length - 1) {
+ System.arraycopy(array, index + 1, result, index, length - index - 1);
+ }
return (T[]) result;
}
-
@SuppressWarnings("unchecked")
public static T[] remove(final T[] array, final int from, final int to) {
assert (to >= from) : to + " - " + from;
@@ -341,22 +333,14 @@ public static T[] remove(final T[] array, final int from, final int to) {
return (T[]) result;
}
- /**
- * @implNote Its dependance on removeFrom may slow down the function.
- */
public static long[] remove(final long[] vals, final int idx) {
- long[] newVals = Arrays.copyOf(removeFrom(vals, idx), vals.length - 1);
- if (idx < newVals.length) {
- System.arraycopy(vals, idx + 1, newVals, idx, newVals.length - idx);
- }
- return newVals;
- }
-
- public static long[] removeFrom(final long[] vals, final int idx) {
- long[] newVals = new long[idx];
+ long[] newVals = new long[vals.length - 1];
if (idx > 0) {
System.arraycopy(vals, 0, newVals, 0, idx);
}
+ if (idx < newVals.length) {
+ System.arraycopy(vals, idx + 1, newVals, idx, newVals.length - idx);
+ }
return newVals;
}
@@ -482,6 +466,11 @@ public static int[] copyOf(final int[] original, final int newLength) {
return copy;
}
+ public static long[] copyOf(final long[] vals, final int idx) {
+ return Arrays.copyOf(vals, idx);
+
+ }
+
public static char[] copyOf(final char[] original, final int newLength) {
final char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
diff --git a/src/test/java/btree4j/ArrayUtilsTest.java b/src/test/java/btree4j/ArrayUtilsTest.java
index 3b34d7a..fa9c02a 100644
--- a/src/test/java/btree4j/ArrayUtilsTest.java
+++ b/src/test/java/btree4j/ArrayUtilsTest.java
@@ -4,8 +4,6 @@
import org.junit.Assert;
import org.junit.Test;
-import java.util.ArrayList;
-
public class ArrayUtilsTest {
// --------------- remove
@Test
@@ -50,46 +48,46 @@ public void shouldRemoveMiddleValuePrimitive() {
Assert.assertArrayEquals(new long[]{1, 3}, a);
}
- // --------------- removeFrom
+ // --------------- copyOf
@Test
public void shouldRemoveFromFirstValue() {
Integer[] a = {1, 2, 3};
- a = ArrayUtils.removeFrom(a, 0);
+ a = ArrayUtils.copyOf(a, 0);
Assert.assertArrayEquals(new Integer[]{}, a);
}
@Test
public void shouldRemoveFromLastValue() {
Integer[] a = {1, 2, 3};
- a = ArrayUtils.removeFrom(a, 2);
+ a = ArrayUtils.copyOf(a, 2);
Assert.assertArrayEquals(new Integer[]{1, 2}, a);
}
@Test
public void shouldRemoveFromMiddleValue() {
Integer[] a = {1, 2, 3};
- a = ArrayUtils.removeFrom(a, 1);
+ a = ArrayUtils.copyOf(a, 1);
Assert.assertArrayEquals(new Integer[]{1}, a);
}
@Test
public void shouldRemoveFromFirstValuePrimitive() {
long[] a = {1, 2, 3};
- a = ArrayUtils.removeFrom(a, 0);
+ a = ArrayUtils.copyOf(a, 0);
Assert.assertArrayEquals(new long[]{}, a);
}
@Test
public void shouldRemoveFromLastValuePrimitive() {
long[] a = {1, 2, 3};
- a = ArrayUtils.removeFrom(a, 2);
+ a = ArrayUtils.copyOf(a, 2);
Assert.assertArrayEquals(new long[]{1, 2}, a);
}
@Test
public void shouldRemoveFromMiddleValuePrimitive() {
long[] a = {1, 2, 3};
- a = ArrayUtils.removeFrom(a, 1);
+ a = ArrayUtils.copyOf(a, 1);
Assert.assertArrayEquals(new long[]{1}, a);
}
}
From ec39a20a816df3ed4991b201e08cf2aa612ec10c Mon Sep 17 00:00:00 2001
From: Abtin Bateni
Date: Fri, 24 May 2019 01:02:59 +0430
Subject: [PATCH 09/12] Add braces for consistancy
---
src/main/java/btree4j/BTree.java | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/main/java/btree4j/BTree.java b/src/main/java/btree4j/BTree.java
index 3896f75..f9e73e6 100644
--- a/src/main/java/btree4j/BTree.java
+++ b/src/main/java/btree4j/BTree.java
@@ -667,8 +667,9 @@ void removeFrom(Value searchKey) throws BTreeException {
break;
case LEAF:
leftIdx = (leftIdx < 0) ? -(leftIdx + 1) : leftIdx;
- if (leftIdx < keys.length && leftIdx < ptrs.length)
+ if (leftIdx < keys.length && leftIdx < ptrs.length) {
set(ArrayUtils.copyOf(keys, leftIdx), ArrayUtils.copyOf(ptrs, leftIdx));
+ }
this._next = -1;
break;
default:
@@ -676,10 +677,11 @@ void removeFrom(Value searchKey) throws BTreeException {
"Invalid page type '" + ph.getStatus() + "' in removeValue");
}
- if (getParent() == null)
+ if (getParent() == null) {
while (_rootNode.ptrs.length == 1) {
_rootNode = _rootNode.getChildNode(0);
}
+ }
}
/** @return pointer of left-most matched item */
From 03217aaca46bccc7a8bbdd11a5a7c72c1b1f3c38 Mon Sep 17 00:00:00 2001
From: Abtin Bateni
Date: Fri, 24 May 2019 01:06:28 +0430
Subject: [PATCH 10/12] calculate data length after removeFrom
---
src/main/java/btree4j/BTree.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/java/btree4j/BTree.java b/src/main/java/btree4j/BTree.java
index f9e73e6..cfa67b7 100644
--- a/src/main/java/btree4j/BTree.java
+++ b/src/main/java/btree4j/BTree.java
@@ -682,6 +682,7 @@ void removeFrom(Value searchKey) throws BTreeException {
_rootNode = _rootNode.getChildNode(0);
}
}
+ calculateDataLength();
}
/** @return pointer of left-most matched item */
From 4ef6f004b273662d2183adb8c795016773098ea9 Mon Sep 17 00:00:00 2001
From: Abtin Bateni
Date: Fri, 24 May 2019 01:13:58 +0430
Subject: [PATCH 11/12] Clear parent when updating root node
---
src/main/java/btree4j/BTree.java | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/main/java/btree4j/BTree.java b/src/main/java/btree4j/BTree.java
index cfa67b7..fc9dacf 100644
--- a/src/main/java/btree4j/BTree.java
+++ b/src/main/java/btree4j/BTree.java
@@ -523,6 +523,14 @@ protected BTreeNode(final BTreeRootInfo root, final Page page) {
this.ph = (BTreePageHeader) page.getPageHeader();
}
+ private void clearParent() {
+ if (parentCache != null || ph.parentPage != Paged.NO_PAGE) {
+ ph.parentPage = Paged.NO_PAGE;
+ this.parentCache = null;
+ this.dirty = true;
+ }
+ }
+
private BTreeNode getParent() {
if (parentCache != null) {
return parentCache;
@@ -681,6 +689,7 @@ void removeFrom(Value searchKey) throws BTreeException {
while (_rootNode.ptrs.length == 1) {
_rootNode = _rootNode.getChildNode(0);
}
+ _rootNode.clearParent();
}
calculateDataLength();
}
From f2cf42d735cb8a0230d15e2b1c5d061d77b29cb8 Mon Sep 17 00:00:00 2001
From: Abtin Bateni
Date: Fri, 24 May 2019 13:31:07 +0430
Subject: [PATCH 12/12] move ArrayUtilsTest to utility package
---
src/test/java/{btree4j => utility}/ArrayUtilsTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
rename src/test/java/{btree4j => utility}/ArrayUtilsTest.java (99%)
diff --git a/src/test/java/btree4j/ArrayUtilsTest.java b/src/test/java/utility/ArrayUtilsTest.java
similarity index 99%
rename from src/test/java/btree4j/ArrayUtilsTest.java
rename to src/test/java/utility/ArrayUtilsTest.java
index fa9c02a..cc1b17d 100644
--- a/src/test/java/btree4j/ArrayUtilsTest.java
+++ b/src/test/java/utility/ArrayUtilsTest.java
@@ -1,4 +1,4 @@
-package btree4j;
+package utility;
import btree4j.utils.lang.ArrayUtils;
import org.junit.Assert;