JavaSE复习之ArrayList源码分析

想尝试做源码分析好久了,总是想着要不停去学习java新的知识,静不下心来回顾学过的知识。今天终于是开了个头,希望能一直坚持吧! 其实网上关于各种源码分析的文章或者博客已经很多了,很多也写的特别好,但我想着,还是要自己真正的过一遍才能有更深刻的印象和更深入的了解,才能够学的更加扎实。 每天进步一点点!

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

1. 成员变量

private static final Object[] EMPTY_ELEMENTDATA = {};//使用有参构造显式传入0时使用该数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//无参构造器时使用该数组
transient Object[] elementData;//ArrayList内部维护的数组
private int size;//实际元素的个数

2. 构造器

/**
 * 构造具有指定初始容量的空list
 *
 * @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的空列表。
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
 * 构造一个列表,其中包含指定集合的元素,按集合的迭代器返回元素的顺序排列。
 *
 * @param c 指定集合
 * @throws NullPointerException 指定集合为null时抛出异常
 */
public ArrayList(Collection<? extends E> c) {
    //将指定集合转成数组赋值给elementData
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        //指定集合长度不为0时
        if (elementData.getClass() != Object[].class)//这个操作没看懂...
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        //指定集合长度为0,返回空数组
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

3. 成员方法

  • /**
     * 确认是否需要扩容
     *
     * @param minCapacity 所需的最小容量
     */
    public void ensureCapacity(int minCapacity) {
        if (minCapacity > elementData.length
            && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
                 && minCapacity <= DEFAULT_CAPACITY)) {
            /*
            扩容条件分析:
            	1.minCapacity > elementData.length:所需的最小容量大于数组长度
            	2.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA:使用无参构造器创建的新数组
            	3.minCapacity <= DEFAULT_CAPACITY:所需的最小容量小于默认容量
            	满足1 且 (不满足2 或 不满足3)
            */
            modCount++;
            grow(minCapacity);//扩容
        }
    }
    
  • /**
     * 扩容
     * 只在add、addAll方法中被调用
     * 即只有在添加元素时才会判断是否需要扩容
     *
     * @param minCapacity 所需的最小容量
     * @throws OutOfMemoryError 所需的最小容量小于0时抛出异常
     */
    private Object[] grow(int minCapacity) {
        int oldCapacity = elementData.length;
        if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //当前容量大于0或者不是使用无参构造器创建的新数组(???感觉怪怪的...)
            //生成新的容量,生成方法在下面分析
            int newCapacity = ArraysSupport.newLength(oldCapacity,/* 当前容量 */
                    minCapacity - oldCapacity, /* 所需容量和当前容量差值 */
                    oldCapacity >> 1           /* 当前容量一半 */);
            //返回新容量大小的数组
            return elementData = Arrays.copyOf(elementData, newCapacity);
        } else {
            //数组为空时,新建数组,长度取默认容量和所需容量的较大值
            return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
        }
    }
    //供内部调用
    private Object[] grow() {
        return grow(size + 1);
    }
    //生成新容量的方法(ArraysSupport类提供的静态方法):
    public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
        //增量选择为 所需容量与当前容量差值 和 当前容量一半 中的较大值
        int newLength = Math.max(minGrowth, prefGrowth) + oldLength;
        if (newLength - MAX_ARRAY_LENGTH <= 0) {
            /* 
              要分配的最大数组长度(除非必要)
              某些虚拟机在数组中保留一些标头字
              尝试分配更大的数组可能会导致内存溢出错误
            */
            /* public static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8; */
            //如果新的容量没有超过最大数组长度,返回新的容量
            return newLength;
        }
        //如果新的容量超过了最大数组长度,调用hugeLength方法
        return hugeLength(oldLength, minGrowth);
    }
    //hugeLength方法(ArraysSupport类中的静态方法)
    private static int hugeLength(int oldLength, int minGrowth) {
        int minLength = oldLength + minGrowth; //所需容量
        if (minLength < 0) { // 超过int范围,抛出异常
            throw new OutOfMemoryError("Required array length too large");
        }
        if (minLength <= MAX_ARRAY_LENGTH) { //所需容量小于最大数组长度
            return MAX_ARRAY_LENGTH; //返回最大数组长度
        }
        return Integer.MAX_VALUE; //否则,返回整型最大值
    }
    
  • //获取元素
    public E get(int index) {
        //检查索引
        Objects.checkIndex(index, size);
        return elementData(index);
    }
    
  • //设置元素的值
    public E set(int index, E element) {
        //检查索引
        Objects.checkIndex(index, size);
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
    
  • //增加元素
    public boolean add(E e) {
        //修改次数+1
        modCount++;
        //调用下面的方法
        add(e, elementData, size);
        return true;
    }
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            //实际元素的个数和数组长度相等时,扩容
            elementData = grow();
        //将e加入数组中
        elementData[s] = e;
        //实际元素的个数+1
        size = s + 1;
    }
    //插入元素
    public void add(int index, E element) {
        //检查索引
        rangeCheckForAdd(index);
        //修改次数+1
        modCount++;
        final int s;
        Object[] elementData;
        if ((s = size) == (elementData = this.elementData).length)
            //实际元素的个数和数组长度相等时,扩容
            elementData = grow();
        //待插入位置之后的元素后移
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);
        elementData[index] = element;
      size = s + 1;
    }
    
  • //移除元素
    public E remove(int index) {
        //检查索引
        Objects.checkIndex(index, size);
        final Object[] es = elementData;
    
        @SuppressWarnings("unchecked") E oldValue = (E) es[index];
        //调用下面方法
        fastRemove(es, index);
    
        return oldValue;
    }
    
    private void fastRemove(Object[] es, int i) {
        //修改次数+1
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            //要移除的元素不是最后一个,后面的元素前移
            System.arraycopy(es, i + 1, es, i, newSize - i);
        //最后一个元素的引用置空
        es[size = newSize] = null;
    }
    

4. 关于迭代

所有的Collection都实现了Iterable接口,可以使用for-each循环进行迭代

for-each循环非常好用,我们都知道,for-each背后是用迭代器实现的,编译器会将其转换为类似如下代码:

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    it.next();
}

也因此在ArrayList中使用for-each循环会遇到一些问题

比如,我们想在遍历的过程中添加某个元素:

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
for (String s : list) {
    if ("A".equals(s)) {
        list.add("C");
    }
}

看起来是比较正常的需求,然而,这段代码会抛出异常:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1009)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:963)
	at com.riven.ArrayListTest.main(ArrayListTest.java:12)

并发修改异常,为什么呢?因为迭代器内部会维护一些索引位置相关的数据,要求在迭代过程中,容器不能发生结构性变化,否则这些索引位置就失效了。所谓结构性变化就是添加、插入和删除元素,只是修改元素内容不算结构性变化。

如何避免异常呢?可以使用迭代器的remove方法,如下所示:

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("A")) {
        it.remove();
    }
}

那么这是什么原理呢?通过观察前面ArrayList的一些常用方法的源码中,在添加、插入和删除方法中,都出现了一个字段modCount,这个字段继承自AbstractList,表示修改次数,官方文档注释中,对这个字段的解释是:子类可以通过使用这个字段来提供 fail-fast iterators。那么这个fail-fast是怎么实现的呢?我们来看一下ArrayList跟迭代器有关的源码

public Iterator<E> iterator() {
    return new Itr();
}

ArrayList通过上面这个方法实现了一个迭代器,其中,Itr是一个内部类,实现了Iterator接口

private class Itr implements Iterator<E>

内部有三个成员变量

int cursor;       // 下一个要返回的元素位置
int lastRet = -1; // 最后一个返回的索引位置,如果没有,返回-1
int expectedModCount = modCount; //期望的修改次数,初始化为外部类当前的修改次数modCount

先看一下hasNext()方法的实现

public boolean hasNext() {
    return cursor != size;
}

没什么可说的,主要看一下next()方法的实现

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;
    return (E) elementData[lastRet = i];
}

这个方法的第一行,调用了checkForComodification()方法

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

在这个方法里面,判断了expectedModCountmodCount是否相等,如果不相等,便会抛出异常。我们前面说过,ArrayList中的添加、删除、插入等方法会改变modCount的值,迭代器每一次迭代,都会调用next()方法,一旦我们调用了使容器发生结构性变化的方法,就会抛出并发修改异常。

那么为什么调用迭代器的remove()方法就不会抛出异常了呢?我们来看一下remove()方法

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

在这个方法里面,迭代器调用了ArrayList的remove方法,然后更新了三个成员变量的值,让expectedModCount等于modCount,于是,可以正确删除。但是要注意的是,调用remove()方法之前必须调用next()方法,因为remove()方法删除的是lastRet即最后一个返回的索引位置的元素。

5. 特点

对于ArrayList,它的特点是内部采用动态数组实现,这决定了以下几点。

  1. 可以随机访问,按照索引位置进行访问效率很高,用算法描述中的术语,效率是O(1),简单说就是可以一步到位。
  2. 除非数组已排序,否则按照内容查找元素效率比较低,具体是O(N), N为数组内容长度,也就是说,性能与数组长度成正比。
  3. 添加元素的效率还可以,重新分配和复制数组的开销被平摊了,具体来说,添加N个元素的效率为O(N)。
  4. 插入和删除元素的效率比较低,因为需要移动元素,具体为O(N)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值