用这篇ArrayList讲解来完爆面试官!!

用这篇ArrayList讲解来完爆面试官!!

前言

​ 作为一个95后的新时代码农,一个在互联网公司生存的工具人,一个专科毕业的码农,每次面试都被面试官面的完无体肤,于是在一个寂寞难耐的夜晚,我痛定思痛,希望能帮助各位读者。

​ 前言只是我的一个嘘头,我们应该有一颗谦逊的心(我现在是真的很菜),所以希望大家怀着心态好好学,一起进步!

类图

ArrayList类图

  • 实现了 RandomAccess 接口,可以随机访问。
  • 实现了 Cloneable 接口,可以进行克隆。
  • 实现了 Serializable 接口,可以进行序列化和反序列化。
  • 实现了 List 接口,是 List实现类之一。
  • 实现了 Iterable 接口,可以使用for-each进行循环迭代。

属性

    // 序列化版本UID
    private static final long serialVersionUID = 8683452581122892189L;

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

    /** 用于空实例的共享空数组实例 new ArrayList(0);*/
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**用于提供默认大小的实例的共享空数组实例*/
    private static final Object[]
            DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 存储ArrayList元素的数组缓冲区
     * ArrayList的容量,是数组的长度
     */
    transient Object[] elementData;

    /**ArrayList中元素的数量*/
    private int size;

  1. 为什么空实例默认数组有的时候是EMPTY_ELEMENTDATA,而又有的时候是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
  2. 为什么elementData要被transient修饰
  3. 为什么elementData没有被private修饰?

让我们带着问题,继续往下看!

构造方法

带初始容量的构造方法
/**
 * 带一个初始容量参数的构造方法
 *
 * @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);
    }
}
  • 如果容量大于0.则 elementData 等于一个Object长度的数组。
  • 如果容量等于0,则使用 EMPTY_ELEMENTDATA
  • 其他情况(容量小于0的情况下),会抛出异常。
无参构造方法
/**
 * 无参构造方法 将elementData 赋值为
 *   DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 */
public ArrayList() {
    this.elementData =
            DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
带一个集合参数的构造方法
/**
 * 带一个集合参数的构造方法
 *
 * @param c 集合,代表集合中的元素会被放到list中
 * @throws 如果集合为空,抛出NullPointerException
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    // 如果 size != 0
    if ((size = elementData.length) != 0) {
        // c.toArray 可能不正确的,不返回 Object[]
        // https://bugs.openjdk.java.net/browse/JDK-6260652
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(
                    elementData, size, Object[].class);
    } else {
        // size == 0
        // 将EMPTY_ELEMENTDATA 赋值给 elementData
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
  • 使用将集合转换为数组的方法。
  • 为了防止c.toArray()方法不正确的执行,导致没有返回Object[],特殊做了处理
  • 如果数组大小等于0,则使用 EMPTY_ELEMENTDATA

什么情况下c.toArray()会不返回Object[]呢?

那么,让我们来写一个测试demo看看:

在代码的最后一行,会爆出 存储错误错误,为什么呢?我们来debug来看一下。

我们可以看出 asList的toarray没有返回Object[],而list的toarray(),返回了Object[],那么,这究竟是为什么呢????

在这里插入图片描述

稳住,别慌!! 我们看ArrayList的toArray()方法源码:

在这里插入图片描述

我们看到使用了Arrays.copyOf()方法:

我们debug追到了这里,我们看到了 调用的cppyOf方法:在这里插入图片描述

我们看到 使用了三目运算来判断类型,如果newType是Object[] copy 数组 类型就是 Object 否则就是 newType 类型。ArrayList中elementData就是Object[]类型,所以ArrayList的toArray()方法必然会返回Object[]

在这里插入图片描述

某同学心里提问:“在demo中,为什么asList返回的不是Object呢??”

​ 我们可以看到,我们是使用Arrays.asList的方法:

在这里插入图片描述

这时候,某同学又提问了!!!这不就是带参数的ArrayList吗??? 错!!!

​ 我们这里的a是一个泛型,而我们使用的构造器是 Arrays内部的ArrayList:在这里插入图片描述

所以呢,我们看到java.util.Arrays的内部ArrayList的toArray()方法,是构造方法接收什么类型的数组,就返回什么类型的数组。所以,我们在我们的例子中,赋值为 Obejct自然会抛异常!!

插入方法

add(E e) 在列表的最后添加指定元素
/**
 * 在列表最后添加指定元素
 *
 * @param e 要添加的指定元素
 * @return true
 */
public boolean add(E e) {
    // 增加 modCount !!
    ensureCapacityInternal(size + 1); 
    elementData[size++] = e;
    return true;
}
add(int index, E element) 在指定位置添加指定元素
/**
 * 在指定位置添加指定元素
 * 如果指定位置已经有元素,就将该元素和随后的元素移动到右面一位
 *
 * @param index 待插入元素的下标
 * @param element 待插入的元素
 * @throws 可能抛出 IndexOutOfBoundsException
 */
public void add(int index, E element) {
    rangeCheckForAdd(index);


    // 增加 modCount !!
    ensureCapacityInternal(size + 1);
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

在父类AbstractList上,定义了modCount 属性,用于记录数组修改的次数。

**欸??又有同学提问了!!!!这个 ensureCapacityInternal 是干嘛的,直接插入到最后不就可以了吗??? **

在这里插入图片描述

判断是否为之后,我们可以看到 代码会从 默认容量和 minCapactiy中选出一个最小的容量当作elementData 数组长度。

而在 ensureExplicitCapacity 方法中:

在这里插入图片描述

就是在这里记录的 modCount,然后我们看到了最小容量 的校验,调用了grow方法:在这里插入图片描述

  • 通常情况新容量是原来容量的1.5倍
  • 如果原容量的1.5倍比minCapacity小,那么就扩容到minCapacity
  • 特殊情况扩容到Integer.MAX_VALUE

看到这里我们要不要想一下我们的第一个问题?

为什么空实例默认数组有的时候是EMPTY_ELEMENTDATA,而又有的时候是DEFAULTCAPACITY_EMPTY_ELEMENTDATA

原来,new ArrayList()会将elementData 赋值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,

new ArrayList(0)会将elementData 赋值为 EMPTY_ELEMENTDATA,EMPTY_ELEMENTDATA,

添加元素会扩容到容量为1,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA扩容之后容量为10

我们看到,我们使用有参构造0的时候,ensureCapacityInternal()的执行:

在这里插入图片描述

我们可以看到 minCapacity为1 ,因为 ementData在有参构造的时候,赋值为:EMPTY_ELEMENTDATA,而 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 并不等于 EMPTY_ELEMENTDATA

在这里插入图片描述

我们再来看扩容的方法,为容量最小为1的时候:

在这里插入图片描述

这里我们注意一下,因为 我们刚初始化(使用有参构造),所以elementData里面是没有元素的,而0右移一位,还是0;

删除元素方法

remove(Object o) 移除指定元素方法
/**
 * 移除第一个在列表中出现的指定元素
 * 如果存在,移除返回true
 * 否则,返回false
 *
 * @param o 指定元素
 */
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(int index) 删除指定下标元素
/**
 * 移除列表中指定下标位置的元素
 * 将所有的后续元素,向左移动
 *
 * @param 要移除的指定下标
 * @return 返回被移除的元素
 * @throws 下标越界会抛出IndexOutOfBoundsException
 */
public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
            System.arraycopy(elementData, 
                    index+1, elementData, index,  numMoved);
    // 将引用置空,让GC回收
    elementData[--size] = null;

    return oldValue;
}

以为为List 最常用的 添加和删除,我们来总结一下:

  • ArrayList底层的数据结构是数组
  • ArrayList可以自动扩容,不传初始容量或者初始容量是0,都会初始化一个空数组,但是如果添加元素,会自动进行扩容,所以,创建ArrayList的时候,给初始容量是必要的
  • Arrays.asList()方法返回的是的Arrays内部的ArrayList,用的时候需要注意

文章中没有提到点,或者你有什么其他好奇的地方,欢迎留言讨论。我们再见…

点个赞吧

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值