Java中Hashtable、HashMap、ConcurrentHashMap之间的区别
文章目录
- 前言
- 1.Hashtable与ConcurrentHashMap比较
-
- 1.1 Hashtable
- 1.2 ConcurrentHashMap
- 2. Hashtable与HashMap的比较
前言
Hashtable是java早期发布时提供的一种键值映射的数据结构,而HashMap产生于JDK1.2。对于在单线程中使用,由于Hashtable为保证线程安全在许多关键方法中都加锁以至于效率相比于HashMap低。对于在多线程中我们又有ConcurrentHashMap在jdk1.8之前通过加分段锁,在jdk1.8通过直接给每个哈希桶(链表分配一个锁)实现线程安全 效率相比于Hashtable高 。所以目前Hashtable基本上已经被弃用了。
总结: 在多线程中我们可以使用ConcurrentHashMap,在单线程中我们可以使用Hashtable。
1.Hashtable与ConcurrentHashMap比较
二者都是线程安全的,都可以在多线程环境下使用,但是二者的实现线程安全的方式不同,以下是将介绍二者的加锁不同之处。
1.1 Hashtable
Hashtable通过对关键方法加上synchronized关键字,这就相当于对Hashtable对象本身加锁,此时会出现以下情况:
- 一个Hashtable只有一把锁,如果多个线程访问同一个 Hashtable中的任何数据时都会发生锁冲突。
- size 属性通过 synchronized 来控制同步,一旦触发扩容, 就由该线程完成整个扩容过程.。这个过程会涉及到大量的元素拷贝, 效率会非常低。
如图所示为Hashtable的加锁方式:
1.2 ConcurrentHashMap
在jdk 1.8之前,ConcurrentHashMap通过加分段锁实现线程安全,简单来说就是将若干个哈希桶分成一个段,针对每个段加锁,在jdk1.8就改为直接对每个哈希桶加锁。
相比于Hashtable进行的优化:
- 读操作没有加锁(使用volatile 保证从内存读取结果), 只对写操作进行加锁.。
- 加锁的方式是用 synchronized, 但是不是锁整个对象, 而是 “锁桶” (用每个链表的头结点作为锁对象), 大大降低了锁冲突的概率。
- 充分利用 CAS 特性。比如 size 属性通过 CAS 来更新,避免出现重量级锁的情况。
- 优化了扩容方式: 化整为零。
- 发现需要扩容的线程, 只需要创建一个新的数组, 同时只搬几个元素过去。
- 扩容期间, 新老数组同时存在,后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程,每个操作负责搬运一小部分元素。
- 搬完最后一个元素再把老数组删掉
- 插入只往新数组中插入,查找需要同时查询数组和老数组。
2. Hashtable与HashMap的比较
- 底层实现不同
Hashtable和HashMap都可以用于单线程情况,HashMap底层在jdk1.7以前是通过哈希表(数组+链表的方式)实现来解决哈希冲突,但是在jdk1.8之后, HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间而Hashtable 没有变化。
关于哈希表的概念以及哈希冲突,Hashtable的存储过程可以参照我的另一篇博客HashMap和HashSet之间的关系
-
继承的父类不同
- HashTable是继承自Dictionary(已被废弃)
public class Hashtable<K, V> extends Dictionary<K, V> implements Map<K, V>, Cloneable, Serializable
- HashMap是继承自AbstractMap类
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable
- HashMap和Hashtable都实现了map、Cloneable、Serializable这三个接口。
- HashTable是继承自Dictionary(已被废弃)
-
键值不同
- Hashtable不支持key为null或者value为null。
- HashMap中,键可以为null但是这样的键只有一个,可以有一个或多个键所对应的值为null。
注意: 当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
-
线程安全
Hashtable线程安全,HashMap线程不安全。 -
遍历方式不同
Hashtable、HashMap可以使用Iterator。而Hashtable还支持使用Enumeration的方式 。 -
初始容量不同
- Hashtable的初始长度是11,之后每次扩充容量变为之前的2n+1.
- HashMap的初始长度为16,之后每次扩充变为原来的两倍。
- 如果创建时给定了容量初始值,那么Hashtable会直接使用你给定的大小,而HashMap会将其扩充为最接近该值的2的幂次方大小。
注意:二者负载因子均为0.75
-
计算哈希值的方法不同
- Hashtable使用对象的hashCode。HashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数发来获得终的位置。 除法运算是非常耗费时间的。效率很低
- HashMap提高计算效率,将哈希表的大小固定为了2的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。