前言
很多人学习集合的时候第一个学习的就是ArrayList,在平时的工作中ArrayList也是很常用的。
List 特点:线性集合,底层由数组实现。有序结果、顺序遍历、索引、允许有重复值
不多说,国际惯例先上结构图
框架结构
ArrayList继承了AbstractList类,实现了Serializable, Cloneable, RandomAccess接口
实现RandomAccess接口:可以通过下标序号快速访问
实现了Cloneable,能被克隆
实现了Serializable,支持序列化
成员变量
// 初始容量
private static final int DEFAULT_CAPACITY = 10;
// 空集合
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认空集合
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 真正存放数据的地方
transient Object[] elementData; // non-private to simplify nested class access
// 数组长度
private int size;
构造函数
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);
}
}
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;
}
}
第一个构造函数创建一个自定义长度数组。
第二个构造函数先创建一个空的数组,在操作之前会先生成一个长度为10的数组。
第三个构造函数使用已有集合来创建ArraList,将集合里的值复制到ArrayList中。首先把集合转换成数组,然后判断转换的数组类型是否为Obejct[]类型,如果是则将数组的值拷贝到list中,否则或者容量为0,则赋予一个空数组。
常用方法
add:
// 添加某一元素(从尾部添加)
public boolean add(E e) {
// 检查数组长度
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, 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;
// 获取新长度(移位:向左移位 旧长度*2,向右移位 旧长度/2, 这里新长度为旧长度的1.5倍)
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);
}
// 指定位置插入元素
public void add(int index, E element) {
// 检查下标是否越界
rangeCheckForAdd(index);
// 判断扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 重新生成数组copy
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
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;
}
// 元素删除
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
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
}
此方法在使用的时候需要注意:
使用集合的都知道,在for循环遍历集合时不可以对集合进行删除操作,因为删除会导致集合大小改变,从而导致数组遍历时数组下标越界,严重时会抛ConcurrentModificationException异常
而使用迭代器则不会报错
源码:
public Iterator<E> iterator() {
return new Itr();
}
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;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
常见面试题
1、简述ArrayList原理
线性表,底层由数组实现,是一个可以自动扩容的动态数组,非线程安全,默认初始长度为10,再说明扩容过程
2、优点和缺点
优点:查找效率快
缺点:操作效率低(增加,删除元素)
3、删除操作会碰到的问题
循环时删除可能会引起数组越界报错
4、对比LinkedList, Vector