【Java集合】JDK1.8源码之ArrayList(详细注释+常见问题)

详细注释

类属性与继承关系:

public class ArrayList<E> extends AbstractList<E>
        implements List, 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; // non-private to simplify nested class access
    /**
     *    实际元素大小(默认值为0)
     */
    private int size;
    /**
     *    最大数组容量
     */    
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}

1.ArrayList 实现了RandomAccess, Cloneable, java.io.Serializable三个标记接口,表示它自身支持快速随机访问克隆序列化

2.类的属性中核心的属性为elementData,类型为Object[],用于存放实际元素,并且被标记为transient,表明其不使用Java默认的序列化机制来实例化,但是该属性是ArrayList的底层数据结构,在网络传输中一定需要将其序列化,之后使用的时候还需要反序列化,那不采用Java默认的序列化机制,那采用什么呢?直接翻到源码的最下边有两个方法:writeObject(java.io.ObjectOutputStream s)与readObject(java.io.ObjectInputStream s)发现ArrayList自己实现了序列化和反序列化的方法。

构造方法:

    public ArrayList() {
         // 无参构造函数,设置元素数组为空
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    public ArrayList(int initialCapacity) {
        // 当初始容量大于0时
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        // 当初始容量等于0时
        } else if (initialCapacity == 0) {
            // 为空对象数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            // 当初始容量小于0时,抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                    initialCapacity);
        }
    }
    public ArrayList(Collection c) {
        // 将参数集合c转化为数组
        elementData = c.toArray();
        // 当参数c为非空集合时
        if ((size = elementData.length) != 0) {
            // 当未成功转化为Object[]类型数组时
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 当参数c为空集合时,设置元素数组为空
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

add操作:

public boolean add(E e) {
    // 确保对象数组elementData有足够的容量,如不足,就进行扩容
    ensureCapacityInternal(size + 1);
    // 将新元素e插入数组并且size+1
    elementData[size++] = e;
    return true;
}
public void add(int index, E element) {
    // 校验index
    rangeCheckForAdd(index);
    // 确保对象数组elementData有足够的容量,如不足,就进行扩容
    ensureCapacityInternal(size + 1); 
    // 将当前数组index至size-1 的元素统一 往后移动一位 
    System.arraycopy(elementData, index, elementData, index + 1,
    size - index);
    // 将新元素插入数组
    elementData[index] = element;
    // size+1
    size++;
}

扩容的过程及操作:

    private void ensureCapacityInternal(int minCapacity) {
        // 判断当前元素数组为默认提供的空数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // 比较参数minCapacity与默认容量值DEFAULT_CAPACITY ,取其较大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        //结构性修改+1
        modCount++;
        // 当参数容量值minCapacity 大于当前元素数组长度时
        if (minCapacity - elementData.length > 0)
            //对数组进行扩容
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
        // 旧容量
        int oldCapacity = elementData.length;
        // 新容量为旧容量的1.5倍

        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 当前新容量小于参数指定容量时
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 当前新容量大于最大容量值时
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 拷贝扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

其他常用方法:

    public E set(int index, E element) {
        // 校验索引index
        rangeCheck(index);
        // 旧值
        E oldValue = elementData(index);
        // 该位置替换为新值
        elementData[index] = element;
        // 返回旧值
        return oldValue;
    }

    public E get(int index) {
        // 校验索引index
        rangeCheck(index);
        // 返回元素
        return elementData(index);
    }

    E elementData(int index) {
        //返回元素,并将Object转型为E
        return (E) elementData[index];
    }
    public E remove(int index) {
        // 校验索引index        
        rangeCheck(index);
        // 结构性修改+1
        modCount++;
        // 旧值
        E oldValue = elementData(index);
        // 需要移动元素的个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 赋值为null,有利于进行GC                   
        elementData[--size] = null; 
        // 返回旧值
        return oldValue;
    }

    public boolean remove(Object o) {
        // 当参数o为null时,移除数组中第一个null
        if (o == null) {
            // 遍历数组并依次判断是否为null
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    //该方法与remove(int index)类似,只是不进行对索引index校验
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
    /*
    * 返回第一个出现参数o的索引值
    */
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        //若不包含,返回-1
        return -1;
    }   

    public void clear() {
        //结构性修改+1
        modCount++;
        // 遍历数组并依次赋值为null,有利于进行GC
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }   

常见问题

1.关于ArrayList的描述:
以数组实现。节约空间,但数组有容量限制。超出限制时会增加50%容量,用System.arraycopy()复制到新的数组。因此最好能给出数组大小的预估值。默认第一次插入元素时创建大小为10的数组。

按数组下标访问元素-get(i)、set(i,e) 的性能很高,这是数组的基本优势。

如果按下标插入元素、删除元素-add(i,e)、 remove(i)、remove(e),则要用System.arraycopy()来复制移动部分受影响的元素,性能就变差了。

越是前面的元素,修改时要移动的元素越多。直接在数组末尾加入元素-常用的add(e),删除最后一个元素则无影响。

2.什么情况下你会使用ArrayList?什么时候你会选择LinkedList?
我们知道ArrayList和LinkedList的数据结构是不同的,ArrayList是以连续的数组进行存储的,所以它的get是常数级别的,LinkedList是双向链表存储的。他的查询最坏情况是n。以为ArrayList是数组存储的,所以当你查找某一指定索引的数据时,它每次删除和指定索引添加都要移动数组的位置,其内部的实现方式是数组复杂用到System.arraycope,是比较影响性能的,而双向链表删除和插入只要找到相应的结点位置,关联下指针,所以性能会更好。

3.如何复制某个ArrayList到另一个ArrayList中去?写出你的代码?
下面就是把某个ArrayList复制到另一个ArrayList中去的几种技术:
使用clone()方法,比如ArrayList newArray = oldArray.clone()
使用ArrayList构造方法,比如:ArrayList myObject = new ArrayList(myTempObject)
其他

4.在索引中ArrayList的增加或者删除某个对象的运行过程?效率很低吗?
这个问题同2,因为添加删除某个索引的数据时,需要整体移动数组,所以效率比较低。

5.ArrayList和Vector的关系?
Vector与ArrayList基本是一致的,也是基于数组实现的,都有扩容机制,都允许元素为null。
但是不同点也是很明显的,这也是面试容易被问到的地方:
(1)Vector是线程安全的,会在可能出现线程安全的方法前面加上synchronized关键字。
(2)在构造方法上面, Vector比 ArrayList多了一个方法,在这个方法中可以新传入了一个叫capacityIncrement的参数,用于自定义扩容。

public Vector(int initialCapacity, int capacityIncrement) {  
        super();  
        if (initialCapacity < 0)  
            throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);  
        this.elementData = new Object[initialCapacity];  
        this.capacityIncrement = capacityIncrement;  
} 

(3)两者的扩容方案不同。

//Vector的扩容方法  
private void grow(int minCapacity) {  
        int oldCapacity = elementData.length;  
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);  
        if (newCapacity - minCapacity < 0)  
            newCapacity = minCapacity;  
        if (newCapacity - MAX_ARRAY_SIZE > 0)  
            newCapacity = hugeCapacity(minCapacity);  
        elementData = Arrays.copyOf(elementData, newCapacity);  
}  

在ArrayList中,扩容的时候一般都是增加0.5倍左右,而在 Vector中,如果在构造方法中指定了capacityIncrement的值,就会在原来容量的基础上线性增加capacityIncrement个,如果没有指定这个参数,则默认增加一倍容量(如果新容量还不够,则直接设置新容量为所需的容量),而后同样用Arrays.copyof()方法将元素拷贝到新的数组。

(4)Vector已经被废弃掉

参考
http://www.cnblogs.com/huaizuo/p/5379334.html
http://blog.csdn.net/seu_calvin/article/details/52742373
http://calvin1978.blogcn.com/articles/collection.html/
http://www.importnew.com/9928.html/

你可能感兴趣的:(Java)