Map接口没有继承Collection接口,用于保存具有映射关系的数据。Map中保存了key和value,它们都可以使任何引用类型的数据,但是key不能重复。通过指定的key可以取出对应的value。
Map接口的实现类包括:
- HashMap
HashMap底层是一个动态数组,数组的每一项是链表。默认容量为16,默认加载因子0.75,即超过容量*0.75时扩容,扩容后为原来的两倍。HashMap通过计算key的hashCode决定元素的存放位置,当key的hashCode一样时(Hash冲突)使用链表存放在同一个位置,当链表过长时(长度超过8时)使用红黑树存储。HashMap的key可以使用null,但只能有一个为null的key。除此之外,HashMap是线程不安全的,即多个线程操作同一个HashMap可能导致数据不一致。 - LinkHahsMap
LinkHashMap继承了HashMap,在HashMap的基础上使用双向链表,在put时加入到HashMap和链表中,借助链表实现元素有序。 - TreeMap
TreeMap使用红黑树存储键值对,TreeMap的元素是有序的,加入TreeMap的元素需要实现compareTo接口,通过compareTo方法或TreeMapp绑定的比较器进行排序。 - HashTable
HashTable跟HashMap一样都是散列表,HashMap是线程安全,效率比HashMap低。JDK5后出现了ConcurrentHashMap可以替代HashTable,因此HashTable不太常用。 - ConcureentHashMap
HashMap不是线程安全的,在多线程环境下使用可能导致死锁,HashTable的方法使用synchronized修饰,当有线程访问HashTable其余线程处于阻塞状态,即使A线程使用put插入数据时,B线程使用get获取数据也会被阻塞,性能较低。JDK5后推出了ConcurrentHashMap,采用分段锁的概念解决了上述弊端实现了高效的线程安全。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构,一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。如使用size等需要访问所有数据的方法时,需要按顺序获取所有Segment锁然后按顺序释放所有Segment锁。JDK1.8后不再使用Segment锁,采用CAS+Synchrnized的方式,将锁细化到每一个Node节点。
总结:
不需要保证线程安全并且不需要实现元素有序遍历的情况下使用效率较高的HashMap,需要保证线程安全的情况下使用ConcurrentHashMap。需要保证Map遍历的顺序和插入顺序一致时使用LinkedHashMap,需要遍历时自然排序或自定义排序的情况下使用TreeMap。