HashMap和HashTable有什么不同?全面解释

本文详细对比了HashMap与HashTable在作者、支持null键值、数据结构、算法、线程安全性等方面的区别。指出HashTable已过时,推荐使用HashMap或ConcurrentHashMap。
从JDK1.0产生,HashTable也就诞生了,而HashMap还在娘胎酝酿之中(开个玩笑哈);JDK1.2产生,HashMap破壳而出,一直到现在都在不断地升级更新。
下面讲的是JDK1.7.0_67的区别:
以下是HashTable作者:
 * @author  Arthur van Hoff
 * @author  Josh Bloch
 * @author  Neal Gafter
以下是HashMap作者:
 * @author  Doug Lea
 * @author  Josh Bloch
 * @author  Arthur van Hoff
 * @author  Neal Gafter

一、
HashMap的类继承体系:
<AbstractClass> AbstractMap (<Interface>Map);<Interface>Cloneable;<Interface>Serializable
接口有:size();isEmpty();containsKey();containsValue();get();put();removew();putALLMap();clear();keySet();values();entrySet();equals();hashCode();toString();clone();
HashTable的类继承体系:
<Interface>Map<Interface>Cloneable;<Interface>Serializable;<AbstractClass>Dictionary
接口有:size();isEmpty();containsKey();containsValue();get();put();removew();putALLMap();clear();keySet();values();entrySet();equals();hashCode();toString();clone();elements();contains();

综合上述对比:HashMap继承自抽象类AbstractMap,而HashTable继承自抽象类Dictionary

hashtable类里面有这么一句话:
NOTE: This class is obsolete.  New implementations should
 * implement the Map interface, rather than extending this class.
(注意:这个类已经过时了。新的实现应该实现Map接口,而不是扩展这个类。)
但是hashtable比hashmap多了两个方法:
elements就是下面代码:自己可以试试。contains();其实上面都有containsValue();个人觉得并没什么卵用。但是不知道大神怎么想的,我是理解不透;
1
     Vector<Integer> vec = new Vector<Integer>(4);
2
      vec.add(4);
3
      vec.add(3);
4
      vec.add(2);
5
      vec.add(1);      
6
      Enumeration e=vec.elements();
7
      System.out.println("Numbers in the enumeration are :- "); 
8
      while (e.hasMoreElements()) {         
9
         System.out.println("Number = " + e.nextElement());
10
      }

二、
HashMap是支持null键和null值的,而HashTable在遇到null时,会抛出NullPointerException异常。这并不是因为HashTable有什么特殊的实现层面的原因导致不能支持null键和null值,这仅仅是因为HashMap在实现时对null做了特殊处理,将null的hashCode值定为了0,从而将其存放在哈希表的第0个bucket中。我们一put方法为例,看一看代码的细节:
这个是hashtable:可以看出有同步锁
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.
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                V old = e.value;
                e.value = value;
                return old;
            }
        }
        modCount++;
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();
            tab = table;
            hash = hash(key);
            index = (hash & 0x7FFFFFFF) % tab.length;
        }
        // Creates the new entry.
        Entry<K,V> e = tab[index];
        tab[index] = new Entry<>(hashkeyvaluee);
        count++;
        return null;
    }
这个是hashmap
 /**
     * Offloaded version of put for null keys
     */
    private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != nulle = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, nullvalue, 0);
        return null;
    }
三、
数据结构和算法层面的区别
HashMap和HashTable都使用哈希表来存储键值对。在数据结构上是基本相同的,都创建了一个继承自Map.Entry的私有的内部类Entry,每一个Entry对象表示存储在哈希表中的一个键值对。

HashMap/HashTable还需要有算法来将给定的键key,映射到确定的hash桶(数组位置)。需要有算法在哈希桶内的键值对多到一定程度时,扩充哈希表的大小(数组的大小)。

     /** hashtable
     * The hash table data.
     */
    private transient Entry<K,V>[] table;


     /** hashmap
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

HashMap/HashTable还需要有算法来将给定的键key,映射到确定的hash桶(数组位置)。需要有算法在哈希桶内的键值对多到一定程度时,扩充哈希表的大小(数组的大小)。本小节比较这两个类在算法层面有哪些不同。

初始容量大小和每次扩充容量大小的不同。先看代码:

/** hashtable 扩容

* The hash table data.

*/

    public Hashtable() {
        this(11, 0.75f);
    }

   public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }


    /** hashmap 扩容
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    void addEntry(int hash, K key, V valueint bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hashtable.length);
        }
        createEntry(hashkeyvaluebucketIndex);
    }

可以看到HashTable默认的初始大小为11,之后每次扩充为原来的2n+1。HashMap默认的初始化大小为16,之后每次扩充为原来的2倍。还有我没列出代码的一点,就是如果在创建时给定了初始化大小,那么HashTable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。


也就是说HashTable会尽量使用素数、奇数。而HashMap则总是使用2的幂作为哈希表的大小。我们知道当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀,所以单从这一点上看,HashTable的哈希表大小选择,似乎更高明些。


但另一方面我们又知道,在取模计算时,如果模数是2的幂,那么我们可以直接使用位运算来得到结果,效率要大大高于做除法。所以从hash计算的效率上,又是HashMap更胜一筹。

所以,事实就是HashMap为了加快hash的速度,将哈希表的大小固定为了2的幂。当然这引入了哈希分布不均匀的问题,所以HashMap为解决这问题,又对hash算法做了一些改动。具体我们来看看,在获取了key对象的hashCode之后,HashTable和HashMap分别是怎样将他们hash到确定的哈希桶(Entry数组位置)中的。


HashMap由于使用了2的幂次方,所以在取模运算时不需要做除法,只需要位的与运算就可以了。但是由于引入的hash冲突加剧问题,HashMap在调用了对象的hashCode方法之后,又做了一些位运算在打散数据。


如果你有细心读代码,还可以发现一点,就是HashMap和HashTable在计算hash时都用到了一个叫hashSeed的变量。这是因为映射到同一个hash桶内的Entry对象,是以链表的形式存在的,而链表的查询效率比较低,所以HashMap/HashTable的效率对哈希冲突非常敏感,所以可以额外开启一个可选hash(hashSeed),从而减少哈希冲突。


因为这是两个类相同的一点,所以本文不再展开了。事实上,这个优化在JDK 1.8中已经去掉了,因为JDK 1.8中,映射到同一个哈希桶(数组位置)的Entry对象,使用了红黑树来存储,从而大大加速了其查找效率。


四、

线程安全


我们说HashTable是同步的,HashMap不是,也就是说HashTable在多线程使用的情况下,不需要做额外的同步,而HashMap则不行。


HashTable 有synchronized有线程同步 而HashMap没有 这个自己可以看源码;不做过多解释
不过HashTable已经被淘汰了;

简单来说就是,如果你不需要线程安全,那么使用HashMap,如果需要线程安全,那么使用ConcurrentHashMap。HashTable已经被淘汰了,不要在新的代码中再使用它。
虽然HashMap和HashTable的公开接口应该不会改变,或者说改变不频繁。但每一版本的JDK,都会对HashMap和HashTable的内部实现做优化,比如上文曾提到的JDK 1.8的红黑树优化。所以,尽可能的使用新版本的JDK吧,除了那些炫酷的新功能,普通的API也会有性能上有提升。
















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值