(二十六) ArrayList

前言:之前学习hashmap,重点学习了hashmap的数据结构以及如何扩容,相关联的其实有ArrayList、LinkedList和HashTable这些,本文简单地学习一下ArrayList。

PS: 源码分析针对于Android O 的jdk,即jdk1.8


demo地址:jiatai的ArrayList demo



1. ArrayList简单介绍

就我印象,ArrayList在Java中扮演的角色对应于数据结果中的数组,或者说ArrayList是基于数组写出来的一个类,用于存储数据。

说一个小问题,如果我想在ArrayList里存12个数,是用无参的ArrayList好,还是有参的好,好在哪里?(有参的好,好在不用扩容)


2. ArrayList简单使用

简单的往Arraylist里面塞两个数字1和2:
package com.example.demo_26_arraylist;

import java.util.ArrayList;

public class MyClass {
    public static void main(String[] args){
        ArrayList arrayList = new ArrayList<Integer>();
        System.out.println("the size is " + arrayList.size());
        arrayList.add(1);
        System.out.println("the size is " + arrayList.size());
        arrayList.add(2);
        System.out.println("the size is " + arrayList.size());
        System.out.println(arrayList);
    }
}

对应log:

the size is 0
the size is 1
the size is 2
[1, 2]


3. 简单使用所对应的内在

上面的使用主要涉及了四个方法

  • 构造方法
  • add()
  • size()
  • toString()

下面来简单地看下内在=-=

3.1 构造方法

构造方法有3个,这里先看下平常用的最多的无参构造方法。
/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

无参构造方法是最简单的,也是我们比较常用的,内部其实就初始化了一下elementData这个成员变量,来看下elementData 和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是啥?

/**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    // Android-note: Also accessed from java.util.Collections
    transient Object[] elementData; // non-private to simplify nested class access 

DEFAULTCAPACITY_EMPTY_ELEMENTDATA其实就是个空的数组。而elementData这里注释讲的很清楚,是用来存放数组成员的数组缓存,Arraylist的长度就是这个elementData的长度。并且任何空的数组,也就是用无参构造器初始的数组,在add第一个成员的时候会被扩展成DEFAULT_CAPACITY。

 /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

也就是说空的数组第一次被add元素的时候会被扩展为容量为10的数组。

这里接着就看下add的源码。


3.2 add(E 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;
    }

可以看到add分两个步骤

  1. 确保容量足够存储新来的成员
  2. 添加成员,并使size++

这里主要看下ensureCapacityInternal这个方法的后续:

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

这里可以看到

1.将minCapacity初始化为DEFAULT_CAPACITY,即10,这和之前看无参构造器注释一样的。

2.依据minCapacity的值调用grow方法进行扩容


 /**
     * 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;

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

这边扩容不是说直接用 minCapacity 为ArrayList的大小就开始扩容了。

举个例子:现在数组长度为10,则新的扩容建议大小为10+10/2,即15,然后以15和minCapacity(11)继续比较,15比较大,所以当数组长度为10的数组新添一个元素后,长度会变为15。(第4节中有例子来验证)

对应于初始化就是minCapacity是10,而oldCapacity是0,没有可比较性,直接用minCapacity作为扩容后数组大小。

确定好扩容后数组大小后,用Arrays的copyOf方法将原数组元素复制到新的数组中。

/**
     * Copies the specified array, truncating or padding with nulls (if necessary)
     * so the copy has the specified length.  For all indices that are
     * valid in both the original array and the copy, the two arrays will
     * contain identical values.  For any indices that are valid in the
     * copy but not the original, the copy will contain <tt>null</tt>.
     * Such indices will exist if and only if the specified length
     * is greater than that of the original array.
     * The resulting array is of exactly the same class as the original array.
     *
     * @param <T> the class of the objects in the array
     * @param original the array to be copied
     * @param newLength the length of the copy to be returned
     * @return a copy of the original array, truncated or padded with nulls
     *     to obtain the specified length
     * @throws NegativeArraySizeException if <tt>newLength</tt> is negative
     * @throws NullPointerException if <tt>original</tt> is null
     * @since 1.6
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }


3.3 size

注意区分size不是Arraylist的实际长度,而是其中包含的元素的数目,对应与上面的“当数组大小为10的时候,再添加一个元素会造成数组进行扩容为15”的例子,size是11,而不是15,elementData的数组长度才是15。

 /**
     * Returns the number of elements in this list.
     *
     * @return the number of elements in this list
     */
    public int size() {
        return size;
    }

另外试了下arrayList是支持添加null元素的,并且size可以正常进行+1


3.4 toString

toString方法是继承的父类AbstractCollection的toString方法,将其所包含的元素都打印出来。

 /**
     * Returns a string representation of this collection.  The string
     * representation consists of a list of the collection's elements in the
     * order they are returned by its iterator, enclosed in square brackets
     * (<tt>"[]"</tt>).  Adjacent elements are separated by the characters
     * <tt>", "</tt> (comma and space).  Elements are converted to strings as
     * by {@link String#valueOf(Object)}.
     *
     * @return a string representation of this collection
     */
    public String toString() {
        Iterator<E> it = iterator();
        if (! it.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (;;) {
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
    }


3.5 指定位置插入add(int index, E element)

数组在指定位置插入元素效率比较低的,从下面源码可以看出,是将指定位置及后面的源码都往后挪一下,给指定元素挪一个位置出来让该元素落座。remove方法就是相反的操作=-=

/**
     * 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++;
    }


4. 扩容

扩容其实在3.2节中有提及,如果不考虑边界情况,即初始化为10和数目大于整数最大值的时候,扩容其实就是将数组大小变为n+n/2,然后调用Arrays.copyOf方法将之前的数组成员复制到新数组中去并返回新数组。

写了个简单的例子来验证上面说的ArrayList的扩容原理和之前举的例子:

当数组大小为10的时候,再添加一个元素会造成数组进行扩容为15,其中4个空档会用null占据。


5. 总结

ArrayList本质可以看成是一个固定大小的数组,然后一直往里面塞成员,不够塞了就扩容一下,继续赛。这里想起来之前阿里编码规范里有说,如果确定了ArrayList大小,则指定大小,不然比如10到11会造成一次数组扩容,影响效率。

猜你喜欢

转载自blog.csdn.net/sinat_20059415/article/details/80010993