一、HashMap、TreeMap都继承AbstractMap抽象类;TreeMap实现SortedMap接口,因此TreeMap是有序的!HashMap是无序的。
接口层次:
public interface SortedMap<K,V> extends Map<K,V>
public interface NavigableMap<K,V> extends SortedMap<K,V>
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializablejava
二、两种常规Map性能
HashMap:适用于在Map中插入、删除和定位元素。
Treemap:适用于按天然顺序或自定义顺序遍历键(key)。算法
使用场景:HashMap一般比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,在须要排序的Map时候才用TreeMap。数组
三、HashMap和Hashtable的区别安全
HashMap和Hashtable都实现了Map接口,主要的区别有:线程安全性,同步(synchronization),以及速度。
HashMap几乎能够等价于Hashtable,除了HashMap是非synchronized的,并能够接受null(HashMap能够接受为null的键值(key)和值(value),而Hashtable则不行)。
HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程能够共享一个Hashtable;而若是没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
另外一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。因此当有其它线程改变了HashMap的结构(增长或者移除元素),将会抛出ConcurrentModificationException,但迭代器自己的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并非一个必定发生的行为,要看JVM。这条一样也是Enumeration和Iterator的区别。
因为Hashtable是线程安全的也是synchronized,因此在单线程环境下它比HashMap要慢。若是你不须要同步,只须要单一线程,那么使用HashMap性能要好过Hashtable。
HashMap不能保证随着时间的推移Map中的元素次序是不变的。数据结构
咱们可否让HashMap同步?
HashMap能够经过下面的语句进行同步:
Map m = Collections.synchronizeMap(hashMap);多线程
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的状况下HashTable的效率很是低下。由于当一个线程访问HashTable的同步方法时,其余线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,而且也不能使用get方法来获取元素,因此竞争越激烈效率越低。并发
HashTable容器在竞争激烈的并发环境下表现出效率低下的缘由,是由于全部访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不一样数据段的数据时,线程间就不会存在锁竞争,从而能够有效的提升并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分红一段一段的存储,而后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其余段的数据也能被其余线程访问。app
四、ConcurrentMap 高并发
ConcurrentHashMap 表现区别:不能够有null键,线程安全,原子操做。一个ConcurrentHashMap 由多个segment 组成,每一个segment 包含一个Entity 的数组。这里比HashMap 多了一个segment 类。该类继承了ReentrantLock 类,因此自己是一个锁。当多线程对ConcurrentHashMap 操做时,不是彻底锁住map, 而是锁住相应的segment 。这样提升了并发效率。缺点:当遍历ConcurrentMap中的元素时,须要获取全部的segment 的锁,使用遍历时慢。锁的增多,占用了系统的资源。使得对整个集合进行操做的一些方法性能
Segment的get操做实现很是简单和高效。先通过一次再哈希,而后使用这个哈希值经过哈希运算定位到segment,再经过哈希算法定位到元素,代码以下:
public V get(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).get(key, hash); }
get操做的高效之处在于整个get过程不须要加锁,除非读到的值是空的才会加锁重读,咱们知道HashTable容器的get方法是须要加锁的,那么ConcurrentHashMap的get操做是如何作到不加锁的呢?缘由是它的get方法里将要使用的共享变量都定义成volatile,如用于统计当前Segement大小的count字段和用于存储值的HashEntry的value。定义成volatile的变量,可以在线程之间保持可见性,可以被多线程同时读,而且保证不会读到过时的值,可是只能被单线程写(有一种状况能够被多线程写,就是写入的值不依赖于原值),在get操做里只须要读不须要写共享变量count和value,因此能够不用加锁。之因此不会读到过时的值,是根据Java内存模型的happen before原则,对volatile字段的写入操做先于读操做,即便两个线程同时修改和获取volatile变量,get操做也能拿到最新的值,这是用volatile替换锁的经典应用场景。
transient volatile int count; volatile V value;
ConcurrentHashMap的Put操做在定位元素的代码里咱们能够发现定位HashEntry和定位Segment的哈希算法虽然同样,都与数组的长度减去一相与,可是相与的值不同,定位Segment使用的是元素的hashcode经过再哈希后获得的值的高位,而定位HashEntry直接使用的是再哈希后的值。其目的是避免两次哈希后的值同样,致使元素虽然在Segment里散列开了,可是却没有在HashEntry里散列开。
hash >>> segmentShift) & segmentMask//定位Segment所使用的hash算法 int index = hash & (tab.length - 1);// 定位HashEntry所使用的hash算法
如何扩容。扩容的时候首先会建立一个两倍于原容量的数组,而后将原数组里的元素进行再hash后插入到新的数组里。为了高效ConcurrentHashMap不会对整个容器进行扩容,而只对某个segment进行扩容。因为put方法里须要对共享变量进行写入操做,因此为了线程安全,在操做共享变量时必须得加锁。Put方法首先定位到Segment,而后在Segment里进行插入操做。插入操做须要经历两个步骤,第一步判断是否须要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置而后放在HashEntry数组里。
是否须要扩容。在插入元素前会先判断Segment里的HashEntry数组是否超过容量(threshold),若是超过阀值,数组进行扩容。值得一提的是,Segment的扩容判断比HashMap更恰当,由于HashMap是在插入元素后判断元素是否已经到达容量的,若是到达了就进行扩容,可是颇有可能扩容以后没有新元素插入,这时HashMap就进行了一次无效的扩容。
五、HashSet和HashMap的区别
HashSet是基于HashMap实现的。
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<>(); } public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } public boolean add(E e) { return map.put(e, PRESENT)==null; } public boolean remove(Object o) { return map.remove(o)==PRESENT; } ....... }
HashMap | HashSet |
HashMap实现了Map接口 | HashSet实现了Set接口 |
HashMap储存键值对 | HashSet仅仅存储对象 |
使用put()方法将元素放入map中 | 使用add()方法将元素放入set中 |
HashMap中使用键对象来计算hashcode值 | HashSet使用成员对象来计算hashcode值,对于两个对象来讲hashcode可能相同,因此equals()方法用来判断对象的相等性,若是两个对象不一样的话,那么返回false |
HashMap比较快,由于是使用惟一的键来获取对象 | HashSet较HashMap |