Java 集合框架03---ArrayList的源码分析

上篇我们学习了Collection的相关源码,下面我们将继续学习List 家族中最常用的一个集合ArrayList
我们将从以下几个方面剖析ArrayList

  1. ArrayList的简介
  2. ArrayList的数据结构
  3. ArrayList的扩容机制
  4. ArrayList的遍历

注意事项
全文对ArrayList的源码解析均是基于JDK1.8

ArrayList的简介

ArrayList的定义

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

ArrayList的类图如下:
《Java 集合框架03---ArrayList的源码分析》
从上述定义和类图中我们可以得到以下几点信息:

  1. ArrayList是一个集合,其继承了AbstractList,实现了List 接口,有集合的基本操作,如添加,查找,删除。
  2. ArrayList实现了Serializable接口,说明其可以被序列化,可以序列化之后进行传输。
  3. ArrayList实现了RandomAccess接口,说明其可以被随机访问。
  4. ArrayList实现了Cloneable接口,说明其可以被克隆
  5. ArrayList支持泛型。

ArrayList的数据结构

看到本结我们心中不免有几个疑问:例如,
1. ArrayList到底是一个怎样的数据结构呢?
2. ArrayList是如何实现自动扩容的呢?
带着疑问我们来从源码中寻找答案:

        //初始大小
        private static final int DEFAULT_CAPACITY = 10;
        //初始数组
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. * 简译:没有指定初始大小的`ArrayList`再添加第一个元素之后初始大小变成10。 */
    transient Object[] elementData; // non-private to simplify nested class access
    //构造指定初始容量的集合
    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;
    }
    //将一个继承自`Collection`的集合转化成`ArrayList`
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

小结:从上述源码中可以看出,ArrayList是一个数组结构,所以其拥有数组的所有特性。
默认构造成Object[],如果传入泛型则构造成对应泛型的数组。

ArrayList的扩容机制

接着我们在看看ArrayList是如何实现自动扩容的。

        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;


    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!(确保当前ArrayList维护的数组具有存储新元素的能力,经过处理之后将元素存储在数组elementData的尾部)
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_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);
    }

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

本部分源码的调用结构是

add--调用->ensureCapacityInternal--调用->ensureExplicitCapacity--调用-->grow--调用-->hugeCapacity

从中我们可知
1. ArrayList的极限大小是2*32 -1。(存储元素的个数为整型的范围。)
2. ArrayList扩容时首先会设计新的存储能力为 原来的1.5倍。

 int newCapacity = oldCapacity + (oldCapacity >> 1);

确定ArrayList扩容之后最新的可存储元素之后,调用

elementData = Arrays.copyOf(elementData, newCapacity);

进行元素的元素的复制。
扩展:

  public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

  public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

arraycopy标识为native意味JDK的本地库,不可避免的会进行IO操作,如果频繁的对ArrayList进行扩容,毫不疑问会降低ArrayList的使用性能,因此当我们确定添加元素的个数的时候,我们可以事先知道并指定ArrayList的可存储元素的个数,这样当我们向ArrayList中加入元素的时候,就可以避免ArrayList的自动扩容,从而提高ArrayList的性能。

ArrayList的遍历

ArrayList的遍历的遍历主要有三种方式:

package com.jay.collection;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/** * Created by xiang.wei on 2018/3/4 * * @author xiang.wei */
public class ArrayListIterTest {
    public static void main(String[] args) {
        //待遍历的ArrayList
        List<Integer> testList = new ArrayList<>();
        for (int i = 0; i < 500000; i++) {
            testList.add(i);
        }
        testFori(testList);
        testFor(testList);
        testIter(testList);

    }

    private static void testFori(List<Integer> list) {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
            list.get(i);
        }
        System.out.println("RandomAccess用时" + (System.currentTimeMillis() - startTime) + "ms");
    }

    private static void testFor(List<Integer> list) {
        long startTime = System.currentTimeMillis();
        for (Integer integer : list) {
                ;
        }
        System.out.println("for迭代用时" + (System.currentTimeMillis() - startTime) + "ms");
    }

    private static void testIter(List<Integer> list) {
        long startTime = System.currentTimeMillis();
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            iterator.next();
        }
// for(Iterator<Integer> it = list.iterator(); it.hasNext(); ) {
// it.next();
// }
        System.out.println("iterator迭代器用时" + (System.currentTimeMillis() - startTime) + "ms");
    }
}

我运行了5次,结果如下:

实验次数RandomAccess(ms)For(ms)Iterator(ms)
第一次10219
第二次101617
第三次101911
第四次111818
第五次132112
平均数10.81913.4

从上述表格,我们可以看出RandomAccess(随机索引)访问的效率最高,For迭代的效率最低。

如果疑问,欢迎讨论。

相关的引用:
Java ArrayList的自动扩容机制

http://blog.csdn.net/yang1464657625/article/details/59109133

    原文作者:java集合源码分析
    原文地址: https://blog.csdn.net/u014534808/article/details/79393983
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞