【Java】ArrayList

一、ArrayList 简介

底层是数组队列,相当于动态数组。与Java的数组相比,容量可以动态增长。在添加大量元素前,可使用 ensureCapacity增加ArrayList实例的容量。可减少递增式再分配的数量。

ArrayList 继承于 AbstractList ,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。

public class ArrayList<E> extends AbstractList<E>
    					  implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}
  • List : 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。
  • RandomAccess :标志接口,表明实现这个接口的 List 集合是支持 快速随机访问 的。
  • Cloneable :表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作。
  • Serializable : 表明它可以进行序列化操作,j即将对象转换为字节流进行持久化存储或网络传输。

问题:ArrayList 和 Vector 的区别?

  • ArrayListList 的主要实现类,底层使用 Object[]存储,适用于频繁的查找工作,线程不安全 。
  • VectorList 的古老实现类,底层使用Object[] 存储,线程安全。

问题:ArrayList 可以添加 null 值吗?

可以存储任何类型的对象,包括 null 值。不建议向ArrayList 中添加 null 值,会让代码难以维护比如忘记做判空处理就会导致空指针异常。

问题:ArrayList 和 LinkedList 区别?

  1. 是否线程安全?

    都是不同步的,即不保证线程安全。

  2. 底层数据结构?

    ArrayList 底层使用的是 Object 数组LinkedList 底层使用的是 双向链表 数据结构。

  3. 插入和删除是否受元素位置影响?

    • ArrayList采用数组存储,插入和删除时间复杂度受元素位置的影响。如果不在末尾则需要 O(n) 因为可能需要执行元素移位操作。
    • LinkedList采用链表存储,所以在头尾插入和删除不受元素位置影响。但如何在指定位置则时间复杂度为O(n),因为需要遍历到指定位置。
  4. 是否支持快速随机访问?

    LinkedList 不支持高效的随机元素访问,而 ArrayList(实现了 RandomAccess 接口) 支持。

  5. 内存空间占用?

    ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间。LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(存放直接后继和直接前驱以及数据)。

二、ArrayList类的成员变量

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8683452581122892189L;

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

    /**
     * 空数组(用于空实例)。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //用于默认大小空实例的共享空数组实例。
    //我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 保存ArrayList数据的数组
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList 所包含的元素个数
     */
    private int size;

}

ArrayList 扩容机制

private static final int DEFAULT_CAPACITY = 10;//默认初始容量大小
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

ArrayList 的构造函数

(JDK8)ArrayList有三种初始化的方式:

  1. 默认构造函数,使用初始容量为 10 构造一个空列表(无参构造)

以无参数构造方法创建 **ArrayList** 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
  1. 带初始容量参数的构造函数。(用户自己指定容量)
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {//初始容量大于0
        //创建initialCapacity大小的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {//初始容量等于0
        //创建空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {//初始容量小于0,抛出异常
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    }
}
  1. 构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回(如果指定的集合为null,throws NullPointerException)
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;
    }
}

add()

以无参构造函数创建的 ArrayList 为例

将指定的元素追加到此列表的末尾。

public boolean add(E e) {
    //添加元素之前,先调用ensureCapacityInternal方法
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;//ArrayList添加元素的实质就相当于为数组赋值
    return true;
}

注意:JDK11 移除了 ensureCapacityInternal()ensureExplicitCapacity() 方法

ensureCapacityInternal()

private void ensureCapacityInternal(int minCapacity) {//得到最小扩容量
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);// 获取默认的容量和传入参数的较大值
    }
    ensureExplicitCapacity(minCapacity);
}

当要add()进第 1 个元素时,minCapacity 为 1,在 Math.max()方法比较后,minCapacity 为 10。

ensureExplicitCapacity()

private void ensureExplicitCapacity(int minCapacity) {//判断是否需要扩容
    modCount++;
    if (minCapacity - elementData.length > 0)// overflow-conscious code
        grow(minCapacity);//调用grow方法进行扩容,调用此方法代表已经开始扩容了
}
  1. 当无参构造函数创建ArrayList,加入第一个元素时,elementData.length为0(因为此时是一个空的List)
  2. 执行ensureCapacityInternal()方法:使 minCapacity = 10
  3. 执行ensureExplicitCapacity()方法:进入grow(minCapacity)方法。

add(e),添加第2到第10个元素,不会执行grow方法,因为数组容量为10。

直到添加第11个元素,mincapacity = 11 > elementData.length = 10。进入 grow()方法进行扩容。

grow()

//要分配的最大数组大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//ArrayList扩容的核心方法。
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;//oldCapacity为旧容量,newCapacity为新容量
    int newCapacity = oldCapacity + (oldCapacity >> 1);//将新容量更新为旧容量的1.5倍
    //检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    //如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE
    //如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则新容量大小为 MAX_ARRAY_SIZE 为 `Integer.MAX_VALUE - 8`
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);// minCapacity is usually close to size, so this is a win:
}

当add第一个元素时,数组扩容为10;当add第11个元素时,扩展为15。

java 中的 length属性是针对数组。

java 中的 length() 方法是针对字符串

java 中的 size() 方法是针对泛型集合

hugeCapacity()

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

System.arraycopy() 和 Arrays.copyof()

System.arraycopy() 方法

/**
 *   复制数组
 * @param src 源数组
 * @param srcPos 源数组中的起始位置
 * @param dest 目标数组
 * @param destPos 目标数组中的起始位置
 * @param length 要复制的数组元素的数量
 */
public static native void arraycopy(Object src,  int srcPos,
                                    Object dest, int destPos, int length);

使用场景:列表的指定位置插入指定元素

  1. 先调用 rangeCheckForAdd对 index进行界限检查;调用 ensureCapacityInternal保证 capacity足够。
  2. 再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
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++;
}

Arrays.copyof() 方法

public static int[] copyOf(int[] original, int newLength) {
    // 申请一个新的数组
    int[] copy = new int[newLength];
    // 调用System.arraycopy,将源数组中的数据进行拷贝,并返回新的数组
    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    return copy;
}

场景:返回一个包含此列表的所有元素的数组,返回的数组的运行时类型是指定数组的运行时类型。

public Object[] toArray() {
    //elementData:要复制的数组;size:要复制的长度
    return Arrays.copyOf(elementData, size);
}

两者联系和区别

联系:copyOf()内部实际调用了 System.arraycopy() 方法

区别:

arraycopy() 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,且可以选择拷贝的起点和长度以及放入新数组中的位置。

copyOf() 是系统自动在内部新建一个数组,并返回该数组。

ensureCapacity()

内部没有被调用过,故是提供给用户调研。

public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // any size if not default element table
        ? 0
        // larger than default for default empty table. It's already
        // supposed to be at default size.
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}
  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值