2019年一文读懂ArrayList

ArrayList


一、简介

ArrayList 底层的数据结构是 “数组”,它会随着元素的增加而动态扩容,它是有序可重复、线程不安全的集合,可存入多个 null 对象。

1.1 源码分析
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}

从源码分析,ArrayList 继承于 AbstractList,实现了 List,RandomAccess,Cloneable 和 Serializable 接口:

  1. 实现 List,并继承 AbstractList,获得集合接口 List 的基础功能;

  2. 实现 RandomAccess ,获得了快速随机访问存储元素的功能(RandomAccess 是一个标记接口,没有任何方法);

    // Collections类
    public static <T>
    int binarySearch(List<? extends Comparable<? super T>> list, T key) {
        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
            return Collections.indexedBinarySearch(list, key);
        else
            return Collections.iteratorBinarySearch(list, key);
    }
    /**
     *区别:
     *  实现RandomAccess接口的List集合采用一般的for循环遍历,而未实现这接口则采用迭代器。
     *  ArrayList用for循环遍历比iterator迭代器遍历快,LinkedList用iterator迭代器遍历比for循环遍历快。
     */
    
  3. 实现 Cloneable,重写 clone,实现克隆功能;

  4. 实现 Serializable,可被序列化。

1.2 使用建议
  1. 当集合中对插入元素数据的速度要求不高,但是要求快速访问元素数据,则使用ArrayList。
  2. 当需要运行在高版本jdk 环境下,经常采用尾部插入操作时,建议使用 ArrayList。

二、特点

2.1 初始化ArrayList的大小,未指定时默认是10
// 默认初始容量:10
private static final int DEFAULT_CAPACITY = 10;

// 容量最大值:2147483647
Integer.MAX_VALUE

2.2 数据都存储在数组中
private static final Object[] EMPTY_ELEMENTDATA = {};

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //用于校验是否为空数组

transient Object[] elementData;

2.3 扩容
public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}

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

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

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

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

  • 第一种情况:默认构造函数
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

默认构造函数初始化空数组。在调用添加元素方法的时候,会初始大小为10的数组,再将元素存入数组中。不断存入元素会不断扩容,当 newCapacity 扩容量大于数组定义的最大阀值时,会调用 hugeCapacity 进行判断。如果待扩容大小已经超过 Integer 的最大值(溢出为负数),将抛出 OutOfMemoryError (内存溢出错误)。否则返回 Integer的最大值或 MAX_ARRAY_SIZE。故 ArrayList 的最大容量是 Integer 的最大值(-2的31次方~2的31次方减1)。

  • 第二种情况:自定义初始容量的构造函数
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);
    }
}
  1. 当传入值 > 0 时,会创建指定容量大小的数组,之后会按照此大小 “理论的1.5倍” 扩容;
  2. 当传入值 = 0 时,那么前四次的扩容每次只加1,第五次之后的才按照 “理论的1.5倍” 扩容;

注意:

“理论的1.5倍” 扩容是指:扩容后的大小 = 原大小 + 原大小 / 2。如果 原大小 / 2 刚好约去小数部分,那么扩容的大小并没有原大小的1.5倍。

  • 第三种情况:传入 Collection 集合方式的构造函数
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // 指此时的 elementData数组是Object子类的数组,
        // 若对数组中某一位置赋值Object父类时,将抛出向下转型异常,故单独判断不做拷贝
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
  1. 若 Collection.toArray 返回的是 Object 的数组:

    将直接使用返回值(已将原数组中的数据浅拷贝到 elementData)。

  2. 若 Collection.toArray 返回的是 Object 子类的数组:

    将调用 Arrays.copyOf(System.arraycopy)方式创建 “toArray” 大小的数组,并将原数组中的数据浅拷贝到 elementData。

  • 注意:为什么要将大小减8?

因为数据存储在数组中,有些虚拟机会在数组对象中额外存储一些信息(如描述对象状态的信息、对象是否同步的信息、数组的大小等),单独判断一次是为了避免内存溢出。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值