关于ArrayList源码的一些自我理解以及解析(二):神奇的arraycopy

    /**
     * Trims the capacity of this ArrayList instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an ArrayList instance.
     */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

在ArrayList中,这个方法的作用应该是尽可能的减少数组所占用的内存。当定义的一个ArrayList的容量是100,但实际上里面存储的只有20个数据,那么当调用这个方法的时候,这个ArrayList的容量就是它当前包含的元素数量也就是20.这对于减少内存的占用应该很有用,不然也不会设定这样一个方法。

    /**
     * Returns true if this list contains the specified element.
     * More formally, returns true if and only if this list contains
     * at least one element e such that
     * (o==null ? e==null : o.equals(e)).
     *
     * @param o element whose presence in this list is to be tested
     * @return true if this list contains the specified element
     */
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
    /**
     * 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 i such that
     * (o==null ? get(i)==null : o.equals(get(i))),
     * 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;
    }

当调用contains方法去检查整个ArrayList里面是否包含某个元素的时候,实际上是使用indexOf方法去检查。indexOf是返回某个元素所在的下标,如果这个元素存在,返回的下标值肯定就不是一个负数,如果不存在,则返回-1这个数。indexOf这个方法也支持null的检查,默认该元素不存在,如果找到该元素,则返回下标。

    /**
     * Returns a shallow copy of this ArrayList instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this ArrayList instance
     */
    public Object clone() {
        try {
            ArrayList v = (ArrayList) 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(e);
        }
    }
    /**
     * Returns an array containing all of the elements in this list
     * in proper sequence (from first to last element).
     *
     * 

The returned array will be "safe" in that no references to it are * maintained by this list. (In other words, this method must allocate * a new array). The caller is thus free to modify the returned array. * *

This method acts as bridge between array-based and collection-based * APIs. * * @return an array containing all of the elements in this list in * proper sequence */ public Object[] toArray() { return Arrays.copyOf(elementData, size); }

clone方法的返回值是Object对象,也就是说,如果我们当时创建的ArrayList存储的元素是int类型的,那么克隆之后返回的也是存储int类型的ArrayList。并且,这个克隆的工作并不是ArrayList自己完成,而是调用了父级的方法去完成。

和clone方法不同,toArray方法返回的是一个object对象数组,在注释中说明这个方法是“safe”的,也就是说,toArray拿出来的是这个数组的引用,不会实际影响到这个ArrayList。

    /**
     * Returns an array containing all of the elements in this list in proper
     * sequence (from first to last element); the runtime type of the returned
     * array is that of the specified array.  If the list fits in the
     * specified array, it is returned therein.  Otherwise, a new array is
     * allocated with the runtime type of the specified array and the size of
     * this list.
     *
     * 

If the list fits in the specified array with room to spare * (i.e., the array has more elements than the list), the element in * the array immediately following the end of the collection is set to * null. (This is useful in determining the length of the * list only if the caller knows that the list does not contain * any null elements.) * * @param a the array into which the elements of the list are to * be stored, if it is big enough; otherwise, a new array of the * same runtime type is allocated for this purpose. * @return an array containing the elements of the list * @throws ArrayStoreException if the runtime type of the specified array * is not a supertype of the runtime type of every element in * this list * @throws NullPointerException if the specified array is null */ @SuppressWarnings("unchecked") public 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; } /** * 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) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); return (E) elementData[index]; }

toArray还有一个重载方法,我们设定源数据是一个char类型的ArrayList,而我们设定的是存储int类型的a,如果说a的容量没有源数据大,那么ArrayList会自动帮我们创建一个新的数组来存储。源数据是包含30个元素,而你的a只设定容量为20,那么当你使用这个方法的时候,你的a会将源数据的30个元素添加进来,从原先的20容量,变成了30容量。同样的,如果你a本来设定的容量就比源数据大,那么它会调用System.arraycopy方法。

src Object: the source array.

 

srcPos int: starting position in the source array.

 

dest Object: the destination array.

 

destPos int: starting position in the destination data.

 

length int: the number of array elements to be copied.

 

arraycopy这个方法传递5个参数,第一个参数是不会改动长度大小的数组src,第二个参数是这个src的开始位置,从这个位置开始一直复制;第三个参数是待改动的数组dest,也就是说,因为加入了东西,所以它的长度大小会被改变;第四个参数,是dest的开始位置,要从哪个位置开始;第五个参数是长度,也就是说需要被改变的长度。

ArrayList的get方法也很好理解,就直接返回对应数组下标的元素。

    /**
     * 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 i such that
     * (o==null ? get(i)==null : o.equals(get(i))),
     * 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;
    }

lastIndexOf方法,获取ArrayList中某个元素最后出现的下标。实现方法也很简单,就是从最后开始检查,直接每一个都遍历一遍。当这个元素并不存在的时候,就返回-1.

    /**
     * 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) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        E oldValue = (E) elementData[index];
        elementData[index] = element;
        return oldValue;
    }
    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return true (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

set方法是有返回值的,当我们对一个ArrayList数组操作的时候,将指定下标的元素先拿出来,然后放入我们指定的元素,然后ArrayList将拿出来的旧的oldVlaue给我们看,你看看,你换出来的是这个东西。我觉得这个是为了节省我们一些操作,比如,如果它不给我们返回值,而我们需要替换出来的这个值。那么我们肯定会先将这个值拿出来存着,然后再去替换,最后再使用这个值去做别的事情。或者说,它将旧oldValue给我们看,是为了确保我们没有换错想换的值,可以在代码中进行后续的判断。

add方法,添加一个元素,如果不指定添加的位置的话,默认就是添加到末尾。而这个方法还有一个重载方法。

    /**
     * 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) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

这个add方法传递进去两个参数,一个是添加位置的下标,一个是添加进入的元素。它先判断,你要插入的位置是不是超过了我们数组的长度,如果是,就抛出数组越界的错误。如果你插入的位置没毛病,那么ArrayList先自己“扩容 ,也就是容量+1.然后又会用到一个方法System.arraycopy方法。不同的是,这次的源数据和目标数据都是它自己,也就是说,它将插入位置后面的所有元素都复制下来,然后移动一个位置,放在自己的身上。比如,一个长度为4的,内容是1234的数据。我在第二个位置插入一个5。那么它先自己扩容成为1234null,然后复制第二个位置(包含第二个位置)之后的所有数,放到自己里面,也就是往后挪一个位置。此时数据成为了12234。然后将需要加入的元素替换掉,于是成为了15234。

    /**
     * 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) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        modCount++;
        E oldValue = (E) 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;
    }

remove方法,移除指定下标位置的元素。如果指定的下标位置超出了数组长度,则抛出数组长度溢出错误。之后统计所有需要往左移动的元素的数量,如果移除的是最后一个位置的元素,那么ArrayList直接将长度缩短。否则,调用System.arraycopy方法,将需要移动的元素copy到自己本身里面。返回被移除的元素的值。

其实就是ArrayList偷个懒,如果移除最后一个位置的元素,那我自己不必动手,交给GC去做就好了。好比,能够借刀杀人,何必脏了自己的手。

    /**
     * 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
     * i such that
     * (o==null ? get(i)==null : o.equals(get(i)))
     * (if such an element exists).  Returns true 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 true 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; // clear to let GC do its work
    }
    /**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     */
    public void clear() {
        modCount++;
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }

remove还有一个重载的方法,传递的参数是一个对象,当我不知道这个对象在ArrayList哪个位置的时候,我只要传递这个对象进去,那么ArrayList就会帮我们把找到的第一个匹配的对象移除。而移除的方法fastRemove中,也就是说,remove找到相应对象所在的下标,然后将下标传递给fastRemove,让它来做移除的工作。

和之前的按下标移除一样,如果移除的对象是最后一个,那么直接将最后一个设置为null,然后容量大小-1就行了。如果不是,那么将需要往左移的数组复制然后拷贝到自己相应的位置,然后再将容量大小-1.

clear方法,将整个数组list设置为null,然后将长度定义为0即可。剩下的交给垃圾回收GC处理。

    /**
     * Appends all of the elements in the specified collection to the end of
     * this list, in the order that they are returned by the
     * specified collection's Iterator.  The behavior of this operation is
     * undefined if the specified collection is modified while the operation
     * is in progress.  (This implies that the behavior of this call is
     * undefined if the specified collection is this list, and this
     * list is nonempty.)
     *
     * @param c collection containing elements to be added to this list
     * @return true if this list changed as a result of the call
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(Collection c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
    /**
     * Inserts all of the elements in the specified collection into this
     * list, starting at the specified position.  Shifts the element
     * currently at that position (if any) and any subsequent elements to
     * the right (increases their indices).  The new elements will appear
     * in the list in the order that they are returned by the
     * specified collection's iterator.
     *
     * @param index index at which to insert the first element from the
     *              specified collection
     * @param c collection containing elements to be added to this list
     * @return true if this list changed as a result of the call
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(int index, Collection c) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

addAll有两个重载方法,一个只需要传递添加的数组;另一个是在指定的下标位置添加数组。

第一个很好理解,先获取你要插入的数组的长度,然后再调用”扩容“方法ensureCapacityInternal,然后再调用System.arraycopy方法来将你的数组添加到原先数组的末尾。

第二个就有点复杂,你指定的下标如果数组越界,那就不处理给你报个错误。如果插入位置的下标没有错误,那我记录你插入数组的长度,然后调用”扩容“方法。再然后,记录我需要移动的数的个数,如果不是0个,则乖乖的挪动,然后将你要添加的数添加进来,然后告诉size变动了。最后就是返回值了,如果你需要添加的这个数组,确确实实的change改动了ArrayList,那么它就会返回true给你,如果你加进来的东西,根本没有改变任何东西,那么它将返回false。

    /**
     * Removes from this list all of the elements whose index is between
     * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
     * Shifts any succeeding elements to the left (reduces their index).
     * This call shortens the list by {@code (toIndex - fromIndex)} elements.
     * (If {@code toIndex==fromIndex}, this operation has no effect.)
     *
     * @throws IndexOutOfBoundsException if {@code fromIndex} or
     *         {@code toIndex} is out of range
     *         ({@code fromIndex < 0 ||
     *          fromIndex >= size() ||
     *          toIndex > size() ||
     *          toIndex < fromIndex})
     */
    protected void removeRange(int fromIndex, int toIndex) {
        // Android-changed: Throw an IOOBE if toIndex < fromIndex as documented.
        // All the other cases (negative indices, or indices greater than the size
        // will be thrown by System#arrayCopy.
        if (toIndex < fromIndex) {
            throw new IndexOutOfBoundsException("toIndex < fromIndex");
        }
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);
        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }

除了单个的移除,ArrayList也可以按照下标进行整段的移除。第一个参数是开始下标,第二个参数是结束下标。比如,一个长度为10,内容为0123456789的数组。当我第一个参数设定为2,第二个参数设定为4的时候,表示,我要把第三个数和第四个数给移除掉。也就是说,移除的内容,包含第一个参数下标索引的元素,但不包括第二个参数下标索引的元素。所以,它移除的是2个元素,分别是第三个元素和第四个元素。当移除之后,长度就变成了8,内容成为了:01456789

如果说,你开始的索引fromIndex小于0或者大于数组长度,那么就会报数组越界的错误。同样的,结束索引如果超过了数组长度,也会报错,以及结束索引比开始索引小也会报错。使用的时候,一定要确保fromIndex大于toIndex。

该方法在运行的时候,会先判断结束索引是否会比开始索引小。然后记录移除的元素个数,然后调用System.arraycopy移动需要移动的元素。然后记录ArrayList新的容量大小size,然后将toIndex后面的所有元素设置为null,然后更改ArrayList的size大小。

还是拿上面那个例子来说明吧。0123456789变成了0145678989,然后又变成了01456789nullnull,然后由于GC垃圾回收机制,将null回收掉了,于是成为了01456789.

    /**
     * Constructs an IndexOutOfBoundsException detail message.
     * Of the many possible refactorings of the error handling code,
     * this "outlining" performs best with both server and client VMs.
     */
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

outOfBoundsMsg方法,只在抛出了数组越界异常的时候,告诉我们,你设置的下标index,已经超过了ArrayList数组的size长度。

    /**
     * Removes from this list all of its elements that are contained in the
     * specified collection.
     *
     * @param c collection containing elements to be removed from this list
     * @return {@code true} if this list changed as a result of the call
     * @throws ClassCastException if the class of an element of this list
     *         is incompatible with the specified collection
     * (optional)
     * @throws NullPointerException if this list contains a null element and the
     *         specified collection does not permit null elements
     * (optional),
     *         or if the specified collection is null
     * @see Collection#contains(Object)
     */
    public boolean removeAll(Collection c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
    /**
     * Retains only the elements in this list that are contained in the
     * specified collection.  In other words, removes from this list all
     * of its elements that are not contained in the specified collection.
     *
     * @param c collection containing elements to be retained in this list
     * @return {@code true} if this list changed as a result of the call
     * @throws ClassCastException if the class of an element of this list
     *         is incompatible with the specified collection
     * (optional)
     * @throws NullPointerException if this list contains a null element and the
     *         specified collection does not permit null elements
     * (optional),
     *         or if the specified collection is null
     * @see Collection#contains(Object)
     */
    public boolean retainAll(Collection c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }

除了单个元素,下标索引片段这个移除方法,还有种方法是移除所有指定数组removeAll。同时ArrayList还给了一个反向的方法,retainAll方法是只保留你指定的数组。两者差别举个例子就很好的说明了。一个长度为10,内容为0120120120的数组为例子。

当我们调用removeAll方法,并传递01进去,那么这个方法会移除所有的01,之后的数组就变成了:222。

当我们调用retainAll方法,并传递01进去,那么这个方法会保留所有的01,之后的数组就变成了010101。原理和removeAll刚好相反。

既然retainAll方法和removeAll方法实现的原理相反,那么可以单独设定一个方法,根据传递的参数不同,就可以达到不同的效果了。于是这两个方法最后都调用了batchRemove这个方法,在这个方法中,当你想要保留的时候,第二个参数传递true,当你是想移除的时候,第二个参数就传递false。

在调用batchRemove方法之前,会先调用Object.requireNonNull方法来判断,传递进来的c是不是null,如果是null的话,就会抛出NullPointerException异常。

 

你可能感兴趣的:(Android)