HashMap的特点
存储数据(key,value)键值对的形式、key可以为null,同样的key会被覆盖掉
数据结构
底层采用数组、链表、红黑树实现(1.8)
存储方式的特点通过哈希算法,将任意长度的key通过哈希算法(散列算法)变换成一个长度固定的key(地址)
哈希算法
会先将key提取出来,然后把key中的每一个字符转换成ascii码,进行取模,就可以算出哈希表的数组下标。通过取莫得方式是为了节省空间,想想如果有一个名字转换成ascii码后是上万的数字,那么为了存储这一个ascii码就要开辟一个上万容量的数字,造成的浪费可谓是相当大,通过取莫把值控制在一定的范围内。
但是如果仅仅以数组作为数据结构,那么就会出现一个新的问题,如果是这时候又存入一个值foes,它取取模之后也是9,那么就会出现冲突,按照数组的定义,这时候就会把lies替换为foes,这就是hash冲突
为了解决hash冲突,又引入了链表,当出现冲突的时候,就挂在链表上。
新数据的插入在jdk7采用的是头插法(插在链表头部),在jdk8采用的是尾插法(插在链表尾部)
在jdk8中,又引入了红黑树得概念,因为链表得遍历是通过头节点进行遍历的,当链表过长的时候,假设有70w条数据,要查最后一个数据,就需要遍历70w次,而红黑树可以通过二分查找大量的减少遍历次数。当链表长度大于等于8的时候就会转变成红黑树,当红黑树节点小于等于6的时候就会变成链表
jdk7采用的头插法会产生的问题(cpu100)先分析单线程情况下的插入运行流程
现在要存储A、B两个元素
此时达到了扩容的阈值,准备进行扩容。扩容的时候会遍历链表中的元素,转移到新数组,使用的是头插法,而先插入B,再插入A,那么遍历链表的时候就会先读取到A,然后插入数组,再读取到B。所以扩容重排之后就会由A->B变为B->A。在单线程模式下是安全的
分析一下多线程模式下的扩容过程
两个线程同时插入同一个位置。
当插入AB完毕之后,先由线程1对数组进行扩容,而此时扩容已经完成,但是数据还没有完成迁移,在即将要对数据进行重排得时候,时间片用完了
接下来由线程2获得一个扩容后的线程,这时候要对数据进行迁移,所以此时线程而得数组是这样 ,也就是由B指向A。
此时得情况就是,线程1中使A指向B,2线程中使B指向A。两个地址互相指向,造成了一个死循环。



