Java源码详解五:ArrayList源码分析--openjdk java 11源码

文章目录

    • 注释
    • 类的继承与实现
    • 构造函数
    • add操作
    • 扩容函数
    • remove函数
    • subList函数
    • 总结


本系列是Java详解,专栏地址:Java源码分析


ArrayList 官方文档:ArrayList (Java Platform SE 8 )
ArrayList .java源码共1760行,下载地址见我的文章:Java源码下载和阅读(JDK1.8/Java 11)

文件地址:openjdk-jdk11u-jdk-11.0.6-3/src/java.base/share/classes/java/util


在看源码前,先说一下为什么要看源码。因为我关注的公众号三天两头推送这种文章:《为什么阿里巴巴要求谨慎使用ArrayList中的subList方法》,这让我很恼火。于是今天就打算自己看源码。

注释

1.实现了List接口,并允许空元素。
2.提供了操作内部的数组的方法,类似Vector,但不是线程安全的。
3.size, isEmpty, get, set, iterator, listIterator 操作是O(1)时间复杂度的。add操作在时间均摊后是O(1)时间复杂度。
4.与LinkedList相比,constant factor常数因子较低。
5.每个ArrayList实例都有capacity,也就是在列表中存储元素的数组的大小。它总是至少与列表大小一样大。将元素添加到ArrayList后,其容量会自动增长。
6.可以使用ensureCapacity手动扩容,减少自动扩容带来的开销。
7.ArrayList不是synchronized,不是线程安全的。可以通过Collections.synchronizedList实现线程安全。
8.迭代器是fail-fast,如果在创建迭代器之后的任何时间对列表进行结构修改(除了通过迭代器自己的 方法remove或 add方法外),迭代器都将抛出异常ConcurrentModificationException

类的继承与实现

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

继承自AbstractList,实现了接口ListRandomAccess

构造函数

private static final int DEFAULT_CAPACITY = 10;//默认初始容量
private static final Object[] EMPTY_ELEMENTDATA = {};//空实例的空数组 
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//使用无参构造初始化ArrayList时默认的空数组
transient Object[] elementData;//存储ArrayList元素的数组。
private int size;//the number of elements it contains

之所以存储数据的数组不设为private是为了让嵌套类访问更方便。

    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);
        }
    }
	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // defend against c.toArray (incorrectly) not returning Object[]
            // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

add操作

add操作可以说是ArrayList最重要的操作之一了。

    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
    /**
     * This helper method split out from add(E) to keep method
     * bytecode size under 35 (the -XX:MaxInlineSize default value),
     * which helps when add(E) is called in a C1-compiled loop.
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

这里有个问题,那就是代码明明很简单,为什么要写2个函数,不直接在一个函数中写完。根据注释,这是为了让add(E)函数的字节码的长度小于35,更有效的被调用于C1-compiled loop,这应该是JIT优化的手段。根据我查阅资料,函数的字节码小于35才有可能成为内联函数。

扩容函数

当数组满时,自动扩容:

    private Object[] grow() {
        return grow(size + 1);
    }
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

扩容函数表示一般扩容50%
一个很明显的缺点:使用数组的拷贝,并没有利用原来的数组。

remove函数

既然我们知道了ArrayList的底层实现就是数组,那么我很好奇ArrayList会如何实现删除操作:

    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) {
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }

根据注释,移除元素后,剩下的所有元素都要左移。使用系统提供的拷贝函数进行拷贝。

subList函数

到了关键的subList函数。

    public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList<>(this, fromIndex, toIndex);
    }
    private static class SubList<E> extends AbstractList<E> implements RandomAccess {
        private final ArrayList<E> root;
        private final SubList<E> parent;
        private final int offset;
        private int size;

        /**
         * Constructs a sublist of an arbitrary ArrayList.
         */
        public SubList(ArrayList<E> root, int fromIndex, int toIndex) {
            this.root = root;
            this.parent = null;
            this.offset = fromIndex;
            this.size = toIndex - fromIndex;
            this.modCount = root.modCount;
        }
        public E set(int index, E element) {
            Objects.checkIndex(index, size);
            checkForComodification();
            E oldValue = root.elementData(offset + index);
            root.elementData[offset + index] = element;
            return oldValue;
        }

        public E get(int index) {
            Objects.checkIndex(index, size);
            checkForComodification();
            return root.elementData(offset + index);
        }
    }

看到返回一个新的对象,内部类的实例SubList
代码中的注释提到了要点:SubList函数提供原始list的一个视图
所以就有了以下注意事项:

1.subList函数返回的对象不能被转为ArrayList,只能当做List用。因为SubList只是ArrayList的内部类,他们之间并没有继承关系,故无法直接进行强制类型转换。
2.SubList并没有重新创建一个List,而是直接引用了原有的List(返回了父类的视图),只是指定了一下他要使用的元素的范围而已(从fromIndex(包含),到toIndex(不包含))。
3.当我们尝试通过set方法,改变subList中某个元素的值得时候,我们发现,原来的那个List中对应元素的值也发生了改变。
4.对父(sourceList)子(subList)List做的非结构性修改(non-structural changes),都会影响到彼此。
5.对子List做结构性修改,操作同样会反映到父List上。
6.对父List做结构性修改,子List会抛出异常ConcurrentModificationException。

总结

1.ArrayList 是一个数组队列,相当于 动态数组。
2.ArrayList中的操作不是线程安全的。所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
参考:

  • 原创 | 为什么阿里巴巴要求谨慎使用ArrayList中的subList方法
  • Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 - 如果天空不死 - 博客园

你可能感兴趣的:(java源码分析,java)