源码剖析之ArrayList

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测可视化 Grafana 版,10个用户账号 1个月
简介: ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。

1. ArrayList的基本介绍

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
   
   
}

ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。

​ ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
​ ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。

​ ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。

​ ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。和Vector不同,ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。

2. ArrayList属性

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
   
   
    // 序列化id
    private static final long serialVersionUID = 8683452581122892189L;
    // 默认初始的容量
    private static final int DEFAULT_CAPACITY = 10;
    // 一个空对象
    private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
    // 一个空对象,如果使用默认构造函数创建,则默认对象内容默认是该值
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
    // 当前数据对象存放地方,当前对象不参与序列化
    transient Object[] elementData;
    // 当前数组长度
    private int size;
    // 数组最大长度
    private static final int MAX_ARRAY_SIZE = 2147483639;

    // 省略方法。。
}

3. ArrayList的构造方法

//此时我们创建的ArrayList对象中的elementData中的长度是1,size是0,当进行第一次add的时候,elementData将会变成默认的长度:10.
public ArrayList() {
   
   
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
 //  带int类型的构造函数
public ArrayList(int initialCapacity) {
   
   
        if (initialCapacity > 0) {
   
   
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
   
   
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
   
   
            //如果用户传入的参数小于0,则抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
}

特别注意:对于以上2个构造方法在使用的时候推荐使用带int参数的构造方法,因为不带参数的可能会出现大量扩容的情况,从而导致性能低下。

/**
1)将collection对象转换成数组,然后将数组的地址的赋给elementData。
2)更新size的值,同时判断size的大小,如果是size等于0,直接将空对象EMPTY_ELEMENTDATA的地址赋给elementData
3)如果size的值大于0,则执行Arrays.copy方法,把collection对象的内容(可以理解为深拷贝)copy到elementData中。
*/
public ArrayList(Collection<? extends E> c) {
   
   
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
   
   
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            //深度拷贝
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
   
   
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

特别注意:this.elementData = list.toArray(); 这里执行的简单赋值时浅拷贝,所以要执行Arrays,copy 做深拷贝

4. 核心的方法

4.1 add方法

​ add主要的执行逻辑如下:
​ 1)确保数组已使用长度(size)加1之后足够存下 下一个数据
​ 2)修改次数modCount 标识自增1,如果当前数组已使用长度(size)加1后的大于当前的数组长度,则调用 grow方法,增长数组,grow方法会将当前数组的长度变为原来容量的1.5倍。
​ 3)确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。
​ 4)返回添加成功布尔值。

添加元素方法入口:

    public boolean add(E e) {
   
   
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

确保添加的元素有地方存储,当第一次添加元素的时候this.size+1 的值是1,所以第一次添加的时候会将当前elementData数组的长度变为10:

    private void ensureCapacityInternal(int minCapacity) {
   
   
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
   
   
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

将修改次数(modCount)自增1,判断是否需要扩充数组长度,判断条件就是用当前所需的数组最小长度与数组的长度对比,如果大于0,则增长数组长度。

    private void ensureExplicitCapacity(int minCapacity) {
   
   
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

如果当前的数组已使用空间(size)加1之后 大于数组长度,则增大数组容量,扩大为原来的1.5倍。

    private void grow(int arg0) {
   
   
        int arg1 = this.elementData.length;
        int arg2 = arg1 + (arg1 >> 1);
        if (arg2 - arg0 < 0) {
   
   
            arg2 = arg0;
        }
        if (arg2 - 2147483639 > 0) {
   
   
            arg2 = hugeCapacity(arg0);
        }
        this.elementData = Arrays.copyOf(this.elementData, arg2);
    }

add(int index, E element)方法

这个方法其实和上面的add类似,该方法可以按照元素的位置,指定位置插入元素,具体的执行逻辑如下:
1)确保数插入的位置小于等于当前数组长度,并且不小于0,否则抛出异常
2)确保数组已使用长度(size)加1之后足够存下 下一个数据
3)修改次数(modCount)标识自增1,如果当前数组已使用长度(size)加1后的大于当前的数组长度,则调用grow方法,增长数组
4)grow方法会将当前数组的长度变为原来容量的1.5倍。
5)确保有足够的容量之后,使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位。
6)将新的数据内容存放到数组的指定位置(index)上

    public void add(int index, E element) {
   
   
        rangeCheckForAdd(index);
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
        size - index);
        elementData[index] = element;
        size++;
    }

注意:使用该方法的话将导致指定位置后面的数组元素全部重新移动,即往后移动一位。

4.2 get方法

返回指定位置上的元素。

public E get(int index) {
   
   
      rangeCheck(index);
      checkForComodification();
      return ArrayList.this.elementData(offset + index);
 }

4.3 set方法

确保set的位置小于当前数组的长度(size)并且大于0,获取指定位置(index)元素,然后放到oldValue存放,将需要设置的元素放到指定的位置(index)上,然后将原来位置上的元素oldValue返回给用户。

    public E set(int index, E element) {
   
   
        rangeCheck(index);
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

4.4 contains方法

调用indexOf方法,遍历数组中的每一个元素作对比,如果找到对于的元素,则返回true,没有找到则返回false。

    public boolean contains(Object o) {
   
   
        return indexOf(o) >= 0;
    }
    public int indexOf(Object o) {
   
   
        if (o == null) {
   
   
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
   
   
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

4.5 remove方法

循环遍历所有对象,得到对象所在索引位置,然后调用fastRemove方法,执行remove操作

    public boolean remove(Object o) {
   
   
        if (o == null) {
   
   
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
   
   
                    fastRemove(index);
                    return true

                }
        } else {
   
   
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
   
   
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

定位到需要remove的元素索引,先将index后面的元素往前面移动一位(调用System.arraycooy实现),然后将最后一个元素置空。

    private void fastRemove(int index) {
   
   
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
        numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

4.6 clear方法

添加操作次数(modCount),将数组内的元素都置空,等待垃圾收集器收集,不减小数组容量。

    public void clear() {
   
   
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }
目录
相关文章
|
9月前
|
存储 Java 索引
ArrayList源码分析
ArrayList源码分析
35 1
|
9月前
|
存储 算法 Java
HashSet源码剖析
HashSet源码剖析
33 0
|
10月前
源码剖析之LinkedHashMap
LinkedHashMap是一个有序的map集合,他的特点就是在map的基础上增加了顺序从而让无序的map成为一个有序的集合,同时LinkedHashMap底层的实现也非常有意思。
78 0
|
11月前
ArrayList与LinkedList获取指定元素对比(源码分析)
ArrayList与LinkedList获取指定元素对比(源码分析)
64 0
|
11月前
|
存储 Java 容器
一文带你进行ArrayList 源码分析
一文带你进行ArrayList 源码分析
10867 1
|
11月前
|
存储 Java 程序员
ArrayList 的底层原理
ArrayList 是 Java 中常用的动态数组实现,它的底层是基于数组实现的。当创建一个 ArrayList 对象时,实际上是创建了一个 Object 类型的数组,初始容量为 10。当添加元素时,如果数组已满,ArrayList 会自动扩容,它会创建一个新的数组,并将原数组中的元素复制到新数组中。
906 0
ArrayList 的底层原理
|
存储 索引
ArrayList与LinkedList区别源码分析
1、ArrayList是基于数组,LinkedList是基于链表 2、基于数组的ArrayList对于根据索引值查找比较高效;基于链表的LinkedList对于增加、删除操作比较高效 3、剖析CRUD:
189 0
|
存储 Java
LinkedList源码分析
Java中List是一个必须要掌握的基础知识,List是一个接口,实现List接口的基础类有很多,其中最具有代表性的两个:ArrayList和LinkedList。
202 0
LinkedList源码分析
|
存储 安全 Java
java集合系列(3)ArrayList(源码分析)
这篇文章开始介绍ArrayList。ArrayList基本上是我们在平时的开发当中,使用最多的一个集合类了,它是一个其容量能够动态增长的动态数组。所以这篇文章,旨在从源码的角度进行分析和理解。为了使得文章更加有条理,还是先给出这篇文章的大致脉络: 首先,ArrayList的基本介绍和源码API(只给出方法分析,重要的方法给出详细代码)。 然后,介绍遍历ArrayList的几种方式 接下来,叙述一下ArrayList与其他集合关键字的区别和优缺点 最后,进行一个总结
217 0
java集合系列(3)ArrayList(源码分析)