brouter/brouter-util/src/main/java/btools/util/CompactLongMap.java
2016-11-20 22:31:10 +01:00

328 lines
7.4 KiB
Java

package btools.util;
import java.util.ArrayList;
/**
* Memory efficient Map to map a long-key to an object-value
*
* Implementation is such that basically the 12 bytes
* per entry is allocated that's needed to store
* a long- and an object-value.
* This class does not implement the Map interface
* because it's not complete (remove() is not implemented,
* CompactLongMap can only grow.)
*
* @author ab
*/
public class CompactLongMap<V>
{
private long[][] al;
private int[] pa;
private int size = 0;
private int _maxKeepExponent = 14; // the maximum exponent to keep the invalid arrays
protected V value_in;
protected V value_out;
protected static final int MAXLISTS = 31; // enough for size Integer.MAX_VALUE
private static boolean earlyDuplicateCheck;
public CompactLongMap()
{
// pointer array
pa = new int[MAXLISTS];
// allocate key lists
al = new long[MAXLISTS][];
al[0] = new long[1]; // make the first array (the transient buffer)
// same for the values
vla = new Object[MAXLISTS][];
vla[0] = new Object[1];
earlyDuplicateCheck = Boolean.getBoolean( "earlyDuplicateCheck" );
}
/*
*
* The Map extension:
* next 5 protected methods are needed to implement value-support
* overwrite them all to support value structures other than the
* long-values implemented here as a sample.
*
* Furthermore, put() and get() method need to be implemented
* to access the values.
*
* Note that this map does not behave exactly like java.util.Map
* - put(..) with already existing key throws exception
* - get(..) with non-existing key thros exception
*
* If you have keys that cannot easily be mapped on long's, use
* a hash-function to do the mapping. But note that, in comparison
* to java.util.HashMap, in that case the keys itself are not saved,
* only the hash-values, so you need to be sure that random duplicate
* hashs are either excluded by the structure of your data or that
* you can handle the possible IllegalArgumentException
*
*/
private Object[][] vla; // value list array
public boolean put( long id, V value )
{
try
{
value_in = value;
if ( contains( id, true ) )
{
return true;
}
vla[0][0] = value;
_add( id );
return false;
}
finally
{
value_in = null;
value_out = null;
}
}
/**
* Same as put( id, value ) but duplicate check
* is skipped for performance. Be aware that you
* can get a duplicate exception later on if the
* map is restructured!
* with System parameter earlyDuplicateCheck=true you
* can enforce the early duplicate check for debugging
*
* @param id the key to insert
* @param value the value to insert object
* @exception IllegalArgumentException for duplicates if enabled
*/
public void fastPut( long id, V value )
{
if ( earlyDuplicateCheck && contains( id ) )
{
throw new IllegalArgumentException( "duplicate key found in early check: " + id );
}
vla[0][0] = value;
_add( id );
}
/**
* Get the value for the given id
* @param id the key to query
* @return the object, or null if id not known
*/
public V get( long id )
{
try
{
if ( contains( id, false ) )
{
return value_out;
}
return null;
}
finally
{
value_out = null;
}
}
/**
* @return the number of entries in this map
*/
public int size()
{
return size;
}
private boolean _add( long id )
{
if ( size == Integer.MAX_VALUE )
{
throw new IllegalArgumentException( "cannot grow beyond size Integer.MAX_VALUE" );
}
// put the new entry in the first array
al[0][0] = id;
// determine the first empty array
int bp = size++; // treat size as bitpattern
int idx = 1;
int n = 1;
pa[0] = 1;
pa[1] = 1;
while ( (bp&1) == 1 )
{
bp >>= 1;
pa[idx++] = n;
n <<= 1;
}
// create it if not existant
if ( al[idx] == null )
{
al[idx] = new long[n];
vla[idx] = new Object[n];
}
// now merge the contents of arrays 0...idx-1 into idx
while ( n > 0 )
{
long maxId = 0;
int maxIdx = -1;
for ( int i=0; i<idx; i++ )
{
int p = pa[i];
if ( p > 0 )
{
long currentId = al[i][p-1];
if ( maxIdx < 0 || currentId > maxId )
{
maxIdx = i;
maxId = currentId;
}
}
}
// current maximum found, copy to target array
if ( n < al[idx].length && maxId == al[idx][n] )
{
throw new IllegalArgumentException( "duplicate key found in late check: " + maxId );
}
--n;
al[idx][n] = maxId;
vla[idx][n] = vla[maxIdx][pa[maxIdx]-1];
--pa[maxIdx];
}
// de-allocate empty arrays of a certain size (fix at 64kByte)
while ( idx-- > _maxKeepExponent )
{
al[idx] = null;
vla[idx] = null;
}
return false;
}
/**
* @return true if "id" is contained in this set.
*/
public boolean contains( long id )
{
try
{
return contains( id, false );
}
finally
{
value_out = null;
}
}
protected boolean contains( long id, boolean doPut )
{
// determine the first empty array
int bp = size; // treat size as bitpattern
int idx = 1;
while ( bp != 0 )
{
if ( (bp&1) == 1 )
{
// array at idx is valid, check
if ( contains( idx, id, doPut ) )
{
return true;
}
}
idx++;
bp >>= 1;
}
return false;
}
// does sorted array "a" contain "id" ?
private boolean contains( int idx, long id, boolean doPut )
{
long[] a = al[idx];
int offset = a.length;
int n = 0;
while ( (offset >>= 1) > 0 )
{
int nn = n + offset;
if ( a[nn] <= id )
{
n = nn;
}
}
if ( a[n] == id )
{
value_out = (V)vla[idx][n];
if ( doPut ) vla[idx][n] = value_in;
return true;
}
return false;
}
protected void moveToFrozenArrays( long[] faid, ArrayList<V> flv )
{
for( int i=1; i<MAXLISTS; i++ )
{
pa[i] = 0;
}
for( int ti = 0; ti < size; ti++ ) // target-index
{
int bp = size; // treat size as bitpattern
int minIdx = -1;
long minId = 0;
int idx = 1;
while ( bp != 0 )
{
if ( (bp&1) == 1 )
{
int p = pa[idx];
if ( p < al[idx].length )
{
long currentId = al[idx][p];
if ( minIdx < 0 || currentId < minId )
{
minIdx = idx;
minId = currentId;
}
}
}
idx++;
bp >>= 1;
}
faid[ti] = minId;
flv.add( (V)vla[minIdx][pa[minIdx]] );
pa[minIdx]++;
if ( ti > 0 && faid[ti-1] == minId )
{
throw new IllegalArgumentException( "duplicate key found in late check: " + minId );
}
}
// free the non-frozen arrays
al = null;
vla = null;
}
}