是什么?谁发明的?用来做什么?特点是什么?
哈希表,JDK自带的存储容器,存储key-value数据,特点是访问快
为啥访问快?底层结构?原理?
底层采用数组+链表/红黑树,利用hash+取余操作快速定位数组下标,数组和红黑树主要解决哈希冲突
什么是哈希冲突?
不同的对象的hash函数+取余操作可能得到相同的值,这个时候数组只有一个位置,放不下多个元素,所以就出现了冲突,需要解决冲突,就是在当前位置拉出一个链表。
那么为啥要出现红黑树?
随着冲突的增加,链表长度会变长,搜索的时候是利用hash函数+取余计算下标,然后遍历链表匹配到值,如果链表太长,遍历链表性能会下降,所以采用红黑树进行优化,红黑树是一个树形数据结构,搜索时间复杂度更低。
为啥要扩容?
ArrayList扩容很容易理解,因为底层是数组,数组长度是固定的。HashMap底层是数组+链表/红黑树,链表和红黑树长度不固定,为啥要扩容呢?
性能考虑:其实这个很好理解,不扩容的话,链表长度或者红黑树层级越来越高,查询、插入、删除、修改效率会越来越慢
减少哈希冲突概率:数组长度越长,哈希冲突概率也就越小。
在JDK 1.7 和 JDK 1.8中的主要变化
程序员是人,不是神,所以JDK一开始也不完美
JDK 1.8,扩容 从头插法改成尾插法,避免了死循环(什么是死循环?)
引入了红黑树
优化的哈希计算:Java 1.8 使用了更简单的哈希计算方法
扩容头插法死循环原因?
多个线程同时调用put方法,并发触发扩容导致扩容后的链表出现环形指针。主要还是因为 HashMap 不支持并发,可以用ConcurrentHashMap代替、或者加锁、或者HashTable(性能不行)
此时调用 HashMap 的get方法获取数据时,如果只是获取循环链上存在的数据,是不会有问题的,因为可以找到。就怕获取循环链上没有的数据,会进入无限循环中导致CPU使用率飙升。
扩容
扩容:在添加元素时,如果添加后的大小超过了阈值(容量 * 负载因子)
容量 :默认16
负载因子:默认0.75
容量可以通过构造函数指定,具体可以根据实际使用场景设置(尽量避免频繁扩容)。
负载因子0.75是JDK官方根据一些统计学方法,计算得到的合适的值,这样很少会出现哈希冲突
扩容后的大小是原来的 2 倍。