文章目录
-
- [1、HashMap 设置值的原理](#1、HashMap 设置值的原理)
- [2、HashMap 获取值原理](#2、HashMap 获取值原理)
- [3、HashMap Hash优化](#3、HashMap Hash优化)
- [4、HashMap 寻址优化](#4、HashMap 寻址优化)
- [5、HashMap 是如何解决Hash冲突的?](#5、HashMap 是如何解决Hash冲突的?)
-
- [5.1 get数据的时候,如果定位到指定位置的元素是一个链表,怎么办呢?](#5.1 get数据的时候,如果定位到指定位置的元素是一个链表,怎么办呢?)
- [5.2 红黑树](#5.2 红黑树)
- 6、数组扩容
-
- [6.1 数组长度为16,计算index](#6.1 数组长度为16,计算index)
- [6.2 数组长度为32, 计算index](#6.2 数组长度为32, 计算index)
- [6.3 扩容总结:](#6.3 扩容总结:)
1、HashMap 设置值的原理
- 根据key计算HashCode,
- 再使用HashCode对 数组长度取模,结果一定是存放到数组中 的某一个位置。
- 得到指定位置索引之后,就往指定位置设置数据即可。
2、HashMap 获取值原理
- 根据key计算hashCode,
- 再使用hashCode对 数据长度取模,得到一个数组索引,
- 然后再根据索引从数组中获取指定数据即可。
3、HashMap Hash优化
java
// JDK 1.8以后的HashMap里面的一段源码
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
答案:hash数组一般不会太大,使用 key 的hashCode 和 key的hashCode 右移16位 进行异或运算的目的就是让 高低16位都参与运算,减少hash冲突
4、HashMap 寻址优化
java
hash & (n - 1)
当n为2的n次方的时候, hash & (n - 1) = hash % n
答案:hash & (n - 1) -> 效果是跟hash对n取模,效果是一样的,但是与运算的性能要比hash对n取模要高很多,数学问题,数组的长度会一直是2的n次方,只要他保持数组长度是2的n次方
5、HashMap 是如何解决Hash冲突的?
使用链表法解决。
在数据的指定位置,挂一个链表,这个链表里放多个元素,让多个 key-value 放在数据的同一个位置
5.1 get数据的时候,如果定位到指定位置的元素是一个链表,怎么办呢?
get 的时候,如果定位到数组发现这个位置挂了一个链表哦,此时就会遍历 链表,从链表里面选择到自己需要的那个 kye-value 就可以了。
5.2 红黑树
为了解决hash冲突的问题,就会在 数据的指定为值挂一个链表,如果链表的长度达到了一定的长度之后,就会将链表转换成红黑树,通过遍历一颗红黑树找到一个元素,此时时间复杂度是 O(logn), 性能比链表要高一些。
如果链表过长,遍历的性能不高,因此当链表长度超过一定限制的时候,就会将链表转换成红黑树,提升搜索性能。
6、数组扩容
6.1 数组长度为16,计算index
java
n - 1 0000 0000 0000 0000 0000 0000 0000 1111
hash1 1111 1111 1111 1111 0000 1111 0000 0101
& 0000 0000 0000 0000 0000 0000 0000 0101 = 5(index = 5的位置)
n - 1 0000 0000 0000 0000 0000 0000 0000 1111
hash2 1111 1111 1111 1111 0000 1111 0001 0101
& 0000 0000 0000 0000 0000 0000 0000 0101 = 5(index = 5的位置)
6.2 数组长度为32, 计算index
java
n-1 0000 0000 0000 0000 0000 0000 0001 1111
hash1 1111 1111 1111 1111 0000 1111 0000 0101
&结果 0000 0000 0000 0000 0000 0000 0000 0101 = 5(index = 5的位置)
n-1 0000 0000 0000 0000 0000 0000 0001 1111
hash2 1111 1111 1111 1111 0000 1111 0001 0101
&结果 0000 0000 0000 0000 0000 0000 0001 0101 = 21(index = 21的位置)
6.3 扩容总结:
数据扩容 -> 2倍扩容 -> 重新对map中的每一个元素进行寻址->通过判断二进制结果是否多出来了一个bit为,判断index的位置是否变化;
如果数组的长度扩容之后 = 32,重新对每个hash值进行寻址,也就是用每个hash值跟新数组的length - 1进行与操作
- 判断二级制结果是否多出来一个bit的1
- 如果没有多,那么还是原来的index
- 如果多了出来,那么新的index = oldIndex + oldCap
- 通过这个方法避免了rehash的时候,用每个hash对数据的长度进行取模,取模的性能不高,位运算的性能比较高。