从源码实现上看Hashtable和HaspMap的异同

Java技术面试中经常会被面试官问道如下问题:谈谈HashTable和HashMap的异同点。

目录

1、继承的父类不同,实现的接口相同。

2、key和value是否允许null值

3、线程安全性不同

4、是否提供contains方法

5、两个遍历方式的内部实现上不同

6、hash值计算方式不同

7、内部实现使用的数组初始化和扩容方式不同


本文从源码上分析对比两者之间的差异,以加深朋友们对两者的理解。

1、继承的父类不同,实现的接口相同。

Hashtable和HashMap的族谱
Hashtable和HashMap的族谱

从上图可以看出:Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map,Cloneable以及Serializable接口。

2、key和value是否允许null值

key value是否为nul可以从他们的put方法上理解。

Hashtable的put方法:

HashMap的put方法:

可以看出:Hashtable的key和value都不能为null。而HashMap的key和value可以为null,HashMap中key可以为null但只允许一个key为null。但Hashtable和HashMap的key都具有唯一性。

3、线程安全性不同

 Hashtable 线程安全。因为Hashtable涉及元素的操作判断方法它每个方法中都加入了Synchronize。

Hashtable 线程安全很好理解,这里我们分析一下HashMap为什么是线程不安全的。

对于HashMap,Java官方的文档有如下描述:

Note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with a key that an instance already contains is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the map. If no such object exists, the map should be "wrapped" using the Collections.synchronizedMap method. This is best done at creation time, to prevent accidental unsynchronized access to the map:

   Map m = Collections.synchronizedMap(new HashMap(...));

从API的介绍看:HashMap是线程非安全的。

HashMap中的方法在缺省情况下是非Synchronize的。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用HashMap时就必须要自己增加同步处理。(结构上的修改是指添加或删除一个或多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问。

例如,HashMap在进行put操作时的代码:

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * 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;
		...
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

当多个线程同时检测到总数量超过门限值的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的Entry数组,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题。

4、是否提供contains方法

Hashtable有三个contains方法:contains,containsValue和containsKey三个方法。

其中:contains和containsValue的作用相同。

    public boolean containsValue(Object value) {
        return contains(value);
    }

HashMap没有contains方法:只有containsValue和containsKey。

5、两个遍历方式的内部实现上不同

Hashtable、HashMap都使用了 Iterator。但Hashtable还使用了Enumeration的方式 。
如下代码:

Hashtable使用Enumeration

Hashtable提供根据key,value,entry等类型获取Enumeration。

关于HashMap Javadoc文档有如下描述:

The iterators returned by all of this class's "collection view methods" are fail-fastt: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove method, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs

HashMap的Iterator是fail-fast迭代器。当有其它线程改变了HashMap的结构(增加,删除,修改元素),将会抛出ConcurrentModificationException。不过,通过Iterator的remove()方法移除元素则不会抛出ConcurrentModificationException异常。

JDK8之前的版本中,Hashtable是没有fast-fail机制的。在JDK8及以后的版本中 ,Hashtable源码中的Enumerator也是fast-fail的, 源码如下:

        public T next() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            return nextElement();
        }

6、hash值计算方式不同

HashTable直接使用对象的hashCode。

计算方式:

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

而HashMap重新计算hash值。

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

Hashtable在计算元素的位置时需要进行一次%运算,而%运算是比较耗时的。
HashMap为了提高计算效率,将哈希表的大小固定为了2的幂,这样只需要做位运算。位运算比%的效率要高很多。
HashMap的效率虽然提高了,但是hash冲突却也增加了。因为它得出的hash值的低位相同的概率比较高,而计算位运算
为了解决这个问题,HashMap重新根据hashcode计算hash值后,又对hash值做了一些运算来打散数据。使得取得的位置更加分散,从而减少了hash冲突。

7、内部实现使用的数组初始化和扩容方式不同

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

Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。



小结

两者之间的异同:

a.继承的父类不同,实现的接口相同;

b.Hashtable的key和value都不能为null。而HashMap的key和value可以为null,HashMap中key可以为null但只允许一个key为null。但Hashtable和HashMap的key都具有唯一性;

c.Hashtable 线程安全,HashMap非线程安全;

d.Hashtable有contains,containsValue和containsKey方法。HashMap没有contains方法;

e.Hashtable、HashMap都使用了 Iterator。但Hashtable还使用了Enumeration的方式 ;

f.hash值的计算方式不同;

g.内部实现使用的数组初始化和扩容方式不同。

 

ps:本文使用源码基于java8源码。

微信扫描关注,收看更多精彩
微信扫码关注,此处有熊更精彩

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT_熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值