一、Hash函数
(1)概念
hash函数就是把一个任意长度的输入映射成固定长度的输出。
(2)hash冲突
① 概念
hash函数也会存在问题,有可能两个val值经过hash算法后,得到相同的hash值,这就是hash冲突(碰撞问题)。
② 解决办法
HashMap是主要通过数组+链表来解决hash冲突的,当发生冲突了,对象将会储存在链表的下一个节点中(第3种)。 HashMap在每个链表节点中储存键值对对象。
1、 开放定址法:
所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入 。
2、再哈希法:
再哈希法又叫双哈希法,有多个不同的Hash函数,当发生冲突时,使用第二个,第三个,….,等哈希函数
计算地址,直到无冲突。虽然不易发生聚集,但是增加了计算时间;
3、链地址法:
链地址法的基本思想是:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向 链表连接起来。
4、建立公共溢出区:
这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。
(3)好的hash算法应该考虑哪些点?
(1)hash算法效率得搞,要做到长的文本也能高效计算出hash值;
(2)hash值不能逆推出原文;
(3)两次输入,只要有一点不同,也得保证hash值不同;
(4)尽量分散,散列。
二、HashMap
(1)存储数据的结构:
基于hash表,JDK1.8中,hash表存储采用数组+链表+红黑树实现。(链表或数组)
每个数据单元都是一个node结构,里头有key、value、next 和hash,next字段就是发生hash冲突时,当前bucket桶位中的node与冲突node连成一个链表要用的字段。
——hash表是一种以键值对存储数据的结构,链式哈希表是一组链表组成的,每个链表称为一个“桶”,将所有元素通过散列的方式放到不同的桶中。
① hash表
- 创建HashMap时,散列表数组的初始长度为16;
- 散列表不是new出HashMap时就创建的,散列表是懒加载机制,只有第一次put数据时,它才创建。
- 默认的负载因子及其用处:默认是0.75,作用是计算hash表扩容阈,默认是16*0.75=12,所以我们往HashMap中put12个值时,它就会自动扩容。
总结HashMap数组扩容(即resize)
- 当hashmap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对hashmap的数组进行扩容。
- 当数组元素个数大于160.75时,会进行扩容,扩容为原来的2倍,216=32。(但是是原数组的数据转移到新数组中,需要计算其位置并复制,这会消耗性能)
② 链表转红黑树
- 链表长度达到8,且当前hash表的数组长度达到64时,链表就会转为红黑树(否则就算到了8,hash表也只是会继续扩容,直到扩容至64)。
- 相对于链表,红黑树结构可以大大减少查询时间(log N,会自平衡,减少层数,从而减少磁盘IO)。
- (当链表长度小的时候,其遍历速度足够快,但是链表长了以后,转为树才行O(log N)。)
(2)HashMap的put工作原理
HashMap在put方法中,它使用hashCode()和equals()方法。当我们通过传递key-value对调用put方法的时候,HashMap使用Key hashCode()和哈希算法来找出存储key-value对的索引。
如果索引处为空,则直接将value插入到对应的数组中;否则,判断是否是红黑树,若是,则在红黑树中插入,否则遍历链表进行插入,若长度大于8,则将链表转为红黑树,转成功之后再插入。
(3)HashMap的get工作原理
用key进行hash算法运算,得到hashcode,根据hashcode找到索引。定位到具体的桶。先判断是否为链表,不是就根据hashCode是否相等来返回值,为链表则遍历,知道hashCode相等再返回值。否则返回null
(4)HashMap为什么线程不安全?
Hash表扩容,如果两个及以上线程同时地遇到Hash表大小达到12的倍数时,就很可能会出现将oldTable转移到newTable的过程中遇到的问题,从而导致最终的hash表的值存储异常。
具体:当多个线程同时检测到总数量超过阈值时,就会同时调用resize方法,各自生成新数组并rehash后赋给map底层的数组table,最后只有一个线程生成的新数组被赋给table变量,其它的线程均丢失。
而且当某些线程已经完成赋值,而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会出问题。
三、红黑树的写入操作
TreeNode结构包括:指向父节点的parent指针、指向左右节点的left和right指针,以及颜色。
(1)找到插入节点的父结点
红黑树满足二叉搜索树的所有排序特性,寻找父节点操作与其一致,
持续更新。。。。。。