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 { 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 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 flv ) { for( int i=1; i>= 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; } }