什么是HashMap

HashMap是基于哈希表的Map接口的实现,提供所有可选的映射操作,允许使用null值和null键,存储的对象时一个键

值对对象Entry<K,V>;

是基于数组+链表的结构实现,在内部维护这一个数组table,数组的每个位置保存着每个链表的表头结点,查找元素时,

先通过hash函数得到key值对应的hash值,再根据hash值得到在数组中的索引位置,拿到对应的链表的表头,最后去遍

历这个链表,得到对应的value值。

/**
* The default initial capacity - MUST be a power of two.
*/
DEFAULT_INITIAL_CAPACITY :
是指HashMap容器的初始大小,MUST be a power of two意思是必须是2的幂次方,二进制格式中,1向左移动4位,
也就是0001 --> 1 0000,即2^4=16。为什么初始容量是16而不是4,8或其他呢?因为16的索引是0~15,在JDK中,
都是用2进制的10进制数%16,结果在0~15之间。HashMap通过key的hashcode值,来进行位运算,在2的幂次方的情
况下,length-1的所有二进制位全是1,hashCode码&(length - 1)的结果取决于hashCode本身,如果hashCode是均
匀的,就可以减少hash碰撞情况(一个hash对应多个字符串),同时汇编处理的进制都是16进制,Java的底层是C,C
的底层是汇编,越接近底层开发效率越快,所以初始容量是16位

MAXIMUM_CAPACITY:
是指容量的极限值,默认设置为2^31

DEFAULT_LOAD_FACTOR:
是指负载因子,默认为0.75f。负载因子的存在是非常重要的,由于初始化HashMap的容量大小是固定的,当存储的数据
超过最大值*负载因子,即16*0.75=12并且这个位置已经有别的数据存在,将在当前容量的基础上进行扩容(如果没有别
的数据是个空位就不会扩容)。JDK1.7版本是使用了rehash方法,创建新的链表,如果在新链表的数组索引位置相同,则
链表元素会倒置。而JDK1.8版本中,链表元素相对位置没有变化, 实际是对对象的内存地址进行操作。

TREEIFY_THRESHOLD :
是指节点数,当一个元素被添加到至少有8(默认值)个节点的桶中时,桶中链表结构将转化为树形结构(红黑树)

UNTREEIFY_THRESHOLD:
也是指节点数,不过是树形结构转换为链表结构

MIN_TREEIFY_CAPACITY:
是指当桶被转化为树形结构的时候,此时桶所拥有的最小容量

 

HashMap的多线程不安全的体现

1.resize扩容时死循环

扩容操作是对链表进行循环操作,如果同时有两个线程在对同一个链表进行transfer操作,线程A在transfer的时候会修改为

value2.next=value1, 线程B操作时,根据原始链表拿到的是value1.next=value2,而由于线程A已经修改为value2.next=value1,

那么就会存在死循环的问题。

2.数据不一致

当一个线程在对HashMap进行resize操作,而另一个线程在进行get操作时,比如原始数组长度时16,扩容之后是32,在进行

get操作根据扩容之前的length拿到index1,而这个时候另一个线程正好对index1的链表做好扩容操作,那么从index1的位置取

出来的元素肯定是null,这时候可以使用ConcurrentHashMap。

HashMap总结

1.HashMap是由数组和链表组成的,数组是HashMap的主体,链表是为了解决哈希冲突的问题,对于HashMap的插入问题,

如果插入位置不含有链表,那么直接插入到链表的表头即可,如果包含链表,需要先遍历链表,判断key是否已经在链表中存

在,存在则替换value值,不存在则插入到链表的表头。
2.HashMap是线程不安全的,对于多线程环境下,无法保证数据的正确性,使用的时候需要注意。