ArrayList浅析

#ArrayList浅析

  • ArrayList的作用、特点
  • ArrayList的数据结构
  • ArrayList的常用方法及实现
  • ArrayList里需要注意的地方

###ArrayList的作用、特点
ArrayList实现了List接口,常用操作有add,remove,get,size。其内部的数据采用数组进行存储。因此随机读写的速度很快,但删除、添加等操作相对会消耗比较多的时间,因为会有相关的一系列节点移动。

###ArrayList的数据结构
ArrayList内部以数组的形式存储数据。默认的是一个空数组,当添加数据后,会扩充为一个长度为10的数组。

默认的空数组
/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA;
}

private static final Object[] EMPTY_ELEMENTDATA = {};

###ArrayList的常用方法及其实现
1.add()

加入元素之前,先确保有足够的空间

public boolean add(E e) {<br/>
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

指定位置加入元素时,之后所有的元素都需要后移

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

addAll

public boolean addAll(Collection<? extends E> 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;
}

2.remove

删除之前,先检查是否越界,同时也有大量的元素移动操作

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

3.contain

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

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

4.clone

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

5.iterator

通过使用cursor和lastRet来进行标记

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;
    ...
}

###ArrayList里需要注意的地方

  • sublist并没有创建一个新的ArrayList,只是加了一个下标的起点

代码:

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

 private class SubList extends AbstractList<E> implements RandomAccess {
    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;
    ...
}

所以在原来的List或者Sublist里的操作,都会影响到另一个list

  • 因为ArrayList是基于数组的,所以当添加的元素超过原来的数组大小时,它需要先创建一个新的数组,并把原来的元素复制过去。这在一定程序上会影响程序的性能。类似的操作有删除,根据上面的实现代码可以知道,每删除一个元素就会对它后面的元素进行移动。可以把那些需要删除的参数用一个list保存下来,然后用removeAll来一次全部删除。

代码

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

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) {
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}
  • ArrayList里只定义了size和elementData两个元素,其他的都来自于父类AbstractList

protected transient int modCount = 0;

private int size;

以下是测试指定容器大小和让容器自动扩张比较的代码

public static void main(String[] args) {
    long startTime = System.currentTimeMillis();
    for(int i=0;i<100000;i++){
        ArrayList list = new ArrayList();
        for(int num=0;num<1000;num++){
            list.add("object");
        }
    }
    System.out.println("用时:"+ (System.currentTimeMillis()-startTime)+"毫秒");
    startTime = System.currentTimeMillis();
    for(int i=0;i<100000;i++){
        ArrayList list = new ArrayList(2000);
        for(int num=0;num<1000;num++){
            list.add("object");
        }
    }
    System.out.println("用时:"+ (System.currentTimeMillis()-startTime)+"毫秒");
}

运行结果:

用时:607毫秒
用时:415毫秒