ArrayList底层源码大总结(扩容机制和初始化)

1、 ArrayList容器类

ArrayList 是 List 接口的实现类。是 List 存储特征的具体实现。

特点:查询快,增删慢,主要用于查询遍历数据,为最常用集合之一;

数据结构:数组;

底层分析:数组结构是有序的元素序列,在内存中开辟一段连续的空间,在空间中存放元素,每个空间都有编号,通过编号可以快速找到相应元素,因此查询快;数组初始化时长度是固定的,要想增删元素,必须创建一个新数组,把源数组的元素复制进来,随后源数组销毁,耗时长,因此增删慢。

1.1 构造器

在这里插入图片描述

List<String> list = new ArrayList<>();
List<String> list1 = new ArrayList<>(list);
List<String> list2 = new ArrayList<>(20);

第二种构造器List<String> list1 = new ArrayList<>(list);

源码

public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        elementData = EMPTY_ELEMENTDATA;
    }
}

参数的源码

public interface List<E> extends Collection<E> {
    int size();
    boolean isEmpty();
}

第三种构造器List<String> list1 = new ArrayList<>(20);

System.out.println(list1.size());//0

看到了没,list1还是一个空列表,只不过她的初始容量是20

1.2 添加元素

在这里插入图片描述

List<String> list = new ArrayList<>();
boolean a = list.add("a");
list.add(1,"b");
System.out.println(list);//[a, b]

list.add(3,"b");//IndexOutOfBoundsException

注意:第二种方法,索引不能大于元素的个数。

1.3 获取元素

在这里插入图片描述

在这里插入图片描述

String s1 = list.get(0);
String s2 = list.get(1);
String s3 = list.get(2);
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

1.4 删除元素

在这里插入图片描述

List<String> list = new ArrayList<>();
list.add(0,"a");
list.add(1,"b");
list.add(2,"c");
list.add(3,"d");
System.out.println("列表:"+list);

String remove = list.remove(2);
System.out.println("删除的元素"+remove);
System.out.println("列表:"+list);
list.remove("d");

输出:

列表:[a, b, c, d]
删除的元素c
列表:[a, b, d]

1.5 替换元素

在这里插入图片描述

List<String> list = new ArrayList<>();
list.add(0,"a");
list.add(1,"b");
list.add(2,"c");
list.add(3,"d");
System.out.println("列表:"+list);

String hello = list.set(0, "Hello");
System.out.println("替换的元素"+hello);
System.out.println("列表:"+list);

输出:

列表:[a, b, c, d]
替换的元素a
列表:[Hello, b, c, d]

1.6 清空元素

在这里插入图片描述

List<String> list = new ArrayList<>();
list.add(0,"a");
list.add(1,"b");
list.add(2,"c");
list.add(3,"d");
System.out.println("列表:"+list);

list.clear();
System.out.println("列表:"+list);

输出:

列表:[a, b, c, d]
列表:[]

1.7 判空

在这里插入图片描述

List<String> list2 = new ArrayList<>();
boolean empty = list2.isEmpty();
System.out.println(empty);//true

1.8 判断容器是否包含指定元素

在这里插入图片描述

List<String> list = new ArrayList<>();
list.add(0,"a");
list.add(1,"b");
list.add(2,"c");
list.add(3,"a");
System.out.println("列表:"+list);

boolean is = list.contains("a");
System.out.println(is);//true

输出:

列表:[a, b, c, a]
true

1.9 查找元素的位置

在这里插入图片描述
在这里插入图片描述

List<String> list = new ArrayList<>();
list.add(0,"a");
list.add(1,"b");
list.add(2,"c");
list.add(3,"a");
System.out.println("列表:"+list);

int index = list.indexOf("a");
System.out.println("a在列表中的第一个位置 "+index);

int lastIndex = list.lastIndexOf("a");
System.out.println("a在列表中的最后一个位置 "+lastIndex);

输出:

列表:[a, b, c, a]
a在列表中的第一个位置 0
a在列表中的最后一个位置 3

1.10 将单例集合转换成数组

转换为 Object 数组

在这里插入图片描述

Object[] array = list.toArray();
for (Object o : array) {
    System.out.print(o + " ");
}

输出:

列表:[a, b, c, a]
a b c a 

注意:不可以将转换后的结果强制类型转换(Object可以强转为String,但是你见过将Object数组强转为String数组吗?除非你一个一个的来强转)。想要实现该效果,请看下一知识

String[] arr = (String[]) list.toArray();
for (String s : arr) {
    System.out.print(s+" ");
}

输出:

Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.String; ([Ljava.lang.Object; and [Ljava.lang.String; are in module java.base of loader 'bootstrap')

转换泛型类型数组

在这里插入图片描述

List<String> list = new ArrayList<>();
list.add(0,"a");
list.add(1,"b");
list.add(2,"c");
list.add(3,"a");
System.out.println("列表:"+list);

String[] array = list.toArray(new String[list.size()]);
for (String s : array) {
    System.out.print(s+" ");
}

输出:

列表:[a, b, c, a]
a b c a 

1.11 容器的并集操作

在这里插入图片描述

List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
System.out.println("列表list1:"+list1);

List<String> list2 = new ArrayList<>();
list2.add("b");
list2.add("c");
list2.add("d");
System.out.println("列表list2:"+list2);

boolean b = list1.addAll(list2);
System.out.println(b);
System.out.println("列表list1:"+list1);
System.out.println("列表list2:"+list2);

输出:

列表list1:[a, b, c]
列表list2:[b, c, d]
true
列表list1:[a, b, c, b, c, d]
列表list2:[b, c, d]

1.12 容器的交集操作

在这里插入图片描述

List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
System.out.println("列表list1:"+list1);

List<String> list2 = new ArrayList<>();
list2.add("b");
list2.add("c");
list2.add("d");
System.out.println("列表list2:"+list2);

boolean b = list1.retainAll(list2);
System.out.println(b);
System.out.println("列表list1:"+list1);
System.out.println("列表list2:"+list2);

输出:

列表list1:[a, b, c]
列表list2:[b, c, d]
true
列表list1:[b, c]
列表list2:[b, c, d]

Process finished with exit code 0

1.13 容器的差集操作

在这里插入图片描述

List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
System.out.println("列表list1:"+list1);

List<String> list2 = new ArrayList<>();
list2.add("b");
list2.add("c");
list2.add("d");
System.out.println("列表list2:"+list2);

boolean b = list1.removeAll(list2);//相当于list1-list2
System.out.println(b);
System.out.println("列表list1:"+list1);
System.out.println("列表list2:"+list2);

输出:

列表list1:[a, b, c]
列表list2:[b, c, d]
true
列表list1:[a]
列表list2:[b, c, d]

Process finished with exit code 0

2、 ArrayList底层的初始化(使用空参构造)

前置知识:ArrayList的重要字段和方法

/**
* 默认初始容量
*/
private static final int DEFAULT_CAPACITY = 10;

/**
* 用于默认大小的空实例的共享空阵列实例。我们将其与EMPTY_ELEMENTDATA区分开来,以了解添加第一个元素
* 时要膨胀多少
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};


/**
* 存储 ArrayList 元素的数组缓冲区。ArrayList 的容量是此数组缓冲区的长度。添加第一个元素时,任何带
* 有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将扩展为 
* DEFAULT_CAPACITY
*/
transient Object[] elementData;

/**
* ArrayList 的大小(它包含的元素数)
*/
private int size;

//空参构造器
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

问题的引出

现在有一个代码,问:list1列表是如何初始化的呢?

List<String> list1 = new ArrayList<>();
list1.add("a");

先说结论:

  • JDK1.7时采用立即初始化,也就是说list1列表会在一开始的时候直接elementData = new Object[10]此时elementData的长度为10。

  • JDK1.8之后采用延迟初始化,底层的elementData会被直接初始化为{}(空数组)只有当向列表中添加元素的时候才会将列表的长度改为10(DEFAULT_CAPACITY)

源码分析

追踪代码中的List list1 = new ArrayList<>();

public ArrayList() {//初始化为空数组{}
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

追踪代码中的add方法

add底层:

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

add(e, elementData, size)底层调用了grow方法,grow方法的底层(其实是底层的底层):

private Object[] grow(int minCapacity) {
    //...
    return elementData = new Object[DEFAULT_CAPACITY];
}

现在你明白了吧,JDK1.8之后的延迟初始化就是在第一次添加元素的时候,进行初始化长度10

现在问你一个问题:此时我们list2.size()的结果是多少呢?答案是1。想一想为什么

3、 ArrayList的扩容机制

目标代码

List<String> list1 = new ArrayList<>();
list1.add("1");
list1.add("2");
list1.add("3");
list1.add("4");
list1.add("5");
list1.add("6");
list1.add("7");
list1.add("8");
list1.add("9");
list1.add("10");
list1.add("11");

追踪list1.add(“1”);

  1. 调用list接口方法
boolean add(E e);
  1. 实际上调用ArrayList类的方法
public boolean add(E e) {
    modCount++;
    add(e, elementData, size);//size 0
    return true;
}
  1. 然后调用ArrayList类的方法
private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)//满足条件
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}
  1. 此时,满足条件(s == elementData.length)
private Object[] grow() {
    return grow(size + 1);
}
  1. grow方法底层,又调用了另一个grow方法
private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = 
            ArraysSupport.newLength(oldCapacity,
                                    minCapacity -oldCapacity,oldCapacity >> 1);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}
  1. 此时,是第一次添加元素,所以不满足if条件,执行else语句

    else语句的本质就是将elementData初始化为DEFAULT_CAPACITY长度

追踪list1.add(“11”);

  1. 调用list接口方法
boolean add(E e);
  1. 实际上调用ArrayList类的方法
public boolean add(E e) {
    modCount++;
    add(e, elementData, size);//size 10
    return true;
}
  1. 然后调用ArrayList类的方法
private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)//elementData.length == 10
        elementData = grow();//这是重点
    elementData[s] = e;
    size = s + 1;
}
  1. 此时,满足条件(s == elementData.length)
private Object[] grow() {
    return grow(size + 1);//size == 10
}
  1. grow方法底层,又调用了另一个grow方法
private Object[] grow(int minCapacity) {//minCapacity == 11
    int oldCapacity = elementData.length;//oldCapacity == 10
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = 
            ArraysSupport.newLength(oldCapacity,
                                    minCapacity -oldCapacity,oldCapacity >> 1);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}
  1. 此时,满足if条件,执行ArraysSupport类中的newLength方法
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
    int prefLength = oldLength + Math.max(minGrowth, prefGrowth); 
    if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
        return prefLength;
    } else {
        return hugeLength(oldLength, minGrowth);
    }
}
  1. 计算得出newCapacity = 15,然后执行 Arrays.copyOf 。最后实现将字符串"11"插入列表,并进行扩容1.5倍的操作

扩容机制总结

当添加一个元素时,检查数组的长度是否足够。如果不够则扩容,够了就不扩容。ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右

注意:ArrayList不存在”当数组剩余容量多少时,我们扩容“

  • 27
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值