(一)ArrayList详解

535 阅读6分钟

ArrayList概述

ArrayList是实现于List接口的集合类,其底层是基于 数组 实现的,支持随机访问

ArrayList类的属性

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    /**
     * ArrayList默认初始容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空数组,无参构造器中创建对象用到
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 空数组,用于控制当ArrayList添加新元素时,计算扩容的容量
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     *存放集合元素
     */
    transient Object[] elementData; 

    /**
     * 集合包含的元素数量,获取集合元素数量时无需遍历,所以时间复杂度为O(1)
     */
    private int size;
    
    /**
     * ArrayList所能存储元素的最大容量
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}

ArrayList的构造方法

无参构造方法

//构造一个初始容量为 10 的空列表。
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

使用无参构造新建一个ArrayList时,当添加第一个元素时因为elementDate会等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,所以会将集合的容量设置为默认的容量10;由此可以看出当我们用无参构造new一个ArrayList集合时,在还没有添加元素之前,集合的容量为0,只有当添加第一个元素时触发扩容,将集合的容量设置为默认容量1

带初始容量的构造方法

public ArrayList(int initialCapacity) {
    //如果初始容量大于10
    if (initialCapacity > 0) {
        //初始化一个大小为initialCapacity的数组赋值给数组elementData
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        //如果初始容量等于0
        //则将前面ArrayList定义的类属性EMPTY_ELEMENTDATA空数组赋值给数组elementData
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        //如果初始化容量小于0,则抛出IllegalArgumentException异常
        throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
    }
}

带集合参数的构造方法

//构造一个包含指定集合的ArrayList
public ArrayList(Collection<? extends E> c) {
    //利用toArray()方法将集合c转换为数组,并将转换后的数组赋值给数组a
    Object[] a = c.toArray();
    //如果数组中的元素个数不等于0
    if ((size = a.length) != 0) {
        //如果集合c的类型为ArrayList
        if (c.getClass() == ArrayList.class) {
            //则将集合c转化后的数组a赋值给数组elementData
            elementData = a;
        } else {
            //否则,复制数组a并将复制后新数组赋值给elementDate数组
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        //数组中元素个数为0,则将EMPTY_ELEMENTDATA(空数组)赋值给elementDate
        elementData = EMPTY_ELEMENTDATA;
    }
}

ArrayList添加元素方法

ArrayList添加元素有两个方法add(E e)、add(int index,E element).

add(E e)方法

//将指定的元素添加到此集合的末尾。
public boolean add(E e) {
    //确保ArrayList的容量能够容纳得下新增的元素
    ensureCapacityInternal(size + 1); 
    //将e元素插入到elementDate数组的size位置,然后数组的长度size+1
    elementData[size++] = e;
    //放回true
    return true;
}

add(int index,E element)方法

//在集合的指定位置插入指定元素
public void add(int index, E element) {
    //检查插入的位置是否合法,如果(index > size || index < 0),则抛出IndexOutOfBoundsException异常
    rangeCheckForAdd(index);
    //确保ArrayList的容量能够容纳得下新增的元素
    ensureCapacityInternal(size + 1);
    //arraycopy方法实现依次将elemenDate数组中下标为index的元素及其以后的所有元素拷贝到下一个位置
    //目的是将下标为index的位置腾出,用于存储新增的元素element
    System.arraycopy(elementData, index, elementData, index + 1,size -index);
    //腾出index位置,将element插入到index位置
    elementData[index] = element;
    //数组长度size+1
    size++;
}

ArrayList添加元素过程详细解析 我们在使用ArrayList的add方法新增元素时,底层会调用ensureCapacityInternal(int minCapacity)方法 [ 方法传入的参数minCapacity,即当前ArrayList需要的最小存储容量,值为当前数组的长度+1] 来判断ArrayList的容量是否足够;其中先通过calculateCapacity()方法计算出当前ArrayList需要的最小存储容量,接着调用ensureCapacityInternal(int minCapacity)方法中的ensureExplicitCapacity(int minCapacity)方法,用来判断当前数组是否需要进行扩容,如果当前ArrayList所需的最小存储容量大与elementData数组的长度,则调用grow(int minCapacity)方法进行扩容,在grow()方法中,首先会判断当前所需最小的存储容量是否小于0,如果小于0,则说明已经产生溢出了,否则,继续判断当前ArrayList所需的最小存储是否大于ArrayList所能存储的最大容量,如果大于则返回Integer.Max_VALUE作为新的容量,否则返回ArrayList所能存储的最大容量MAX_ARRAY_SIZE作为新的存储容量

过程图解 image.png

图文并茂,这下你应该懂了吧! φ(>ω<*)

ArrayList的优缺点

  • ArrayList的优点如下:
    1. ArrayList 底层以数组实现,允许对元素进行快速随机访问。ArrayList 实现了RandomAccess 接口,因此查找的时候非常快。
    2. ArrayList 在顺序添加一个元素的时候非常方便,非顺序添加就不合适。
  • ArrayList 的缺点如下:
    1. 当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
    2. 底层以数组实现则缺点是每个元素之间不能有间隔,当数组大小不满足时需要扩容,就要原数组中的元素复制到新的存储空间中,比较耗费性能。

多线程场景下如何使用 ArrayList

  • ArrayList是非线程安全的,在多线程场景下,可以通过Collections的synchronizedList(List<T> list)方法将其转换成线程安全的容器后再使用。如下:
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
List<Integer> securityList = Collections.synchronizedList(list);

ArrayList的常用方法

  • boolean add(E e) 将指定的元素追加到此列表的末尾。
  • void add(int index, E element) 在此列表中的指定位置插入指定的元素。
  • E get(int index) 返回此列表中指定位置的元素。
  • boolean contains(Object o) 如果此列表包含指定的元素,则返回 true 。
  • int indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
  • boolean isEmpty() 如果此列表不包含元素,则返回 true 。
  • E set(int index, E element) 用指定的元素替换此列表中指定位置的元素。
  • int size() 返回此列表中的元素数。
  • boolean remove(Object o) 从列表中删除指定元素的第一个出现(如果存在)。
  • E remove(int index) 删除该列表中指定位置的元素。
  • boolean addAll(Collection<? extends E> c) 批量将另一个集合c中的元素添加到ArrayList集合的末尾。
  • boolean addAll(int index, Collection<? extends E> c) 将另一个集合c中的所有元素插入插入到指定位置index后。
  • void clear() 从列表中删除所有元素。
  • Iterator<E> iterator() 以正确的顺序返回该列表中的元素的迭代器。
  • int lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
  • ListIterator<E> listIterator() 返回列表中的列表迭代器(按适当的顺序)。
  • ListIterator<E> listIterator(int index) 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。
  • boolean removeAll(Collection<?> c) 用于删除ArrayList与另一个另一个集合c的交集部分的元素。
  • boolean removeIf(Predicate<? super E> filter) 删除满足给定谓词的此集合的所有元素。
  • protected void removeRange(int fromIndex, int toIndex) 从这个列表中删除所有索引在 fromIndex (含)和 toIndex之间的元素。
  • void replaceAll(UnaryOperator<E> operator) 将该列表的每个元素替换为将该运算符应用于该元素的结果。
  • boolean retainAll(Collection<?> c) 仅保留此列表中包含在指定集合中的元素。
  • void sort(Comparator<? super E> c) 使用提供的Comparator对此列表中的元素进行比较并排序。

ArrayList类与数组相互转换

  1. ArrayList类转数组
  • 使用toArray()方法返回一个包含ArrayList中所有元素的数组。
  1. 数组转ArrayList类
  • 使用 Arrays. asList(array)进行转换。

以上就是对ArrayList的详细描述,接下来还会推出其他相关集合类的分析,欢迎大家关注留言、共同进步....

默认标题_动态分割线_2021-07-15-0.gif

往期推荐