HashTable、HashMap和ConcurrentHashMap的区别
HashTable
继承于Dictionary, 不可存储null键和值, 底层基于数组+链表(HashTable数据结构不同于HashMap的是,不会转化为红黑树),父类方法少于AbstractMap, 只有基本的get, put, remote, 没有putAll, keySet等, 线程安全。
线程安全。线程安全主要依靠synchronized关键字,因此效率较低。初始化最好赋值size。
HashMap
继承于AbstractMap,可存储null键和值, key值不重复无序,线程不安全初始化最好赋值size,因为默认的size是16, 当存储的键值对超过这个size时,会触发扩容,而rehash会对所有键值对rehash,造成不必要的资源浪费。
HashMap变量
变量 | 说明 |
---|---|
DEFAULT_INITIAL_CAPACITY |
默认初始容量: 16 |
MAXINUM_CAPACITY |
默认最大容量 |
DEFAULT_LOAD_FACTOR |
默认负载因子: 0.75 |
TREEIFY_THRESHOLD |
链表长度大于8,链表转化为红黑树 |
UNTREEIFY_THRESHOLD |
链表长度小于6, 树退化为链表 |
MIN_TREEIFY_CAPACITY |
键值对大于64, 才会发生转换 |
HashMap结构
HashMap里,是一个数组, 数组的每个元素是一个单向链表,链表中每个每个Node, 包含四个属性:key
、value
、next
、hash
。
ConcurrentHashMap(并发哈希映射)
继承于AbstractMap, 不能存储null键和值,底层基于数组+链表, 线程安全。默认容量是16(即16个桶),默认负载因子0.75,扩容默认增加一倍。初始化最好赋值size。
ConcurrentHashMap结构类似于HashMap,ConcurrentHashMap中使用Segment代替了HashMap中的链表,而Segment继承了ReentrantLock。每次加锁的操作, 实际是锁住了每一个Segment,每个Segment只能被一个线程操作,保证了整体的线程安全。称为分桶锁机制。
JDK1.7,ConcurrentHashMap在分桶锁的基础上引入了读写锁:
- 读锁:允许多个线程同时读,但是不允许线程写
- 写锁:只允许一个线程写,但是不允许线程读
JDK1.8之前,向桶中的链表添加元素的时候,采用的是头插法;从JDK1.8开始,向桶中的链表添加元素的时候,采用的是尾追法。
JDK1.8开始,ConcurrentHashMap引入了红黑树机制:当桶中的元素个数超过8个,桶中链表转成一棵红黑树;当桶中红黑树的节点个数不足7个,红黑树退化为链表。只有当桶的数量>=64的时候,才会开启红黑树机制 。