ArrayList超细详解

ArrayList详解

简介:

概述:ArrayList 的底层是动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacityInternal操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。允许 null 的存在。(可重复、有下标、有序

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

在我们学数据结构的时候就知道了线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以 及取第 i 元素的时间复杂度为O(1)

ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。

ArrayList 实现了Cloneable接口,即覆盖了函数 clone(),能被克隆。ArrayList 实现 java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。

和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList, 而在多线程中可以选择 Vector 或者 CopyOnWriteArrayListCollections.synchronizedList

成员变量
	//默认初始容量
    private static final int DEFAULT_CAPACITY = 10;

	//无参构造时存放的数组
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //保存ArrayList数据的数组
    transient Object[] elementData;

    //集合元素个数
    private int size;

	//这个变量是定义在 AbstractList 中的。记录对 List 操作的次数。主要使用是在 Iterator,是防止在迭代的过程中集合被修改。
	protected transient int modCount = 0;
构造方法
//用户指定大小创建
public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    //创建initialCapacity大小的数组
    this.elementData = new Object[initialCapacity];
}

//无参构造、默认空数组
public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA;
}

//构造一个包含指定集合元素的列表
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    size = elementData.length;
    // 判断c.toArray()返回的是不是object类型的数组
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}
主要方法及解析
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    	//判断是不是空元素,如果是空元素,再从默认容量10和要添加元素大小中取最大值,进行扩容。
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }


private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

由上述源码可知,在每次add添加元素时,都会先通过ensureCapacityInternal()判断容量大小,还够不够添加该元素,如果不够,再调用grow()方法进行扩容。

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    //先1.5倍扩容该数组(如果该集合是空集合的话,扩容后的大小还是0)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //来比较扩容后的数组大小与添加完元素大小比较,如果还是比添加完元素大小小的话,就直接赋值添加元素的后的容量大小
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //比较此时的容量大小是否超过Interger类型的最大值
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 通过copyOf()将原数组复制到扩完容的新数组上
    elementData = Arrays.copyOf(elementData, newCapacity);
}

//如果超过Interge的最大值,就取Integer的最大值作为容量大小,没超过就取原值。
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

可见,grow方法先是1.5倍扩容原数组,要是还是不够的话,就直接扩容成添加完元素后数组大小。但不能超过Integer类型的最大值。超过了就取最大值。

public E remove(int index) {
    
   	//先判断该下标是否在size元素大小内,不在就报数组下标越界exception
    rangeCheck(index);
	//记录操作次数
    modCount++;
    //获取要移除的元素
    E oldValue = elementData(index);

    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

    return oldValue;
}

private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
迭代器 iterator

有使用过集合的都知道,在用 for 遍历集合的时候是不可以对集合进行 remove操作的,因为 remove 操作会改变集合的大小。从而容易造成结果不准确甚至数组下标越界,更严重者还会抛出 ConcurrentModificationException。


至于为啥会报ConcurrentModificationException异常,是因为for循环本质上还是走的是Iterator迭代器循环,而Iterator迭代器中在每次通过next()获取下一个元素时,会首先通过checkForComodification方法,判断修改的次数和被期待的修改次数是否相等,不相等则抛出异常。

public E next() {
    checkForComodification();/***看这行***/
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;//cursor向后挪一位
    return (E) elementData[lastRet = i];//lastRet为当前取出的元素所在索引,后面会用到
}
final void checkForComodification() {/***再看这里***/
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

使用iterator迭代器解决该问题:

直接调用 iterator.remove() 即可。因为在该方法中增加了 expectedModCount = modCount 操作。但是这个 remove 方法也有弊端。

1、只能进行remove操作,add、clear 等 Itr 中没有。
2、调用 remove 之前必须先调用 next。因为 remove 开始就对 lastRet 做了校验。而 lastRet 初始化时为 -1。
3、next 之后只可以调用一次 remove。因为 remove 会将 lastRet 重新初始化为 -1

总结

ArrayList 底层基于数组实现容量大小动态可变。 扩容机制为首先扩容为原始容量的 1.5 倍。如果1.5倍太小的话,则将我们所需的容量大小赋值给 newCapacity,如果1.5倍太大或者我们需要的容量太大,那就直接拿 newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE 来扩容。 扩容之后是通过数组的拷贝来确保元素的准确性的,所以尽可能减少扩容操作。 ArrayList 的最大存储能力:Integer.MAX_VALUE。 size 为集合中存储的元素的个数。elementData.length 为数组长度,表示最多可以存储多少个元素。 如果需要边遍历边 remove ,必须使用 iterator。且 remove 之前必须先 next,next 之后只能用一次 remove

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值