Java List 之 ArrayList (JDK1.8) 学习笔记

246 阅读3分钟

Java 集合框架之 List 系列

ArrayList 学习总结 - 基于JDK1.8

ps:所有总结均是个人查看源码手写总结,未借鉴其他博客,转发请标明出处

ArrayList 继承关系图:

上级接口:List,Cloneable,RandomAccess,Serializable
上级抽象类:AbstractList

先说ArrayList的属性:
transient Object[] elementData:
ArrayList底层实现,Object数组, transient使其不参与序列化反序列化

int size: 记录数组的实际使用个数

int DEFAULT_CAPACITY = 10:默认初始容量,当调用无参构造方法后,使用add时方法会使用

需要区分以下两个数组的区别,在不同场景中使用不同
Object[] EMPTY_ELEMENTDATA = {}:
调用有参构造方法传0时,以及调用参数为Collection的构造方法,但该对象元素个数为0时,设置elementData为该空对象数组

Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}:
调用无参构造方法时会设置elementData为该值

接下来说构造方法:

无参构造方法

public ArrayList() {
        //  设置elementData为此空对象数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
} 

设置ArrayList初始容量的构造方法

public ArrayList(int initialCapacity) {
       if (initialCapacity > 0) {
           // 当参数大于0时直接创建对应大小的Object数组
           this.elementData = new Object[initialCapacity];
       } else if (initialCapacity == 0) {
           // 将底层数组设置为此空对象数组
           this.elementData = EMPTY_ELEMENTDATA;
       } else {
           // 参数小于0时抛出异常
           throw new IllegalArgumentException("Illegal Capacity: "+
                                              initialCapacity);
       }
   }

传入Collection对象的有参构造方法,将该对象的元素添加到ArrayList中

public ArrayList(Collection<? extends E> c) {
        // 将参数转为Object对象数组
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
            // 参数元素不为0时如果参数是ArrayList对象则让elementData直接指向该对象数组
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                // 否则将该参数元素拷贝到elementData中
                // Arrays.copyOf()也是使用了System.arraycopy(),后者为native方法
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // 当该参数元素为0时设置elementDat为此空对象数组
            elementData = EMPTY_ELEMENTDATA;
        }
    }

add(E e)方法执行流程简述:
1.调用ensureCapacityInternal方法,将数组实际容量size + 1后传入该方法
2.调用calculateCapacity方法,计算需要的容量,如果之前构造函数是使用的无参构造,那么会返回DEFAULT_CAPACITY = 10,否则返回的就是之前的size + 1
3.调用ensureExplicitCapacity方法,参数为10 或者 size + 1
4.如果是之前调用的无参构造函数并返回10作为参数进行此方法调用时,必定会直接走到下一步-扩容
5.不然的话会判断size + 1与elementData数组大小,若容量不够,则调用grow进行扩容
6.若容量够,则会返回ensureCapacityInternal方法,执行赋值操作
7.容量不够时调用grow方法进行扩容
8.grow执行流程:按旧数组扩容1.5倍,调用Array.copyOf方法进行拷贝
9.执行elementData(size++) = e, 完成赋值
由于此处代码比较简单,就不上传源码了

add(int index, E element)方法执行流程简述:
在指定位置添加元素,与上一个方法不同之处1: 插入前要判断插入下标是否没有越界,2:扩容之后调用Sysetm.arraycopy方法进行数组拷贝

remove(Object o)方法简述
o为null时从0至size-1判断元素是否为null,遇到第一个null时删除该元素然后退出返回true,否则返回false,非null时则调用equals方法进行判断,后续操同null元素判断

iterator()和listIterator()
ArrayList本身也是实现了iterator方法的,实现原理和AbstractList类似,通过内部类。同时也实现了ListIterator方法,可以实现从后往前迭代ArrayList集合

总结: ArrayList底层实现实际是使用了对象数组,所以它是可以实现随机存取的,读取效率高,在指定位置插入和删除元素的效率低(因为需要挪动其他元素),非线程安全,是快速失败的,不可在循环中通过自身的remove方法删除元素,单线程情况下可以使用iterator对象的remove方法删除元素,多线程情况下则需要对迭代器加锁

推荐:: 使用stream流操作集合会使得代码量减少很多