Java ArrayList类源码分析

前言

  在前面的学习集合中只是介绍了集合的相关用法,我们想要更深入的去了解集合那就要通过我们去分析它的源码来了解它。希望对集合有一个更进一步的理解!

  既然是看源码那我们要怎么看一个类的源码呢?这里我推荐的方法是:

    1)看继承结构

      看这个类的层次结构,处于一个什么位置,可以在自己心里有个大概的了解。

    2)看构造方法

      在构造方法中,看做了哪些事情,跟踪方法中里面的方法。

    3)看常用的方法

      跟构造方法一样,这个方法实现功能是如何实现的

  注:既然是源码,为什么要这样设计类,有这样的继承关系。这就要说到设计模式的问题了。所以我们要了解常用的设计模式,才能更深刻的去理解这个类。

一、ArrayList简介

1.1、ArrayList概述

  1)ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类。

  2)该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加。

    如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能。

  3)ArrayList的用法和Vector相类似,但是Vector是一个较老的集合,具有很多缺点,不建议使用。

    另外,ArrayList和Vector的区别是:ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性,而Vector则是线程安全的。

  4)ArrayList与Collection的关系:

                         

1.2、ArrayList的数据结构

  分析一个类的时候,数据结构往往是它的灵魂所在,理解底层的数据结构其实就理解了该类的实现思路,具体的实现细节再具体分析。

  ArrayList的数据结构是:

    

  说明:底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。我们对ArrayList类的实例的所有的操作底层都是基于数组的。

二、ArrayList源码分析

2.1、继承结构和层次关系

  

  

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

 分析:

    1)为什么要先继承AbstractList,而让AbstractList先实现List<E>?而不是让ArrayList直接实现List<E>?

      这里是有一个思想,接口中全都是抽象的方法,而抽象类中可以有抽象方法,还可以有具体的实现方法,正是利用了这一点,让AbstractList是实现接口中一些通用的方法,而具体的类,

      如ArrayList就继承这个AbstractList类,拿到一些通用的方法,然后自己在实现一些自己特有的方法,这样一来,让代码更简洁,就继承结构 最底层的类中通用的方法都抽取出来,

      先一起实现了,减少重复代码。所以一般看到一个类上面还有一个抽象类,应该就是这个作用。

    2)ArrayList实现了哪些接口?

      List<E>接口:我们会出现这样一个疑问,在查看了ArrayList的父类AbstractList也实现了List<E>接口,那为什么子类ArrayList还是去实现一遍呢?

            这是想不通的地方,所以我就去查资料,有的人说是为了查看代码方便,使观看者一目了然,说法不一,但每一个让我感觉合理的,但是在stackOverFlow中找到了答案,这里其实很有趣。

            网址贴出来 http://stackoverflow.com/questions/2165204/why-does-linkedhashsete-extend-hashsete-and-implement-sete开发这个collection 的作者Josh说。

            这其实是一个mistake,因为他写这代码的时候觉得这个会有用处,但是其实并没什么用,但因为没什么影响,就一直留到了现在。

      RandomAccess接口:这个是一个标记性接口,通过查看api文档,它的作用就是用来快速随机存取,有关效率的问题,在实现了该接口的话,那么使用普通的for循环来遍历,性能更高,例如arrayList

                而没有实现该接口的话,使用Iterator来迭代,这样性能更高,例如linkedList。所以这个标记性只是为了让我们知道我们用什么样的方式去获取数据性能更好。

      Cloneable接口:实现了该接口,就可以使用Object.Clone()方法了。

      Serializable接口:实现该序列化接口,表明该类可以被序列化,什么是序列化?简单的说,就是能够从类变成字节流传输,然后还能从字节流变成原来的类。

2.2、类中的属性

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 版本号
    private static final long serialVersionUID = 8683452581122892189L;
    // 缺省容量
    private static final int DEFAULT_CAPACITY = 10;
    // 空对象数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // 缺省空对象数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 元素数组
    transient Object[] elementData;
    // 实际元素大小,默认为0
    private int size;
    // 最大数组容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}

2.3、构造方法

  ArrayList有三个构造方法:

    

  1)无参构造方法:

 /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    transient Object[] elementData; // 非私有,简化嵌套类获取

/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;  是个空的Object[], 将elementData初始化,elementData也是个Object[]类型。空的Object[]会给默认大小10,等会会解释什么时候赋值的。
    }

 2)有参构造函数一:

/**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};



/**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        //将自定义的容量大小当成初始化elementData的大小,这里将initialCapacity等于0的情况列出来
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

 3)有参构造方法三(不常用)

//这个构造方法不常用,举个例子就能明白什么意思
//Strudent exends Person
//ArrayList<Person>、 Person这里就是泛型
//我还有一个Collection<Student>、由于这个Student继承了Person,那么根据这个构造方法,我就可以把这个Collection<Student>转换为ArrayList<Person>这就是这个构造方法的作用 

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // 防止c.toArray(错误地)不返回Object []
            //每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么久需要使用ArrayList中的方法去改造一下。
            // (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;
        }
    }

 总结:arrayList的构造方法就做一件事情,就是初始化一下储存数据的容器,其实本质上就是一个数组,在其中就叫elementData。

2.4、核心方法

  2.4.1、add()方法(有五个)

    

 1)boolean add(E);//默认直接在末尾添加元素

    //表示此列表被结构修改的次数  
    //结构修改是指更改列表大小或以其他方式干扰列表的方式,即正在进行的迭代可能会产生错误的结果
    protected transient int modCount = 0;


/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        modCount++;   
        add(e, elementData, size);   //调用另外的方法
        return true;
    }

  2)void add(E , Object[] , int); //在Object数组的指定位置添加元素

/**
     * 此辅助方法从add(E)中分离出来,以使方法的字节码大小保持在35以下(-XX:MaxInlineSize默认值),这有助于在C1编译循环中调用add(E)
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }


    private Object[] grow() {
        return grow(size + 1);
    }

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity)); //elementData是原始数组,第二个参数是newLength
    }

    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {  //因为minCapacity是最小界限,所以要先考虑
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow,当0XXXXXXX..变为1XXXXXXXX..的时候就是上溢
                throw new OutOfMemoryError();   //minCapacity不能超过有符号整数的最大限制
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0) //MAX_ARRAY_SIZE = Integer.MAX_VALUE-8
            ? newCapacity
            : hugeCapacity(minCapacity);
    }


    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE)
            ? Integer.MAX_VALUE    //当minCapacity处在MAX_ARRAY_SIZE-Integer.MAX_VALUE之间时用Integer.MAX_VALUE代替
            : MAX_ARRAY_SIZE;
    }


    

 3)void add(int,E);在特定位置添加元素,也就是插入元素

public void add(int index, E element) {
        rangeCheckForAdd(index);  //检查index也就是插入的位置是否合理
        modCount++;
        final int s;
        Object[] elementData;
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow();
        //这个方法就是用来在插入元素之后,要将index之后的元素都往后移一位,
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);
        //在目标位置上存放元素
        elementData[index] = element;
        size = s + 1;  //size增加1
    }

这里关于Arrays.copyOf与System.arraycopy的区别:https://mp.csdn.net/postedit/102293911

private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

我们可以测试一下向ArrayList中添加元素时,它的capacity变化,capacity即element数组的大小,而element数组又是私有的,于是想到利用反射访问

public class ArrayListTest {
    public static void main(String[] args) {
        ArrayList<Integer> lists = new ArrayList<>();
        ArrayList<Integer> arrayList = new ArrayList<>();

        System.out.println(getArrayListCapacity(arrayList));

        //增加元素,使其扩容
        arrayList.add(0);
        System.out.println(getArrayListCapacity(arrayList));

        for(int i = 0; i < 10; ++i)
            arrayList.add(0);
        System.out.println(getArrayListCapacity(arrayList));

        for(int i = 0; i < 5; ++i)
            arrayList.add(0);
        System.out.println(getArrayListCapacity(arrayList));

        //输出结果一次为:0  10  15  22
    }

    public static int getArrayListCapacity(ArrayList<?> arrayList) {
        Class<ArrayList> arrayListClass = ArrayList.class;
        try {
            Field field = arrayListClass.getDeclaredField("elementData");
            field.setAccessible(true);
            Object[] objects = (Object[])field.get(arrayList);
            return objects.length;
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            return -1;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return -1;
        }
    }
    
}

 2.4.2、删除方法

    其实这几个删除方法都是类似的。我们选择几个讲,其中fastRemove(int)方法是private的,是提供给remove(Object)这个方法用的。

    

 1)remove(int):通过删除指定位置上的元素

    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 remove method that skips bounds checking and does not
     * return the value removed.
     */
    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;
    }

  2)remove(Object):这个方法可以看出来,arrayList是可以存放null值的

public boolean remove(Object o) {
        final Object[] es = elementData;
        final int size = this.size;
        int i = 0;
        //先找到o所在位置的索引
        found: {
            if (o == null) {
                for (; i < size; i++)
                    if (es[i] == null)
                        break found;    //这里表示ArrayList里可以存放null值
            } else {
                for (; i < size; i++)
                    if (o.equals(es[i]))   //Obejct.equals(Object obj){ return this == obj};
                        break found;
            }
            return false;
        }
        fastRemove(es, i);
        return true;
}

3)clear():将elementData中每个元素都赋值为null,等待垃圾回收将这个给回收掉,所以叫clear

/**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     */
    public void clear() {
        modCount++;
        final Object[] es = elementData;
        for (int to = size, i = size = 0; i < to; i++)  //代码很简洁
            es[i] = null;    
    }

思考:为什么在以上大部分方法中在对elementData[]做改变时,都要先用final Object[]来做引用,而不是直接在原数组上操作,而且在后面都跟随着modCount++的变化?

 4)removeAll(collection c):

public boolean removeAll(Collection<?> c) {
        return batchRemove(c, false, 0, size);     //批量删除
    }

 5)batchRemove(xx,xx,xx,xx):用于两个方法,一个removeAll():它只清楚指定集合中的元素,retainAll()用来测试两个集合是否有交集。

//这个方法,用于两处地方,如果complement为false,则用于removeAll;如果为true,则给retainAll()用,retainAll()是用来检测两个集合是否有交集的。
boolean batchRemove(Collection<?> c, boolean complement,
                        final int from, final int end) {
        
        Objects.requireNonNull(c);  //检查指定的对象引用c不是null
        final Object[] es = elementData;
        int r;         //r用来控制循环,w是记录有多少个交集
        // Optimize for initial run of survivors
        for (r = from;; r++) {
            if (r == end)
                return false;
            if (c.contains(es[r]) != complement)  //参数中的集合C一次检测集合es中的元素是否有
                break;
        }
        int w = r++;  w是记录第一次满足上面if判断的位置,然后将后续元素不满足上面if判断的元素复制到w位置,且w再次后移
        try {
            for (Object e; r < end; r++)
                if (c.contains(e = es[r]) == complement)
                    es[w++] = e;
        } catch (Throwable ex) {
            // 即使c.contains()抛出异常,仍保留与AbstractCollection的行为兼容性
            System.arraycopy(es, r, es, w, end - r);
            w += end - r;
            throw ex;
        } finally {
            modCount += end - w;
            shiftTailOverGap(es, w, end);  通过向下滑动以下元素来消除从w到end的间隙
        }
        return true;
    }

 总结::remove函数用户移除指定下标的元素,此时会把指定下标到数组末尾的元素向前移动一个单位,并且会把数组最后一个元素设置为null,

      这样是为了方便之后将整个数组不被使用时,会被GC,可以作为小的技巧使用。

2.4.3、set()方法

 /**
     * Replaces the element at the specified position in this list with
     * the specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
         // 检验索引是否合法
        Objects.checkIndex(index, size);
        E oldValue = elementData(index);   //elementData(index) == elementData[index]
        elementData[index] = element;
        return oldValue;  //返回旧值
    }

2.4.4、indexOf()方法

/**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index {@code i} such that
     * {@code Objects.equals(o, get(i))},
     * or -1 if there is no such index.
     */
    public int indexOf(Object o) {
        return indexOfRange(o, 0, size);  // 从首开始查找数组里面是否存在指定元素
    }

    int indexOfRange(Object o, int start, int end) {
        Object[] es = elementData;
        if (o == null) {
            for (int i = start; i < end; i++) {
                if (es[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = start; i < end; i++) {
                if (o.equals(es[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

说明:从头开始查找与指定元素相等的元素,注意,是可以查找null元素的,意味着ArrayList中可以存放null元素的。与此函数对应的lastIndexOf,表示从尾部开始查找。

2.4.5、get()方法

public E get(int index) {
        Objects.checkIndex(index, size);   检查index是否在0(包括)到length(不包括)的范围内
        return elementData(index);
    }

说明:get函数会检查索引值是否合法(只检查是否大于size,而没有检查是否小于0),值得注意的是,在get函数中存在element函数,element函数用于返回具体的元素,具体函数如下:

E elementData(int index) {
        return (E) elementData[index];
    }

说明:返回的值都经过了向下转型(Object -> E),这些是对我们应用程序屏蔽的小细节。

思考:为什么ArrayList的elementData是用transient修饰的?

transient修饰的属性意味着不会被序列化,也就是说在序列化ArrayList的时候,不序列化elementData。

为什么要这么做呢?

  1. elementData不总是满的,每次都序列化,会浪费时间和空间

  2. 重写了writeObject 保证序列化的时候虽然不序列化全部 但是有的元素都序列化

所以说不是不序列化 而是不全部序列化。

三、总结 

1)arrayList可以存放null。
2)arrayList本质上就是一个elementData数组。
3)arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法。
4)arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全是删除集合中的元素。
5)arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
6)arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章