1.HashMap背景和核心技术点:
1.HashMap的作用是什么?
- HashMap就是一个内存数据库,数据库用来存取数据的。数据结构 K,V
- HashMap的核心功能是通过键值对的方式存储和检索数据,通过使用哈希函数将键映射到存储桶中,从而实现高效的数据访问和搜索。
2.使用HashMap
- HashMap map = new HashMap();
- map.put();
- map.get();
3.HashMap这个数据库,数据是存在内存中
Redis、Mysql(bufferpool,通过异步线程进行刷盘)、zk、ectd、nacos也好,他们的数据都是先存在内存中的,然后通过各种多线程或者异步进行持久化。
4.在计算机当中,内存存数据有哪些结构呢?
- 数组
- 链表
- 树
- 图
- 堆
- 栈
- hash表
5.HashMap底层到底是用什么来存数据的?
hash表
6.hash表是什么?
底层就是一个数组
7.既然已经有了数组,为什么又要提出一个hash表的概念呢?hash表跟数组有什么区别呢?
//数组的使用
String[] arr = new String[16];
arr[0] = "test1";
arr[1] = "test2";
//通过数组索引下标取值
System.out.println(arr[0]);
//创建HashMap通过put和get方法进行数据的读写,未指定下标
HashMap map = new HashMap();
map.put("name","test");
map.get("name");`
普通数组在读写数据的时候,需要人为给定下标,这样才能实现读写操作。
而hash表,不需要人为给下标。就可以实现读写操作。
8.Hash表中的下标到底是怎么得来的呢?
Hash值是int类型的
int类型是4Byte字节,1Byte等于8位二进制
int类型是32位数字
hash值 i = hash &(长度-1)
如果数组长度16,那i最大是15,就是4位2进制的最大值
9.通过上面算法去求下标,会不会存在什么问题呢?
hash冲突
10.一旦hash冲突了,怎么解决呢?
- 一个睡床上,一个睡床下;链式地址法,也是HashMap的默认解决方案。node.next();
- 换房间;线性探测法,是ThreadLocal底层的默认解决方案。
for(int i = hash&(c-1);arr[i]!=null;i++)
- 重新计算Hash值
11.有没有什么方法能减少hash冲突发生的概率?
- (h = key.hashCode()) ^ (h >>> 16) 专业术语叫扰动函数能在一定程度减少hash冲突的概率。
- 为什么数组的长度是2的幂次方
12.为什么在new HashMap的时候没有创建数组?
这种思想叫懒加载思想,只有在你真正用的时候,才会创建数组,你实例化的时候,并不会创建
这种思想会从技术底层帮我们节约资源,数组是存在堆内存中,如果实例化就创建数组,就会频繁占用堆内存导致资源浪费。
13.HashMap中的key可不可以为null呢?如果可以,那么null的key可以有多少个?
1.可以为null,只会存在一个为null的key,其余为null的key会被覆盖
14.HashMap底层的数组默认长度是多少?
底层默认长度4>> = 16
15.HashMap的扩容因子是多少?
底层默认是0.75
16.链表转换红黑树条件有两个,而且必须同时满足
- 链表的长度条件必须大于等于8
- 数组的长度必须大于等于64
在使用过程中会出现什么问题?
并发安全问题,加锁
2.原理和源码:
一定要先根据核心功能推到流程,在根据流程去看源码验证。
HashMap的核心功能是存数据的-->首先得有HashMap对象,才能调用它的方法去存
1、new HashMap的流程
public HashMap() {
this.loadFactor = 0.75; // all other fields defaulted
}
//在new对象的时候,HashMap的数组是null,并没有创建
//这种思想叫懒加载思想,只有在你真正用的时候,才会创建数组,你实例化的时候,并不会创建
2、map.put的流程
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
- 计算K的Hash值
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
-
如果key为null,那么三目运算得到的hash值是0 00000000 000000000 00000000 00000000
(h = key.hashCode()) ^ (h >>> 16) 专业术语叫扰动函数 -
会给我们创建一个数组
必须先拿到一个初始数组长度
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = 0;
int oldCap = 0;
int oldThr = threshold;
int newCap,
newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
} else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
} else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = 16;
newThr = (int)(0.75 * 16);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;// 12
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
return newTab;
}
- 再通过i=hash&(长度-1)获得下标
- 然后通过下标去存数据tab[i] = new Node("test",38);
- 在存的过程中会出现冲突
一旦发生hash冲突
-
- 先判断跟我冲突的是不是我自己,如果hash值和key都是我自己,那么进行等值覆盖。
- 如果不是我自己,则判断数组当前的节点是不是红黑树类型,如果是,则执行树的插入
- 如果不是红黑树,那么就进行插入链表