(二十八) HashTable

前言:之前写了HashMap 、 ArrayList 和 LinkedList , 还差 HashTable 没有学习,这篇学习下HashTable。

PS: 源码分析针对于Android O 的jdk,即jdk1.8


参考博客: HashMap 和 HashTable 到底哪不同 ?


简单的demo:jiatai的 hashTable demo

1. HashTable简单介绍

HashTable从实现方面用的数据结构是散列表,它和HashMap差不多。友情提醒,HashTable已经过时了,现在如果非多线程场景推荐用HashMap, 多线程场景推荐用ConcurrentHashMap,虽然HashTable支持并发,但是通过加Synchronized实现的,估计是效率考虑从而过时了。

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简单使用

写一段hashTable源码注释中提及的使用吧:
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.  HashTable 与 HahsMap 的区别

由于HashTable与hashMap很相似,并且hashTable又过时了,那本文就主要就区别介绍一下HashTable和HashMap的区别。

3.1  继承体系不同

参考博客很详细地描述了继承关系并列举了其中的public 方法,具体如下所示:


从图中可以看出,两个类的继承体系有些不同。虽然都实现了Map、Cloneable、Serializable三个接口。但是HashMap继承自抽象类AbstractMap,而HashTable继承自抽象类Dictionary,其中Dictionary类是一个已经被废弃的类。

/**
 * 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()
 * @see     java.util.Hashtable
 * @since   JDK1.0
 */
public abstract
class Dictionary<K,V> {


3.2 Null key & Null Value

hashTable不支持key 或者 value 为空。看一下HashTable的put方法,代码中明显判断了值是否为空,如果为空则抛出空指针异常。key为空则通过直接调用key.hashCode()会导致空指针。

/**
     * 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则支持空的键或值,也顺带看一下HashMap的put方法。

/**
     * 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);
    }

从hash方法可以看出两点

  • hashMap支持key为null,并且会将其hash值计算为0。
  • hashMap不是直接使用key的hash值作为分布元素依据的,而是会加上一步异或操作。

而value值是没有像HashTable一样直接判断是不是空。可以理解为没有限制。


3.3 默认初始容量大小不同

HashTable默认是11

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

而HashMap默认是16

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


3.4 哈希函数

我们要在hash表中新增或查找某个元素,我们通过把当前元素的关键字 通过某个函数映射到数组中的某个位置,通过数组下标一次定位就可完成操作。

        存储位置 = f(关键字)

  其中,这个函数f一般称为哈希函数,这个函数的设计好坏会直接影响到哈希表的优劣。

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 扩容

HashTable:

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

HashMap:

newThr = oldThr << 1; // double threshold


3.6 线程安全

可以观察到hashTable几乎所有方法都加了synchronized关键字,由此得知hashTable是支持并发的,而hashMap是不支持并发的。最开始也讲了,如果非多线程场景推荐用HashMap, 多线程场景推荐用ConcurrentHashMap,虽然HashTable支持并发,但是通过加Synchronized实现的,估计是效率考虑从而过时了。

比如HashTable下面的size()方法前面就加了synchronized关键字

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


3.7 散列冲突时链表处理方法

HashTable:是将新添的元素放在链表的head位置

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:是将元素放在链表的尾巴上的

/**
     * 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. 总结

HashTable已经过时了,现在如果非多线程场景推荐用HashMap, 多线程场景推荐用ConcurrentHashMap。HashTable如果不是兼容老版本的需要,应该是没有必要再次使用了。

猜你喜欢

转载自blog.csdn.net/sinat_20059415/article/details/80054938