ArrayList源码学习

84 阅读4分钟

总体介绍

java.util.ArrayList实现了java.util.List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现

transient Object[] elementData;

size(), isEmpty(), get(), set()方法均能在常数时间内完成,add()方法的时间开销跟插入位置有关,addAll()方法的时间开销跟添加元素的个数成正比。其余方法大都是线性时间。

为追求效率,ArrayList没有实现同步(synchronized),如果需要多个线程并发访问,用户可以手动同步,也可使用Vector替代

构造方法

第一种

-- 默认元素个数为0的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

第二种

/**
  * 构造一个具有指定初始容量的空集合列表
  *
  * @param  initialCapacity  集合初始容量
  * @throws IllegalArgumentException 集合初始容量为负数抛出不合法参数异常
*/
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            // 初始化指定容量大小的空对象数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            // 初始化对象数组大小为0的空对象数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            // 抛出参数不合法异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
}

第三种

/**
 * 初始化ArrayList包含指定集合元素的
*/
public ArrayList(Collection<? extends E> c) {
        //集合转换成数组
		Object[] a = c.toArray();
        if ((size = a.length) != 0) { 
            if (c.getClass() == ArrayList.class) {
				// ArrayList类型直接把传入的集合赋值给对象数组
                elementData = a;
            } else {
				// 对象数组扩容
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // 返回空的对象数组
            elementData = EMPTY_ELEMENTDATA;
        }
}

set方法

set() 方法在指定位置对元素进行更新。

public E set(int index, E element) {
        Objects.checkIndex(index, size); // 下标越界检查
        E oldValue = elementData(index); // 之前在 index 位置的元素
        elementData[index] = element; //赋值到指定位置,复制的仅仅是引用
        return oldValue; // 返回原来索引位置的元素
}

add方法

public boolean add(E e) {
        modCount++; // 记录修改的次数
        add(e, elementData, size);
        return true;
}
private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
        // 对象数组空间不够,进行扩容
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
}
private Object[] grow() {
    return grow(size + 1);
}
	 
private Object[] grow(int minCapacity) {
        int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
           int newCapacity = ArraysSupport.newLength(oldCapacity,
               minCapacity - oldCapacity, /* minimum growth */
               // 默认原来的1.5倍 oldCapacity >> 1 
               oldCapacity >> 1           /* preferred growth */);
               // Arrays.copyOf数组扩容
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

addAll

一次添加多个元素,addAll(Collection<? extends E> c):在集合的末尾添加另一个集合的元素

public boolean addAll(Collection<? extends E> c) {
     // 集合转换成数组
	 Object[] a = c.toArray();
     modCount++;
        int numNew = a.length;
        if (numNew == 0)
            return false;
        Object[] elementData;
        final int s;
        if (numNew > (elementData = this.elementData).length - (s = size))
			// 对象数组扩容
            elementData = grow(s + numNew);
		// 数组复制,
        System.arraycopy(a, 0, elementData, s, numNew);
        size = s + numNew;
        return true;
}

addAll(int index, Collection<? extends E> c):从指定的索引位置,添加另一个集合的元素 addAll()方法能够一次添加多个元素,根据位置不同也有两个版本,一个是在末尾添加的addAll(Collection<? extends E> c)方法,一个是从指定位置开始插入的addAll(int index, Collection<? extends E> c)方法。跟add()方法类似,在插入之前也需要进行空间检查,如果需要则自动扩容;如果从指定位置插入,也会存在移动元素的情况。 addAll()的时间复杂度不仅跟插入元素的多少有关,也跟插入的位置相关。

remove()

remove()方法也有两个版本,一个是remove(int index)删除指定位置的元素,另一个是remove(Object o)删除第一个满足o.equals(elementData[index])的元素。删除操作是add()操作的逆过程,需要将删除点之后的元素向前移动一个位置。需要注意的是为了让GC起作用,必须显式的为最后一个位置赋null值。

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; //清除该位置的引用,让GC起作用
    return oldValue;
}

关于Java GC这里需要特别说明一下,有了垃圾收集器并不意味着一定不会有内存泄漏。对象能否被GC的依据是是否还有引用指向它,上面代码中如果不手动赋null值,除非对应的位置被其他元素覆盖,否则原来的对象就一直不会被回收。