(28) HashTable

Preface: I wrote HashMap, ArrayList and LinkedList before, but I haven't learned HashTable yet. This article will learn HashTable.

PS: The source code analysis is for the jdk of Android O, that is, jdk1.8


Reference blog: What is the difference between HashMap and HashTable?


Simple demo: jiatai's hashTable demo

1. A brief introduction to HashTable

The data structure used by HashTable in terms of implementation is a hash table, which is similar to HashMap. Friendly reminder, HashTable is outdated. Now, HashMap is recommended for non-multi-threaded scenarios, and ConcurrentHashMap is recommended for multi-threaded scenarios. Although HashTable supports concurrency, it is realized by adding Synchronized, which is estimated to be out of date due to efficiency considerations.

Java Collections Framework</a>.  Unlike the new collection
 * implementations, {@code Hashtable} is synchronized.  If a
 * thread-safe implementation is not needed, it is recommended to use
 * {@link HashMap} in place of {@code Hashtable}.  If a thread-safe
 * highly-concurrent implementation is desired, then it is recommended
 * to use {@link java.util.concurrent.ConcurrentHashMap} in place of
 * {@code Hashtable}.


2. HashTable is simple to use

Write a paragraph about the use mentioned in the hashTable source code comments:
package com.example.demo_28_hashtable;

import java.util.Hashtable;

public class MyClass {
    public static void main(String[] args){
        Hashtable<String, Integer> numbers = new Hashtable<String, Integer>();
        numbers.put("one", 1);
        numbers.put("two", 2);
        numbers.put("three", 3);
        System.out.println(numbers);

        Integer n = numbers.get("two");
        if (n != null) {
            System.out.println("two = " + n);
        }
    }
}


3. Difference between HashTable and HahsMap

Since HashTable is very similar to hashMap, and hashTable is outdated, this article mainly introduces the difference between HashTable and HashMap.

3.1 Different inheritance systems

The reference blog describes the inheritance relationship in detail and lists the public methods, as follows:


As can be seen from the figure, the inheritance system of the two classes is somewhat different. Although they all implement the three interfaces of Map, Cloneable and Serializable. But HashMap inherits from the abstract class AbstractMap, and HashTable inherits from the abstract class Dictionary, which is a class that has been abandoned.

/**
 * The <code>Dictionary</code> class is the abstract parent of any
 * class, such as <code>Hashtable</code>, which maps keys to values.
 * Every key and every value is an object. In any one <tt>Dictionary</tt>
 * object, every key is associated with at most one value. Given a
 * <tt>Dictionary</tt> and a key, the associated element can be looked up.
 * Any non-<code>null</code> object can be used as a key and as a value.
 * <p>
 * As a rule, the <code>equals</code> method should be used by
 * implementations of this class to decide if two keys are the same.
 * <p>
 * <strong>NOTE: This class is obsolete.  New implementations should
 * implement the Map interface, rather than extending this class.</strong>
 *
 * @author  unascribed
 * @see     java.util.Map
 * @see     java.lang.Object#equals(java.lang.Object)
 * @see     java.lang.Object#hashCode()
 * esee java.util.Hashtable
 * @since   JDK1.0
 */
public abstract
class Dictionary<K,V> {


3.2 Null key & Null Value

hashTable does not support empty key or value. Looking at the put method of HashTable, the code clearly determines whether the value is empty, and if it is empty, a null pointer exception is thrown. If the key is empty, calling key.hashCode() directly will result in a null pointer.

/**
     * Maps the specified <code>key</code> to the specified
     * <code>value</code> in this hashtable. Neither the key nor the
     * value can be <code>null</code>. <p>
     *
     * The value can be retrieved by calling the <code>get</code> method
     * with a key that is equal to the original key.
     *
     * @param      key     the hashtable key
     * @param      value   the value
     * @return     the previous value of the specified key in this hashtable,
     *             or <code>null</code> if it did not have one
     * @exception  NullPointerException  if the key or value is
     *               <code>null</code>
     * @see     Object#equals(Object)
     * @see     #get(Object)
     */
    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        HashtableEntry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        HashtableEntry<K,V> entry = (HashtableEntry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

HashMap supports empty keys or values, also take a look at the put method of HashMap.

/**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }


/**
     * Computes key.hashCode() and spreads (XORs) higher bits of hash
     * to lower.  Because the table uses power-of-two masking, sets of
     * hashes that vary only in bits above the current mask will
     * always collide. (Among known examples are sets of Float keys
     * holding consecutive whole numbers in small tables.)  So we
     * apply a transform that spreads the impact of higher bits
     * downward. There is a tradeoff between speed, utility, and
     * quality of bit-spreading. Because many common sets of hashes
     * are already reasonably distributed (so don't benefit from
     * spreading), and because we use trees to handle large sets of
     * collisions in bins, we just XOR some shifted bits in the
     * cheapest possible way to reduce systematic lossage, as well as
     * to incorporate impact of the highest bits that would otherwise
     * never be used in index calculations because of table bounds.
     */
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

Two points can be seen from the hash method

  • hashMap supports key as null, and will calculate its hash value as 0.
  • HashMap does not directly use the hash value of the key as the basis for distributing elements, but adds a one-step XOR operation.

And the value value is not directly judged whether it is empty like HashTable. It can be understood that there is no limit.


3.3 The default initial capacity is different

HashTable defaults to 11

/**
     * Constructs a new, empty hashtable with a default initial capacity (11)
     * and load factor (0.75).
     */
    public Hashtable() {
        this(11, 0.75f);
    }

And HashMap default is 16

/**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16


3.4 Hash function

We want to add or find an element in the hash table. We can complete the operation by mapping the keyword of the current element to a certain position in the array through a function, and positioning through the array subscript once.

        storage location = f(keyword)

  Among them, this function f is generally called a hash function , and the design of this function will directly affect the quality of the hash table.

HashTable:

int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;

HashMap:

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
i = (n - 1) & hash

3.5 Expansion

HashTable:

int newCapacity = (oldCapacity << 1) + 1;

HashMap:

newThr = oldThr << 1; // double threshold


3.6 Thread Safety

It can be observed that the synchronized keyword is added to almost all methods of hashTable, which means that hashTable supports concurrency, while hashMap does not support concurrency. As mentioned at the beginning, HashMap is recommended for non-multi-threaded scenarios, and ConcurrentHashMap is recommended for multi-threaded scenarios. Although HashTable supports concurrency, it is realized by adding Synchronized, which is estimated to be out of date due to efficiency considerations.

For example, the size() method under HashTable is preceded by the synchronized keyword.

/**
     * Returns the number of keys in this hashtable.
     *
     * @return  the number of keys in this hashtable.
     */
    public synchronized int size() {
        return count;
    }


3.7 Linked list processing method when hash collision

HashTable: is to put the newly added element in the head position of the linked list

private void addEntry(int hash, K key, V value, int index) {
        modCount++;

        HashtableEntry<?,?> tab[] = table;
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();

            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        HashtableEntry<K,V> e = (HashtableEntry<K,V>) tab[index];
        tab[index] = new HashtableEntry<>(hash, key, value, e);
        count++;
    }

HashMap: is to put elements on the tail of the linked list

/**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }


4. Summary

HashTable is outdated, and now HashMap is recommended for non-multi-threaded scenarios, and ConcurrentHashMap is recommended for multi-threaded scenarios. If HashTable is not compatible with the old version, there should be no need to use it again.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325851830&siteId=291194637
28