ArrayList源码分析

90 阅读3分钟

集合

ArrayList

扩容机制

  1. 无参初始容量:0

image.png

image.png

如果给的是整数参数的有参构造,以整数为容量

如果给的集合为参数,以集合为大小创建初始容量

add方法扩容

  1. 第一次扩容:10

    add第一个元素,触发第一次扩容

以新的数组替换掉旧的数组,长度为0的就不用了

  1. 第二次扩容:上一次容量的1.5倍

    如:创建一个长度为15的新数组,把旧数组的元素拷贝到里面,把新的元素追加新的数组中去。新数组代替旧数组,无人引用作为垃圾会被垃圾回收。

  2. 说是1.5其实是右移动一位,第三次扩容

    15>>1,正数的右移一位相当于除2+原始容量

    15/2=7

    7+15=22

扩容:0,10,15,22,33,49,73,109,163,244,366,549...

addAll方法扩容

添加的是Collection集合

运行规律:当原始容量不够时,下次扩容的容量大小跟元素个数二者之间的较大值作为下次容量

  1. 添加集合小于10时

    第一次扩容:10

  2. 第一次添加集合大于10时

    例如11>10,容量11

  3. 之后添加容量

    如:元素有10个的,容量为10,之后调用addAll加3个元素,正常情况下次扩容为15,但只有13个元素

    15>13,扩容为15

元素有10个的,容量为10,之后调用addAll加6个元素,正常情况下次扩容为15,有16个元素

16>15,扩容为16

迭代器Iterator_FailFast_FailSafe

遍历集合,可以遍历Set集合遍历List集合,

package com.zyl.list;
​
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
​
/**
 * @Auther:zhuangyilong
 * @Date:2023/3/23
 */
public class FailFastVsFailSafe {
    //fail-fast 一旦发现遍历的同事其他人来修改,则立刻抛异常
    //fail-safe  发现遍历的同时其他人来修改应当能有应对策略,例如牺牲一致性来让整个遍历运行完成
    private static void failFast(){
        ArrayList<Student> list = new ArrayList<>();
        list.add(new Student("A"));
        list.add(new Student("B"));
        list.add(new Student("C"));
        list.add(new Student("D"));
        for (Student student :list){
            System.out.println(student);//在这运行途中进行修改
        }
        System.out.println(list);
    }
​
    private static void failSafe(){
        CopyOnWriteArrayList<Student> list =new CopyOnWriteArrayList<>();
        list.add(new Student("A"));
        list.add(new Student("B"));
        list.add(new Student("C"));
        list.add(new Student("D"));
        for (Student student :list){
            System.out.println(student);//在这运行途中进行修改
        }
        System.out.println(list);
    }
​
    public static void main(String[] args) {
//        failFast();
        failSafe();
    }
}
​

Iterator_FailFas源码分析

image.png

增强for循环,底层也有用到迭代器,首次循环new了个迭代器对象:

image.png

之后每次循环都是调用迭代器的hasnext方法看看有没有下一个元素,调用next方法移动到下一个元素

image.png

调用迭代器的构造方法,之后初始化一些成员变量

int expectedModCount = modCount;

记录list集合被修改了多少次

modCount是list集合的成员变量,记录list被修改几次,因为在循环前已经往集合+了4次元素,加一次modCount+1

image.png

expectedModCount迭代器的成员变量,记录迭代器对象刚开始迭代时list的修改次数记录

image.png

之后调用迭代器hasNext和next

image.png

每次调用next时都会调用checkForComodification(),该方法是对ModCount进行检查

image.png

迭代器内部记录下来的修改次数和外面list的修改次数做对比,抛出ConcurrentModificationException()并发修改异常,判断循环开始时和循环中间的修改次数是否一致,断定有没有其他人对集合进行修改

乐观锁的感觉

debug在循环过程加元素

image.png

image.png

modCount变成5

下轮循环调用下轮元素,显示有数据

image.png

之后运行到checkForComodification()方法

image-20230324060444428

image.png 最后抛出异常

Iterator_FailSafe源码分析

CopyOnWriteArrayList<Student> list =new CopyOnWriteArrayList<>();

image.png

ArrayList另一个迭代器实习COWIterator

image.png

进入构造方法

image.png

es会把当前正在遍历的数组记录下来

image.png

会把数组保存在迭代器的snapshot

image.png

循环途中往list新增一个元素

image.png

进入源码

image.png

snapshot还是原来的数组

但外面list 的数组是五个元素

image.png

list的数组和迭代器的数组是两个不同的数组。

迭代器里正在循环的是4个元素的最开始的数组,list中是有5个元素的数组。出现两个数组

add方法源码:

image.png

add方法里把原来的数组复制一份,长度+1,加在复制出来新数组的最后一个元素,遍历时是旧数组,但每次添加时都是新数组

,二者互不干扰,读写分离。

只是旧数组遍历结束就没用了。

总结

  • ArryList是fail-fast的典型代表,遍历的同时不能修改,尽快失败
  • CopyOnWriteArrayList是fail-safe的典型代表,遍历的同时可以修改,原理是读写分离

vector是fail-fast