黑马程序员技术交流社区

标题: 简介之HashMap和Hashtable的区别 [打印本页]

作者: 梧桐花开    时间: 2018-8-14 15:36
标题: 简介之HashMap和Hashtable的区别
本帖最后由 梧桐花开 于 2018-8-14 15:42 编辑

HashMap和Hashtable的区别

1.底层结构不同

1.1继承体系的区别
HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary类(注:此类已过时。新的实现应该实现 Map 接口,而不是扩展此类)。它们都实现了同时实现了Map、Cloneable(可复制)、Serializable(可序列化)这三个接口。

1.2内部实现使用的数组初始化和扩容方式不同
Hashtable:
(1) Hashtable继承于Dictionary类,实现了Map接口。Map是"key-value键值对"接口,Dictionary是声明了操作"键值对"函数接口的抽象类。
(2) Hashtable是通过"拉链法"实现的哈希表。它包括几个重要的成员变量:table, count, threshold, loadFactor, modCount。
  table是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Entry数组中的。
  count是Hashtable的大小,它是Hashtable保存的键值对的数量。
  threshold是Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值="容量*加载因子"。
  loadFactor就是加载因子。
  modCount是用来实现fail-fast机制的

HashMap:
(1) HashMap内存储数据的Entry数组默认是16,如果没有对Entry扩容机制的话,当存储的数据一多,Entry内部的链表会很长,这就失去了HashMap的存储意义了。所以HasnMap内部有自己的扩容机制。HashMap内部有:
    变量size,它记录HashMap的底层数组中已用槽的数量;
    变量threshold,它是HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子)
    变量DEFAULT_LOAD_FACTOR = 0.75f,默认加载因子为0.75
HashMap扩容的条件是:当size大于threshold时,对HashMap进行扩容
  
​   Hashtable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。​      
   Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。

2. 线程安全性的不同
(1)Hashtable是线程安全的,HashMap是线程非安全的。Hashtable的方法是synchronized的,而HashMap不是。
    HashMap是Hashtable的轻量级实现(非线程安全的实现)。
    synchronized意味着在一次仅有一个线程能够更改Hashtable。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。

(2)HashMap运行效率高:线程非安全,计算hash值的方式更快,扩容直接*2(位运算<<1)
    Hashtable运行效率低:线程安全,计算hash值速度慢,扩容方式需要的时间相对长。

(3)HashMap就是为了提高运行速度设计的,相对于Hashtable而言,其计算hash值更容易产生“碰撞”现象,HashMap产生碰撞就以链表结构存储。HashMap的默认空间较大,时间换空间,都是在提高效率。
   
(4)在单线程环境时HashMap效率更高,在多线程中Hashtable能避免死锁,但是JDK1.5新增了concurrent包,引入线程安全的ConcurrentHashMap,使得Map也可以线程安全。它引入了一个“分段锁”的概念,不是所有方法都是同步的,因此效率相比Hashtable更高。所以尽管HashMap和Hashtable非常相似,但现在Hashtable已经很少用了(何况它还不符合驼峰命名规则)。

3. 对null的存储支持不同
HashMap允许存储1个null键和多个null值
Hashtable不允许存储null键和null值

1      public synchronized V put(K key, V value) {
2         // Make sure the value is not null
3         if (value == null) {
4             throw new NullPointerException();
5         }
6
7         // Makes sure the key is not already in the hashtable.
8         Entry<?,?> tab[] = table;
9         int hash = key.hashCode();
10         int index = (hash & 0x7FFFFFFF) % tab.length;
11         @SuppressWarnings("unchecked")
12         Entry<K,V> entry = (Entry<K,V>)tab[index];
13         for(; entry != null ; entry = entry.next) {
14             if ((entry.hash == hash) && entry.key.equals(key)) {
15                 V old = entry.value;
16                 entry.value = value;
17                 return old;
18             }
19         }
20
21         addEntry(hash, key, value, index);
22         return null;
23     }
根据上面的部分Hashtable源码,可以看出value==null时会抛出异常。在key.hashCode();中如果key为null也会抛出异常。

4.遍历方式的内部实现上不同
(1)Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
(2)HashMap的Iterator是fail-fast迭代器。当有其它线程改变了HashMap的结构(增加,删除,修改元素),将会抛出ConcurrentModificationException。不过,通过Iterator的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。
(3)JDK8之前的版本中,Hashtable是没有fast-fail机制的。在JDK8及以后的版本中 ,HashTable也是使用fast-fail的

5.计算hash值的方法不同
为了得到元素的位置,首先需要根据元素的KEY计算出一个hash值,然后再用这个hash值来计算得到最终的位置。

Hashtable直接使用对象的hashCode。hashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数发来获得最终的位置。

Hashtable在计算元素的位置时需要进行一次除法运算,而除法运算是比较耗时的。
HashMap为了提高计算效率,将哈希表的大小固定为了2的幂,这样在取模运算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。

HashMap的效率虽然提高了,但是hash冲突却也增加了。因为它得出的hash值的低位相同的概率比较高
为了解决这个问题,HashMap重新根据hashcode计算hash值后,又对hash值做了一些运算来打散数据。使得取得的位置更加分散,从而减少了hash冲突。当然了,为了高效,HashMap只做了一些简单的位处理。从而不至于把使用2 的幂次方带来的效率提升给抵消掉。

6.小结
(1)基类不同:HashTable基于Dictionary类,而HashMap是基于AbstractMap。Dictionary是什么?它是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的骨干实现,它以最大限度地减少实现此接口所需的工作。
(2)线程安全:HashMap时单线程安全的,Hashtable是多线程安全的。
(3)null不同:HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null。
(4)遍历不同:HashMap仅支持Iterator的遍历方式,Hashtable支持Iterator和Enumeration两种遍历方式。
(5)hash值计算方法不同,HashMap效率更高。
(6)Hashtable:JDK1.0开始。线程安全,效率低。不允许null键和null值
​     HashMap:JDK1.2开始。线程不安全,效率高。允许null键和null值

ps:详细原理大家可以百度更详细的底层实现。









欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2