Java集合------ArrayList源码剖析

现在,我怕的并不是那艰苦严峻的生活,而是不能再学习和认识我迫切想了解的世界。对我来说,不学习,毋宁死。——罗蒙诺索夫

    本篇分析ArrayList的源码,在分析之前 需要先明白数组的概念。

    数组是物理存储连续、逻辑存储连续的顺序表,这种存储方式的优点是查询的时间复杂度为O(1),通过首地址和偏移量就可以直接访问到元素,缺点是不利于修改,插入和删除的时间复杂度最坏能达到O(n)。由于数组直接操作内存,所以数组的性能要比集合类更好一点,这是使用数组的一大优势。但是,数组在初始化必须制定数组大小,并且在后续操作中不能更改数组的大小。

    ArrayList它的底层是基于数组实现的,封装了一个Object[]类型的数组,长度可以动态的增长,因此它具有数组的一些特点。它是线程不安全的。

    继承关系

ArrayList<E> extends AbstractList<E>

    实现接口

List<E>, RandomAccess, Cloneable, java.io.Serializable
//实现了RandomAccess接口,支持快速随机访问  
//实现了Cloneable接口,能够被克隆  
//实现了Serializable接口,支持序列化  

    成员变量

//可序列化版本号
private static final long serialVersionUID = 8683452581122892189L;

//默认初始化容量
private static final int DEFAULT_CAPACITY = 10;

//实例化一个空数组
private static final Object[] EMPTY_ELEMENTDATA = {};

//默认空数组
//与EMPTY_ELEMENTDATA数组的区别在于当第一个元素被加入进来的时候它知道如何扩张;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//然后是一个object数组elementData。
//这个就是最重要的成员了,通过注释我们可以看到这表示这个数组用来存储我们的数据。
//也就是说,我们代码中的add的数据都会放在这个数组里面
transient Object[] elementData; // 不会被序列化

//ArrayList实际元素容量,和存放ArrayList元素的数组长度可能相等,也可能不相等
private int size;

    构造函数


  //无参构造函数
  //构造一个初始容量为10的空列表
  public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  }


  //根据参数的大小做为容量来实例化数组对象,当参数小于0时,抛异常。当参数等于0时,用空的常量数组对象
  //EMPTY_ELEMENTDATA来初始化底层数组elementData
  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);
        }
   }
    //将集合转为ArrayList,首先先调用toArray()方法转为数组赋值给elementData
    //如果集合不为空,先进行是否是object[]类型,如果不是,转换成Object[]类型。
    //如果集合为空,则就将空的常量数组对象EMPTY_ELEMENTDATA赋给了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;
        }
    }   

    从构造函数中可以看出,常量EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是为了初始化elementData的。

    如果为无参构造函数,使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA;如果为含参构造函数,使用EMPTY_ELEMENTDATA:

    首先我们先来看看add方法,源码如下

    public boolean add(E e) {
        //添加之前先检查容量
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //在数组末尾添加元素,并让size+1
        elementData[size++] = e;
        return true;
    }

    在添加元素之前要先检查容量,因此我们看看ensureCapacityInternal这个函数,源码如下

 private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    可以看出ensureCapacityInternal函数调用了ensureExplicitCapacity方法,而calculateCapacity方法的返回值做为参数又传给了ensureExplicitCapacity方法。我们再来看看calculateCapacity方法,源码如下

  private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //如果elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA,设置数组最小容量(>=10)
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    接下来我们再来看看ensureExplicitCapacity函数,源码如下

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 如果最小容量大于数组长度就扩容数组
        if (minCapacity - elementData.length > 0)
            //扩容方法
            grow(minCapacity);
    }

    我们继续看看grow方法,看看是如何进行扩容的,源码如下

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //扩容公式,扩展到原来数组长度的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);    
        //如果newCapacity扩展的过小。则应该至少扩张到所需的空间大小minCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //MAX_ARRAY_SIZE 是数组的最大长度
        //private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        //newCapacity扩张的过大,如果过大,则用Integer.MAX_VALUE来代替
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //将原来的数组中的元素复制扩展到大小为newCapacity的新数组中,并返回这个新数组
        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;
    }

    添加方法可以分为两种方式,不指定位置添加和指定位置添加,接下来我们分析下指定位置添加方法,

源码如下

   public void add(int index, E element) {
        //位置有效性检查,必须在数组的容量范围内
        rangeCheckForAdd(index);
        //判断是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //数组内位置为 index 到 (size-1)的元素往后移动一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

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

    接下来我们看看get方法,源码如下

    public E get(int index) {
        //先进行index合法性判断,如果>=size,直接抛出数组越界异常
        rangeCheck(index);
        //如果合法直接返回index位置的元素
        return elementData(index);
    }

    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    接下来我们在看看修改方法set,源码如下

    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

    修改方法,就是替换指定位置上的元素

    我们在继续看看删除方法,删除操作也存在两种方式:一种是删除指定位置的元素,另一种是删除指定元素。首先我们来看看第一种,源码如下

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

        modCount++;
        //取得该位置元素
        E oldValue = elementData(index);
        
        int numMoved = size - index - 1;
        //将index后面的元素向前挪一位
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //清空末尾元素让 GC 生效,并修改数组中的元素个数
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

    接下来我们在看看第二种删除方式,源码如下

    public boolean remove(Object o) {
        //无论是指定对象o是否为null,都是在ArrayList中找到与此第一个相等的元素的位置,然后调用fastRemove(index)
        //来进行移除;如果没有找打指定对象o的位置,则返回false,表示没有移除成功
        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;
    }


    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
    }

    我们继续看看clear方法,源码如下

   public void clear() {
        modCount++;
        //直接将数组中的所有元素设置为null即可,这样便于垃圾回收
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

    最后我们再来研究一下,为什么ArrayList源码中,为什么又要用transient修饰elementData呢?那么它又是怎么实现序列化的呢?

    先回答第一个问题,

    回想ArrayList的自动扩容机制,elementData数组相当于容器,当容器不足时就会再扩充容量,但是容器的容量往往都是大于或者等于ArrayList所存元素的个数,如果直接序列化elementData数组,那么就会浪费空间,特别是当元素个数非常多时,这种浪费是非常不合算的。所以ArrayList的设计者将elementData设计为transient,然后在writeObject方法中手动将其序列化,并且只序列化了实际存储的那些元素,而不是整个数组。源码如下

    

    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 behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        //循环时是使用i<size而不是 i<elementData.length
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

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

    现在来回答第二个问题,

如果一个类不仅实现了Serializable接口,而且定义了 readObject(ObjectInputStream in)和 writeObject(ObjectOutputStream out)方法,那么将按照如下的方式进行序列化和反序列化:

ObjectOutputStream会调用这个类的writeObject方法进行序列化,ObjectInputStream会调用相应的readObject方法进行反序列化。

那么ObjectOutputStream又是如何知道一个类是否实现了writeObject方法呢?又是如何自动调用该类的writeObject方法呢?

答案是:是通过反射机制实现的。

好了,ArrayList源码就分析到这了,希望对大家有帮助。

参考资料

https://www.cnblogs.com/aoguren/p/4767309.html

猜你喜欢

转载自my.oschina.net/u/3475585/blog/1807764