ArrayList的源码剖析

首先我们来查看Arraylist的底层源码:

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

可以看到ArrayList继承于AbstractList类
且其有3个标记接口,分别是:
RandomAccess标识其支持快速随机访问;
Cloneable标识其支持对象复制;
Serializable标识其可序列化;

**
ArrayList不是线程安全的,只能用在单线程环境下

1.ArrayList的构造方法

一个是无参构造,另一个是有参构造(需要传入初始容量值),底层实现如下:**

/**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;
    //默认初始容量为10

    /**
     *ArrayList实例共享的一个空数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
 
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/*
数组列表元素存储在其中的数组缓冲区。数组列表的容量是这个数组缓冲区的长度。当添加第一个元素时,任何ElEMENTDATA = = DEFAULT CAPACITY _ EMPTY _ ElEMENTDATA的空数组列表都将扩展为DEFAULT_CAPACITY。
*/ 
    transient Object[] elementData; // non-private to simplify nested class access

    private int size;

    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);
        }
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

ArrayList的两个构造方法的目的都是初始化底层数组 elementData。区别在于无参构造方法会将 elementData 初始化一个空数组,当插入第一个元素之后,会默认数组数组容量为10;而有参构造方法会将 elementData 初始化为传入参数值大小(>= 0)的数组。一般情况下,我们使用默认的构造方法;如果知道待插入元素的数量,采用有参构造方法,按需分配,避免浪费

2.元素的插入

对于数组的插入,情况分为两种:1.在元素序列的尾部插入 2.在元素序列的其他位置插入,底层实现如下:

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
        elementData[size++] = e;加入新元素e,size加1
        return true;
    }
/** 
在元素序列 index 位置处插入
 */
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        ensureCapacityInternal(size + 1);  // Increments modCount!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
         //  将新元素插入至 index 处
        size++;
    }

由上可知,当在尾部插入元素时,1. 检查数组的空间是否足够;2. 将元素插入数组的尾部;当插入元素在指定位置index处时,1. 首先检查数组的空间是否足够; 2. 将数组中下标位于index之后的元素,集体向后挪一位; 3. 将元素插在index位置(此种插入方式不推荐,因为其时间复杂度为O(n),在插入、删除操作频繁的时候,更推荐链表)

3.扩容

/*
扩大容量,以确保它至少可以容纳由最小容量参数指定的元素数量。
*/
  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)//若新容量比数组最大容量还大,执行扩容,新数组将十分大,因为数组最大容量为10亿
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
        //将就数组中的数,拷贝到新数组中
    }

我们对数组进行扩容时,扩容后新数组的大小为旧数组大小的1.5倍,旧数组将废弃,故尽量避免此种扩容方式,因为当元素数量巨大的时候,会产生垃圾,造成极大内存浪费。

4.元素的删除

/*
移除列表中指定位置的元素。向左移动任何后续元素(从它们的索引中减去一个)。
*/
 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;
    }
/*从列表中移除指定元素的第一次出现(如果存在)。如果列表不包含该元素,它将保持不变
*/
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;
    }

当在ArrayList中删除指定元素时,找到指定元素,将元素删除之后,将index后的元素整体向前移动,将空出来的位置置为空,方便GC回收。

5.遍历ArrayList

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; //返回的最后一个元素的索引;-1(如果没有返回)
        int expectedModCount = modCount;//标记位:用于判断是否在遍历的过程中,是否发生了add、remove操作
        Itr() {}
            //检测对象数组是否还有元素
        public boolean hasNext() {
            return cursor != size;//如果cursor==size,说明已经遍历完了
        }
       

ArrayList的元素遍历的方式采用 Iterator迭代器

不足之处,欢迎补充。

你可能感兴趣的:(java)