HashMap的底层原理
HashMap 就是基于散列表也就是Hash表的数据结构,通过(key-Value)来存储数据的,key不可重复,Value可以重复的,通过key的哈希值与与数组大小-1按位与来获取元素在数组中的位置,然后通过数组+连表+(java8之后就是数组+链表+红黑树)实现的。
哈希冲突又是什么?
哈希冲突就是多个key的哈希值与数组大小-1按位与 映射到同一个数组的位置(hash算法本来就可能冲突,且数组大小是有限的):tab[i = (n - 1) & hash]),导致Hash冲突。
解决Hash冲突的方法常用就是拉链法(链地址法):将哈希表中每个槽的位置变成一个链表,当多个键的哈希值相同时,且不是相同的对象,将它们存储在同一个链表。也就是同一个桶中的不同位置。
JDK1.7及之前链表的插入采用的都是头插法,每当发生哈希冲突的时候,不是相同的对象的时候,新的节点总是插在链表的头部,老节点依次向后移动,并且新节点自然就指向的是后面老节点。比如进来的顺序先是C B A,然后链表的结构就是 A ->B ->C (A指向B,B指向C ,C指向NULL)
但是在多线程环境下,头插法可能导致链表形成环,特别是在并发扩容的时候。JDK1.8的时候,就改成了尾插法,即新的节点插入到链表的尾部,保持插入的顺序,并且引入了红黑树。当链表的长度大于8且数组大小大于等于64的时候,就把链表转化成红黑树,(红黑树是一种自平衡二叉搜索树,能够将最坏情况下的查找复杂度从O(n)降低到O(Iogn))当红黑树节点小于6的时
候,又会退化成链表。
HahsMap得扩容机制:
HashMap中的扩容是基于负载因子(load factor)来决定的。默认情况下,HashMap的负载因子为0.75,这意味着当HashMap的已存储元素数量超过当前容量的75%时,就会触发扩容操作。
例如,初始容量为16,负载因子为0.75,则扩容阈值为16×0.75=12。当存入第13个元素时,HashMap就会触发扩容。
当触发扩容时,HashMap的容量会扩大为当前容量的两倍。例如,容量从16增加到32,从32增加到64等。
扩容时,HashMap需要重新计算所有元素的哈希值,并将它们重新分配到新的哈希桶中,这个过程称为rehashing。每个元素的存储位置会根据新容量的大小重新计算哈希值,并移动到新的数组中。
整体流程:
1. 在添加元素或初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次每次扩容都是达到了扩容阈值(数组长度*0.75)
2. 每次扩容的时候,都是扩容之前容量的2倍;
3. 扩容之后,会新创建一个数组,需要把老数组中的数据挪动到新的数组中 开始遍历旧表中得每一个通
没有hash冲突的节点,则直接使用e.hash&(newCap-1)计算新数组的索引位置
如果是红黑树,走红黑树的添加
如果是链表,则需要遍历链表,可能需要拆分链表,判断(e.hash&oldCap)是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上