系列文章目录
第一章 ArrayList-Java八股(一)
文章目录
前言
准备春招实习的小伙伴可以一起交流学习,动态每日更新算法题。
一、ArrayList的扩容
其实ArrayList底层就是一个数组,为了方便的讲清楚扩容机制,以下内容会采用图解的方式来说明。
1.1 ArrayList的初始化
这是jdk1.8中ArrayList的空参构造方法的说明,构造一个大小为10的空列表,请注意这个构造方法是已经进行了一次扩容。
模拟构建一个ArrayList集合。
不断的往集合中add元素,直到添加满,总共为10个元素。
1.2 ArrayList的扩容
在添加第11个元素时,集合会扩容为15个元素(一般情况下达到上限之后会扩容为原先大小的1.5倍,但不是绝对,在addAll方法中不适用此规则),具体流程是新建一个新的数组,然后做数据迁移,后面会再详细说明。
1.3 扩容大小计算规则
如何计算1.5倍,在是奇数大小的情况下
1.4扩容流程拆分
对于扩容分为两个步骤先看扩容的结果
第16个元素加入之后集合大小扩容为22个。
那么这期间可以拆分为两个步骤:
- 新建一个大小为22的新数组
- 数组内容的迁移
1.5 add方法源码
1.6 addAll()方法
而对于addAll()方法扩容规则会有所变化。
上述代码分别对一个空集合使用addAll()方法,参数个数为两组分别为3个和11个,
那么对于数量为3的集合来说扩容为10个,而对于数量为11的集合来说扩容数量为11个。
也就是说在使用addAll()方法来说会在下一次扩容数量和需要添加的数量中取一个最大值
二.Fail-Fast/Safe 特性
Fail-safe和Fail-fast,是多线程并发操作集合时的一种失败处理机制。
Fail-fast : 表示快速失败,在集合遍历过程中,一旦发现容器中的数据被修改了,会立刻抛出ConcurrentModificationException异常
2.1 Fail-Fast代码演示
代码使用增强for来进行遍历,而其底层其实为迭代器进行遍历。
在如下循环中加入断点,并在idea中开启两次,也就是两个进程来模拟多线程处理,当程序一停到这边时,程序二完成添加操作。
程序一抛出ConcurrentModificationException异常
2.2 Fail-Fast源码
以下是迭代器的源码,每一次遍历都会使用next()方法取到下一个元素,而当中checkForCmodification()就是抛出此异常的方法。
进入其方法之后,可以看到两个参数modCount和expectedModCount,在初始化时modCount赋值给expectedModCount,也就是说二者其实是相等的,而modCount代表的意思是对集合的操作次数,而expectedModCount代表的是迭代器对集合操作的次数,一旦在程序二在运行时,modCount就发生了变化,因此会抛出异常。
2.3 Fail-Safe源码
在这里我们可以看到数组会先赋值给snapshot快照,而后面参与迭代器计算的就是snapshot而不是es。
在next方法中完全是对snapshot进行操作。
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
三. LinkedList
3.1 ArrayList和LinkedList区别
其实两种集合都一样,ArrayList底层是数组,而LinkedList底层为链表。
3.2 cpu缓存局部性原理
比如说计算a+b,那么在没有缓存的情况下,cpu需要读取在内存a寄存器和b寄存器中的数据,计算完之后放入c寄存器,而IO都是非常耗时的操作。
而在缓存的情况下,将a和b读入之后,那么a和b以及计算后的c会存入缓存中,那么下一次在使用abc的值时会直接存缓存中读取,大大减少了IO操作。
但是在读取时,比如说需要读取数组索引[0]的的数据,那么cpu在读取时,会连续读取一段数据,可能会读取完整个数组,而链表在内存的地址是不连续的,所以没有办法利用局部性原理。