JAVA源码学习-ArrayList

ArrayList其实是支持增加,删除,修改,查询元素的数组,提供一些为我们熟知的方法去使用,例如add,remove,get等,她是动态的,可以自由扩展容量,是一种比较常用的数据结构,为什么常用,因为她方便。

简单概括了ArrayList特点之后,正式开启源码解读之旅

(最前面有一大坨英文,有时间再慢慢翻译,如果觉得有必要的话~)

首先看ArrayList的继承关系和声明


她继承于AbstractList,实现了List, RandomAccess, Cloneable,java.io.Serializable这些接口。
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandomAccess接口,即提供了随机访问功能。RandomAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。
ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆

ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输,但其中一个属性是不允许被序列化的哦,接下来可以看到具体是哪个属性。

Vector不同,ArrayList中的操作不是线程安全的。所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList

ArrayList的属性

    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer.
     */
    private transient Object[] elementData;

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

第一个长整形serialVersionUID是为序列化使用的,所以其实只定义了三个属性,对象数组elementData(存储了ArrayList的元素,ArrayList的容量其实就是这个数组的长度),整型size(存储ArrayList中元素的实际容量), 私有静态final类型MAX_ARRAY_SIZE,是为ArrayList定义动态扩充容量的上限。这里也许有人会有疑问,为什么不直接定义成整型的最大值,而是要定义成整型最大值-8呢?原因是某些特定环境下数组里会默认保留一些头信息,ArrayList在设定最大容量时需要将这些信息的长度排除掉,否则可能会出现明明没到整型最大值,但仍抛出OutOfMemoryError异常。

需要注意的是,这里的elementData是用关键字transient修饰的,使用transient修饰是用来表示一个域不是该对象串行化的一部分,即当ArrayList被串行化的时候,elementData的值不包括在串行化表示中,而非transient的变量size则是被包括进去的。那么问题来了,为什么ArrayList中的元素不能被串行化呢?Mark一下留待以后解决~

ArrayList的构造函数

    /**
     * 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) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

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

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

有三个:

ArrayList(int initalCapacity) :带参数的构造函数

    使用initalCapacity来初始化elementData数组的大小,若小于零,抛出IllegalArgumentException异常;

ArrayList():默认的构造函数

     使用带参数的构造函数初始化一个大小为10的ArrayList;

ArrayList(Collection<E> c)

     先将提供的几何转变为数组,c.toArray()方法有可能返回的不是对象数组,这种情况下使用Arrays.copyOf转为对象数组。


ArrayList的其他方法

1. trimToSize()

/**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() {
        modCount++;
        int oldCapacity = elementData.length;
        if (size < oldCapacity) {
            elementData = Arrays.copyOf(elementData, size);
        }
    }
有些时候对象数组的长度跟ArrayList定义的size属性不一定是相等的,那么可以显式地调用此方法确保二者的统一,从而避免不必要的空间浪费。


2.add(E): 在数组的末尾插入元素

    add(int, E):在指定位置插入元素

这些大家应该都知道,因为这两个方法比较常用。但是他们是如何实现的呢?

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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;
        size++;
    }
add(E), 将新的元素e插入到size的位置,然后将size+1备用,返回添加成功。

add(int,E) 先检查你要插入的位置index,如果小于0或是比ArrayList的size还大,则抛出IndexOutOfBoundsException异常,这个异常我相信大家都有见过。然后调用System.arraycopy将原数组elementData的index位置到size-index位置之间的元素移动到index+1开始以后的位置去,这么说有点绕口,说白了就是将elementData数组里index以后的元素向后平移一个位置,空出index这个位置给要增加的元素element,然后将size属性加1。

比较两个方法我们可以发现二者都使用到了ensureCapacityInternal(size + 1)方法,那么这个方法不说大家也可以猜出来,是扩充容量用的。怎么实现的,看源码:

/**
     * Increases the capacity of this <tt>ArrayList</tt> instance, if
     * necessary, to ensure that it can hold at least the number of elements
     * specified by the minimum capacity argument.
     *
     * @param   minCapacity   the desired minimum capacity
     */
    public void ensureCapacity(int minCapacity) {
        if (minCapacity > 0)
            ensureCapacityInternal(minCapacity);
    }

    private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    /**
     * 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
     */
    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)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        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;
    }
第一个方法是ArrayList暴露出来让你自己扩充容量用的,目前我没使用过,都是让ArrayList内部帮我扩充容量。

真正起作用的是剩下的三个私有方法,综合起来看:我在size的基础上增加一位(因为要add一个元素么),但是发现这时候size+1的值比当前对象数组的长度还要大,此时就要去扩充数组容量了,进入了grow方法,她会根据规则对数组对象进行扩充。先说明一下两个值:

A:minCapacity,就是当前size+1后的值,要求扩充到多大;B:newCapacity,当前数组对象长度*3/2(JKD1.7以前的版本是用算数表达式实现的,1.7的版本变成了位右移表达式,结果是一样的,熟悉位移的童鞋应该知道,这么改是有助于提高执行效率和内存使用的)

扩充的规则是:

如果B<A:不够我要求扩充的容量,那么就按照A来扩充。一般不会有,但特殊情况比如当前size的值比实际数组长度大(没有用trimToSize()释放)。

如果A<B<MAX_ARRAY_SIZE: 这个是正常情况下默认扩充的容量大小,即原数组对象的3/2,即扩充了原长度的一半。

如果B>MAX_ARRAY_SIZE:扩充不了原长度的一半了,则使用上限值去扩充。

为什么说ArrayList是一个动态数组,答案就在这几个方法里面。

那么同理add(Collection)和add(int,Collection)实现原理是一样的,无非多了一个将Collection转换成数组的操作,这里不再赘述。

3 size()

/**
     * Returns the number of elements in this list.
     *
     * @return the number of elements in this list
     */
    public int size() {
        return size;
    }
这里使用这个public方法封装size私有属性,返回的是当前ArrayList的大小,这点也跟数组的length属性区别开了。
4 isEmpty()

/**
     * Returns <tt>true</tt> if this list contains no elements.
     *
     * @return <tt>true</tt> if this list contains no elements
     */
    public boolean isEmpty() {
        return size == 0;
    }

返回ArrayList是否为空。

5 contains(Object)
 /**
     * Returns <tt>true</tt> if this list contains the specified element.
     * More formally, returns <tt>true</tt> if and only if this list contains
     * at least one element <tt>e</tt> such that
     * <tt>(o==null ? e==null : o.equals(e))</tt>.
     *
     * @param o element whose presence in this list is to be tested
     * @return <tt>true</tt> if this list contains the specified element
     */
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
使用indexOf方法来判断当前数组是否包含元素o,接着来看indexOf方法。

6 indexOf(Object)

   lastIndexOf(Object)

/**
     * 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 <tt>i</tt> such that
     * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    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;
        }
        return -1;
    }

    /**
     * Returns the index of the last occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the highest index <tt>i</tt> such that
     * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
使用了for循环对数组进行遍历,indexOf和lastIndexOf无非是前者从前往后遍历,后者从后往前遍历,如果当前元素与o相等,则返回当前元素索引。需要注意的是,ArrayList是支持对null的查找的。

这里多说一下ArrayList的遍历方式,有三种

第一种:利用迭代器遍历。即通过Iterator去遍历

Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();
}

第二种,随机访问,通过索引值遍历(由于实现了RandomAccess接口,支持通过索引访问元素)

Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
    value = (Integer)list.get(i);      
}
第三种,for循环遍历。
Integer value = null;
for (Integer integ:list) {
    value = integ;
}
这三种遍历方式,使用随机访问(即通过索引序号访问)效率最高,而使用迭代器效率最低。至于为什么,有兴趣的童鞋可以下去研究一下。
7 clone()

    /**
     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    public Object clone() {
        try {
            @SuppressWarnings("unchecked")
                ArrayList<E> v = (ArrayList<E>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError();
        }
    }

用父类的clone方法返回一个对象的副本,将返回对象的elementData数组的内容赋值为原对象elementData数组的内容

8 toArray()

  toArray(T[])

public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
@SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
toArray() 调用Arrays.copyOf方法返回一个数组,内容是size个elementData元素,即把elementData中从0到size-1的元素拷贝到新的数组并返回。

toArray(T[]) 如果传入数组的长度小于size,则返回一个新的数组,大小为size,类型与传入数组相同;如果传入数组的长度大于或等于size,则将elementData全部拷贝到a中并返回,其中如果传入数组长度大于size,除了复制外,还将返回数组a中的size位置置为null。

9 elementData(int)

@SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

返回索引为index的数组元素

10 get(int)

/**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
先校验index是否越界,不越界就返回当前索引为index的数组元素。
11 set(int, E)

/**
     * 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) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
先校验index是否越界,不越界就将索引为index的数组元素另存为一个副本oldValue,然后将入参element插入到该索引位置,返回副本值oldValue。原本以为调用set方法无返回值的童鞋是不是很诧异?
12 clear()

/**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     */
    public void clear() {
        modCount++;

        // Let gc do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }
用一个for循环遍历,将所有的元素设置为null,然后将size清零。这里需要注意的是数组elementData的实际长度并没有改变,此时虽然arraylist的大小为0,但仍然占用elementData.length的空间,这就是前面提到的trimToSize方法来释放多余占用的空间。

13 remove(int)

/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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; // Let gc do its work

        return oldValue;
    }
先index索引越界校验,保存原来index位置数组元素的副本oldValue,然后如果index跟elementData最后一位索引值相等,即移除最后一个元素,更新size,否则在移除元素前将数组index之后的所有元素向前移动一位。移除成功后返回移除的元素值

14 remove(Object)

/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    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;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    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; // Let gc do its work
    }
这个方法的作用是remove一个元素对象。那么就需要找到这个元素在数组中位置index,然后remove(index)就行了,查找index的方法大家是不是看着眼熟,其实就是前面讲到的indexOf(Object)方法,只不过找到以后多了一个移除的动作。那么问题来了,移除用了一个新的方法fastRemove,而没用之前写好的remove方法,这是因为此时已经确定找到了index,就不需要像remove方法里先校验一次是否越界了。

15 removeRange(int,int)

    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // Let gc do its work
        int newSize = size - (toIndex-fromIndex);
        while (size != newSize)
            elementData[--size] = null;
    }
方法的执行过程是 将elementData从toIndex位置开始的元素向前移动到fromIndex,然后将toIndex位置之后的元素全部置空顺便修改size。
但是为什么设置为protected受保护的方法呢?下面这个链接做了解释,但看不怎么明白,因为JDK1.7已经在ArrayList方法里实现了SubList,那么绕一大圈貌似没什么卵用。大家可以交流下自己的理解。

http://www.cnblogs.com/hzmark/archive/2012/12/19/ArrayList_removeRange.html

16 removeAll(Collection)

   retainAll(Collection)

public boolean removeAll(Collection<?> c) {
        return batchRemove(c, false);
    }
public boolean retainAll(Collection<?> c) {
        return batchRemove(c, true);
    }

    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

删除或保留ArrayList中包含Collection c中的的元素,这两个方法都依赖batchRemove(Collection<?> c, boolean complement)实现。

看到这里是该说明一下modCount了,这个是个什么鬼?

其实在add()、remove()、addall()、removerange()及clear()方法都会让modCount增长,在add()及addall()方法的对modCount的操作在ensureCapacityInternal中。 modCount用于记录ArrayList的结构性变化的次数,也用于序列化。


17 writeObject(ObjectOutputStream)

/**
     * 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 array length
        s.writeInt(elementData.length);

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

    }

该方法是将ArrayList保存为流,即对modCount,数组长度及元素进行序列化操作。还是个private方法,估计是ArrayList内部使用的。


18 readObject (ObjectOutputStream)

/**
     * 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 {
        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in array length and allocate array
        int arrayLength = s.readInt();
        Object[] a = elementData = new Object[arrayLength];

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

有了writeObject,那么对应就有readObject,将ArrayList反序列化。


虽然洋洋洒洒写了这么多,几乎将源码粘出来了,但还是有个别细节问题模棱两可,比如ArrayList三种遍历方式效率的比较,removeRange为什么是一个保护方法而不能直接使用,内部私有类Itr,ListItr,SubList及其使用等,文章只是浅显地罗列了我们工作中常用的ArrayList的方法和特性,更细节深入的东西留待以后补充。
















  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值