SCViewer/com.minres.scviewer.databas.../src/org/apache/jdbm/BTree.java

707 lines
21 KiB
Java
Raw Blame History

/*******************************************************************************
* Copyright 2010 Cees De Groot, Alex Boisvert, Jan Kotek
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.apache.jdbm;
import java.io.*;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* B+Tree persistent indexing data structure. B+Trees are optimized for
* block-based, random I/O storage because they store multiple keys on
* one tree node (called <code>BTreeNode</code>). In addition, the leaf nodes
* directly contain (inline) small values associated with the keys, allowing a
* single (or sequential) disk read of all the values on the node.
* <p/>
* B+Trees are n-airy, yeilding log(N) search cost. They are self-balancing,
* preventing search performance degradation when the size of the tree grows.
* <p/>
* BTree stores its keys sorted. By default JDBM expects key to implement
* <code>Comparable</code> interface but user may supply its own <code>Comparator</code>
* at BTree creation time. Comparator is serialized and stored as part of BTree.
* <p/>
* The B+Tree allows traversing the keys in forward and reverse order using a
* TupleBrowser obtained from the browse() methods. But it is better to use
* <code>BTreeMap</code> wrapper which implements <code>SortedMap</code> interface
* <p/>
* This implementation does not directly support duplicate keys. It is
* possible to handle duplicates by grouping values using an ArrayList as value.
* This scenario is supported by JDBM serialization so there is no big performance penalty.
* <p/>
* There is no limit on key size or value size, but it is recommended to keep
* keys as small as possible to reduce disk I/O. If serialized value exceeds 32 bytes,
* it is stored in separate record and tree contains only recid reference to it.
* BTree uses delta compression for its keys.
*
*
* @author Alex Boisvert
* @author Jan Kotek
*/
class BTree<K, V> {
private static final boolean DEBUG = false;
/**
* Default node size (number of entries per node)
*/
public static final int DEFAULT_SIZE = 32; //TODO test optimal size, it has serious impact on sequencial write and read
/**
* Record manager used to persist changes in BTreeNodes
*/
protected transient DBAbstract _db;
/**
* This BTree's record ID in the DB.
*/
private transient long _recid;
/**
* Comparator used to index entries (optional)
*/
protected Comparator<K> _comparator;
/**
* Serializer used to serialize index keys (optional)
*/
protected Serializer<K> keySerializer;
/**
* Serializer used to serialize index values (optional)
*/
protected Serializer<V> valueSerializer;
/**
* indicates if values should be loaded during deserialization, set to false during defragmentation
*/
boolean loadValues = true;
/** if false map contains only keys, used for set*/
boolean hasValues = true;
/**
* The number of structural modifications to the tree for fail fast iterators. This value is just for runtime, it is not persisted
*/
transient int modCount = 0;
/**
* cached instance of an insert result, so we do not have to allocate new object on each insert
*/
protected BTreeNode.InsertResult<K, V> insertResultReuse; //TODO investigate performance impact of removing this
public Serializer<K> getKeySerializer() {
return keySerializer;
}
public Serializer<V> getValueSerializer() {
return valueSerializer;
}
/**
* Height of the B+Tree. This is the number of BTreeNodes you have to traverse
* to get to a leaf BTreeNode, starting from the root.
*/
private int _height;
/**
* Recid of the root BTreeNode
*/
private transient long _root;
/**
* Total number of entries in the BTree
*/
protected volatile long _entries;
/**
* Serializer used for BTreeNodes of this tree
*/
private transient BTreeNode<K, V> _nodeSerializer = new BTreeNode();
{
_nodeSerializer._btree = this;
}
/**
* Listeners which are notified about changes in records
*/
protected RecordListener[] recordListeners = new RecordListener[0];
final protected ReadWriteLock lock = new ReentrantReadWriteLock();
/**
* No-argument constructor used by serialization.
*/
public BTree() {
// empty
}
/**
* Create a new persistent BTree
*/
@SuppressWarnings("unchecked")
public static <K extends Comparable, V> BTree<K, V> createInstance(DBAbstract db)
throws IOException {
return createInstance(db, null, null, null,true);
}
/**
* Create a new persistent BTree
*/
public static <K, V> BTree<K, V> createInstance(DBAbstract db,
Comparator<K> comparator,
Serializer<K> keySerializer,
Serializer<V> valueSerializer,
boolean hasValues)
throws IOException {
BTree<K, V> btree;
if (db == null) {
throw new IllegalArgumentException("Argument 'db' is null");
}
btree = new BTree<K, V>();
btree._db = db;
btree._comparator = comparator;
btree.keySerializer = keySerializer;
btree.valueSerializer = valueSerializer;
btree.hasValues = hasValues;
btree._recid = db.insert(btree, btree.getRecordManager().defaultSerializer(),false);
return btree;
}
/**
* Load a persistent BTree.
*
* @param db DB used to store the persistent btree
* @param recid Record id of the BTree
*/
@SuppressWarnings("unchecked")
public static <K, V> BTree<K, V> load(DBAbstract db, long recid)
throws IOException {
BTree<K, V> btree = (BTree<K, V>) db.fetch(recid);
btree._recid = recid;
btree._db = db;
btree._nodeSerializer = new BTreeNode<K, V>();
btree._nodeSerializer._btree = btree;
return btree;
}
/**
* Get the {@link ReadWriteLock} associated with this BTree.
* This should be used with browsing operations to ensure
* consistency.
*
* @return
*/
public ReadWriteLock getLock() {
return lock;
}
/**
* Insert an entry in the BTree.
* <p/>
* The BTree cannot store duplicate entries. An existing entry can be
* replaced using the <code>replace</code> flag. If an entry with the
* same key already exists in the BTree, its value is returned.
*
* @param key Insert key
* @param value Insert value
* @param replace Set to true to replace an existing key-value pair.
* @return Existing value, if any.
*/
public V insert(final K key, final V value,
final boolean replace)
throws IOException {
if (key == null) {
throw new IllegalArgumentException("Argument 'key' is null");
}
if (value == null) {
throw new IllegalArgumentException("Argument 'value' is null");
}
try {
lock.writeLock().lock();
BTreeNode<K, V> rootNode = getRoot();
if (rootNode == null) {
// BTree is currently empty, create a new root BTreeNode
if (DEBUG) {
System.out.println("BTree.insert() new root BTreeNode");
}
rootNode = new BTreeNode<K, V>(this, key, value);
_root = rootNode._recid;
_height = 1;
_entries = 1;
_db.update(_recid, this);
modCount++;
//notifi listeners
for (RecordListener<K, V> l : recordListeners) {
l.recordInserted(key, value);
}
return null;
} else {
BTreeNode.InsertResult<K, V> insert = rootNode.insert(_height, key, value, replace);
boolean dirty = false;
if (insert._overflow != null) {
// current root node overflowed, we replace with a new root node
if (DEBUG) {
System.out.println("BTreeNode.insert() replace root BTreeNode due to overflow");
}
rootNode = new BTreeNode<K, V>(this, rootNode, insert._overflow);
_root = rootNode._recid;
_height += 1;
dirty = true;
}
if (insert._existing == null) {
_entries++;
modCount++;
dirty = true;
}
if (dirty) {
_db.update(_recid, this);
}
//notify listeners
for (RecordListener<K, V> l : recordListeners) {
if (insert._existing == null)
l.recordInserted(key, value);
else
l.recordUpdated(key, insert._existing, value);
}
// insert might have returned an existing value
V ret = insert._existing;
//zero out tuple and put it for reuse
insert._existing = null;
insert._overflow = null;
this.insertResultReuse = insert;
return ret;
}
} finally {
lock.writeLock().unlock();
}
}
/**
* Remove an entry with the given key from the BTree.
*
* @param key Removal key
* @return Value associated with the key, or null if no entry with given
* key existed in the BTree.
*/
public V remove(K key)
throws IOException {
if (key == null) {
throw new IllegalArgumentException("Argument 'key' is null");
}
try {
lock.writeLock().lock();
BTreeNode<K, V> rootNode = getRoot();
if (rootNode == null) {
return null;
}
boolean dirty = false;
BTreeNode.RemoveResult<K, V> remove = rootNode.remove(_height, key);
if (remove._underflow && rootNode.isEmpty()) {
_height -= 1;
dirty = true;
_db.delete(_root);
if (_height == 0) {
_root = 0;
} else {
_root = rootNode.loadLastChildNode()._recid;
}
}
if (remove._value != null) {
_entries--;
modCount++;
dirty = true;
}
if (dirty) {
_db.update(_recid, this);
}
if (remove._value != null)
for (RecordListener<K, V> l : recordListeners)
l.recordRemoved(key, remove._value);
return remove._value;
} finally {
lock.writeLock().unlock();
}
}
/**
* Find the value associated with the given key.
*
* @param key Lookup key.
* @return Value associated with the key, or null if not found.
*/
public V get(K key)
throws IOException {
if (key == null) {
throw new IllegalArgumentException("Argument 'key' is null");
}
try {
lock.readLock().lock();
BTreeNode<K, V> rootNode = getRoot();
if (rootNode == null) {
return null;
}
return rootNode.findValue(_height, key);
} finally {
lock.readLock().unlock();
}
}
/**
* Find the value associated with the given key, or the entry immediately
* following this key in the ordered BTree.
*
* @param key Lookup key.
* @return Value associated with the key, or a greater entry, or null if no
* greater entry was found.
*/
public BTreeTuple<K, V> findGreaterOrEqual(K key)
throws IOException {
BTreeTuple<K, V> tuple;
BTreeTupleBrowser<K, V> browser;
if (key == null) {
// there can't be a key greater than or equal to "null"
// because null is considered an infinite key.
return null;
}
tuple = new BTreeTuple<K, V>(null, null);
browser = browse(key,true);
if (browser.getNext(tuple)) {
return tuple;
} else {
return null;
}
}
/**
* Get a browser initially positioned at the beginning of the BTree.
* <p><b>
* WARNING: If you make structural modifications to the BTree during
* browsing, you will get inconsistent browing results.
* </b>
*
* @return Browser positionned at the beginning of the BTree.
*/
@SuppressWarnings("unchecked")
public BTreeTupleBrowser<K, V> browse()
throws IOException {
try {
lock.readLock().lock();
BTreeNode<K, V> rootNode = getRoot();
if (rootNode == null) {
return EMPTY_BROWSER;
}
return rootNode.findFirst();
} finally {
lock.readLock().unlock();
}
}
/**
* Get a browser initially positioned just before the given key.
* <p><b>
* WARNING: <20>If you make structural modifications to the BTree during
* browsing, you will get inconsistent browing results.
* </b>
*
* @param key Key used to position the browser. If null, the browser
* will be positionned after the last entry of the BTree.
* (Null is considered to be an "infinite" key)
* @return Browser positionned just before the given key.
*/
@SuppressWarnings("unchecked")
public BTreeTupleBrowser<K, V> browse(final K key, final boolean inclusive)
throws IOException {
try {
lock.readLock().lock();
BTreeNode<K, V> rootNode = getRoot();
if (rootNode == null) {
return EMPTY_BROWSER;
}
BTreeTupleBrowser<K, V> browser = rootNode.find(_height, key, inclusive);
return browser;
} finally {
lock.readLock().unlock();
}
}
/**
* Return the persistent record identifier of the BTree.
*/
public long getRecid() {
return _recid;
}
/**
* Return the root BTreeNode, or null if it doesn't exist.
*/
BTreeNode<K, V> getRoot()
throws IOException {
if (_root == 0) {
return null;
}
BTreeNode<K, V> root = _db.fetch(_root, _nodeSerializer);
if (root != null) {
root._recid = _root;
root._btree = this;
}
return root;
}
static BTree readExternal(DataInput in, Serialization ser)
throws IOException, ClassNotFoundException {
BTree tree = new BTree();
tree._db = ser.db;
tree._height = in.readInt();
tree._recid = in.readLong();
tree._root = in.readLong();
tree._entries = in.readLong();
tree.hasValues = in.readBoolean();
tree._comparator = (Comparator) ser.deserialize(in);
tree.keySerializer = (Serializer) ser.deserialize(in);
tree.valueSerializer = (Serializer) ser.deserialize(in);
return tree;
}
public void writeExternal(DataOutput out)
throws IOException {
out.writeInt(_height);
out.writeLong(_recid);
out.writeLong(_root);
out.writeLong(_entries);
out.writeBoolean(hasValues);
_db.defaultSerializer().serialize(out, _comparator);
_db.defaultSerializer().serialize(out, keySerializer);
_db.defaultSerializer().serialize(out, valueSerializer);
}
/**
* Copyes tree from one db to other, defragmenting it allong the way
* @param recid
* @param r1
* @param r2
* @throws IOException
*/
public static void defrag(long recid, DBStore r1, DBStore r2) throws IOException {
try {
byte[] data = r1.fetchRaw(recid);
r2.forceInsert(recid, data);
DataInput in = new DataInputOutput(data);
BTree t = (BTree) r1.defaultSerializer().deserialize(in);
t.loadValues = false;
t._db = r1;
t._nodeSerializer = new BTreeNode(t, false);
BTreeNode p = t.getRoot();
if (p != null) {
r2.forceInsert(t._root, r1.fetchRaw(t._root));
p.defrag(r1, r2);
}
} catch (ClassNotFoundException e) {
throw new IOError(e);
}
}
/**
* Browser returning no element.
*/
private static final BTreeTupleBrowser EMPTY_BROWSER = new BTreeTupleBrowser() {
public boolean getNext(BTreeTuple tuple) {
return false;
}
public boolean getPrevious(BTreeTuple tuple) {
return false;
}
public void remove(Object key) {
throw new IndexOutOfBoundsException();
}
};
/**
* add RecordListener which is notified about record changes
*
* @param listener
*/
public void addRecordListener(RecordListener<K, V> listener) {
recordListeners = Arrays.copyOf(recordListeners, recordListeners.length + 1);
recordListeners[recordListeners.length - 1] = listener;
}
/**
* remove RecordListener which is notified about record changes
*
* @param listener
*/
public void removeRecordListener(RecordListener<K, V> listener) {
List l = Arrays.asList(recordListeners);
l.remove(listener);
recordListeners = (RecordListener[]) l.toArray(new RecordListener[1]);
}
public DBAbstract getRecordManager() {
return _db;
}
public Comparator<K> getComparator() {
return _comparator;
}
/**
* Deletes all BTreeNodes in this BTree
*/
public void clear()
throws IOException {
try {
lock.writeLock().lock();
BTreeNode<K, V> rootNode = getRoot();
if (rootNode != null)
rootNode.delete();
_entries = 0;
modCount++;
} finally {
lock.writeLock().unlock();
}
}
/**
* Used for debugging and testing only. Populates the 'out' list with
* the recids of all child nodes in the BTree.
*
* @param out
* @throws IOException
*/
void dumpChildNodeRecIDs(List<Long> out) throws IOException {
BTreeNode<K, V> root = getRoot();
if (root != null) {
out.add(root._recid);
root.dumpChildNodeRecIDs(out, _height);
}
}
public boolean hasValues() {
return hasValues;
}
/**
* Browser to traverse a collection of tuples. The browser allows for
* forward and reverse order traversal.
*
*
*/
static interface BTreeTupleBrowser<K, V> {
/**
* Get the next tuple.
*
* @param tuple Tuple into which values are copied.
* @return True if values have been copied in tuple, or false if there is no next tuple.
*/
boolean getNext(BTree.BTreeTuple<K, V> tuple) throws IOException;
/**
* Get the previous tuple.
*
* @param tuple Tuple into which values are copied.
* @return True if values have been copied in tuple, or false if there is no previous tuple.
*/
boolean getPrevious(BTree.BTreeTuple<K, V> tuple) throws IOException;
/**
* Remove an entry with given key, and increases browsers expectedModCount
* This method is here to support 'ConcurrentModificationException' on Map interface.
*
* @param key
*/
void remove(K key) throws IOException;
}
/**
* Tuple consisting of a key-value pair.
*/
static final class BTreeTuple<K, V> {
K key;
V value;
BTreeTuple() {
// empty
}
BTreeTuple(K key, V value) {
this.key = key;
this.value = value;
}
}
}