ArrayList源码解析(空间结构和特性、常用方法、遍历时删除元素注意事项等)

1、概念

ArrayList继承了AbstractList,实现了 List ,RandomAccess, Cloneable, Serializable接口,所以他有List的相关功能同时还有动态随机访问、复制和序列化等功能。他的底层是使用数组实现的,所以查询起来相对较快,而插入删除时相对较慢。由于ArrayList里面的方法没有使用synchronized修饰,所以不是线程安全的。

2、空间结构

对象序列化的UID,类序列化时会传入一个serialVersionUID。在反序列化时,JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException。

private static final long serialVersionUID = 8683452581122892189L;

默认的初始容量大小为10。

private static final int DEFAULT_CAPACITY = 10;

一个空的数组实例,如果传进来的size是空,则使用这个空数组

private static final Object[] EMPTY_ELEMENTDATA = {};

默认大小为空的数组实例,如果没传进来初始化size就是用这个空数组。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

用来装ArrayList元素的数组,所以他底层是使用数组实现。

transient Object[] elementData

集合实际的元素个数,与容量不同。

private int size;

计数器,用于记录对List的修改次数,这里涉及到一个fail-fast快速失败机制,单线程时迭代器遍历集合中remove元素和多线程中一个线程遍历另一个线程remove元素时都会抛出ConcurrentModificationException。因为迭代器遍历时候,内部会将modCount赋给expectedModCount ,当集合结构改变时,modCount会被修改,迭代器每次next()和hahNext()方法都会检查expectedModCount与modCount是否相等,被改变时,抛出ConcurrentModificationException。所以在对集合遍历操作时,建议大家在迭代器中增删元素,不要直接操作集合的remove。引出fail-safe是快速安全机制,在多线程中使用(ConcurrentHashMap中)。任何对集合结构的修改都会在一个复制的集合上进行,因此不会抛出ConcurrentModificationException,但无法保证最终读取的数据是正确的元素,且需要复制集合,开销大。(这个modCount方法中都在用,翻了一会子类父类源码没发现,然后藏在601行)

protected transient int modCount = 0;

三个构造方法:

  1. 初始化一个指定容量的构造器,直接指定elementData数组的容量,如果容量<0,默认还是用空的实例。
  2. 初始化一个空的数组,在添加元素时候再对数组elementData扩容。
  3. 初始化一个集合参数的数组,把集合通过Arrays.copyOf拷到elementData中,容量和指定集合容量相同,如果容量<0,默认还是用空的实例。
    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) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

3、常用方法

扩容

大概增加元素时候先判断是否要扩容,然后再判断扩容的大小,默认是扩容1.5倍,但是如果新的长度扩容1.5倍也不能容纳或者扩容得太大,就扩容到需要的容量。

	//其他方法先调用此方法看看是否需要扩容
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

	//如果是初始化时没有指定大小,然后调用add方法,默认返回默认大小和当前size+1的最大值
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    //如果添加后的实际容量大于当前容量则需要调用grow方法扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
	//扩容1.5倍后的容量和传入的容量取大值进行扩容,如果新的容量特别大时给Interger最大值或者Integer.MAX_VALUE - 8(搞不懂为啥是-8这样设计)
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    //判断容量大时的情况
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) 
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    
    //这个方法是专为为DenseIntMapImpl类的extend方法提供。
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)table? 0: DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

数组元素数量

    public int size() {
        return size;
    }

数组元素数量是否为0

    public boolean isEmpty() {
        return size == 0;
    }

添加元素
传入元素,先判断扩容,然后添加到数组中。

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

传入index和元素,先判断index在数组中是否存在,然后判断扩容,然后将原数组复制一份,在中间index位置加入element。

    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  
        //复制一个数组,把index后得一段整体后移一位。
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

传入集合,先判断容量,然后复制一个数组,把这个集合整体加到原集合后。

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

获取元素
先判断index是否存在,然后返回数组对应下标,可以看出直接从数组随机访问,速度比较快。

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

获取元素索引
返回给定元素第一次出现的位置,没有则返回-1。

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

返回给定元素最后一次出现的位置,没有则返回-1。

    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

是否包含指定元素
使用indexOf判断是否包含元素

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

删除元素
传入index,先判断index是否存在,然后判断是否是最后一个元素,如果不是就重新拷贝一个数组,将index后的数组元素都向前移动一个位置,然后把数组得最后一位内存释放出来。

    public E remove(int index) {
        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;
    }

传入对象,如果o为空就,就删除所有null的元素,否则删除下标为index的元素。

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
	//与remove方法类似
    private void fastRemove(int index) {
        modCount++;
        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
    }

传入索引的一段范围,删除这段范围内的所有元素。

    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }

删除指定元素removeAll和保留指定元素retainAll,都调用的batchRemove函数,通过传入true或者false执行不同的功能(JDK源码有点子巧妙~)。

	//删除集合中所有在集合c中出现过的元素。
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
 	//删除集合中所有没有在c集合中出现过的元素。
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }

    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
            	//判断是存入包含的元素还是不包含的元素
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
          	//正常情况下r=size是成立的,这里稍微有点不明白,自己理解大概就是出异常的时候将未判断的直接赋值给数组,凑齐数组长度,让w==size,好让后续if (w != size) 不执行,返回false,
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            //如果方法最终w=size,证明没有删除过元素或者出现异常执行了if (r != size),所以返回false并释放内存
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

复制集合
调用Arrays.copyOf方法进行元素复制。

    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

将集合转化为数组

	public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

清空集合
把所有值赋为null让gc回收,并把size变为0。

    public void clear() {
        modCount++;
 
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
 
        size = 0;
    }

序列化和反序列化
通过writeObject方法和readObject方法来遍历elementData数组把数组中的元素写入ObjectOutputStream ,ObjectInputStream 中的。因为ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        int expectedModCount = modCount;
        s.defaultWriteObject();

        s.writeInt(size);

        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 {
        elementData = EMPTY_ELEMENTDATA;

        s.defaultReadObject();
        s.readInt(); 

        if (size > 0) {
            int capacity = calculateCapacity(elementData, size);
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            ensureCapacityInternal(size);

            Object[] a = elementData;
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

迭代器
ArrayList的listIterator和iterator实际上是返回ListItr和Itr对象,他是ArrayList通过内部类的方式来实现的。listIterator有previous方法支持前后遍历、迭代过程添加元素等操作。for-each就是Java提供的语法糖,实际上也是通过iterator遍历元素。
    在用 for 遍历集合的时候是不可以对集合进行 remove操作的,因为 remove 操作会改变集合的大小。从而容易造成结果不准确甚至数组下标越界,更严重者还会抛出 ConcurrentModificationException。因为在创建迭代器时候会有这么一句代码:

int expectedModCount = modCount;

每当操作集合时,modCount都会自增,而每次iterator调用next方法时都会调用一下代码:

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

判断expectedModCount和modCount是否相等,不相等所以抛出ConcurrentModificationException异常。而iterator自带的remove方法每次都会将modCount重新赋给expectedModCount,所以遍历时删除元素需要使用集合的iterator的remove,以下是ListItr 和Itr的源码:

    public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }

    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

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


    private class Itr implements Iterator<E> {
        int cursor;     
        int lastRet = -1; 
        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
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
    
    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

4、总结

  1. ArrayList 底层基于数组实现的,所以插入或删除元素时,需要移动插入位置之后的所有元素,开销较大,所以适合随机查询,不适合增删多的操作。
  2. ArrayList初始容量默认为10。扩容机制为首先扩容为原始容量的 1.5 倍再与当前size取最小值,京可能节省开销。但是由于扩容是通过Arrays.copyOf每次创建新的数组,所以创建ArrayList时候尽量传入合适的容量,减小内存开销。
  3. ArrayList不是线程安全的,所以有modCount的存在,直接修改集合时都会使modCount++,所以遍历ArrayList时删除元素时使用iterator的remove方法,该方法会同步modCount值,避免出现ConcurrentModificationException异常。
发布了36 篇原创文章 · 获赞 28 · 访问量 1505

猜你喜欢

转载自blog.csdn.net/tc979907461/article/details/105322599