第二章 ArrayList源码解析

1、ArrayList的创建(构造器)

  常见的两种方式:

List<String> strList = new ArrayList<String>();
List<String> strList2 = new ArrayList<String>(2);
View Code

  基本属性:

    //对象数组:ArrayList的底层数据结构
    private transient Object[] elementData;
    //elementData中已存放的元素的个数,注意:不是elementData的容量
    private int size;
View Code
  • ArrayList实现了java.io.Serializable,即采用Java默认的序列化机制。
  • elementData(Object数组)是ArrayList的底层实现,使用transient关键字修饰,说明该属性不采用Java默认的序列化机制,ArrayList自己实现了序列化和反序列化的方法,见下面代码。
/**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @serialData The length of the array backing the <tt>ArrayList</tt>
     *             instance is emitted (int), followed by all of its elements
     *             (each an <tt>Object</tt>) in the proper order.
     */
    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.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

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

    /**
     * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
     * deserialize it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            int capacity = calculateCapacity(elementData, size);
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }
View Code

  从上边源码可以看出,先调用java.io.ObjectOutptuStream的defaultWriterObject方法,进行默认的序列化操作,用transient修饰的字段没有被序列化。

  • 好处:ArrayList会开辟多余的空间来保存数据,序列化和反序列化这些没有存放数据的空间会消耗更多资源,所以ArrayList的数组用transient修饰,告诉虚拟机不采用默认的序列化机制,使用自己重写的序列化和反序列化方法,仅仅序列化已经存放的数据。

  构造器:

   /**
     * 创建一个容量为initialCapacity的空的(size==0)对象数组
     */
    public ArrayList(int initialCapacity) {
        super();//即父类protected AbstractList() {}
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity:" + initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    /**
     * 默认初始化一个容量为10的对象数组
     */
    public ArrayList() {
        this(10);//即上边的public ArrayList(int initialCapacity){}构造器
    }
View Code
  • 上边有参构造器中super()调用的ArrayList的父类AbstractList的构造器:
protected AbstractList() {

}

  实际应用中,如果我们能判断出所需的ArrayList的大小,有两个好处:

  • 节省内存空间,如果我们只需要存储两个对象到容器中,就 new ArrayList(2);

避免扩容引起的性能消耗(扩容下面会讲到)。

2、get()和set()方法

  get(int index)源码:

    /**
     * 按照索引查询对象E
     */
    public E get(int index) {
        RangeCheck(index);//检查索引范围
        return (E) elementData[index];//返回元素,并将Object转型为E
    }
View Code

  set(int index, E element)源码:

    /**
     * 更换特定位置index上的元素为element,返回该位置上的旧值
     */
    public E set(int index, E element) {
        RangeCheck(index);//检查索引范围
        E oldValue = (E) elementData[index];//旧值
        elementData[index] = element;//该位置替换为新值
        return oldValue;//返回旧值
    }
View Code
    /**
     * 检查索引index是否超出size-1
     */
    private void RangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size);
    }
View Code
  • get()和set()方法都对索引index进行了检查(调用RangeCheck()方法),是为了详细报错信息并且缩小检查范围(index < 0 || index >= size,size是数组已存放数据数量),对于数组而言,如果index不满足要求(index < 0 || index > length,length是数组容量),会抛出下标越界异常,但如果length是10,size = 2,get(9)会返回null,这也是为什么缩小检查范围的原因;
  • get()和set()方法都可以在常数时间内完成。

3、add()和addAll()方法

  add(E element)和add(int index, E element)源码:

    /**
     * 紧接着数组中最后一个数据后插入
     *
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 确保数组有足够容量,不够扩容
        elementData[size++] = e;
        return true;
    }
View Code
    /**
     * index索引处插入数据
     * 
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);   //检查索引是否满足条件

        ensureCapacityInternal(size + 1);  // 确保数组容量够用,不够扩容
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);    //将index及index后的元素后移,空出index位置
        elementData[index] = element;
        size++;
    }
View Code
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);    //原来的1.5倍
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;    
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);    
        elementData = Arrays.copyOf(elementData, newCapacity);    //扩展空间并复制
    }
View Code
  • 从源码看,插入数据时,都会先先检查是否需要扩容,如果需要通过grow()方法完成。

  ArrayList自动扩容过程:

  • new一个容量为new_capacity的对象数组;
  • 再将old_elementData数组里数据复制到新数组elementData中。

  空间问题解决,开始插入数据:

  • add(E element)方法是在最后插入(index = size处)
  • add(int index, E element)需要先将index及index后的元素后移,让出index位置,再将数据插入到index位置,该方法有着线性的时间复杂度。

原文地址:https://www.cnblogs.com/lmmblogs/p/8604900.html