HashMap作为Java的一种特殊类型,在我们的编码过程中被广泛使用,专门用于存放类似于(key:value)这样的数据。它就像一个糖罐子,只是它里面每项数据都由两个值组成的。在HashMap出来之前,java已经存在可以用于存放(key:value)数据类型的对象,如Dictionary和Hashtable,由于他们先前设计上的缺陷,目前Dictionary已经废弃了,而Hashtable也不怎么使用了。目前技术条件下的编码过程中,如果遇到(key:value)数据类型程序员们优先考虑使用HashMap类型。接下来我们从顶层设计开始由浅入深一步一步认识HashMap。下图是Hash的设计类图:
从图中可以看出HashMap是继承AbstractMap,并且HashMap的存储内容是Entity实体组成的,且都实现了自己特有的方法。那么具体使用的时候HashMap数据在内存中是如何存储的呢?啥都不说我们先看下图:
紫色部分即代表哈希表本身(其实是一个数组),数组的每个元素都是一个单链表的头节点,链表是用来解决hash地址冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中保存。HashMap设计初衷为了提升通过key获取value的效率,所以HashMap的Entity设计初衷是通过二叉树-红黑树进行存放的,只是HashMap的内容过多就退化为单项链表了。接下来我们去看一看具体操作的流程,结合程序设计的流程图HashMap的Put操作是如何实现的呢?
图中显示了HashMap的一个Put操作的流程,应用程序的put语句的后台实现原理。这里面涉及到两个HashMap重要的操作resize-扩容和hash值计算。接下来分别讲解这两个操作:
扩容,简单说就是HashMap有一张Hash表,Hash表是由数组构成,在Java中数组的初始化需要制定长度,在HashMap中默认初始化大小为16,即只能存放16个(key:vaule)内容。16是开发HashMap的作者根据大多应用场景得出的能应付多数应用场景的值,在数据量较大且无法估计HashMap大小的场景中就需要启用扩容机制。HashMap每次扩充,容量变为原来的2倍。
扩容涉及到扩容的两个重要参数initialCapacity和loadFactor。initialCapacity设置初始化HashMap大小的值,默认是16。loadFactor是加载因子,默认是0.75。当存储个数threshold即size > initialCapacity *loadFactor,hashmap内部resize方法就被调用。同样initialCapacity和loadFactor也可以在实例化HashMap的时候调用构造函数进行初始化,但需要注意一下两点。
1.initialCapacity容量较大,那么会使迭代器效率降低。所以理想的情况还是在使用HashMap前估计一下数据量。
2.加载因子默认值是0.75,是JDK权衡时间和空间效率之后得到的一个相对优良的数值。如果这个值过大,虽然空间利用率是高了,但是对于HashMap中的一些方法的效率就下降了。
根据HashMap的实现,它在每次扩容后的容量是原先的一倍。扩充结果都为2的幂次方大小。即HashMap总是使用2的幂作为哈希表的大小。这样做可以成倍提升计算Hash值的效率,为什么这么说呢?
因为HashMap大小是2的幂次方,所以它使用取模计算时,可以直接使用位运算来得到结果,效率大大高于以前使用除法的效率。比如在HashMap执行增加、删除、查找键值对时,定位到哈希位置是很关键的一步,源码中是通过下面3个操作来完成这一步:1)拿到key的hashCode值;2)将hashCode的高位参与运算,重新计算hash值;3)将计算出来的hash值与(table.length - 1)进行&运算。
虽然使用HashMap在效率上会有提升,但是为了提升效率,HashMap在实现过程中未进行线程安全性限制,所以HashMap是线程不安全的。具体的线程不安全体现在以下两方面:
第一,如果多个线程同时使用put方法添加元素
假设正好存在两个put的key发生了碰撞(hash值一样),那么根据HashMap的实现,这两个key会添加到数组的同一个位置,这样最终就会发生其中一个线程的put的数据被覆盖。
第二,如果多个线程同时检测到元素个数超过数组大小*loadFactor
这样会发生多个线程同时对hash数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给table,也就是说其他线程的都会丢失,并且各自线程put的数据也丢失。且会引起死循环的错误。
HashMap给出了他自己的解决并发办法,多线程需要使用HashMap建议采用concurrent并发包下的concurrentHashMap。
今天和大家简单的分享了Java的HashMap,当然HashMap还有很多特性我一文不可能全部列举完整,希望和大家一起交流学习,共同进步。