HashTable Source Parsing

Posted by jjfletch on Tue, 09 Nov 2021 06:52:29 +0100

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.

Topics: Java JDK Algorithm