Grow and shrink the hash so that our performance stays near O(1).

(Altered Serializable compatability).
Reformatted tabs into 8 spaces.


git-svn-id: https://samskivert.googlecode.com/svn/trunk@755 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
ray
2002-05-23 23:24:08 +00:00
parent 18cf7f6763
commit ea1c96d2d7
@@ -1,5 +1,5 @@
// //
// $Id: HashIntMap.java,v 1.7 2001/12/16 23:10:15 mdb Exp $ // $Id: HashIntMap.java,v 1.8 2002/05/23 23:24:08 ray Exp $
// //
// samskivert library - useful routines for java programs // samskivert library - useful routines for java programs
// Copyright (C) 2001 Michael Bayne // Copyright (C) 2001 Michael Bayne
@@ -46,13 +46,32 @@ public class HashIntMap
*/ */
public final static int DEFAULT_BUCKETS = 64; public final static int DEFAULT_BUCKETS = 64;
/**
* The default load factor.
*/
public final static float DEFAULT_LOAD_FACTOR = 1.75f;
/**
* The shrink factor.
* When the number of elements multiplied by this number is less
* than the size of the array, we shrink the array by half.
*/
protected static final int SHRINK_FACTOR = 8;
/** /**
* Constructs an empty hash int map with the specified number of hash * Constructs an empty hash int map with the specified number of hash
* buckets. * buckets.
*/ */
public HashIntMap (int buckets) public HashIntMap (int buckets, float loadFactor)
{ {
_buckets = new Record[buckets]; // force the capacity to be a power of 2
int capacity = 1;
while (capacity < buckets) {
capacity <<= 1;
}
_buckets = new Record[capacity];
_loadFactor = loadFactor;
} }
/** /**
@@ -61,13 +80,13 @@ public class HashIntMap
*/ */
public HashIntMap () public HashIntMap ()
{ {
this(DEFAULT_BUCKETS); this(DEFAULT_BUCKETS, DEFAULT_LOAD_FACTOR);
} }
// documentation inherited // documentation inherited
public int size () public int size ()
{ {
return _size; return _size;
} }
// documentation inherited // documentation inherited
@@ -79,7 +98,7 @@ public class HashIntMap
// documentation inherited // documentation inherited
public boolean containsKey (int key) public boolean containsKey (int key)
{ {
return get(key) != null; return get(key) != null;
} }
// documentation inherited // documentation inherited
@@ -104,13 +123,13 @@ public class HashIntMap
// documentation inherited // documentation inherited
public Object get (int key) public Object get (int key)
{ {
int index = Math.abs(key) % _buckets.length; int index = keyToIndex(key);
for (Record rec = _buckets[index]; rec != null; rec = rec.next) { for (Record rec = _buckets[index]; rec != null; rec = rec.next) {
if (rec.key == key) { if (rec.key == key) {
return rec.value; return rec.value;
} }
} }
return null; return null;
} }
// documentation inherited // documentation inherited
@@ -127,30 +146,33 @@ public class HashIntMap
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
int index = Math.abs(key)%_buckets.length; // check to see if we've passed our load factor, if so: resize
Record rec = _buckets[index]; checkGrow();
// either we start a new chain int index = keyToIndex(key);
if (rec == null) { Record rec = _buckets[index];
_buckets[index] = new Record(key, value);
_size++; // we're bigger
return null;
}
// or we replace an element in an existing chain // either we start a new chain
Record prev = rec; if (rec == null) {
for (; rec != null; rec = rec.next) { _buckets[index] = new Record(key, value);
if (rec.key == key) { _size++; // we're bigger
return null;
}
// or we replace an element in an existing chain
Record prev = rec;
for (; rec != null; rec = rec.next) {
if (rec.key == key) {
Object ovalue = rec.value; Object ovalue = rec.value;
rec.value = value; // we're not bigger rec.value = value; // we're not bigger
return ovalue; return ovalue;
} }
prev = rec; prev = rec;
} }
// or we append it to this chain // or we append it to this chain
prev.next = new Record(key, value); prev.next = new Record(key, value);
_size++; // we're bigger _size++; // we're bigger
return null; return null;
} }
@@ -163,43 +185,103 @@ public class HashIntMap
// documentation inherited // documentation inherited
public Object remove (int key) public Object remove (int key)
{ {
int index = Math.abs(key) % _buckets.length; Object removed = removeImpl(key);
Record rec = _buckets[index]; if (removed != null) {
checkShrink();
}
return removed;
}
// if there's no chain, there's no object /**
if (rec == null) { * Remove an element with no checking to see if we should shrink.
return null; */
} protected Object removeImpl (int key)
{
int index = keyToIndex(key);
Record prev = null;
// maybe it's the first one in this chain // go through the chain looking for a match
if (rec.key == key) { for (Record rec = _buckets[index]; rec != null; rec = rec.next) {
_buckets[index] = rec.next; if (rec.key == key) {
_size--; if (prev == null) {
return rec.value; _buckets[index] = rec.next;
} } else {
prev.next = rec.next;
}
_size--;
return rec.value;
}
prev = rec;
}
// or maybe it's an element further down the chain return null;
for (Record prev = rec; rec != null; rec = rec.next) {
if (rec.key == key) {
prev.next = rec.next;
_size--;
return rec.value;
}
prev = rec;
}
return null;
} }
// documentation inherited // documentation inherited
public void clear () public void clear ()
{ {
// abandon all of our hash chains (the joy of garbage collection) // abandon all of our hash chains (the joy of garbage collection)
for (int i = 0; i < _buckets.length; i++) { for (int i = 0; i < _buckets.length; i++) {
_buckets[i] = null; _buckets[i] = null;
} }
// zero out our size // zero out our size
_size = 0; _size = 0;
}
/**
* Turn the specified key into an index.
*/
protected final int keyToIndex (int key)
{
// multiply the key by -127 and take the low bits
return ((key - (key << 7)) & (_buckets.length - 1));
}
/**
* Check to see if we want to shrink the table.
*/
protected void checkShrink ()
{
if ((_buckets.length > DEFAULT_BUCKETS) &&
(_size * SHRINK_FACTOR < _buckets.length)) {
resizeBuckets(_buckets.length >> 1);
}
}
/**
* Check to see if we want to grow the table.
*/
protected void checkGrow ()
{
if (_size > (int) (_buckets.length * _loadFactor)) {
resizeBuckets(_buckets.length << 1);
}
}
/**
* Resize the hashtable.
*
* @param newsize MUST be a power of 2.
*/
protected void resizeBuckets (int newsize)
{
Record[] oldbuckets = _buckets;
_buckets = new Record[newsize];
// we shuffle the records around without allocating new ones
int index = oldbuckets.length;
while (index-- > 0) {
Record oldrec = oldbuckets[index];
while (oldrec != null) {
Record newrec = oldrec;
oldrec = oldrec.next;
// always put the newrec at the start of a chain
int newdex = keyToIndex(newrec.key);
newrec.next = _buckets[newdex];
_buckets[newdex] = newrec;
}
}
} }
// documentation inherited // documentation inherited
@@ -268,7 +350,7 @@ public class HashIntMap
} }
// remove the record the hard way // remove the record the hard way
HashIntMap.this.remove(_last.key); HashIntMap.this.removeImpl(_last.key);
_last = null; _last = null;
} }
@@ -300,11 +382,12 @@ public class HashIntMap
private void writeObject (ObjectOutputStream s) private void writeObject (ObjectOutputStream s)
throws IOException throws IOException
{ {
// write out number of buckets // write out number of buckets
s.writeInt(_buckets.length); s.writeInt(_buckets.length);
s.writeFloat(_loadFactor);
// write out size (number of mappings) // write out size (number of mappings)
s.writeInt(_size); s.writeInt(_size);
// write out keys and values // write out keys and values
for (Iterator i = entrySet().iterator(); i.hasNext(); ) { for (Iterator i = entrySet().iterator(); i.hasNext(); ) {
@@ -321,50 +404,51 @@ public class HashIntMap
private void readObject (ObjectInputStream s) private void readObject (ObjectInputStream s)
throws IOException, ClassNotFoundException throws IOException, ClassNotFoundException
{ {
// read in number of buckets and allocate the bucket array // read in number of buckets and allocate the bucket array
_buckets = new Record[s.readInt()]; _buckets = new Record[s.readInt()];
_loadFactor = s.readFloat();
// read in size (number of mappings) // read in size (number of mappings)
int size = s.readInt(); int size = s.readInt();
// read the keys and values // read the keys and values
for (int i=0; i<size; i++) { for (int i=0; i<size; i++) {
int key = s.readInt(); int key = s.readInt();
Object value = s.readObject(); Object value = s.readObject();
put(key, value); put(key, value);
} }
} }
protected static class Record implements Entry protected static class Record implements Entry
{ {
public Record next; public Record next;
public int key; public int key;
public Object value; public Object value;
public Record (int key, Object value) public Record (int key, Object value)
{ {
this.key = key; this.key = key;
this.value = value; this.value = value;
} }
public Object getKey () public Object getKey ()
{ {
return new Integer(key); return new Integer(key);
} }
public Object getValue () public Object getValue ()
{ {
return value; return value;
} }
public Object setValue (Object value) public Object setValue (Object value)
{ {
Object ovalue = this.value; Object ovalue = this.value;
this.value = value; this.value = value;
return ovalue; return ovalue;
} }
public boolean equals (Object o) public boolean equals (Object o)
{ {
if (o instanceof Record) { if (o instanceof Record) {
Record or = (Record)o; Record or = (Record)o;
@@ -374,7 +458,7 @@ public class HashIntMap
} }
} }
public int hashCode () public int hashCode ()
{ {
return key ^ ((value == null) ? 0 : value.hashCode()); return key ^ ((value == null) ? 0 : value.hashCode());
} }
@@ -385,6 +469,7 @@ public class HashIntMap
} }
} }
private Record[] _buckets; protected Record[] _buckets;
private int _size; protected int _size;
protected float _loadFactor;
} }