HashMap、Hashtable、ConcurrentHashMap的原理与区别

若是你去面试,面试官不问你这个问题,你来找我^_^

下面直接来干货,先说这三个Map的区别:php

HashTable

  • 底层数组+链表实现,不管key仍是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap作了相关优化
  • 初始size为11,扩容:newsize = olesize*2+1
  • 计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

HashMap

  • 底层数组+链表实现,以存储null键和null值,线程不安全
  • 初始size为16,扩容:newsize = oldsize*2,size必定为2的n次幂
  • 扩容针对整个Map,每次扩容时,原来数组中的元素依次从新计算存放位置,并从新插入
  • 插入元素后才判断该不应扩容,有可能无效扩容(插入后若是扩容,若是没有再次插入,就会产生无效扩容)
  • 当Map中元素总数超过Entry数组的75%,触发扩容操做,为了减小链表长度,元素分配更均匀
  • 计算index方法:index = hash & (tab.length – 1)

 

HashMap的初始值还要考虑加载因子:html

  •  哈希冲突:若干Key的哈希值按数组大小取模后,若是落在同一个数组下标上,将组成一条Entry链,对Key的查找须要遍历Entry链上的每一个元素执行equals()比较。
  • 加载因子:为了下降哈希冲突的几率,默认当HashMap中的键值对达到数组大小的75%时,即会触发扩容。所以,若是预估容量是100,即须要设定100/0.75=134的数组大小。
  • 空间换时间:若是但愿加快Key查找的时间,还能够进一步下降加载因子,加大初始大小,以下降哈希冲突的几率。

HashMap和Hashtable都是用hash算法来决定其元素的存储,所以HashMap和Hashtable的hash表包含以下属性:java

  • 容量(capacity):hash表中桶的数量
  • 初始化容量(initial capacity):建立hash表时桶的数量,HashMap容许在构造器中指定初始化容量
  • 尺寸(size):当前hash表中记录的数量
  • 负载因子(load factor):负载因子等于“size/capacity”。负载因子为0,表示空的hash表,0.5表示半满的散列表,依此类推。轻负载的散列表具备冲突少、适宜插入与查询的特色(可是使用Iterator迭代元素时比较慢)

除此以外,hash表里还有一个“负载极限”,“负载极限”是一个0~1的数值,“负载极限”决定了hash表的最大填满程度。当hash表中的负载因子达到指定的“负载极限”时,hash表会自动成倍地增长容量(桶的数量),并将原有的对象从新分配,放入新的桶内,这称为rehashing。面试

HashMap和Hashtable的构造器容许指定一个负载极限,HashMap和Hashtable默认的“负载极限”为0.75,这代表当该hash表的3/4已经被填满时,hash表会发生rehashing。算法

“负载极限”的默认值(0.75)是时间和空间成本上的一种折中:数组

  • 较高的“负载极限”能够下降hash表所占用的内存空间,但会增长查询数据的时间开销,而查询是最频繁的操做(HashMap的get()与put()方法都要用到查询)
  • 较低的“负载极限”会提升查询数据的性能,但会增长hash表所占用的内存开销

程序猿能够根据实际状况来调整“负载极限”值。安全

ConcurrentHashMap

  • 底层采用分段的数组+链表实现,线程安全
  • 经过把整个Map分为N个部分,能够提供相同的线程安全,可是效率提高N倍,默认提高16倍。(读操做不加锁,因为HashEntry的value变量是不稳定的,也能保证读取到最新的值。)
  • Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap容许多个修改操做并发进行,其关键在于使用了锁分离技术
  • 有些方法须要跨段,好比size()和containsValue(),它们可能须要锁定整个表而而不只仅是某个段,这须要按顺序锁定全部段,操做完毕后,又按顺序释放全部段的锁
  • 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不须要扩容,有效避免无效扩容

 

Hashtable和HashMap都实现了Map接口,可是Hashtable的实现是基于Dictionary抽象类的。Java5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。多线程

HashMap基于哈希思想,实现对数据的读写。当咱们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,而后找到bucket位置来存储值对象。当获取对象时,经过键对象的equals()方法找到正确的键值对,而后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞时,对象将会储存在链表的下一个节点中。HashMap在每一个链表节点中储存键值对对象。当两个不一样的键对象的hashcode相同时,它们会储存在同一个bucket位置的链表中,可经过键对象的equals()方法来找到键值对。若是链表大小超过阈值(TREEIFY_THRESHOLD,8),链表就会被改造为树形结构。并发

在HashMap中,null能够做为键,这样的键只有一个,但能够有一个或多个键所对应的值为null。当get()方法返回null值时,便可以表示HashMap中没有该key,也能够表示该key所对应的value为null。所以,在HashMap中不能由get()方法来判断HashMap中是否存在某个key,应该用containsKey()方法来判断。而在Hashtable中,不管是key仍是value都不能为null。性能

Hashtable是线程安全的,它的方法是同步的,能够直接用在多线程环境中。而HashMap则不是线程安全的,在多线程环境中,须要手动实现同步机制(同步块,同步方法,Collections.synchronizedMap(map))

Hashtable与HashMap另外一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。因此当有其它线程改变了HashMap的结构(增长或者移除元素),将会抛出ConcurrentModificationException,但迭代器自己的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并非一个必定发生的行为,要看JVM。解决办法:只须要将ArrayList替换成java.util.concurrent包下对应的类便可,如

替换为 private static List<String> list = new CopyOnWriteArrayList<String>();private static List<String> list = new ArrayList<String>(); 

 

先看一下简单的类图:

  

从类图中能够看出来在存储结构中ConcurrentHashMap比HashMap多出了一个类Segment,而Segment是一个可重入锁。

ConcurrentHashMap是使用了锁分段技术来保证线程安全的。

锁分段技术:首先将数据分红一段一段的存储,而后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其余段的数据也能被其余线程访问。 

ConcurrentHashMap提供了与Hashtable和SynchronizedMap不一样的锁机制。Hashtable中采用的锁机制是一次锁住整个hash表,从而在同一时刻只能由一个线程对其进行操做;而ConcurrentHashMap中则是一次锁住一个桶。

ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等经常使用操做只锁住当前须要用到的桶。这样,原来只能一个线程进入,如今却能同时有16个写线程执行,并发性能的提高是显而易见的。

 

原文地址:http://www.javashuo.com/article/p-yeyxwyda-v.html

相关文章
相关标签/搜索