hashmap底层实现
key的hashcode值得计算过程
首先求出传入key的hash值
通过异或运算
将hash值与运算(容器大小-1)计算出存入数组的位置
hash碰撞:对key的hash值进行取余之后发现目标位置上已经存在一个相同的hash元素,这就称之为hash碰撞。
解决办法:采用的是拉链法通过equal的值比较他们的值,如果一样就覆盖,如果不一样遍历到尾结点下
这样的话hashmap的结构就变成了数组+链表,这样的话导致结果查询的时间复杂度就到了O(n),所以就采用了数组+链表+红黑树;
hashmap的初始容量只能是2的指数次幂
如果你输入的初始值不是2的指数次幂,他也会将它转换成最接近指数次幂的值(输入3-->4 7-->8 14-->16)
为什么hashmap的容量必须是2的指数次幂
方便我们计算余数的时候不需要使用取模运算,而是直接采用与运算
当容量为16的时候,计算他的模的时候只需要进行与运算,效率比取模运算高很多
加载因子为什么为0.75?
如果loadfactor=1的话,节省了空间复杂度,增加了时间复杂度。因为不可能所有的元素都能均匀的放入到数组中,肯定会出现hash碰撞然后形成链表,这样就会增加时间复杂度;
如果loadfactor=0.5的话,节省了时间复杂度,但是增加的空间复杂度,在最理想的情况下整整有一半的空间没有使用,而且肯定也会存在hash碰撞,这样的话又会有更多的数组空间没有被使用到。
所有hashmap采用了折中的方法也就是使用0.75。
为什么链表到8之后会变成红黑树
底层是通过统计学中的泊松分布进行统计得出在链表值达到8以及负载因子为0.75的时候产生红黑树的可能性极小,hashmap底层源码给出了计算出来的比较值
在hashmap1.8之前多线程情况下链表下的节点之间会出现死循环(在扩容的时候多个线程同时对某个节点扩容操作的时候在转移的时候使得节点指向颠倒然后出现链循环导致死循环)
hashmap1.8之后的解决办法:
通过两个高位指针和两个低位指针指向链表,这样的话在迁移的时候就不会出现节点之间指向颠倒了
由于hashtable对整个hashtable都加了锁,所以性能上很低,所以就使用concurrenthashmap
concurrentHashMap1.7之前采用分段锁,内部细分了若干个小的 HashMap,称之为段(Segment)。默认情分为 16 个段,称之为锁的并发度。某一个区域共用一个segment锁,通过的下面hashentry来保持数据,Segment 是一种可重入锁 ReentrantLock,在 ConcurrentHashMap 里扮演锁的角色, HashEntry 则用于存储键值对数据
而1.8之后首先判断是否数组上该位置为空,如果为空不加锁,使用cas进行判断赋值;当数组上有值的时候使用synchronized加锁进行操作。