- /**
- * filename: HashTable.java
- * package:
- * author: Nick Ma
- * email: nick.ma85@yahoo.com
- * date: Nov 27, 2008
- * description: this class implements a hash table data structure.
- */
- import java.util.*;
- public class HashTable<K,V> implements Map<K,V> {
- /**
- * the inner class of hash table to implement a key-value pair
- */
- public static class Entry<K,V> implements Map.Entry<K,V> {
- // Data Field
- private K key; // the key
- private V value; // the value
- private Entry<K,V> next; // pointer to next entry with the same key
- /** this field is to store the hash code, for preventing from
- * frequently reading the code by calling hashCode() function.
- */
- private int hash;
- /**
- * description: the default constructor
- */
- public Entry() {
- // TODO Auto-generated constructor stub
- this ( null , null , null );
- }
- /**
- * description: the constructor with fields to create
- * a new key-value pair.
- * @param key - the key
- * @param value - the value
- * @param next - the next entry
- */
- public Entry(K key, V value, Entry<K,V> next) {
- this .key = key;
- this .value = value;
- this .hash = this .setHashCode();
- this .next = next;
- }
- /* (non-Javadoc)
- * @see java.util.Map.Entry#getKey()
- */
- @Override
- public K getKey() {
- // TODO Auto-generated method stub
- return this .key;
- }
- /* (non-Javadoc)
- * @see java.util.Map.Entry#getValue()
- */
- @Override
- public V getValue() {
- // TODO Auto-generated method stub
- return this .value;
- }
- /* (non-Javadoc)
- * @see java.util.Map.Entry#setValue(java.lang.Object)
- */
- @Override
- public V setValue(V object) {
- // TODO Auto-generated method stub
- V old = this .value;
- this .value = object;
- this .hash = this .setHashCode();
- return old;
- }
- /**
- * @return the next
- */
- public Entry<K, V> getNext() {
- return next;
- }
- /**
- * @param next the next to set
- */
- public void setNext(Entry<K, V> next) {
- this .next = next;
- }
- /* (non-Javadoc)
- * @see java.lang.Object#clone()
- */
- @Override
- protected Object clone() throws CloneNotSupportedException {
- // TODO Auto-generated method stub
- return new Entry<K,V>( this .key, this .value, this .next);
- }
- /* (non-Javadoc)
- * @see java.lang.Object#hashCode()
- */
- @Override
- public int hashCode() {
- // TODO Auto-generated method stub
- return this .hash;
- }
- /* (non-Javadoc)
- * @see java.lang.Object#equals(java.lang.Object)
- */
- @Override
- public boolean equals(Object o) {
- // TODO Auto-generated method stub
- //check for self-comparison
- if ( this == o){
- return true ;
- }
- // check for class type
- // here is an alternative:
- // if(o == null || o.getClass() != this.getClass())
- if (!(o instanceof Entry)) {
- return false ;
- }
- // cast to native object is now safe
- Entry<K,V> entry = (Entry<K,V>)o;
- // now a proper field-by-field evaluation can be made,
- // and it has nothing to do with the field of next pointer,
- // just comparing the pair, key and value.
- return entry.key.equals( this .key)
- && entry.value.equals( this .value)
- && entry.hash == this .hash;
- }
- /* (non-Javadoc)
- * @see java.lang.Object#toString()
- */
- @Override
- public String toString() {
- // TODO Auto-generated method stub
- return getKey() + "=" + getValue();
- }
- /**
- * description: set the hash code
- * @return - the hash code
- */
- private int setHashCode() {
- return ( this .key == null ) ? 0 : this .key.hashCode();
- }
- }
- // Data Field
- /** a table to store data, which is resized as necessary */
- private Entry[] table;
- /** the number of key-value mappings contained in the table but not in the chain */
- private int numOfEntries;
- /** the number of key-value mappings contained in the table */
- private int numOfPairs;
- /** the number of deleted entries */
- private int numOfDeletedEntries;
- /** the upper bound of load factor */
- private double loadFactor;
- /** the default size of the table */
- private static final int DEFAULT_SIZE = 16 ;
- /**
- * the maximum capacity, used if a higher value is implicitly specified
- * by either of the constructors with arguments.
- */
- private static final int MAXIMUM_SIZE = 1 << 30 ;
- /** the default load factor */
- private static final double LOAD_FACTOR = 0.75 ;
- /** the deleted entry to reference */
- private final Entry<K,V> DELETED_ENTRY = new Entry<K,V>();
- /** the table to store the possible hash table size */
- private int [] sizeTable = new int [ 30 ];
- /** the current index of size table */
- private int currentSizeTableIndex;
- /** variables of statistics */
- private int numOfCollision; // the number of collisions during insertions
- private int totalChainLength; // total chain length during all the insertion and look-up
- private int avgChainLength; // average chain length, totalChainLength/countChains
- private int countChains; // counter of insertion and look-up
- private int maxChainLength; // max chain length during an insertion or look-up
- /**
- * description: the default constructor
- */
- public HashTable() {
- // TODO Auto-generated constructor stub
- this (HashTable.DEFAULT_SIZE, HashTable.LOAD_FACTOR);
- }
- /**
- * description: the constructor to initialize the table with the given size
- * @param size - the user-defined size of the table
- */
- public HashTable( int size) {
- this (size, HashTable.LOAD_FACTOR);
- }
- /**
- * description: the constructor to initialize the table with the given size and load factor
- * @param size - the user-defined size of the table
- * @param loadFactor - the user-defined max load factor of this hash table
- * @throws IllegalArgumentException - if the size and the load factor is illegal
- */
- public HashTable( int size, double loadFactor) {
- if (size < 0 ) {
- throw new IllegalArgumentException(
- "Illegal initial capacity: " + size);
- }
- if (loadFactor <= 0 || Double.isNaN(loadFactor)) {
- throw new IllegalArgumentException(
- "Illegal load factor: " + loadFactor);
- }
- // using power-of-two sizes to initialize the hash table
- initializeSizeTable();
- // get the current size of entries from size table
- for ( int i = 0 ; i < sizeTable.length; i++) {
- if (sizeTable[i] == size) {
- this .currentSizeTableIndex = i;
- }
- }
- // initialize the entries of the hash table
- this .table = new Entry[ this .sizeTable[ this .currentSizeTableIndex]];
- this .numOfEntries = 0 ;
- this .numOfPairs = 0 ;
- this .numOfDeletedEntries = 0 ;
- this .loadFactor = loadFactor;
- this .totalChainLength = 0 ;
- this .maxChainLength = 0 ;
- this .avgChainLength = 0 ;
- this .numOfCollision = 0 ;
- this .countChains = 0 ;
- }
- /* (non-Javadoc)
- * @see java.util.Map#clear()
- */
- @Override
- public void clear() {
- // TODO Auto-generated method stub
- Entry[] tab = this .table;
- for ( int i = 0 ; i < tab.length; i++) {
- tab[i] = null ;
- }
- this .numOfEntries = 0 ;
- this .numOfPairs = 0 ;
- this .numOfDeletedEntries = 0 ;
- this .totalChainLength = 0 ;
- this .maxChainLength = 0 ;
- this .avgChainLength = 0 ;
- this .numOfCollision = 0 ;
- this .countChains = 0 ;
- this .loadFactor = HashTable.LOAD_FACTOR;
- }
- /* (non-Javadoc)
- * @see java.util.Map#containsKey(java.lang.Object)
- */
- @Override
- public boolean containsKey(Object arg0) {
- // TODO Auto-generated method stub
- return this .getEntry((K)arg0) != null ;
- }
- /* (non-Javadoc)
- * @see java.util.Map#containsValue(java.lang.Object)
- */
- @Override
- public boolean containsValue(Object arg0) {
- // TODO Auto-generated method stub
- if (arg0 == null ) {
- Entry[] tab = this .table;
- for ( int i = 0 ; i < tab.length ; i++) {
- if (tab[i] != null ) {
- countChains++; // increment the counter of totalChainLength
- }
- for (Entry<K,V> e = tab[i]; e != null ; e = e.next) {
- if (e.getValue() == null ) {
- return true ;
- }
- this .totalChainLength++;
- }
- }
- } else {
- Entry[] tab = this .table;
- for ( int i = 0 ; i < tab.length ; i++) {
- if (tab[i] != null ) {
- countChains++; // increment the counter of totalChainLength
- }
- for (Entry<K,V> e = tab[i]; e != null ; e = e.next) {
- if (e.getValue().equals((V)arg0)) {
- return true ;
- }
- this .totalChainLength++;
- }
- }
- }
- return false ;
- }
- /* (non-Javadoc)
- * @see java.util.Map#get(java.lang.Object)
- */
- @Override
- public V get(Object arg0) {
- // TODO Auto-generated method stub
- return this .getEntry((K)arg0).getValue();
- }
- /* (non-Javadoc)
- * @see java.util.Map#isEmpty()
- */
- @Override
- public boolean isEmpty() {
- // TODO Auto-generated method stub
- return this .numOfEntries == 0 ;
- }
- /* (non-Javadoc)
- * @see java.util.Map#put(java.lang.Object, java.lang.Object)
- */
- @Override
- public V put(K arg0, V arg1) {
- // TODO Auto-generated method stub
- int hash = (arg0 == null ) ? 0 : hash(arg0.hashCode());
- int index = this .indexFor(hash, this .table.length);
- Entry<K,V> entry = this .table[index];
- if (entry != null && ! this .isDeletedEntry(entry)) {
- countChains++; // increment the counter of totalChainLength
- for (Entry<K,V> e = entry;
- e != null ; e = e.getNext()) {
- if (e.hash == hash && (arg0 == e.getKey() || e.getKey().equals(arg0))) {
- return e.setValue(arg1);
- }
- this .numOfCollision++;
- this .totalChainLength++;
- if (e.getNext() == null ) {
- // add to the chain
- e.setNext( new Entry<K,V>(arg0, arg1, null ));
- break ;
- }
- }
- } else {
- // add to entry
- this .table[index] = new Entry<K,V>(arg0, arg1, null );
- this .numOfEntries++;
- }
- this .numOfPairs++;
- // make sure the load factor is less than or equal to the user-defined one
- // and make sure the number of collisions is small enough
- // (less than the length of the hash table)
- // otherwise, increment the size of the current table
- if ( this .numOfEntries >= this .table.length * this .loadFactor
- || this .numOfCollision > this .table.length) {
- this .resize();
- }
- return null ;
- }
- /* (non-Javadoc)
- * @see java.util.Map#remove(java.lang.Object)
- */
- @Override
- public V remove(Object arg0) {
- // TODO Auto-generated method stub
- int hash = (arg0 == null ) ? 0 : hash(((K)arg0).hashCode());
- int index = this .indexFor(hash, this .table.length);
- Entry<K,V> prev = this .table[index];
- Entry<K,V> e = prev;
- if (e != null ) {
- countChains++; // increment the counter of totalChainLength
- }
- while (e != null ) {
- if (e.hash == hash && (e.getKey() == (K)arg0
- || (arg0 != null && ((K)arg0).equals(e.getKey())))) {
- this .numOfPairs--;
- // if the entry which needs to be deleted is in the array
- if (prev == e) {
- if (e.getNext() != null ) {
- table[index] = e.getNext();
- } else {
- table[index] = this .DELETED_ENTRY;
- this .numOfEntries--;
- this .numOfDeletedEntries++;
- }
- } else {
- // the entry which needs to be deleted is in the chain
- prev.setNext(e.getNext());
- }
- return e.getValue();
- }
- this .totalChainLength++;
- // go to next entry
- prev = e;
- e = e.getNext();
- }
- return null ;
- }
- /* (non-Javadoc)
- * @see java.util.Map#size()
- */
- @Override
- public int size() {
- // TODO Auto-generated method stub
- return this .numOfEntries;
- }
- /* (non-Javadoc)
- * @see java.lang.Object#toString()
- */
- @Override
- public String toString() {
- // TODO Auto-generated method stub
- StringBuilder sb = new StringBuilder();
- //sb.append("no_entries: " + this.numOfEntries + "/n");
- //sb.append("no_length: " + this.table.length + "/n");
- //sb.append("no_pairs: " + this.numOfPairs + "/n");
- sb.append( "no_collisions: " + this .getNumOfCollision() + "/n" );
- sb.append( "average_chain_length: " + this .getAvgChainLength() + "/n" );
- sb.append( "max_chain_length: " + this .getMaxChainLength() + "/n" );
- sb.append( "load_factor: " + this .getCurrentLoadFactor() + "/n" );
- return sb.toString();
- }
- /**
- * @return the current load factor
- */
- public double getCurrentLoadFactor() {
- return ( double ) this .numOfEntries / ( double ) this .table.length;
- }
- /**
- * @return the numOfCollision
- */
- public int getNumOfCollision() {
- return this .numOfCollision;
- }
- /**
- * @return the avgChainLength
- */
- public int getAvgChainLength() {
- this .setAvgChainLength();
- return this .avgChainLength;
- }
- /**
- * @return the maxChainLength
- */
- public int getMaxChainLength() {
- Entry[] tab = this .table;
- int maxLength = 0 ;
- for ( int i = 0 ; i < tab.length ; i++) {
- for (Entry<K,V> e = tab[i];
- e != null && ! this .isDeletedEntry(e); e = e.getNext()) {
- maxLength++;
- }
- if (maxLength > this .maxChainLength) {
- this .maxChainLength = maxLength;
- }
- maxLength = 0 ;
- }
- return this .maxChainLength;
- }
- /* (non-Javadoc)
- * @see java.util.Map#putAll(java.util.Map)
- */
- @Override
- public void putAll(Map<? extends K, ? extends V> arg0) {
- // TODO Auto-generated method stub
- Iterator<?> iterator = arg0.entrySet().iterator();
- while (iterator.hasNext()) {
- Entry<K,V> entry = (Entry<K,V>)(iterator.next());
- this .put(entry.getKey(), entry.getValue());
- }
- }
- /* (non-Javadoc)
- * @see java.util.Map#values()
- */
- @Override
- public Collection<V> values() {
- // TODO Auto-generated method stub
- Collection<V> list = new ArrayList<V>();
- for ( int i = 0 ; i < this .table.length; i++) {
- for (Entry<K,V> e = table[i];
- e != null ; e = e.getNext()) {
- list.add(e.getValue());
- }
- }
- return list;
- }
- /* (non-Javadoc)
- * @see java.util.Map#keySet()
- */
- @Override
- public Set<K> keySet() {
- // TODO Auto-generated method stub
- HashSet<K> set = new HashSet<K>();
- for ( int i = 0 ; i < this .table.length; i++) {
- for (Entry<K,V> e = table[i];
- e != null ; e = e.getNext()) {
- set.add(e.getKey());
- }
- }
- return set;
- }
- /* (non-Javadoc)
- * @see java.util.Map#entrySet()
- */
- @Override
- public Set<java.util.Map.Entry<K, V>> entrySet() {
- // TODO Auto-generated method stub
- HashSet<java.util.Map.Entry<K, V>> set = new HashSet<java.util.Map.Entry<K, V>>();
- for ( int i = 0 ; i < this .table.length; i++) {
- for (Entry<K,V> e = table[i];
- e != null ; e = e.getNext()) {
- set.add(e);
- }
- }
- return set;
- }
- /**
- * description: return the index for a hash code
- * @param hash - the hash code
- * @param length - the length of the hash table
- * @return - the index for the hash code
- */
- private int indexFor( int hash, int length) {
- // mapping the hash code to the index by mod operation
- return hash & (length -1);
- }
- /**
- * description: returns the entry associated with the specified key
- * @param key - the key
- * @return - the entry with the specified key
- */
- private Entry<K,V> getEntry(K key) {
- // get the hash code
- int hash = (key == null ) ? 0 : hash(key.hashCode());
- // find the corresponding entry with the specified key by the hash code
- // by iterating the hash table
- Entry<K,V> entry = this .table[ this .indexFor(hash, this .table.length)];
- if (entry != null && ! this .isDeletedEntry(entry)) {
- countChains++; // increment the counter of totalChainLength
- }
- for (Entry<K,V> e = entry;
- e != null && ! this .isDeletedEntry(e); e = e.getNext()) {
- if (key != null && key.equals(e.getKey())) {
- return e;
- }
- this .totalChainLength++;
- }
- return null ;
- }
- /**
- * @param avgChainLength the avgChainLength to set
- */
- private void setAvgChainLength() {
- if ( this .countChains == 0 ) {
- this .avgChainLength = 0 ;
- } else {
- double avg = ( double ) this .totalChainLength / ( double ) this .countChains;
- this .avgChainLength = ( int )Math.ceil(avg);
- }
- }
- /**
- * description: transfers all entries from current table to newTable
- * @param newTable - the new table
- */
- private void transfer(Entry[] newTable) {
- Entry[] src = this .table;
- for ( int i = 0 ; i < src.length; i++) {
- Entry<K,V> e = src[i];
- if (e != null && ! this .isDeletedEntry(e)) {
- src[i] = null ;
- }
- while (e != null && ! this .isDeletedEntry(e)) {
- // get every single entry in the old table
- // then store it to the new table with new index
- int index = this .indexFor(hash(e.hashCode()), newTable.length);
- Entry<K,V> next = e.getNext();
- e.setNext( null );
- if (newTable[index] != null ) {
- for (Entry<K,V> entry = newTable[index];
- entry != null ; entry = entry.getNext()) {
- if (entry.getNext() == null ) {
- entry.setNext(e);
- break ;
- }
- }
- } else {
- newTable[index] = e;
- }
- e = next;
- }
- }
- }
- /**
- * description: resize the contents of this map into a new array
- * with a larger capacity. This method is called automatically
- * when the number of keys in this map reaches its threshold.
- */
- private void resize() {
- int index = ++ this .currentSizeTableIndex;
- int newSize = 0 ;
- if (index >= this .sizeTable.length) {
- newSize = HashTable.MAXIMUM_SIZE;
- } else {
- // get the new size from the size table
- newSize = this .sizeTable[ this .currentSizeTableIndex];
- }
- //System.out.println(newSize);
- Entry[] newTable = new Entry[newSize];
- this .transfer(newTable);
- this .table = newTable;
- }
- /**
- * description: determine if the entry's deleted entry
- * @param entry - the entry
- * @return - true if it is deleted entry
- */
- private boolean isDeletedEntry(Entry<K,V> entry) {
- return entry == this .DELETED_ENTRY;
- }
- /**
- * description: initialize the size table using power-of-two size
- */
- private void initializeSizeTable() {
- int init = 1 ;
- for ( int i = 0 ; i < sizeTable.length; i++) {
- init = init << 1 ;
- sizeTable[i] = init;
- }
- }
- /**
- * description: make sure the hash code isn't negative
- * @param hashCode - the original hash code
- * @return - the legal hash code
- */
- private static int hash( int hashCode) {
- return hashCode & 0x7FFFFFFF ;
- }
- }
Hash Table
最新推荐文章于 2024-05-30 15:28:19 发布