All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.graphhopper.coll.GHLongIntBTree Maven / Gradle / Ivy

/*
 *  Licensed to GraphHopper GmbH under one or more contributor
 *  license agreements. See the NOTICE file distributed with this work for
 *  additional information regarding copyright ownership.
 *
 *  GraphHopper GmbH licenses this file to you 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 com.graphhopper.coll;

import com.graphhopper.util.Helper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

/**
 * An in-memory simple B-Tree. Later we'll use DataAccess to allow on-disc storage for very large
 * data sets. Delete not supported.
 * 

* * @author Peter Karich */ public class GHLongIntBTree implements LongIntMap { private final int noNumberValue = -1; private Logger logger = LoggerFactory.getLogger(getClass()); private long size; private int maxLeafEntries; private int initLeafSize; private int splitIndex; private float factor; private int height; private BTreeEntry root; public GHLongIntBTree(int maxLeafEntries) { this.maxLeafEntries = maxLeafEntries; if (maxLeafEntries < 1) { throw new IllegalArgumentException("illegal maxLeafEntries:" + maxLeafEntries); } if (maxLeafEntries % 2 == 0) { maxLeafEntries++; } splitIndex = maxLeafEntries / 2; if (maxLeafEntries < 10) { factor = 2; initLeafSize = 1; } else if (maxLeafEntries < 20) { factor = 2; initLeafSize = 4; } else { factor = 1.7f; initLeafSize = maxLeafEntries / 10; } clear(); } // LATER: see OSMIDMap for a version where we use DataAccess static int binarySearch(long keys[], int start, int len, long key) { int high = start + len, low = start - 1, guess; while (high - low > 1) { // use >>> for average or we could get an integer overflow. guess = (high + low) >>> 1; long guessedKey = keys[guess]; if (guessedKey < key) { low = guess; } else { high = guess; } } if (high == start + len) { return ~(start + len); } long highKey = keys[high]; if (highKey == key) { return high; } else { return ~high; } } @Override public int put(long key, int value) { if (key == noNumberValue) { throw new IllegalArgumentException("Illegal key " + key); } ReturnValue rv = root.put(key, value); if (rv.tree != null) { height++; root = rv.tree; } if (rv.oldValue == noNumberValue) { // successfully inserted size++; if (size % 1000000 == 0) optimize(); } return rv.oldValue; } @Override public int get(long key) { return root.get(key); } int height() { return height; } @Override public long getSize() { return size; } /** * @return memory usage in MB */ @Override public int getMemoryUsage() { return Math.round(root.getCapacity() / Helper.MB); } void clear() { size = 0; height = 1; root = new BTreeEntry(initLeafSize, true); } int getNoNumberValue() { return noNumberValue; } void flush() { throw new IllegalStateException("not supported yet"); } private int getEntries() { return root.getEntries(); } @Override public void optimize() { if (getSize() > 10000) { // StopWatch sw = new StopWatch().start(); // int old = memoryUsage(); root.compact(); // logger.info(size + "| osmIdMap.optimize took: " + sw.stop().getSeconds() // + " => freed: " + (old - memoryUsage()) + "MB"); } } @Override public String toString() { return "Height:" + height() + ", entries:" + getEntries(); } void print() { logger.info(root.toString(1)); } static class ReturnValue { int oldValue; BTreeEntry tree; public ReturnValue() { } public ReturnValue(int oldValue) { this.oldValue = oldValue; } } class BTreeEntry { int entrySize; long keys[]; int values[]; BTreeEntry children[]; boolean isLeaf; public BTreeEntry(int tmpSize, boolean leaf) { this.isLeaf = leaf; keys = new long[tmpSize]; values = new int[tmpSize]; if (!isLeaf) { // in a b-tree we need one more entry to point to all children! children = new BTreeEntry[tmpSize + 1]; } } /** * @return the old value which was associated with the specified key or if no update it * returns noNumberValue */ ReturnValue put(long key, int newValue) { int index = binarySearch(keys, 0, entrySize, key); if (index >= 0) { // update int oldValue = values[index]; values[index] = newValue; return new ReturnValue(oldValue); } index = ~index; ReturnValue downTreeRV; if (isLeaf || children[index] == null) { // insert downTreeRV = new ReturnValue(noNumberValue); downTreeRV.tree = checkSplitEntry(); if (downTreeRV.tree == null) { insertKeyValue(index, key, newValue); } else if (index <= splitIndex) { downTreeRV.tree.children[0].insertKeyValue(index, key, newValue); } else { downTreeRV.tree.children[1].insertKeyValue(index - splitIndex - 1, key, newValue); } return downTreeRV; } downTreeRV = children[index].put(key, newValue); if (downTreeRV.oldValue != noNumberValue) // only update { return downTreeRV; } if (downTreeRV.tree != null) { // split this treeEntry if it is too big BTreeEntry returnTree, downTree = returnTree = checkSplitEntry(); if (downTree == null) { insertTree(index, downTreeRV.tree); } else if (index <= splitIndex) { downTree.children[0].insertTree(index, downTreeRV.tree); } else { downTree.children[1].insertTree(index - splitIndex - 1, downTreeRV.tree); } downTreeRV.tree = returnTree; } return downTreeRV; } /** * @return null if nothing to do or a new sub tree if this tree capacity is no longer * sufficient. */ BTreeEntry checkSplitEntry() { if (entrySize < maxLeafEntries) { return null; } // right child: copy from this int count = entrySize - splitIndex - 1; BTreeEntry newRightChild = new BTreeEntry(Math.max(initLeafSize, count), isLeaf); copy(this, newRightChild, splitIndex + 1, count); // left child: copy from this // avoid: http://stackoverflow.com/q/15897869/194609 BTreeEntry newLeftChild = new BTreeEntry(Math.max(initLeafSize, splitIndex), isLeaf); copy(this, newLeftChild, 0, splitIndex); // new tree pointing to left + right tree only BTreeEntry newTree = new BTreeEntry(1, false); newTree.entrySize = 1; newTree.keys[0] = this.keys[splitIndex]; newTree.values[0] = this.values[splitIndex]; newTree.children[0] = newLeftChild; newTree.children[1] = newRightChild; return newTree; } void copy(BTreeEntry fromChild, BTreeEntry toChild, int from, int count) { System.arraycopy(fromChild.keys, from, toChild.keys, 0, count); System.arraycopy(fromChild.values, from, toChild.values, 0, count); if (!fromChild.isLeaf) { System.arraycopy(fromChild.children, from, toChild.children, 0, count + 1); } toChild.entrySize = count; } void insertKeyValue(int index, long key, int newValue) { ensureSize(entrySize + 1); int count = entrySize - index; if (count > 0) { System.arraycopy(keys, index, keys, index + 1, count); System.arraycopy(values, index, values, index + 1, count); if (!isLeaf) { System.arraycopy(children, index + 1, children, index + 2, count); } } keys[index] = key; values[index] = newValue; entrySize++; } void insertTree(int index, BTreeEntry tree) { insertKeyValue(index, tree.keys[0], tree.values[0]); if (!isLeaf) { // overwrite children children[index] = tree.children[0]; // set children[index + 1] = tree.children[1]; } } int get(long key) { int index = binarySearch(keys, 0, entrySize, key); if (index >= 0) { return values[index]; } index = ~index; if (isLeaf || children[index] == null) { return noNumberValue; } return children[index].get(key); } /** * @return used bytes */ long getCapacity() { long cap = keys.length * (8 + 4) + 3 * 12 + 4 + 1; if (!isLeaf) { cap += children.length * 4; for (int i = 0; i < children.length; i++) { if (children[i] != null) { cap += children[i].getCapacity(); } } } return cap; } int getEntries() { int entries = 1; if (!isLeaf) { for (int i = 0; i < children.length; i++) { if (children[i] != null) { entries += children[i].getEntries(); } } } return entries; } void ensureSize(int size) { if (size <= keys.length) { return; } int newSize = Math.min(maxLeafEntries, Math.max(size + 1, Math.round(size * factor))); keys = Arrays.copyOf(keys, newSize); values = Arrays.copyOf(values, newSize); if (!isLeaf) { children = Arrays.copyOf(children, newSize + 1); } } void compact() { int tolerance = 1; if (entrySize + tolerance < keys.length) { keys = Arrays.copyOf(keys, entrySize); values = Arrays.copyOf(values, entrySize); if (!isLeaf) { children = Arrays.copyOf(children, entrySize + 1); } } if (!isLeaf) { for (int i = 0; i < children.length; i++) { if (children[i] != null) { children[i].compact(); } } } } String toString(int height) { String str = height + ": "; for (int i = 0; i < entrySize; i++) { if (i > 0) { str += ","; } if (keys[i] == noNumberValue) { str += "-"; } else { str += keys[i]; } } str += "\n"; if (!isLeaf) { for (int i = 0; i < entrySize + 1; i++) { if (children[i] != null) { str += children[i].toString(height + 1) + "| "; } } } return str; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy