![JAR search and dependency download from the Maven repository](/logo.png)
com.bigdata.btree.raba.MutableKeyBuffer Maven / Gradle / Ivy
/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.bigdata.btree.raba;
import java.io.DataInput;
import java.io.IOException;
import java.io.OutputStream;
import com.bigdata.util.BytesUtil;
/**
* A flyweight mutable implementation exposing the backing byte[][] and
* supporting search.
*
* @author Bryan Thompson
* @version $Id$
*/
public class MutableKeyBuffer extends AbstractKeyBuffer {
/**
* The #of defined keys.
*/
public int nkeys;
/**
* An array containing the keys. The size of the array is the maximum
* capacity of the key buffer.
*/
final public byte[][] keys;
/**
* Allocate a mutable key buffer capable of storing capacity keys.
*
* @param capacity
* The capacity of the key buffer.
*/
public MutableKeyBuffer(final int capacity) {
nkeys = 0;
keys = new byte[capacity][];
}
/**
* Constructor wraps an existing byte[][].
*
* @param nkeys
* The #of defined keys in the array.
* @param keys
* The array of keys.
*/
public MutableKeyBuffer(final int nkeys, final byte[][] keys ) {
assert nkeys >= 0; // allow deficient root.
assert keys != null;
assert keys.length >= nkeys;
this.nkeys = nkeys;
this.keys = keys;
}
/**
* Creates a new instance using a new array of keys but sharing the key
* references with the provided {@link MutableKeyBuffer}.
*
* @param src
* An existing instance.
*/
public MutableKeyBuffer(final MutableKeyBuffer src) {
assert src != null;
// assert capacity > src.nkeys;
this.nkeys = src.nkeys;
// note: dimension to the capacity of the source.
this.keys = new byte[src.keys.length][];
// copy the defined keys.
for (int i = 0; i < nkeys; i++) {
// Note: copies the reference.
this.keys[i] = src.keys[i];
}
}
/**
* Builds a mutable key buffer.
*
* @param capacity
* The capacity of the new instance (this is based on the
* branching factor for the B+Tree).
* @param src
* The source data.
*
* @throws IllegalArgumentException
* if the capacity is LT the {@link IRaba#size()} of the
* src.
* @throws IllegalArgumentException
* if the source is null
.
*/
public MutableKeyBuffer(final int capacity, final IRaba src) {
if (src == null)
throw new IllegalArgumentException();
if (capacity < src.capacity())
throw new IllegalArgumentException();
nkeys = src.size();
assert nkeys >= 0; // allows deficient root.
keys = new byte[capacity][];
int i = 0;
for (byte[] a : src) {
keys[i++] = a;
}
}
/**
* Returns a reference to the key at that index.
*/
final public byte[] get(final int index) {
/*
* @todo nkeys is not always updated before using this method by the
* btree code so the range check causes errors.
*/
// assert index >= 0 && index < nkeys;
return keys[index];
}
final public int length(final int index) {
assert index >= 0 && index < nkeys;
byte[] tmp = keys[index];
if(tmp==null) throw new NullPointerException();
return tmp.length;
}
final public int copy(final int index, final OutputStream out) {
assert index >= 0 && index < nkeys : "index="+index+" not in [0:"+nkeys+"]";
final byte[] tmp = keys[index];
try {
out.write(tmp, 0, tmp.length);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return tmp.length;
}
final public boolean isNull(final int index) {
assert index >= 0 && index < keys.length;
return index >= nkeys;
}
final public boolean isEmpty() {
return nkeys == 0;
}
final public int size() {
return nkeys;
}
/**
* The maximum #of keys that may be held in the buffer (its capacity).
*/
final public int capacity() {
return keys.length;
}
/**
* True iff the key buffer can not contain another key.
*/
final public boolean isFull() {
return nkeys == keys.length;
}
/**
* Mutable.
*/
final public boolean isReadOnly() {
return false;
}
/**
* Instances are searchable and do not allow null
s.
*/
final public boolean isKeys() {
return true;
}
/*
* Mutation api. The contents of individual keys are never modified. Some of
* the driver logic in Leaf and Node uses loops where nkeys is being
* decremented while keys are being processed from, e.g., a split point to
* the last key position. This has the effect that an assert such as
*
* index < nkeys
*
* will fail. Those looping constructs are not wrong as originally written
* but a move to encapsulate the key buffer puts their treatment of nkeys at
* odds with index testing since nkeys is temporarily inconsistent with the
* keys[].
*
* @todo maintain the prefix length. A trivial example of shortening the
* shared prefix occurs when keys are inserted into the root leaf. Consider
* that we first insert 4,5,6
. Since there is only one key
* in the root leaf, the length of the prefix is the same as the length of
* the key. If we then insert 4,5,6,1
the prefix does not
* change. However, if we then insert 4,5,2
the prefix is now
* shortened to 4,5
. If we insert 5
, then
* the prefix is shortened to an empty byte[]. Prefix shortening can also
* occur in trees with more than one level whenever a key is inserted into a
* leaf and becomes either the first or last key in that leaf. Likewise, it
* is possible for the prefix length to either grow when a leaf overflows
* and keys are redistributed.
*/
/**
* Set the key at the specified index.
*
* @param index
* The index in [0:nkeys-1].
* @param key
* The key (non-null).
*
* @todo Who uses this? Track prefixLength?
*/
final public void set(final int index, final byte[] key) {
assert index >= 0 && index < nkeys;
keys[index] = key;
}
// /**
// * Set the key at the specified index to null
. This is used
// * to clear elements of {@link #keys} that are no longer defined. The caller
// * is responsible for updating {@link #nkeys} when using this method.
// *
// * @param index
// * The key index in [0:maxKeys-1];
// */
// final public void zeroKey(int index) {
//
//// assert index >= 0 && index < nkeys;
//
// keys[index] = null;
//
// }
final public int add(final byte[] key) {
assert nkeys < keys.length;
// assert key != null;
keys[nkeys++] = key;
return nkeys;
}
final public int add(byte[] key, int off, int len) {
assert nkeys < keys.length;
// assert key != null;
byte[] b = new byte[len];
for(int i=0; i= 0 && index <= nkeys;
if( index == nkeys ) {
// append
return add(key);
}
/* index = 2;
* nkeys = 6;
*
* [ 0 1 2 3 4 5 ]
* ^ index
*
* count = keys - index = 4;
*/
final int count = nkeys - index;
assert count >= 1;
System.arraycopy(keys, index, keys, index+1, 1);
keys[index] = key;
return ++nkeys;
}
/**
* Remove a key in the buffer at the specified index, decrementing the #of
* keys in the buffer by one and moving up all keys from that index on down
* by one (towards the start of the array).
*
* @param index
* The index in [0:nkeys-1].
* @param key
* The key.
*
* @return The #of keys in the buffer.
*
* @todo if index==0 || index==nkeys-1 then update prefixLength, lazily
* compute prefix (requires that the application never directly
* modifies keys).
*/
final public int remove(final int index) {
assert index >= 0 && index < nkeys;
/*
* Copy down to cover up the hole.
*/
final int length = nkeys - index - 1;
if(length > 0) {
System.arraycopy(keys, index + 1, keys, index, length);
}
keys[--nkeys] = null;
return nkeys;
}
public String toString() {
return AbstractRaba.toString(this);
// final StringBuilder sb = new StringBuilder();
//
// sb.append(getClass().getName());
// sb.append("{ nkeys=" + nkeys);
// sb.append(", maxKeys=" + keys.length);
// sb.append(", prefix=" + BytesUtil.toString(getPrefix()));
// sb.append(", [\n");
//
// for (int i = 0; i < nkeys/*keys.length*/; i++) {
//
// if (i > 0)
// sb.append(",\n");
//
// final byte[] key = keys[i];
//
// if (key == null) {
//
// sb.append("null");
//
// } else {
//
// sb.append(BytesUtil.toString(key));
//
// }
//
// }
//
// sb.append("]}");
//
// return sb.toString();
}
final public int search(final byte[] searchKey) {
if (searchKey == null)
throw new IllegalArgumentException("searchKey is null");
if( nkeys == 0 ) {
/*
* If there are no keys in the buffer, then any key would be
* inserted at the first buffer position.
*/
return -1;
}
/*
* The length of the prefix shared by all keys in the buffer.
*/
final int prefixLength = getPrefixLength();
/*
* Attempt to match the shared prefix. If we can not then return the
* insert position, which is either before the first key or after the
* last key in the buffer.
*/
final int insertPosition = _prefixMatchLength(prefixLength, searchKey);
if( insertPosition < 0 ) {
return insertPosition;
}
/*
* Search keys, but only bytes from prefixLength on in each key.
*/
if (nkeys < 16) {
return _linearSearch(prefixLength, searchKey);
} else {
return _binarySearch(prefixLength, searchKey);
}
}
final protected int _prefixMatchLength(final int prefixLength,
final byte[] searchKey) {
final int searchKeyLen = searchKey.length;
/*
* Do not compare more bytes than remain in either the search key or the
* prefix, e.g., compareLen := min(searchKeyLen, prefixLen).
*/
final int compareLen = (searchKeyLen <= prefixLength) ? searchKeyLen
: prefixLength;
int ret = BytesUtil.compareBytesWithLenAndOffset(//
0, compareLen, searchKey,//
0, compareLen, keys[0]//
);
if (ret < 0) {
/* insert before the first key. */
return -1;
} else if (ret > 0) {
/* insert after the last key. */
return -(nkeys) - 1;
} else {
/*
* For the case when the search key is _shorter_ than the prefix,
* matching on all bytes of the search key means that the search key
* will be ordered before all keys in the buffer.
*/
if (searchKeyLen < prefixLength)
return -1;
/*
* entire prefix matched, continue to search the remainder for each
* key.
*/
return 0;
}
}
final protected int _linearSearch(final int searchKeyOffset,
final byte[] searchKey) {
// #of bytes to search in the search key after the prefix match.
final int searchKeyLen = searchKey.length - searchKeyOffset;
// searching zero or more bytes in the search key after the prefix match.
assert searchKeyLen >= 0;
for (int i = 0; i < nkeys; i++) {
final byte[] key = keys[i];
final int keyLen = key.length - searchKeyOffset;
assert keyLen >= 0;
// skip the first offset bytes, then compare no more bytes than
// remain in the key.
final int ret = BytesUtil.compareBytesWithLenAndOffset(//
searchKeyOffset, keyLen, key,//
searchKeyOffset, searchKeyLen, searchKey//
);
if (ret == 0)
return i;
if (ret > 0)
return -(i + 1);
}
return -(nkeys + 1);
}
final protected int _binarySearch(final int searchKeyOffset,
final byte[] searchKey) {
final int searchKeyLen = searchKey.length - searchKeyOffset;
assert searchKeyLen >= 0;
int low = 0;
int high = nkeys - 1;
while (low <= high) {
final int mid = (low + high) >>> 1;
final byte[] key = keys[mid];
final int keyLen = key.length - searchKeyOffset;
assert keyLen >= 0;
// skip the first offset bytes, then compare no more bytes than
// remain in the key.
final int ret = BytesUtil.compareBytesWithLenAndOffset(//
searchKeyOffset, keyLen, key,//
searchKeyOffset, searchKeyLen, searchKey//
);
if (ret < 0) {
low = mid + 1;
} else if (ret > 0) {
high = mid - 1;
} else {
// Found: return offset.
return mid;
}
}
// Not found: return insertion point.
return -(low + 1);
}
/**
* Verifies that the keys are in sort order and that undefined keys are
* [null].
*/
public final void assertKeysMonotonic() {
for (int i = 1; i < nkeys; i++) {
if (BytesUtil.compareBytes(keys[i], keys[i - 1]) <= 0) {
throw new AssertionError("Keys out of order at index=" + i
+ ", keys=" + this.toString());
}
}
for (int i = nkeys; i < keys.length; i++) {
if (keys[i] != null) {
throw new AssertionError("Expecting null at index=" + i);
}
}
}
/**
* Computes the length of the prefix by computed by counting the #of leading
* bytes that match for the first and last key in the buffer.
*/
public int getPrefixLength() {
if( nkeys == 0 ) return 0;
if( nkeys == 1 ) return keys[0].length;
return BytesUtil.getPrefixLength(keys[0], keys[nkeys - 1]);
}
/**
* Computes the #of leading bytes shared by all keys and returns a new
* byte[] containing those bytes.
*/
public byte[] getPrefix() {
if( nkeys == 0 ) return EMPTY_PREFIX;
if( nkeys == 1) return keys[0];
return BytesUtil.getPrefix(keys[0], keys[nkeys-1]);
}
private static final transient byte[] EMPTY_PREFIX = new byte[]{};
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy