1. hashMap简单认识
优点:hashMap的存储方式是键值对(键可以包括null),查询速度,存储方便,存储数量最大为十几亿。
缺点:主要是线程不安全,容易在hashmap扩容时形成死循环;
2.hashMap从源码角度简单认识
jdk1.7 使用的数组 + 链表
jdk1.8 使用的数组 + 链表 + 红黑树
分析为什么使用数组 + 链表 + 红黑树:
数组:主要是方便查找,且在内存中是连续的。
链表:主要是方便插入,删除,然而hashMap把两个者有点结合在一起。
红黑树:主要是jdk1.8为了对链表进行优化时,增加的数据结构。那么在它们如何使用的呢? ,可以从put()函数中看出
loadFactor | 负载因子,默认值75% |
threshold | 临界值,超过临界值需要重新分配 |
modCount | 统计删除和修改的次数 |
Entry | 实体类,主要有四个参数组成,分别是key,hash,value,next |
hashmap的构造函数
1.无参构造
2.有参构造
分析hashmap的构造函数:
通过在平时项目观察发现,大部分人都会使用无参的构造函数,因为会有初始值16,那么问题来了,如果我们存储数量大于16 呢,看过源码的人当然会说,需要进行扩容操作,可是数据量比较大的时候是不是申请很多内存空间,把一个数组里面的数据重新放到另一个数组里面去,这样可能比较消耗时间和内存,如果在多线程情况下,是不是容易出现数据问题,我个人观点,提前指定所需要的空间,那么有人会说,我可以知道需要申请多少空间,如果盲目申请内存是不是也会产生浪费,这样的观点是没有错的,但是我这里强调的是,在你大概知道需要多少数据量,应指定所需要申请的空间。
hashmap里面的put操作
addEntry函数:
createEntry:
putForNullKey函数:
hashMap里面put方法:
1. 首先初始化数组
2. 判断键是不是null,如果是null,用putForNullKey()来处理
3. 计算出哈希值
4. 用哈希值和数组长度进行indexFor()运算;得到下标i;
5. 通过下标i在数组里面找到对应的实体类,比较实体类里面hash值和key的==和equals方法,都相等的话,说明是要找的value
否则话,进行增加实体类
6. 在增加实体类时,都会进行判断,是否超过threshold值,如果超过就把原来数组的大小变为2倍。
分析put方法:
可以从源码和上述过程中,可以看出,在进行put操作时,首先会进行哈希值进行比较,然后在比较key的==和equal方法,那么在什么时候使用equal方法呢?,其实涉及到一个哈希冲突的问题,就是hashcode可能存在冲突,最后会和buckIndex的值一样,在这一种情况下,会调用equal方法,因为equal是Object方法,它直接比较的是内存大小。
hashmap里面的put操作通过hashcode值可以存放数据。当不同对象的哈希值相同时,会通过单链表的方式解决,将新元素插入表头,它的next指向原来的元素。(为什么插入表头不插入表尾,正在研究)
分析put方法的线程不安全:
hashmap很容易在put操作时,形成死循环。
分析死循环形成的原因:
1. 从上述源码中,可以看出死循环的原因主要是因为在进行扩容操作所造成的。
2. 首先把原来的大小变为2倍。
3. 遍历原来的数组
4. 因为是单链表,所以要保存下一个节点, Entry<k,v> next = e.next;
5. 因为e要插入表头,并且需要e指向链表的第一个元素,所以需要把e.next = newTable[i];
6. 然后让e变为数组的头指针。newTable[i] = e;
其实上述过程实际是单向链表的反转,在形成死循环时,主要是在第4 ,5 ,6步。
3.hashmap所涉及到的数据结构
1. 链表
2. 哈希运算
3. 红黑树(jdk1.8自己正在研究中)