HashMap、HashTable和concurrentHashMap的区别

HashMap和Hashtable的区别

HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。

  1. HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
  2. HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
  3. 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
  4. 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
  5. HashMap不能保证随着时间的推移Map中的元素次序是不变的。

要注意的一些重要术语:

1) sychronized意味着在一次仅有一个线程能够更改Hashtable。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。

2) Fail-safe和iterator迭代器相关。如果某个集合对象创建了Iterator或者ListIterator,然后其它的线程试图“结构上”更改集合对象,将会抛出ConcurrentModificationException异常。但其它线程可以通过set()方法更改集合对象是允许的,因为这并没有从“结构上”更改集合。但是假如已经从结构上进行了更改,再调用set()方法,将会抛出IllegalArgumentException异常。

3) 结构上的更改指的是删除或者插入一个元素,这样会影响到map的结构。

我们能否让HashMap同步?

HashMap可以通过下面的语句进行同步:
Map m = Collections.synchronizeMap(hashMap);

结论

Hashtable和HashMap有几个主要的不同:线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable,而如果你使用Java 5或以上的话,请使用ConcurrentHashMap吧。

HashMap和ConcurrentHashMap分享

大家一看到这两个类就能想到HashMap不是线程安全的,ConcurrentHashMap是线程安全的。除了这些,还知道什么呢?

先看一下简单的类图: 

从类图中可以看出来在存储结构中ConcurrentHashMap比HashMap多出了一个类Segment,而Segment是一个可重入锁。 
ConcurrentHashMap是使用了锁分段技术技术来保证线程安全的。 
锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

属性说明: 
我们会发现HashMap和Segment里的属性值基本是一样的,因为Segment的本质上就是一个加锁的HashMap,下面是每个属性的意义: 
table:数据存储区 
size,count: 已存数据的大小 
threshold:table需要扩容的临界值,等于table的大小*loadFactor 
loadFactor: 装载因子 
modCount: table结构别修改的次数

hash算法和table数组长度: 
仔细阅读HashMap的构造方法的话,会发现他做了一个操作保证table数组的大小是2的n次方。 
如果使用new HashMap(10)新建一个HashMap,你会发现这个HashMap中table数组实际的大小是16,并不是10.
为什么要这么做呢?这就要从HashMap里的hash和indexFor方法开始说了。

Java代码  

  1. static int hash(int h) {
  2. // This function ensures that hashCodes that differ only by
  3. // constant multiples at each bit position have a bounded
  4. // number of collisions (approximately 8 at default load factor).
  5. h ^= (h >>> 20) ^ (h >>> 12);
  6. return h ^ (h >>> 7) ^ (h >>> 4);
  7. }
  8. /**
  9. * Returns index for hash code h.
  10. */
  11. static int indexFor(int h, int length) {
  12. return h & (length-1);
  13. }
  14. int hash = hash(key.hashCode());
  15. int i = indexFor(hash, table.length);

HashMap里的put和get方法都使用了这两个方法将key散列到table数组上去。 
indexFor方法是通过hash值和table数组的长度-1进行于操作,来确定具体的位置。 
为什么要减1呢?因为数组的长度是2的n次方,减1以后就变成低位的二进制码都是1,和hash值做与运算的话,就能得到一个小于数组长度的数了。 
那为什么对hashCode还要做一次hash操作呢?因为如果不做hash操作的话,只有低位的值参与了hash的运算,而高位的值没有参加运算。hash方法是让高位的数字也参加hash运算。 
假如:数组的长度是16 我们会发现hashcode为5和53的散列到同一个位置. 
hashcode:53  00000000 00000000 00000000 00110101 
hashcode:5    00000000 00000000 00000000 00000101 
length-1:15     00000000 00000000 00000000 00001111 
只要hashcode值的最后4位是一样的,那么他们就会散列到同一个位置。 
hash方法是通过一些位运算符,让高位的数值也尽可能的参加到运算中,让它尽可能的散列到table数组上,减少hash冲突。

ConcurrentHashMap的初始化: 
仔细阅读ConcurrentHashMap的构造方法的话,会发现是由initialCapacity,loadFactor, concurrencyLevel几个参数来初始化segments数组的。 
segmentShift和segmentMask是在定位segment时的哈希算法里需要使用的,让其能够尽可能的散列开。 
initialCapacity:ConcurrentHashMap的初始大小 
loadFactor:装载因子 
concurrencyLevel:预想的并发级别,为了能够更好的hash,也保证了concurrencyLevel的值是2的n次方 
segements数组的大小为concurrencyLevel,每个Segement内table的大小为initialCapacity/ concurrencyLevel

ConcurrentHashMap的put和get

Java代码  

  1. int hash = hash(key.hashCode());
  2. return segmentFor(hash).get(key, hash);

可以发现ConcurrentHashMap通过一次hash,两次定位来找到具体的值的。 
先通过segmentFor方法定位到具体的Segment,再在Segment内部定位到具体的HashEntry,而第二次在Segment内部定位的时候是加锁的。 
ConcurrentHashMap的hash算法比HashMap的hash算法更复杂,应该是想让他能够更好的散列到数组上,减少hash冲突。

HashMap和Segment里modCount的区别: 
modCount都是记录table结构被修改的次数,但是对这个次数的处理上,HashMap和Segment是不一样的。 
HashMap在遍历数据的时候,会判断modCount是否被修改了,如果被修改的话会抛出ConcurrentModificationException异常。 
Segment的modCount在ConcurrentHashMap的containsValue、isEmpty、size方法中用到,ConcurrentHashMap先在不加锁的情况下去做这些计算,如果发现有Segment的modCount被修改了,会再重新获取锁计算。

HashMap和ConcurrentHashMap的区别: 
如果仔细阅读他们的源码,就会发现HashMap是允许插入key和value是null的数据的,而ConcurrentHashMap是不允许key和value是null的。这个是为什么呢?ConcurrentHashMap的作者是这么说的: 
The main reason that nulls aren‘t allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) is that ambiguities that may be just barely tolerable in non-concurrent maps can‘t be accommodated. The main one is that if map.get(key) returns null, you can‘t detect whether the key explicitly maps to null vs the key isn‘t mapped. In a non-concurrent map, you can check this via map.contains(key), but in a concurrent one, the map might have changed between calls.

为什么重写了equals方法就必须重写hashCode方法呢? 
绝大多数人都知道如果要把一个对象当作key使用的话,就需要重写equals方法。重写了equals方法的话,就必须重写hashCode方法,否则会出现不正确的结果。那么为什么不重写hashCode方法就会出现不正确结果了呢?这个问题只要仔细阅读一下HashMap的put方法,看看它是如何确定一个key是否已存在的就明白了。关键代码:

Java代码  

  1. int hash = hash(key.hashCode());
  2. int i = indexFor(hash, table.length);
  3. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  4. Object k;
  5. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  6. V oldValue = e.value;
  7. e.value = value;
  8. e.recordAccess(this);
  9. return oldValue;
  10. }
  11. }

首先通过key的hashCode来确定具体散列到table的位置,如果这个位置已经有值的话,再通过equals方法判断key是否相等。 
如果只重写equals方法而不重写hashCode方法的话,即使这两个对象通过equals方法判断是相等的,但是因为没有重写hashCode方法,他们的hashCode是不一样的,这样就会被散列到不同的位置去,变成错误的结果了。所以hashCode和equals方法必须一起重写。

时间: 2024-10-16 13:35:26

HashMap、HashTable和concurrentHashMap的区别的相关文章

集合 HashMap 的原理,与 Hashtable、ConcurrentHashMap 的区别

一.HashMap 的原理 1.HashMap简介 简单来讲,HashMap底层是由数组+链表的形式实现,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可:如果定位到的数组包含链表,对于添加操作,首先遍历链表,存在即覆盖,否则新增:对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找.当新建一个HashMap的时候,就会初始化一个数组(

HashMap、HashTable、ConcurrentHashMap的区别

一.相关概念 1.Map的概念 javadoc中对Map的解释如下: An objectthat maps keys to values . Amap cannot contain duplicatekeys; each key can map to at most one value. This interface takes the place of the Dictionary class, which was atotally abstract class rather than an

HashMap,HashTable,ConcurrentHashMap的区别

之前都是JDK1.5,1.6版本的,今天不巧系统升级了..JavaSE6跟系统不兼容..只好升级了JDK1.8 然后对比一下,发现各个版本的CurrentHashMap竟然还都不一样.. 这里就对比一下1.8的了.. 先看一下数据域,基本都是一个内部类的数组,不同的Map都是Node的,table是Entry的  transient Entry<?,?>[] table;        //Hashtable  transient Node<K,V>[] table;       

HashMap,Hashtable以及ConcurrentHashMap的比较(源码)

一.概述 以前学习的时候应该都知道HashMap以及Hashtable: HashMap是线程不安全的,Hashtable是线程安全的. 这里就一源代码的角度看看为什么Hashtable是线程安全的,以及另外一个线程安全的ConcurrentHashMap与Hashtable的比较. 小提示:在Ecilpse中可以用ctrl+shitf+T查找类,这样就容易查看源代码了. 在查看hashtable源代码的时候,不小心进了com.sun.org.apache.xalan.internal.xslt

HashMap,HashTable,LinkedHashMap,TreeMap的区别

1.   Map简介 Map 集合类用于存储元素对(称作"键"和"值"),其中每个键映射到一个值.Map用于存储键值对,根据键得到值,因此不允许键重复,值可以重复. 1.1.  HashMap HashMap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度.HashMap最多只允许一条记录的键为Null;允许多条记录的值为Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap

HashMap,HashTable,ConcurrentHashMap的实现原理及区别

一.哈希表 哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值. 链式哈希表从根本上说是由一组链表构成.每个链表都可以看做是一个“桶”,我们将所有的元素通过散列的方式放到具体的不同的桶中.插入元素时,首先将其键传入一个哈希函数(该过程称为哈希键),函数通过散列的方式告知元素属于哪个“桶”,然后在相应的链表头插入元素.查找或删除元素时,用同们的方式先找到元素的“桶”,然后遍历相应的链表,直到发现我们想要的元素.因为每个“桶”都是

HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的原理和区别

HashMap 是否是线程安全的,如何在线程安全的前提下使用 HashMap,其实也就是HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的原理和区别.当时有些紧张只是简单说了下HashMap不是线程安全的:Hashtable 线程安全,但效率低,因为是 Hashtable 是使用 synchronized 的,所有线程竞争同一把锁:而 ConcurrentHashMap 不仅线程安全而且效率高,因为它包含一个 segment 数组,将

HashMap Hashtable ConcurrentHashMap 一点区别

HashMap  ConcurrentHashMap Hashtable 工作中经常会用到, HashMap用的最多, ConcurrentHashMap次之,hashTable用的最少. 简单看了下源码,其实原因还是挺明显的.从JDK的发展历程来看,hashTable是1.0就发布的,属于最早的key-value形式的存储, 到了1.2才有hashMap, ConcurrentHashMap是1.5中发布的,它们的实现有很多类同,也有很多细节上的区别.这里主要通过查看源码来比较下它们在写入与读

JDK1.8中的HashMap.HashTable, ConcurrentHashMap有什么区别?

JDK1.8中的HashMap,HashTable,ConcurrentHashMap有什么区别? 答:HashMap是线程不安全的,底层采用数组+链表+红黑树的结构 HashTable是线程安全的,因为使用了Synchronized锁住了整个table,底层采用了数组+链表 ConcurrentHashMap是线程安全的,采用了CAS+同步锁Synchronized对链表头节点进行锁定,底层使用数组+链表+红黑树 HashMap的key和value可以是null,其他两个不行. 原文地址:ht