/*
* $Id: IntHashMap.java,v 1.1.2.1 2007/01/12 19:32:19 idegaweb Exp $
* Created on 18.9.2004
*
* Copyright (C) 2004 Idega Software hf. All Rights Reserved.
*
* This software is the proprietary information of Idega hf.
* Use is subject to license terms.
*/
package com.idega.util.datastructures;
/**
* A hash map mapping int values to objects.
* This offers the benefit of not having to use objects as keys, which can result in performance benefits.
* Last modified: $Date: 2007/01/12 19:32:19 $ by $Author: idegaweb $
*
* @author <a href="mailto:aron@idega.com">aron</a>
* @version $Revision: 1.1.2.1 $
*/
public class IntHashMap {
/** The default capacity for hash map instances. */
public static final int DEFAULT_CAPACITY = 17;
/** The maximum allowed capacity for hash map instances. */
public static final int MAXIMUM_CAPACITY = 1 << 30;
/** The default load factor for hash map instances. */
public static final float DEFAULT_LOADFACTOR = 0.75f;
private MapElement[] map = null; // The first bucket for each key
private int[] count = null; // The count of buckets in each chain
private int contents = 0;
private int objectCounter = 0; // Counter for objects created
private int capacity = DEFAULT_CAPACITY;
private int initialCap = DEFAULT_CAPACITY;
private float loadFactor = DEFAULT_LOADFACTOR;
private int maxLoad = 0;
private boolean rehashing = true;
/**
* Constructs an empty instance with the default initial capacity and the default load factor
*/
public IntHashMap() {
this(DEFAULT_CAPACITY, DEFAULT_LOADFACTOR);
}
/**
* Constructs an empty instance with the given initial capacity and the default load factor
*
* <p></p>
*
* @param initialCapacity The initial capacity for this hash map.
*/
public IntHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOADFACTOR);
}
/**
* Constructs an empty instance with the given initial capacity and the given load factor
*
* <p></p>
*
* @param initialCapacity The initial capacity for this hash map.
* @param loadFactor The load factor for this hash map.
*/
public IntHashMap(int initialCapacity,
float loadFactor) {
construct(initialCapacity, loadFactor);
}
/**
* Constructs a new HashMap with the same mappings as the specified Map. The HashMap is created with default load factor and an initial capacity
* sufficient to hold the mappings in the specified Map.
*
* <p></p>
*
* @param m The map whose mappings are to be placed in this map. Throws: <p>
*
* @throws IllegalArgumentException if the specified map is<code>null</code>.
*/
public IntHashMap(IntHashMap m) {
if (m == null) {
throw new IllegalArgumentException("m may not be null");
}
//.... Determine parameters
this.loadFactor = DEFAULT_LOADFACTOR; // As per standard API for java.util.HashMap
this.capacity = (int) (m.size() / this.loadFactor);
if (this.capacity < DEFAULT_CAPACITY) { // Avoid underflow
this.capacity = DEFAULT_CAPACITY;
} else if ((this.capacity % 2) == 0) { // Make sure we have an odd value
this.capacity++;
}
//.... Standard initialization for the internal map elements
this.maxLoad = (int) ((this.loadFactor * this.capacity) + 0.5f); // Max. number of elements before a rehash occurs
this.initialCap = this.capacity;
this.objectCounter += 2;
this.map = new MapElement[this.capacity];
this.count = new int[this.capacity];
//.... Copy the elements to the new map
int[] keys = m.keySet();
for (int i = 0; i < m.size(); i++) {
put(keys[i], m.get(keys[i]));
}
}
/**
* Return the current number of mappings in the hash map.
*
* <p></p>
*
* @return The current number of mappings in the hash map.
*/
public int size() {
return this.contents;
}
/**
* Returns <code>true</code> if this map contains no key-value mappings.
*
* @return DOCUMENT ME!
*/
public boolean isEmpty() {
return (this.contents == 0) ? true : false;
}
/**
* Removes all mappings from this map.
*/
public void clear() {
construct(this.initialCap, this.loadFactor);
}
/**
* Return the number of objects created in / by this instance
*
* <p></p>
*
* @return The number of objects created
*/
public int getObjectCounter() {
return this.objectCounter;
}
/**
* Return the current capacity of the instance. If rehashing is enabled (which it is per default), the capacity may have been increased as
* necessary from the initial value.
*
* <p></p>
*
* @return The current capacity for this hash map.
*/
public int getCapacity() {
return this.capacity;
}
/**
* Return the load factor of the instance.
*
* <p></p>
*
* @return The load factor for this hash map.
*/
public float getLoadFactor() {
return this.loadFactor;
}
/**
* Return the keys in the hash map
*
* <p></p>
*
* @return An array containing the keys for which mappings are stored in this hash map.
*/
public int[] keySet() {
this.objectCounter++;
int[] keys = new int[this.contents];
int cnt = 0;
MapElement me = null;
for (int i = 0; i < this.capacity; i++) {
if (this.map[i] != null) {
me = this.map[i];
for (int j = 0; j < this.count[i]; j++) {
keys[cnt++] = me.getKey();
me = me.getNext();
}
}
}
return keys;
}
/**
* Enable/disable rehashing (defaults to <code>true</code>).
*
* <p></p>
*
* @param rehashing A boolean indicating the desired rehashing status.
*/
public void setRehash(boolean rehashing) {
this.rehashing = rehashing;
}
/**
* Associates the specified value with the specified key in this map. If the map previously contained a mapping for this key, the old value is
* replaced.
*
* <p></p>
*
* @param key The key with which the specified value is to be associated.
* @param value The value to be associated with the specified key. <p>
*
* @return Previous value associated with specified key, or <code>null</code> if there was no mapping for key. A <code>null</code> return can
* also indicate that the HashMap previously associated <code>null</code> with the specified key.
*/
public Object put(int key,
Object value) {
int index = key % this.capacity;
if (index < 0) {
index = -index;
}
//.... This is a new key since no bucket exists
if (this.map[index] == null) {
this.objectCounter++;
this.map[index] = new MapElement(key, value);
this.count[index]++;
this.contents++;
if (this.contents > this.maxLoad) {
rehash();
}
return null;
//.... A bucket already exists for this index: check whether we already have a mapping for this key
} else {
MapElement me = this.map[index];
while (true) {
if (me.getKey() == key) { // We have a mapping: just replace the value for this element
Object previous = me.getValue(); // Return the current value - same as for java.util.HashMap.put()
me.setValue(value);
return previous;
} else {
if (me.getNext() == null) { // No next element: so we have no mapping for this key
this.objectCounter++;
me.setNext(new MapElement(key, value));
this.count[index]++;
this.contents++;
if (this.contents > this.maxLoad) {
rehash();
}
return null;
} else {
me = me.getNext();
}
}
}
}
}
/**
* Returns the value to which the specified key is mapped in this identity hash map, or <code>null</code> if the map contains no mapping for
* this key. A return value of <code>null</code> does not necessarily indicate that the map contains no mapping for the key; it is also
* possible that the map explicitly maps the key to <code>null</code>. The <code>containsKey</code> method may be used to distinguish these
* two cases.
*
* <p></p>
*
* @param key The key whose associated value is to be returned. <p>
*
* @return The value to which this map maps the specified key, or <code>null</code> if the map contains no mapping for this key.
*/
public Object get(int key) {
MapElement me = exists(key);
if (me == null) {
return null;
} else {
return me.getValue();
}
}
/**
* Returns <code>true</code> if this map contains a mapping for the specified key.
*
* <p></p>
*
* @param key The key whose presence in this map is to be tested. <p>
*
* @return <code>true</code> if this map contains a mapping for the specified key.
*/
public boolean containsKey(int key) {
if (exists(key) == null) {
return false;
} else {
return true;
}
}
/**
* Removes the mapping for this key from this map if present.
*
* <p></p>
*
* @param key The key whose mapping is to be removed from the map. <p>
*
* @return Previous value associated with specified key, or <code>null</code> if there was no mapping for key. A <code>null</code> return can
* also indicate that the map previously associated <code>null</code> with the specified key.
*/
public Object remove(int key) {
int index = key % this.capacity;
if (index < 0) {
index = -index;
}
if (this.map[index] == null) {
return null;
} else {
MapElement me = this.map[index];
MapElement prev = null;
while (true) {
if (me.getKey() == key) { // Keys match
if (prev == null) { // The first element in the chain matches
this.map[index] = me.getNext();
} else { // An element further down in the chain matches - delete it from the chain
prev.setNext(me.getNext());
}
this.count[index]--;
this.contents--;
return me.getValue();
} else { // Keys don't match, try the next element
prev = me;
me = me.getNext();
if (me == null) {
return null;
}
}
}
}
}
/**
* Helper method: returns the element matching the key, or <code>null</code> if no such element exists
*
* @param key DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private MapElement exists(int key) {
int index = key % this.capacity;
if (index < 0) {
index = -index;
}
if (this.map[index] == null) {
return null;
} else {
MapElement me = this.map[index];
while (true) {
if (me.getKey() == key) {
return me;
} else {
me = me.getNext();
if (me == null) {
return null;
}
}
}
}
}
/**
* Increase the capacity of the map to improve performance
*/
private void rehash() {
if (this.rehashing) {
int newCapacity = (2 * this.capacity) + 1;
if (newCapacity > MAXIMUM_CAPACITY) {
return;
}
this.objectCounter += 2;
MapElement[] newMap = new MapElement[newCapacity];
int[] newCount = new int[newCapacity];
MapElement me = null;
MapElement t = null;
MapElement next = null;
int newIndex = 0;
for (int index = 0; index < this.capacity; index++) {
me = this.map[index];
while (me != null) {
next = me.getNext();
newIndex = me.getKey() % newCapacity;
if (newIndex < 0) {
newIndex = -newIndex;
}
newCount[newIndex]++;
if (newMap[newIndex] == null) { // No element yet for this new index
newMap[newIndex] = me;
me.setNext(null);
} else { // Hook the element into the beginning of the chain
t = newMap[newIndex];
newMap[newIndex] = me;
me.setNext(t);
}
me = next;
}
}
this.map = newMap;
this.count = newCount;
this.capacity = newCapacity;
this.maxLoad = (int) ((this.loadFactor * this.capacity) + 0.5f); // Max. number of elements before a rehash occurs
newMap = null;
}
}
/**
* Construction helper method
*
* @param initialCapacity DOCUMENT ME!
* @param loadFactor DOCUMENT ME!
*
* @throws IllegalArgumentException DOCUMENT ME!
*/
private void construct(int initialCapacity,
float loadFactor) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("Invalid initial capacity: " + initialCapacity);
}
if (initialCapacity < DEFAULT_CAPACITY) {
initialCapacity = DEFAULT_CAPACITY;
}
if (initialCapacity > MAXIMUM_CAPACITY) {
initialCapacity = MAXIMUM_CAPACITY;
}
if ((loadFactor <= 0.0f) || Float.isNaN(loadFactor)) {
throw new IllegalArgumentException("Invalid load factor: " + loadFactor);
}
this.initialCap = initialCapacity;
this.capacity = initialCapacity;
this.loadFactor = loadFactor;
this.objectCounter += 2;
this.maxLoad = (int) ((loadFactor * this.capacity) + 0.5f); // Max. number of elements before a rehash occurs
this.map = new MapElement[this.capacity];
this.count = new int[this.capacity];
this.contents = 0;
}
/**
* Statistical output for this map.
*
* <p></p>
*
* @param full A boolean indicating whether just a short of the full information should be printed.
*/
public void printStatistics(boolean full) {
if (full) {
for (int i = 0; i < this.capacity; i++) {
System.out.println("Count[" + i + "] = " + this.count[i]);
}
}
System.out.println("Initial capacity = " + this.initialCap);
System.out.println("Capacity = " + this.capacity);
System.out.println("Number of elements = " + this.contents);
}
/**
*
*/
class MapElement {
private int key = 0;
private Object value = null;
private MapElement next = null;
/**
* Constructor
*
* @param key DOCUMENT ME!
* @param value DOCUMENT ME!
*/
public MapElement(int key,
Object value) {
this.key = key;
this.value = value;
}
/**
* Getter method for <code>key</code> property
*
* <p></p>
*
* @return The value for the <code>key</code> property
*/
public int getKey() {
return this.key;
}
/**
* Setter method for <code>value</code> property
*
* <p></p>
*
* @param value The value for the <code>value</code> property
*/
public void setValue(Object value) {
this.value = value;
}
/**
* Getter method for <code>value</code> property
*
* <p></p>
*
* @return The value for the <code>value</code> property
*/
public Object getValue() {
return this.value;
}
/**
* Setter method for <code>next</code> property
*
* <p></p>
*
* @param next The value for the <code>next</code> property
*/
public void setNext(MapElement next) {
this.next = next;
}
/**
* Getter method for <code>next</code> property
*
* <p></p>
*
* @return The value for the <code>next</code> property
*/
public MapElement getNext() {
return this.next;
}
}
}