Java源码解读扫盲【集合--ArrayList】

一、类描述

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

ArrayList是一个数组队列,相当于一个动态数组,和数组相比,它可以扩容。它继承了AbstractList,实现了List,RandomAccess,Cloneable,Serializable接口。

ArrayList继承了AbstractList,实现了List,它是一个数组队列,拥有了基本的添加,删除,修改,遍历查询等功能。

ArrayList实现RandomAccess接口,提供随机访问功能,RandmoAccess是java中用来被List实现,为List提供快速访问功能的,ArrayList可以根据序号快速访问元素对象。

ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。

ArrayList 实现了Serializable接口,说明ArrayList能被序列化进行传输。

特别的ArrayList是线程不安全的。

二、属性和构造方法

//默认的数组存储容量
private static final int DEFAULT_CAPACITY = 10;

//当指定数组的容量为0的时候使用这个变量赋值
private static final Object[] EMPTY_ELEMENTDATA = {};

//默认的实例化的时候使用此变量赋值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//真正存放数据的对象数组,并不被序列化  transient 关键字修饰的属性不被序列化
transient Object[] elementData; // non-private to simplify nested class access

//数组中的真实元素个数,它小于或等于elementData.length
private int size;

//数组中最大存放元素的个数
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
 * 构造函数一,如果指定容量就分配指定容量的大小
 * 没有指定就使用EMPTY_ELEMENTDATA赋值
 */
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);
    }
}

/**
 * 构造函数二,使用默认的DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * 构造一个传入的集合,作为数组的数据
 */
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;
    }
}

三、常用方法

(1)添加

添加有两个方法,第一个add(E e)方法的调用链涉及5个方法,分别如下:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
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);
}
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);
}
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

这里一步步分析,在调用了add(E e)的方法第一步,我们看到了它调用了ensureCapacityInternal(size + 1)方法,在这个方法里面首先判断了数组是不是一个长度为0的空数组,如果是的话就给它容量赋值为默认的容量大小也就是10,然后调用了ensureExplicitCapacity方法,这个方法里面记录了modCount+1之后,并判断了当前的容量是否小于数组当前的长度,如果大于当前数组的长度就开始进行扩容操作调用方法 grow(minCapacity),扩容的长度是增加了原来数组数组的一半大小,然后并判断了是否达到了数组扩容的上限并赋值,接着把旧数组的数据拷贝到扩容后的新数组里面再次赋值给旧数组,最后把新添加的元素赋值给了扩容后的size+1的位置里面。

接着看第2个add方法:

public void add(int index, E element) {
    rangeCheckForAdd(index);//判断数组下标是否越界

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //这里面用到了 System.arraycopy方法,参数含义如下:
    //(原数组,原数组的开始位置,目标数组,目标数组的的开始位置,拷贝的个数)
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

这个方法的意思是给指定位置添加一个元素,ArrayList首先检查是否索引越界,如果没有越界,就检查是否需要扩容,然后将index位置之后的所有数据,整体拷贝到index+1开始的位置,然后就可以把新加入的数据放到index这个位置,而index前面的数据不需要移动,在这里我们可以看到给指定位置插入数据ArrayList是一项大动作比较耗性能。

(2)删除

根据下标删除指定未知的元素

public E remove(int index) {
    //检测下标是否越界
    rangeCheck(index);
    //记录修改次数
    modCount++;
    //获取移除位置上的值
    E oldValue = elementData(index);
    //获取要移动元素的个数
    int numMoved = size - index - 1;
    //拷贝移动的所有数据到index位置上
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //把size-1的位置的元素赋值null,方便gc垃圾回收
    elementData[--size] = null; // clear to let GC do its work
    //最终返回被删除的数据
    return oldValue;
}

根据元素删除

public boolean remove(Object o) {
    if (o == null) {//移除null值
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {//找到集合里面第一个等于null的元素
                fastRemove(index);//移除
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {//非null情况下,遍历每一个元素通过equals比较
                fastRemove(index);//移除
                return true;
            }
    }
    return false;
}

/*
 * 该方法与通过下标移除的原理一样,整体左移
 */
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
}

添加移除元素有可能会做大量的数据移动,所以是一个比较耗性能的。

(3)查询(O(1))

public E get(int index) {
    //检测下标是否越界
    rangeCheck(index);
    return elementData(index);
}

(4)清空

public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

(5)是否包含

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

这个没啥好说的

总结

源码不复杂,底层就是一个数组,不过这个数组可以扩容。允许添加所有元素包括null,还有ArrayList的所有方法都是线程不安全的,可以用 Collections.synchronizedList(list) 转线程安全。

猜你喜欢

转载自my.oschina.net/u/3737136/blog/1634777