328 lines
7.4 KiB
Java
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;
|
|
}
|
|
|
|
}
|