总体介绍
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值,除非对应的位置被其他元素覆盖,否则原来的对象就一直不会被回收。