原创

Java中集合(ArrayList)


  • ArrayList是List接口的一个重要实现类。我们打开源码,第一段:

/**
 * Resizable-array implementation of the <tt>List</tt> interface.  Implements
 * all optional list operations, and permits all elements, including
 * <tt>null</tt>.  In addition to implementing the <tt>List</tt> interface,
 * this class provides methods to manipulate the size of the array that is
 * used internally to store the list.  (This class is roughly equivalent to
 * <tt>Vector</tt>, except that it is unsynchronized.)
  • 这段给我们几个关键信息:

  1. ArrayList是一个可变的数组,(底层数据结构是数组)。

  2. ArrayList 和 Vector 有着相似的结构。但是ArrayList是不同步的(线程不安全的),言外之意就是Vector是同步的,线程安全的。

然后这一段:

 * <p>The <tt>size</tt>, <tt>isEmpty</tt>, <tt>get</tt>, <tt>set</tt>,
 * <tt>iterator</tt>, and <tt>listIterator</tt> operations run in constant
 * time.  The <tt>add</tt> operation runs in <i>amortized constant time</i>,
 * that is, adding n elements requires O(n) time.  All of the other operations
 * run in linear time (roughly speaking).  The constant factor is low compared
 * to that for the <tt>LinkedList</tt> implementation.
  • 大致意思就是:ArrayList的add()方法需要花费的O(n)时间复杂度。而他的常数因子比LinkedList要低。

接下来这一段很重要:

 * <p>Each <tt>ArrayList</tt> instance has a <i>capacity</i>.  The capacity is
 * the size of the array used to store the elements in the list.  It is always
 * at least as large as the list size.  As elements are added to an ArrayList,
 * its capacity grows automatically.  The details of the growth policy are not
 * specified beyond the fact that adding an element has constant amortized
 * time cost.
  • 每一个ArrayList实例都有一个容量,这个容量的大小总是和ArrayList的大小一致。当一个元素被添加到ArrayList中,容量会自动增长。

然后这一段:

 * <p>An application can increase the capacity of an <tt>ArrayList</tt> instance
 * before adding a large number of elements using the <tt>ensureCapacity</tt>
 * operation.  This may reduce the amount of incremental reallocation.
  • 我们可以增加ArrayList的容量,(也就是说构造实例时可以指定ArrayList的大小),因为ArrayList每次插入元素先都要调用ensureCapacity()方法保证自增长。这样可以减少该方法的调用次数。

下面这一段再次强调:ArrayList是不同步的,多线程同时访问实例时,改变他的结构(添加或删除元素)会有线程安全问题。

 * <p><strong>Note that this implementation is not synchronized.</strong>
 * If multiple threads access an <tt>ArrayList</tt> instance concurrently,
 * and at least one of the threads modifies the list structurally, it
 * <i>must</i> be synchronized externally.  (A structural modification is
 * any operation that adds or deletes one or more elements, or explicitly
 * resizes the backing array; merely setting the value of an element is not
 * a structural modification.)  This is typically accomplished by
 * synchronizing on some object that naturally encapsulates the list.

然后接下来一段:

 * If no such object exists, the list should be "wrapped" using the
 * {@link Collections#synchronizedList Collections.synchronizedList}
 * method.  This is best done at creation time, to prevent accidental
 * unsynchronized access to the list:<pre>
 *   List list = Collections.synchronizedList(new ArrayList(...));</pre>
  • 给我们介绍了一个同步的列表类:Collections.synchronizedList,通过他我们可以将ArrayList包装成一个线程安全的List(但是这有啥真正的用途呢?) 例如,可以这样创建实例: List list = Collections.synchronizedList(new ArrayList());


  • 然后我们来看ArrayList的定义:

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

我们可以得到以下信息:

  1. ArrayList是一个可以被继承,并且可以被序列化的对象。

  2. ArrayList是一个支持快速随机访问,并且可以被克隆的对象。

  • ArrayList的成员变量

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

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] 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.
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;
  • 可以得到以下信息:

  1. ArrayList的初始容量是10;

  2. 用户指定ArrayList的容量为0时,返回EMPTY_ELEMENTDATA,否则默认空参数构造函数返回该对象内容DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

  3. elementData是ArrayList存储元素的数组(底层是一个对象数组),并且该数组的默认大小是10。

然后看构造函数:

/**
     * 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) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
  • 有参的构造函数:

  1. elementData会构造一个initialCapacity大小数组作为初始值,如果小于0,直接抛出异常。

/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  • 无参的构造函数

  1. 会构造一个初始容量为0的elementData,当进行第一次add的时候,elementData将会变成默认的长度: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();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
  • 带Collection的参数的构造函数

  1. 构造一个elementData容量等于参数集合容量.将collection对象转换成数组,然后将数组的地址的赋给elementData。

  2. 更新size的值,同时判断size的大小,如果是size等于0,直接将空对象EMPTY_ELEMENTDATA的地址赋给elementData
  3. 如果size的值大于0,则执行Arrays.copy方法,把collection对象的内容(深拷贝)copy到elementData中。
  • 然后我们再看一下 add()方法

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

  1. 确保数组已使用长度(size)加1之后足够存下一个数据。

  2. 修改次数modCount标识自增1,如果当前数组已使用长度(size)加1后的大于当前的数组长度,则调用grow方法,增长数组,grow方法会将当前数组的长度变为原来容量的1.5倍。

  3. 确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。

  4. 返回添加成功布尔值。

  • add(int index, E element)

  1. 确保数插入的位置小于等于当前数组长度,并且不小于0,否则抛出异常

  2. 确保数组已使用长度(size)加1之后足够存下 下一个数据

  3. 修改次数(modCount)标识自增1,如果当前数组已使用长度(size)加1后的大于当前的数组长度,则调用grow方法,增长数组

  4. grow方法会将当前数组的长度变为原来容量的1.5倍。

  5. 确保有足够的容量之后,使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位。

  6. 将新的数据内容存放到数组的指定位置(index)上

  • remove(int index)

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

这个还是很容易的:

  1. 判断索引有没有越界,否则抛出异常

  2. 自增(modCount)修改次数

  3. 将指定位置(index)上的元素保存到oldValue

  4. 将指定位置(index)上的元素都往前移动一位

  5. 将最后面的一个元素置空,好让垃圾回收器回收

  6. 将原来的值oldValue返回

  • remove(Object o):

 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;
    }
  • 根据对象移除也比较简单:

  1. 循环遍历所有对象,得到对象所在索引位置(注意这里是找到第一个匹配的),然后调用fastRemove(),执行remove操作。

  2. fastRemove()基本同remove(int index,E e)。

总结

  • ArrayList自己实现了序列化和反序列化的方法,因为它自己实现了 private void writeObject(java.io.ObjectOutputStream s)和 private void readObject(java.io.ObjectInputStream s) 方法

  • ArrayList基于数组方式实现,最大容量为int的最大值(会扩容)

  • 添加元素时可能要扩容(所以最好预判一下),删除元素时不会减少容量(若希望减少容量,使用trimToSize()),删除元素时,将删除掉的位置元素置为null,下次gc就会回收这些元素所占的内存空间。

  • 线程不安全

  • add(int index, E element):添加元素到数组中指定位置的时候,需要将该位置及其后边所有的元素都整块向后复制一位

  • get(int index):获取指定位置上的元素时,可以通过索引直接获取(O(1))

  • remove(Object o)需要遍历数组

  • remove(int index)不需要遍历数组,只需判断index是否符合条件即可,效率比remove(Object o)高

  • contains(E)需要遍历数组

  • 使用iterator遍历可能会引发多线程异常

数据结构
基础
javase
  • 作者:管理员(联系作者)
  • 发表时间:2020-03-19 10:23
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 公众号转载:请在文末添加作者公众号二维码
  • 微信公众号

    评论

    留言