ArrayList

Posted tkzl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ArrayList相关的知识,希望对你有一定的参考价值。

前言

集合的作用就是以一定的方式组织、存储数据。

分析集合,有四点要特别注意:

  1. 是否线程安全

  2. 是否允许存储 null

  3. 是否允许存储重复数据

  4. 是否有序,有序的意思是读取数据的顺序和存放数据的顺序一致。

ArrayList 基本结构

首先我们来看一下 ArrayList 在 Collection 集合框架中的结构图,蓝线代表继承关系,绿线代表接口实现。

技术图片

图 1-1 : ArrayList 关系图

从结构图来看,ArrayList 类继承了 AbstractList 具备了添加、删除、修改、遍历等功能;实现了 RandomAccess 接口,提供了随机访问功能,也就是通过索引快速访问数组元素;实现了 Cloneable 接口,重写 clone() 函数,可以被复制;实现了 Serializable 接口,支持序列化。

ArrayList 底层存储数据结构是通过数组实现的。接下来,我们看一下 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;

ArrayList 方法概要

属于 Collection 接口的方法

boolean             add(E object)
boolean             addAll(Collection<? extends E> collection)
void                clear()
boolean             contains(Object object)
boolean             containsAll(Collection<?> collection)
boolean             equals(Object object)
int                 hashCode()
boolean             isEmpty()
boolean             remove(Object object)
boolean             removeAll(Collection<?> collection)
boolean             retainAll(Collection<?> collection)
int                 size()
<T> T[]             toArray(T[] array)
Object[]            toArray()

属于 AbstractCollection 抽象类中的方法

void                add(int location, E object)
boolean             addAll(int location, Collection<? extends E> collection)
E                   get(int location)
int                 indexOf(Object object)
int                 lastIndexOf(Object object)
ListIterator<E>     listIterator(int location)
ListIterator<E>     listIterator()
E                   remove(int location)
E                   set(int location, E object)
List<E>             subList(int start, int end)

属于 ArrayList 自己的方法

Object               clone()
void                 ensureCapacity(int minimumCapacity)
void                 trimToSize()
void                 removeRange(int fromIndex, int toIndex)

从上面的类的 API 来看事实上涉及到集合的本身的东西基本就在 Collection 接口中定义好了。

接下来我们就要看看源码了。

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

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() 
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    

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

接下来看一下 add(E e) 方法

public boolean add(E e) 
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;                  
private void ensureCapacityInternal(int minCapacity)   // 最小容量 = 当前数组元素个数 + 1
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) 
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    

    ensureExplicitCapacity(minCapacity);

如果初始化集合的时候并未设置集合容量,那么最小容量大于当前数组元素个数其实是必然的,因为 minCapacity = size + 1,而 elementData.lenth = size。所以对于未初始化容量的集合而言,每次 add 的时候都会进行扩容,复制的操作。如果初始化时始设置了 elementData 数组的长度为 20,那么在这里就不会成立,下面就不会进行 grow 扩容,也不会继续数组赋值,所以如果我们能够预测我们的 arrayList 存储多少值,我们就先分配好
数组的容量,后面就不会每次添加元素的收进行扩容。

private void ensureExplicitCapacity(int minCapacity) 
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)  // 如果最小容量大于当前数组长度则扩容
        grow(minCapacity);

扩容的时候会去调用 grow() 方法来进行动态扩容,在 grow() 方法中采用了位运算,我们知道位运算的速度远远快于整除运算;
有一点需要注意的是,容量拓展,是创建一个新的数组,然后将旧数组上的元素 copy 到新数组,这是一个很大的消耗,所以在我们使用 ArrayList 时,最好能预计数据量。

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 这个方法就是是动态扩展的精髓
private void grow(int minCapacity) 
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 将 oldCapacity 右移一位,其效果相当于 oldCapacity/2,整句的结果就是设置新数组的容量为原来数组的 1.5 倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 再判断一下新数组的容量够不够,够了就直接使用这个长度创建新数组,
     // 不够就将数组长度设置为需要的长度
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
      // 将原来数组的值 copy 新数组中去, ArrayList 的引用指向新数组
      // 这会新创建数组,如果数据量很大,重复的创建的数组,那么还是会影响效率,
      // 因此鼓励在合适的时候通过构造方法指定默认的 capaticy 大小
    // 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;

看一下 JDK1.6 的动态扩容的实现原理:

public void ensureCapacity(int minCapacity) 
  modCount++;
  int oldCapacity = elementData.length;
  if (minCapacity > oldCapacity) 
    Object oldData[] = elementData;
    int newCapacity = (oldCapacity * 3)/2 + 1;
    if (newCapacity < minCapacity)
    newCapacity = minCapacity;
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
  

从代码上,我们可以看出区别:

  1. 在容量进行扩展的时候,其实例如整除运算将容量扩展为原来的 1.5 倍加 1,而 jdk1.8 是利用位运算,从效率上,jdk1.8 就要快于 jdk1.6。

  2. 在算出 newCapacity 时,其没有和 ArrayList 所定义的 MAX_ARRAY_SIZE 作比较,为什么没有进行比较呢,原因是 jdk1.8 没有定义这个MAX_ARRAY_SIZE 最大容量,也就是说,没有最大容量限制的,但是 jdk1.8 做了一个改进,进行了容量限制。

以上是关于ArrayList的主要内容,如果未能解决你的问题,请参考以下文章

泛型集合

Linkedlist

Vector

hashSet

TreeSet

笔记--集合