美文网首页
ArrayList源码分析

ArrayList源码分析

作者: NiNko | 来源:发表于2019-08-21 17:38 被阅读0次

    ArrayList源码分析


    java version : 10.0.1


    1. 概览

    在IDEA中双击Shift键,调出search everywhere框,搜索ArrayList,点击进去即可阅读源码。

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

    ArrayList是基于数组实现的,因此支持快速访问。继承自AbstractList类,并实现了RandomAccess,Cloneable,Serializable接口。
    ArrayList的默认大小是10。private static final int DEFAULT_CAPACITY = 10

    2. ArrayList扩容

        private void add(E e, Object[] elementData, int s) {
            if (s == elementData.length)
                elementData = grow();
            elementData[s] = e;
            size = s + 1;
        }
    
        public boolean add(E e) {
            modCount++;
            add(e, elementData, size);
            return true;
        }
    
        public void add(int index, E element) {
            rangeCheckForAdd(index);
            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;
        }
    

    观察源码可知,向ArrayList末尾添加元素使用add(E e)函数,先将modCount加一,再添加元素到末尾,如果容量不够还要扩容。扩容使用grow()函数,扩容后容量为原数组长度的1.5倍,扩容后使用Arrays.copyOf()函数将原数组的内容复制到新数组中。往指定位置添加元素的逻辑同上。不同的是使用arraycopy方法复制数组。
    Arrays.copyOf()方法有两个参数,第一个是原数组,第二个是新数组的长度,若新数组长度超过原数组,则保留原数组内容。

    public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
    代码解释:
        Object src  : 原数组
        int srcPos  : 从元数据的起始位置开始
        Object dest : 目标数组
        int destPos : 目标数组的开始起始位置
        int length  : 要copy的数组的长度
    
        private Object[] grow() {
            return grow(size + 1);
        }
    
        private Object[] grow(int minCapacity) {
            return elementData = Arrays.copyOf(elementData,
                                               newCapacity(minCapacity));
        }
    
        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);
        }
    

    3. 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;
        }
    
        public boolean remove(Object o) {
            final Object[] es = elementData;
            final int size = this.size;
            int i = 0;
            found: {
                if (o == null) {
                    for (; i < size; i++)
                        if (es[i] == null)
                            break found;
                } else {
                    for (; i < size; i++)
                        if (o.equals(es[i]))
                            break found;
                }
                return false;
            }
            fastRemove(es, i);
            return true;
        }
    
        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;
        }
    

    删除指定位置元素使用arraycopy方法将待删除元素之后的数组内容往前移动一个位置,此方法复杂度为O(N)。而删除指定元素,也是先找出元素的位置,再执行上述操作。

    4. Fail-Fast机制

    在AbstractList类中有一成员变量modCount,protected transient int modCount = 0。而ArrayList同样继承了这个成员变量。这个成员变量是用来记录ArrayList结构发生变化的次数的,比如添加元素或删除元素,都会引起结构发生变化。但是仅仅设置元素值并不算结构发生变化。

    java常用容器中Collection继承了Iterable接口,其中的iterator()方法能够产生一个Iterator对象,通过这个对象就可以迭代遍历Collection中的元素。

    从 JDK 1.5 之后可以使用 foreach 方法来遍历实现了 Iterable 接口的聚合对象。

        public Iterator<E> iterator() {
            return new Itr();
        }
    
        private class Itr implements Iterator<E> {
            int cursor;       // index of next element to return
            int lastRet = -1; // index of last element returned; -1 if no such
            int expectedModCount = modCount;
    
            Itr() {}
    
            public boolean hasNext() {
                return cursor != size;
            }
    
            @SuppressWarnings("unchecked")
            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];
            }
    
            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();
                }
            }
    
            @Override
            public void forEachRemaining(Consumer<? super E> action) {
                Objects.requireNonNull(action);
                final int size = ArrayList.this.size;
                int i = cursor;
                if (i < size) {
                    final Object[] es = elementData;
                    if (i >= es.length)
                        throw new ConcurrentModificationException();
                    for (; i < size && modCount == expectedModCount; i++)
                        action.accept(elementAt(es, i));
                    // update once at end to reduce heap write traffic
                    cursor = i;
                    lastRet = i - 1;
                    checkForComodification();
                }
            }
    
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }
    

    在ArrayList中,当调用list.iterator()时,返回了一个Itr()对象,Itr是一个内部类,实现了Iterator接口。其中有三个成员变量

        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;
    

    cursor是遍历集合元素的索引,lastRet是刚被访问的元素的索引,而expectedModCount就是Fail-Fast机制的关键。观察源码中的next方法可以看到,每次在访问元素之前,会调用checkForComodification方法,而此方法中会抛出ConcurrentModificationException异常。
    在这段代码中当modCount != expectedModCount时,就会抛出异常。而expectedModCount除了在初始化时等于modCount之后,在整个遍历过程中就没有再发生变化,所以发生变化的只能是modCount。在前面的分析中,我们知道当ArrayList添加或删除元素时,集合的结构会发生变化,modCount变量就会改变。这样在迭代时就会抛出异常。

    5. 序列化

        private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException {
            // Write out element count, and any hidden stuff
            int expectedModCount = modCount;
            s.defaultWriteObject();
    
            // Write out size as capacity for behavioral compatibility with clone()
            s.writeInt(size);
    
            // Write out all elements in the proper order.
            for (int i=0; i<size; i++) {
                s.writeObject(elementData[i]);
            }
    
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
        }
    
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
    
            // Read in size, and any hidden stuff
            s.defaultReadObject();
    
            // Read in capacity
            s.readInt(); // ignored
    
            if (size > 0) {
                // like clone(), allocate array based upon size not capacity
                SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
                Object[] elements = new Object[size];
    
                // Read in all elements in the proper order.
                for (int i = 0; i < size; i++) {
                    elements[i] = s.readObject();
                }
    
                elementData = elements;
            } else if (size == 0) {
                elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new java.io.InvalidObjectException("Invalid size: " + size);
            }
        }
    

    ArrayList基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。
    保存元素的数组elementData使用transient修饰,该关键字声明数组默认不会被序列化。

    transient Object[] elementData; // non-private to simplify nested class access
    

    ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。

    序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。而 writeObject() 方法在传入的对象存在 writeObject() 的时候会去反射调用该对象的 writeObject() 来实现序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。

    ArrayList list = new ArrayList();
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
    oos.writeObject(list);
    

    相关文章

      网友评论

          本文标题:ArrayList源码分析

          本文链接:https://www.haomeiwen.com/subject/himisctx.html