HashTable
Attribute members
//hash table array private transient Entry<?,?>[] table; //Number of elements private transient int count; //Expansion threshold private int threshold; //Load factor private float loadFactor; //Number of changes private transient int modCount = 0;
Constructor
/** non-parameter constructor Construct an empty hash table with default initial capacity (11) and load factor (0.75). */ public Hashtable() { this(11, 0.75f); } /** Constructs a new empty hash table with the specified initial capacity and default load factor (0.75). @param initialCapacity Initial capacity of the hash table. @exception IllegalArgumentException Throw an exception if initial capacity is less than zero */ public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } /** Constructs a new empty hash table using the specified initial capacity and specified load factor. @param initialCapacity Initial capacity of the hash table. @param loadFactor Load factor for hash table. @exception IllegalArgumentException If the initial capacity is less than zero or the load factor is non-positive. */ public Hashtable(int initialCapacity, float loadFactor) { //If the specified capacity is less than 0, error occurs when no storage is needed if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); //If the load factor is less than 0, or the loadFactor is a non-numeric number if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load: "+loadFactor); //Set to 1 if initial capacity is empty if (initialCapacity==0) initialCapacity = 1; //Load factor assignment this.loadFactor = loadFactor; //Initialize table table = new Entry<?,?>[initialCapacity]; //Expansion threshold assignment threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); } /** Construct a new hash table with the same mapping relationship as the given Map. The initial capacity of the hash table is sufficient to accommodate the mapping and default load factor (0.75) in a given Map. @param The map whose mapping will be placed in this map. If the specified mapping is empty, @throws NullPointerException. */ public Hashtable(Map<? extends K, ? extends V> t) { //Set the capacity to twice the incoming map, or use the default capacity of 11 if it is less than the default capacity this(Math.max(2*t.size(), 11), 0.75f); //Put the incoming hash table t into the table putAll(t); }
hash value and index calculation
hash value
- hash value calculation in HashTable directly uses the hashcode() method of Object
- HashMap(1.8):
// 1. Take hashCode value: h = key.hashCode() // 2. High Participates in Low Operations: H ^ (h >>>> 16) static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
index
- The index subscript in HashTable (the subscript to the bit bucket array) directly uses the hash value to take the remainder of the table length (hash & 0x7FFFFFFF)%tab.length;
- Index calculation of HashMap(1.8): the result and operation (&) (array length-1) after perturbing the hash code are obtained, and the position stored in the array table (that is, array subscript, index) H & (length-1) is obtained.
Core approach
Thread Security
HashTable prefixes the core method with the synchronized keyword, which is expressed as a synchronization method to achieve the purpose of thread synchronization. However, this method is very inefficient in the case of intense thread competition. Because when one thread accesses the HashTable's synchronization method, other threads may enter a blocking or polling state when they access the HashTable's synchronization method. If Thread 1 uses put to add elements, Thread 2 cannot use either the put method to add elements or the get method to get elements, so the more intense the competition, the less efficient it is.
put method
public synchronized V put(K key, V value): The key-value pair stored in a key cannot be null for both the key-value key and the value
/** The stored key-value pair key-value key and value cannot be null. The value can be retrieved by calling the get method with the same key as the original key. @param key Hash table key @ param value value @return Specify the previous value of the key in this hash table, or null if it does not have an @exception NullPointerException if the key or value is null @see Objectequals(Object)@see get(Object) */ public synchronized V put(K key, V value) { //Ensure that the value is not empty if (value == null) { throw new NullPointerException(); } //Create a tab array to point to a table Entry<?,?> tab[] = table; //Calculate hash value int hash = key.hashCode(); //Bit bucket subscript for key (hash value balances table length) int index = (hash & 0x7FFFFFFF) % tab.length; //Create a bit bucket head node whose entry points to the corresponding location @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; //Traversing a list of chains for(; entry != null ; entry = entry.next) { //Determine whether the hash value and key of the node are equal to the incoming if ((entry.hash == hash) && entry.key.equals(key)) { //If old values are saved equally V old = entry.value; //Assigning new values entry.value = value; //Return Old Value return old; } } //If no nodes with equal key s are found, call the addEntry method to add the nodes to the table addEntry(hash, key, value, index); //Return empty return null; } private void addEntry(int hash, K key, V value, int index) { //Number of Changes+1 modCount++; //Create Pointer to Save table Address Entry<?,?> tab[] = table; //If Number of Elements > Expansion Threshold if (count >= threshold) { // If the threshold is exceeded, the list is re-hashed (expanded) rehash(); //Point to New Table tab = table; //Recalculate hash value hash = key.hashCode(); //Calculate Location index = (hash & 0x7FFFFFFF) % tab.length; } // Create a new node //Bit bucket array index position //Create secondary pointer e to header node @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; //The head insertion node is placed in the bucket, and the next pointer of the new node points to e (the origin node) to complete the head insertion tab[index] = new Entry<>(hash, key, value, e); //Number of elements + 1 count++; }
resize() expansion method
protected void rehash() { //Save old capacity int oldCapacity = table.length; //oldMap saves old tables Entry<?,?>[] oldMap = table; // New capacity is twice the old capacity+1 int newCapacity = (oldCapacity << 1) + 1; //If new capacity > integer max-8 if (newCapacity - MAX_ARRAY_SIZE > 0) { //If old capacity is integer max-8 if (oldCapacity == MAX_ARRAY_SIZE) // Continue running with old capacity return; //Old capacity set to integer max-8 newCapacity = MAX_ARRAY_SIZE; } //Create hash tables with newCapacity capacity Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; //Number of changes+ modCount++; //Recalculating the expansion threshold threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); //Table points to a new table table = newMap; //Traversing through old tables for data transfer for (int i = oldCapacity ; i-- > 0 ;) { //Traverse from the last bucket, old pointing to the chain header node for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { //Auxiliary pointer e first points to the head node Entry<K,V> e = old; //old Move Back old = old.next; //Calculate New Location int index = (e.hash & 0x7FFFFFFF) % newCapacity; //next of e points to the header node of the corresponding location of the new table e.next = (Entry<K,V>)newMap[index]; //Head Interpolation (e already connected to the origin node) newMap[index] = e; } } }
get method
public synchronized V get(Object key): Returns the value to which the specified key is mapped, or null if the map does not contain a key mapping
public synchronized V get(Object key) { Entry<?,?> tab[] = table; //Calculate hash value of key int hash = key.hashCode(); //Calculate Subscript int index = (hash & 0x7FFFFFFF) % tab.length; //Traverse the bucket at the corresponding position, starting from the first node for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { //Returns the hash value and key if they are found equal to the incoming value if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } //No return null found return null; }
remove method
public synchronized V remove(Object key): Delete the key (and its corresponding value) from this hash table. This method does nothing if the key is not in the hash table.
/** @param key Key to delete @return The value to which the key is mapped in this hash table, or null if the key is not mapped */ public synchronized V remove(Object key) { Entry<?,?> tab[] = table; //Calculate hash value int hash = key.hashCode(); //Calculate Location int index = (hash & 0x7FFFFFFF) % tab.length; //Auxiliary Pointer to Head Node @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; //prev records the previous node, e is the current node //Traversing bucket for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) { //If the hash value corresponds to the key and node if ((e.hash == hash) && e.key.equals(key)) { //Number of Changes+1 modCount++; if (prev != null) { //The previous node points to the last node of the current node (crossing the current node is equivalent to deleting) prev.next = e.next; } else { //If the header node is the one to be deleted, set the header node to the next node of the header node tab[index] = e.next; } //Number of elements-1 count--; //Save old values V oldValue = e.value; //Set the value of e to null e.value = null; //Return Old Value return oldValue; } } //If no corresponding return null is found return null; }
Differences between HashMap and Hashtable
-
HashMap is non-thread-safe, Hashtable is thread-safe because the synchronized keyword is used to ensure thread security.
-
HashMap allows both key and value to be null, while Hashtable cannot be null.
-
Hashtable and HashMap do not expand in the same way. The default size of arrays in Hashtable is 11, expanded in old*2+1. The default size of arrays in HashMap is 16, and must be an exponent of 2, which doubles the original size.
-
The hash values are hashed into hash tables using different algorithms. Hashtable is an old method of dividing residuals that directly uses the hashcode of Object, which is the power of the compulsory capacity of 2. Recalculating hash values from hashcode is also equivalent to using hash and (hash table length-1), but it is more efficient, achieves a more dispersed and even number of locations. Odd numbers are guaranteed to be spread out. The former cannot be guaranteed.