#HashMap
简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度依然为O(1),因为最新的Entry会插入链表头部,仅需简单改变引用链即可,而对于查找操作来讲,此时就需要遍历链表,然后通过key对象的equals方法逐一比对查找但是值得一提的是其key可以为空。性能考虑,HashMap中的链表出现越少,性能才会越好。最后HashMap是线程不安全的。
#HashTable
在讨论currentHashMap前HashTable我们先了解以下该类,hashtable是通过数组与链表来储存数据,但是它与hashmap不同的是它的key不能为空同时其为线程安全的。虽然hashmap是线程安全的不过其保证线程安全的手段低效,它只是简单的对每个方法加上synchronized相当于就是对底层的数组加上一把大锁。这种方式出现锁冲突的概率非常大,因为不管是读还是写都需要去竞争同一把锁所以其效率低下。
#CurrentHashMap
CurrentHashMap相较于hashtable做出了一定的改变,其保证线程安全不在是通过对于整个数组进行加锁而是通过数组中的每个头节点加锁,将锁细化了,这样就大大降低了锁冲突的概率,删除或是添加不同数组下标的元素不在产生锁冲突,同时其读操作没有加锁而是通过volatile来保证内存可见性。并且其优化了扩容方式当发现需要扩容的线程, 只需要创建一个新的数组, 同时只搬几个元素过去. 扩容期间, 新老数组同时存在. 后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程. 每个操作负责搬运一小部分元素. 搬完最后一个元素再把老数组删掉. 这个期间, 插入只往新数组加. 这个期间, 查找需要同时查新数组和老数组。这样将任务分发给多个线程有利于提高效率。最后值得一提的是,上述为jdk1.8后的版本的CurrentHashMap,在之前的版本中其对于数组的加锁是将数组分成几部分来加锁而不是通过每个数组下标的头节点加锁。
#总结
HashMap: 线程不安全. key 允许为 null Hashtable: 线程安全. 使用 synchronized 锁
Hashtable 对象, 效率较低. key 不允许为 null.
ConcurrentHashMap: 线程安全. 使用 synchronized 锁每个链表头结点, 锁冲突概率低, 充分利用 CAS 机制. 优化了扩容方式. key 不允许为 null