Java集合框架体系
Hash - 哈希表 Linked 链表 Tree 红黑树
md,我觉得这部分应该直接看八股文,听视频实在是太慢了
数组这部分内容可以快速带过,如下:
ArrayList底层是使用动态数组实现的
ArrayList进行扩容时,每次是原先容量的1.5倍,每次扩容都需要拷贝数组
经典问题:
ArrayList list = new ArrayList(10);其中的list扩容了几次?
答案:这个只是声明和实例化了一个ArrayList,指定了容量为10,并没有进行扩容!
List转换到数组:转换完成之后,如果修改List,数组也会变,因为实际上两者地址是一模一样的
数组转换到List:这边转换完成后,数组改变,但是List不会改变,因为复制的过程是进行了新的拷贝!
直接看文档吧,来不及了
还是的细致的听一下课,不然很多细节会漏掉,答不上来
ArrayList是基于动态数组的数据结构实现
LinkedList是双向链表的数据结构实现
注意LinkedList不支持根据下标查询
ArrayList和LinkedList都不是线程安全的!
- 二叉树
- 红黑树
- 散列表
- 节点红色或者黑色
- 根节点必须是黑色的
- 叶子结点都是黑色的空节点
- 红黑树中,红色节点的子节点都是黑色
- 从任一节点到叶子结点的所有路径,都包含相同数目的黑色节点
红黑树增删改查的时间复杂度都是O(log n)
散列法-链表法(拉链): 插入时间复杂度 O(1) 查找时间 平均时间复杂度是 O(1),最坏时间复杂度是O(n)
所以,需要将链表法中的链表改造为红黑树,从而将时间复杂度优化为 O(log n)
修改为红黑树还有一个好处,可以预防DDos攻击
注意,DDos攻击指的是分布式拒绝服务攻击
问题:jdk1.7和jdk1.8中,HashMap的区别?
回答:红黑树是在jdk1.8之后才有的,之前没有。
扩容阈值 = 数组容量
上图是HashMap添加数据的全流程,很复杂
具体流程回答如下:
- 判断键值对数组Table是否为空或者null,否则执行resize()进行扩容
- 根据键值 key 计算 Hash 值得到数组索引
这边为什么要计算 e.hash & oldCap 呢 ? 因为e.hash实际上是通过新的NewCap运算到的Index,然后判断这个index在OldCap中,还是在NewCap中
如下图,HashMap的详细扩容机制
h = key.hashCode() ^ (h >>> 16)
这种 HashValue 算法能够将不随机的原始数据经过处理后,变得更随机(即扰动算法)
(n - 1) & hash 即 hash % n 但是前者性能更好!
- 计算索引时,效率更高:如果是2的n次幂,可以使用位运算代替取模
- 扩容时,重新计算索引效率更高:hash & oldCap。如果 = 0,就在原位置;否则,newIndex = oldIndex + oldCap
我终于整明白这边所谓的头插法是什么意思了,在HashMap扩容的过程中,旧的数组中的数据要拷贝到新的数组中去。但是这会同时带来对链表的操作,而所谓的头插法,就是比如 1 -> A -> B -> null,但是对链表处理的过程中,链表中的节点以头插法的形式插入到新的链表中去,也就变成了 1 -> B -> A -> null
但是在多线程环境下,可能线程A在执行的过程中,突然时间片被抢了。然后线程B执行了头插法扩容,结果此时节点指向易位,最终导致了死循环(e -> next -> e -> next)
由此,jdk1.8将扩容机制改成了尾插法,从而解决了这个问题







