diff --git a/src/main/java/btree4j/BTree.java b/src/main/java/btree4j/BTree.java index 4e8830e..3617ba8 100644 --- a/src/main/java/btree4j/BTree.java +++ b/src/main/java/btree4j/BTree.java @@ -39,22 +39,17 @@ 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 com.google.common.annotations.Beta; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import javax.annotation.CheckForNull; import javax.annotation.Nonnegative; import javax.annotation.Nonnull; import javax.annotation.Nullable; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import java.io.*; +import java.nio.ByteBuffer; +import java.util.Arrays; /** * BTree represents a Variable Magnitude Simple-Prefix B+Tree File. @@ -96,6 +91,7 @@ public class BTree extends Paged { private BTreeRootInfo _rootInfo; private BTreeNode _rootNode; + private BTreeNode _firstNode; public BTree(@Nonnull File file) { this(file, true); @@ -106,7 +102,7 @@ public BTree(@Nonnull File file, boolean duplicateAllowed) { } public BTree(@Nonnull File file, @Nonnegative int pageSize, int caches, - boolean duplicateAllowed) { + boolean duplicateAllowed) { super(file, pageSize); BTreeFileHeader fh = getFileHeader(); fh.incrTotalPageCount(); // for root page @@ -169,6 +165,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; @@ -197,6 +194,7 @@ public boolean create(boolean close) throws BTreeException { if (close) { close(); } + this._firstNode = this._rootNode; return true; } return false; @@ -206,6 +204,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 _firstNode.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. @@ -249,6 +265,10 @@ public synchronized int removeValue(@Nonnull Value key, long pointer) throws BTr } } + 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. * @@ -532,6 +552,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; @@ -557,6 +585,84 @@ 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()); + clearParent(); + } + } + 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(); + } else if (this._prev != -1) { // Very unlikely, mostly for robustness + return getBTreeNode(root, this._prev).getFirstNode(); + } else 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(@Nonnull Value key, final long pointer) throws IOException, BTreeException { int idx = searchRightmostKey(keys, key, keys.length); switch (ph.getStatus()) { @@ -655,6 +761,43 @@ 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 + 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; + getChildNode(leftIdx).removeFrom(searchKey); + + if (leftIdx < keys.length && leftIdx + 1 < ptrs.length) + 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.copyOf(keys, leftIdx), ArrayUtils.copyOf(ptrs, leftIdx)); + } + this._next = -1; + break; + default: + throw new BTreeCorruptException( + "Invalid page type '" + ph.getStatus() + "' in removeValue"); + } + + if (getParent() == null) { + while (_rootNode.ptrs.length == 1) { + _rootNode = _rootNode.getChildNode(0); + } + _rootNode.clearParent(); + } + calculateDataLength(); + } + /** @return pointer of left-most matched item */ long removeValue(Value searchKey) throws IOException, BTreeException { int leftIdx = searchLeftmostKey(keys, searchKey, keys.length); @@ -1064,6 +1207,10 @@ private void write() throws IOException, BTreeException { setDirty(false); } + private void resetDataLength() { + currentDataLen = -1; + } + private int calculateDataLength() { if (currentDataLen > 0) { return currentDataLen; 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/main/java/btree4j/utils/lang/ArrayUtils.java b/src/main/java/btree4j/utils/lang/ArrayUtils.java index 1e5578f..617b8ae 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; @@ -389,6 +390,105 @@ public static boolean equals(final char[] a, final char[] a2, final int off, fin return true; } + public static char[] copyOfRange(final char[] original, final int from, final int to) { + final int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + final char[] copy = new char[newLength]; + System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); + return copy; + } + + public static byte[] copyOfRange(final byte[] original, final int from, final int to) { + final int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + final byte[] copy = new byte[newLength]; + System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); + return copy; + } + + public static int[] copyOfRange(final int[] original, final int from, final int to) { + final int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + final int[] copy = new int[newLength]; + System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); + return copy; + } + + public static long[] copyOfRange(final long[] original, final int from, final int to) { + final int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + final long[] copy = new long[newLength]; + System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); + return copy; + } + + @SuppressWarnings("unchecked") + public static T[] copyOfRange(final T[] original, final int from, final int to) { + return copyOfRange(original, from, to, (Class) original.getClass()); + } + + @SuppressWarnings("unchecked") + private static T[] copyOfRange(final U[] original, final int from, final int to, + final Class newType) { + final int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + final T[] copy = ((Object) newType == (Object) Object[].class) ? (T[]) new Object[newLength] + : (T[]) Array.newInstance(newType.getComponentType(), newLength); + System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); + return copy; + } + + public static byte[] copyOf(final byte[] original, final int newLength) { + final byte[] copy = new byte[newLength]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); + return copy; + } + + public static int[] copyOf(final int[] original, final int newLength) { + final int[] copy = new int[newLength]; + System.arraycopy(original, 0, copy, 0, Math.min(original.length, 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)); + return copy; + } + + @SuppressWarnings("unchecked") + public static T[] copyOf(T[] original, int newLength) { + return (T[]) copyOf(original, newLength, original.getClass()); + } + + @SuppressWarnings("unchecked") + public static T[] copyOf(U[] original, int newLength, Class newType) { + final T[] copy = ((Object) newType == (Object) Object[].class) ? (T[]) new Object[newLength] + : (T[]) Array.newInstance(newType.getComponentType(), newLength); + System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); + 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/BTreeIndexTest.java b/src/test/java/btree4j/BTreeIndexTest.java index a419c2d..84855be 100644 --- a/src/test/java/btree4j/BTreeIndexTest.java +++ b/src/test/java/btree4j/BTreeIndexTest.java @@ -21,21 +21,13 @@ import btree4j.utils.lang.ArrayUtils; import btree4j.utils.lang.Primitives; import btree4j.utils.lang.PrintUtils; +import org.junit.Assert; +import org.junit.Test; import java.io.File; import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Random; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; - -import org.junit.Assert; -import org.junit.Test; public class BTreeIndexTest { private static final boolean DEBUG = true; diff --git a/src/test/java/btree4j/BTreeTest.java b/src/test/java/btree4j/BTreeTest.java index 5b223fe..86c8ee7 100644 --- a/src/test/java/btree4j/BTreeTest.java +++ b/src/test/java/btree4j/BTreeTest.java @@ -15,9 +15,11 @@ */ package btree4j; -import btree4j.indexer.BasicIndexQuery.IndexConditionBW; 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; @@ -25,14 +27,15 @@ 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; @Test - public void test() throws BTreeException { + public void shouldAddThenPeekMin() throws BTreeException { File tmpDir = FileUtils.getTempDir(); Assert.assertTrue(tmpDir.exists()); File tmpFile = new File(tmpDir, "BTreeTest1.idx"); @@ -44,37 +47,271 @@ public void test() throws BTreeException { BTree btree = new BTree(tmpFile); btree.init(/* bulkload */ false); - for (int i = 0; i < 1000; i++) { + Range range = getRangeOfTen(MAXN); + BTreeKey keyValue; + for (int i = range.getMax(); 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); - long v = i; - btree.addValue(k, v); + 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()); } - for (int i = 0; i < 1000; i++) { + 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 { + 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 expected = i; long actual = btree.findValue(k); - Assert.assertEquals(expected, actual); + 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.search(new IndexConditionBW(new Value("k" + 900), new Value("k" + 910)), - new BTreeCallback() { + 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 { + File tmpDir = FileUtils.getTempDir(); + Assert.assertTrue(tmpDir.exists()); + File tmpFile = new File(tmpDir, "BTreeTest1.idx"); + tmpFile.deleteOnExit(); + if (tmpFile.exists()) { + Assert.assertTrue(tmpFile.delete()); + } - @Override - public boolean indexInfo(Value value, long pointer) { - //System.out.println(pointer); - return true; - } + BTree btree = new BTree(tmpFile); + btree.init(/* bulkload */ false); - @Override - public boolean indexInfo(Value key, byte[] value) { - throw new UnsupportedOperationException(); - } - }); + 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); + btree.removeValue(k); + } + + for (int i = 0; i < MAXN; i++) { + Value k = new Value("k" + i); + long actual = btree.findValue(k); + Assert.assertEquals(KEY_NOT_FOUND, actual); + } + } + + @Test + 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"); + 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/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 shouldAddRemoveRandom1m() throws BTreeException { File tmpDir = FileUtils.getTempDir(); Assert.assertTrue(tmpDir.exists()); File indexFile = new File(tmpDir, "test10m.idx"); @@ -88,7 +325,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 +352,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); } } diff --git a/src/test/java/utility/ArrayUtilsTest.java b/src/test/java/utility/ArrayUtilsTest.java new file mode 100644 index 0000000..cc1b17d --- /dev/null +++ b/src/test/java/utility/ArrayUtilsTest.java @@ -0,0 +1,93 @@ +package utility; + +import btree4j.utils.lang.ArrayUtils; +import org.junit.Assert; +import org.junit.Test; + +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); + } + + // --------------- copyOf + @Test + public void shouldRemoveFromFirstValue() { + Integer[] a = {1, 2, 3}; + a = ArrayUtils.copyOf(a, 0); + Assert.assertArrayEquals(new Integer[]{}, a); + } + + @Test + public void shouldRemoveFromLastValue() { + Integer[] a = {1, 2, 3}; + a = ArrayUtils.copyOf(a, 2); + Assert.assertArrayEquals(new Integer[]{1, 2}, a); + } + + @Test + public void shouldRemoveFromMiddleValue() { + Integer[] a = {1, 2, 3}; + a = ArrayUtils.copyOf(a, 1); + Assert.assertArrayEquals(new Integer[]{1}, a); + } + + @Test + public void shouldRemoveFromFirstValuePrimitive() { + long[] a = {1, 2, 3}; + a = ArrayUtils.copyOf(a, 0); + Assert.assertArrayEquals(new long[]{}, a); + } + + @Test + public void shouldRemoveFromLastValuePrimitive() { + long[] a = {1, 2, 3}; + a = ArrayUtils.copyOf(a, 2); + Assert.assertArrayEquals(new long[]{1, 2}, a); + } + + @Test + public void shouldRemoveFromMiddleValuePrimitive() { + long[] a = {1, 2, 3}; + a = ArrayUtils.copyOf(a, 1); + Assert.assertArrayEquals(new long[]{1}, a); + } +} 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); + } +}