java为数据结构中的映射定义了一个接口java.util.Map;它有三个实现类,分别是HashMap、Hashtable 和TreeMap.
Map是用来存储键值对的数据结构,在数组中通过数组下标来对其内容进行索引的,而在Map中,则是通过对象来进行索引,用来索引的对象叫做key,其对应的对象叫value。
Hashmap 是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。由于HashMap与Hashtable都采用了hash法进行索引,因此二者具有许多相似之处,它们主要有如下的一些区别:
1.HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key)(但需要注意,最多只允许一条记录的键为null,不允许多条记录的值为null),而Hashtable不允许。
2. HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。 Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。
3.Hashtable的方法是同步的,这个类中的一些方法加入了synchronized关键字,保证了Hashtable中的对象是线程安全的。而HashMap不支持线程的同步,所以它不是线程安全的。在多个线程访问Hashtable时,不需要开发人员对它进行同步,而对于HashMap,开发人员必须提供额外的同步机制。所以,就效率来言,HashMap可能高于Hashtable。
4.Hashtable使用Enumeration,HashMap使用Iterator。
5.Hashtable和HashMap采用的hash/rehash算法都大概一样,所以一般来说性能不会有很大的差别。
注意:
它们都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为HashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,HashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。
6.在Hashtable中,hash数组默认大小是11,增加的方式是old*2+1。在HashMap中,hash数组的默认大小是16,而且一定是2的指数。
7.hash值的使用不同,Hashtable直接使用对象的HashCode。
注意事项:
1.不能保证其中的键值对顺序
2.尽量不要使用可变对象作为它们的key值。(https://mp.csdn.net/postedit/84638297,这篇博客下方有标题:为什么要同时重写equals和hashcode方法,其中有对此的解释)
HashMap可以通过Map m=Collections.synchronizedMap(new HashMap())来达到同步的效果。具体而言,该方法返回一个同步的Map,该Map封装了底层的HashMap的所有方法,使得底层的HashMap即使是在多线程的环境中也是安全的。
hashmap底层是以数组方式进行存储。将key-value对作为数组中的一个元素进行存储。
key-value都是Map.Entry中的属性。其中将key的值进行hash之后进行存储,即每一个key都是计算hash值,然后再存储。每一个Hash值对应一个数组下标,数组下标是根据hash值和数组长度计算得来。
由于不能的key有可能hash值相同,即该位置的数组中的元素出现两个,对于这种情况,hashmap采用链表形式进行存储。
WeakHashMap和HashMap类似,二者的不同之处在于WeakHashMap中key采用的是“弱引用”的方式,只要WeakHashMap中的key不再被外部引用,它就可以被垃圾回收器回收。而HashMap中key采用的是“强引用的方式”,当HashMap中的key没有被外部引用时,只有这个key从HashMap中删除后,才可以被垃圾回收器回收。
WeakHashMap是通过散列表table保存Entry(键值对);每个Entry实际上就是一个链表来实现的。当某“弱键”不再被其它对象引用,就会被GC回收时,这个“弱键”也同时被添加到ReferenceQueue队列中。当下一步我们需要操作WeakHashMap时,会先同步table、queue,table中保存了全部的键值对,而queue中保存的是GC回收的键值对;同步他们,就是删除table中被GC回收的键值对。
TreeMap实现了SortMap()接口,基于红黑树对所有key进行排序
排序方式:自然排序和定制排序
TreeMap的key以TreeSet的方式存储,对key的要求与TreeSet对元素的要求大体一致。
LinkedHashMap 是HashMap的一个子类
1.使用双向链表来维护键值对顺序。
2.迭代顺序和键值对的插入顺序保持一致,LinkedHashMap需要维护键值对的插入顺序,所以性能略低于HashMap。
3.在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
内部存储的元素的模型
entry是下面这样的,相比HashMap,多了两个属性,一个before,一个after。next和after有时候会指向同一个entry,有时候next指向null,而after指向entry。
LinkedHashMap存储结构
linkedHashMap和HashMap在存储操作上是一样的,但是LinkedHashMap多的东西是会记住在此之前插入的元素,这些元素不一定是在一个桶中,画个图。
也就是说,对于linkedHashMap的基本操作还是和HashMap一样,在其上面加了两个属性,也就是为了记录前一个插入的元素和记录后一个插入的元素。也就是只要和hashmap一样进行操作之后把这两个属性的值设置好,就OK了。注意一点,会有一个header的实体,目的是为了记录第一个插入的元素是谁,在遍历的时候能够找到第一个元素。
IdentityHashMap与HashMap类似,IdentityHashMap也允许使用null,但不能保证键值对顺序。
区别:
IdentityHashMap使用==来判断key是否相等,HashMap使用equals来判断key是否相等。
底层采用分段的数组+链表实现,线程安全
Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
引入了一个“分段锁”的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放到哪个HashTable中。在ConcurrentHashMap中,就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中.通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
有些方法需要跨段怎么办呢?
比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
如何扩容?
段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
使用最多的是HashMap。
HashMap里面存入的键值对在取出时没有固定的顺序,是随机的。一般而言,在Map中插入、删除和定位元素,HashMap是最好的选择。
在使用多线程时,考虑线程安全,优先使用HashTable。
由于TreeMap实现了SortMap接口,能够把它保存的记录根据键排序,因此,取出来的是排序的键值对,如果需要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序排列。
参考博客:
https://www.cnblogs.com/whgk/p/6169622.html
https://blog.csdn.net/junchenbb0430/article/details/78643100