ArrayList详解

简介

ArrayList 是 java 集合框架中比较常用的数据结构了。继承自 AbstractList,实现了 List 接口。底层基于数组实现容量大小动态变化。允许 null 的存在。同时还实现了 RandomAccess、Cloneable、Serializable 接口,所以ArrayList 是支持快速访问、复制、序列化的。

成员变量:

在这里插入图片描述
从上图我们可以看到,ArrayList的初始长度为10,我们要注意下ArrayList真实结构上的这段注释

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */

这段注释讲了四点

1、这个Object[]elementData是ArrayList存储元素的数组缓冲区
2、ArrList的长度就是这个数组缓冲区的长度
3、任何初始的空ArrList的初始时默认使用Object[]DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数组 
4、只有当第一个元素添加的时候才会被扩容至默认的10长度的数组

ArrayList的构造方法

/**
     * 构造具有指定初始容量的空列表。
     * @param  initialCapacity  列表的初始容量
     * @throws IllegalArgumentException
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
 
    /**
     * 构造一个初始容量为10的空列表。
     * MARK:
     * 这里其实是赋值了一个共享的空数组,数组在第一次添加元素时会判断elementData是否等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,假如等于则会初始化容量为DEFAULT_CAPACITY 也就是10
     * 符合 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的注释说明
     * private static int calculateCapacity(Object[] elementData, int minCapacity) 这个方法里可以看出
     *
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
 
    /**
     * 构造一个包含指定元素的列表集合
     * @param c 要将其元素放入此列表的集合
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        //  这里说明所有的 Collection 都可以用数组来承载
        //  这个步骤可能会抛空指针 NullPointerException
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            //必须是Object数组
            if (elementData.getClass() != Object[].class)
                // 然后再copy一份到 elementData 并不是引用 所有改变不会影响到原先的Collection
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 有参构造函数 当初始化为空数组时 赋值为 EMPTY_ELEMENTDATA
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
 
    /**
     * 这里属于针对初始化时的扩容判断 当为DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时说明是无参构造函数创建的,则可以直接扩容为DEFAULT_CAPACITY也就是10为初始容量
     */
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

ArrayList存在两种构造函数:

  • 无参构造,直接默认的空数组赋值给到了elementData
  • 有参构造,根据传入的容量大小来创建数组,不一样的是,这样初始化长度为0的时赋值的不是默认的空数组,而是另一个空数组
    这里有个小疑问,为什么两者都用空数组,为什么要用两个,大家都共享一个空数组不就行了,为什么要不同?我查阅了一些资料,下面来详解一下这个问题

从上面3个构造函数里可以看出,无参构造函数的空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值,有参构造函数的空数组会用EMPTY_ELEMENTDATA赋值,在增加元素时会先做扩容判断调用calculateCapacity方法,假如elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA就会直接扩容为默认容量10。

这个意思我理解,这是当使用无参构造创建实例,并且向这个实例中添加元素,会自动触发扩容到10的操作。如果构造函数都共享一个空数组时,就会全部触发扩容到10这个机制,但是这未必是我们想要的,也许我们只需要5个容量呢,所以这里使用了两个空数组来对应无参构造函数和有参构构造函数!下面用代码去验证一下,所以还是我想的太多,用两个不同的空数组还是有其道理的,可以有效应对不同的需求

import java.lang.reflect.Field;
import java.util.*;

public class Solution {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        ArrayList<Integer> list1 = new ArrayList<Integer>(0);
        list.add(1);//会触发自动扩容机制,扩容到10
        System.out.println(getArrayListCapacity(list));
        list1.add(1);//不会触发上面的扩容机制,容量应该为1
        System.out.println(getArrayListCapacity(list1));
        for(int i=0;i<10;i++){//当容量容纳不下元素,会自动扩容原数组长度的0.5倍,新数组长度为原来的1.5倍,将旧数组元素拷贝到新数组
            list.add(0);
        }
        System.out.println(getArrayListCapacity(list1));
    }
    //利用反射获取私有的element数组的长度,即ArrayList的容量
    public static int getArrayListCapacity(ArrayList<?> arrayList) {
        Class<ArrayList> arrayListClass = ArrayList.class;
        try {
            Field field = arrayListClass.getDeclaredField("elementData");
            field.setAccessible(true);
            Object[] objects = (Object[]) field.get(arrayList);
            return objects.length;
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            return -1;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return -1;
        }
    }
}

输出:

10
1
15

这里还学习了ArrayList的扩容机制!!!

常用方法解析

  • add()方法,向容器中插入元素
public class Solution {
    public static void main(String[] args) {
        ArrayList<Integer> list=new ArrayList<>();
        for(int i=0;i<10;i++){
            list.add(i);
        }
        System.out.println(list);
    }
}
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • get(int index):ArrayList无法直接使用下标来访问对应的元素,通过get()方法,来获取下标对应的元素
public class Solution {
    public static void main(String[] args) {
        ArrayList<Integer> list=new ArrayList<>();
        for(int i=0;i<10;i++){
            list.add(i);
        }
        System.out.println(list);
        System.out.println(list.get(0));
    }
}

输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
0
  • size()方法:容器中元素个数
public class Solution {
    public static void main(String[] args) {
        ArrayList<Integer> list=new ArrayList<>();
        for(int i=0;i<10;i++){
            list.add(i);
        }
        System.out.println(list.size());
    }
}

输出:

10
  • clear():清空容器中所有元素
public class Solution {
    public static void main(String[] args) {
        ArrayList<Integer> list=new ArrayList<>();
        for(int i=0;i<10;i++){
            list.add(i);
        }
        System.out.println(list);
        list.clear();
        System.out.println(list);
    }
}

输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
  • contains()方法,判断容器中是否含有某个元素
public class Solution {
    public static void main(String[] args) {
        ArrayList<Integer> list=new ArrayList<>();
        for(int i=0;i<10;i++){
            list.add(i);
        }
        boolean isExist=list.contains(1);
        boolean isExist1=list.contains(100);
        System.out.println(isExist);
        System.out.println(isExist1);
    }
}

输出:

true
false
  • remove()方法:移除某个元素
remove方法有两种重载的方式:①移除某个元素值,②移除下标为index的元素
public class Solution {
    public static void main(String[] args) {
        ArrayList<Integer> list=new ArrayList<>();
        for(int i=0;i<10;i++){
            list.add(i);
        }
        boolean success=list.remove((Integer)1);
        System.out.println(list);
        Integer removeNumber=list.remove(1);
        System.out.println(list);
        System.out.println(success);
        System.out.println(removeNumber);
    }
}

输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8]
[0, 2, 3, 4, 5, 6, 7, 8]
true
1

重点:

  • ArrayList底层是基于数组实现容量大小动态可变。当触发扩容机制时候,新数组的容量为原数组的1.5倍,再将原数组拷贝到新数组。

总结平常遇到的问题和知识,如果有什么不对的地方请指赐教!

文章内容参考了一下博客:

https://blog.csdn.net/m0_53121042/article/details/110464378
https://blog.csdn.net/lucky1521/article/details/79966743
https://blog.csdn.net/jy317358306/article/details/111633841
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值