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> 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;