List包含ArrayList, Vector 和 LinkedList3 个常用子类,如果要使用List接口进行操作,就必须依靠其子类,今天我们就来一起学习它最常用的一个子类ArrayList。
目录
基本介绍
常用方法
源码解析
关键变量
关键方法解析
从集合中删除元素
for循环删除元素有哪些坑?
正确的方法
总结
基本介绍
ArrayList的底层实际上也是由数组实现的,在开发中我们如果不确定数据量大小的时候,一般选用集合而非数组,其原因主要是集合是支持动态扩容的,而数组不支持。
常用方法
public ArrayList(int initialCapacity)
public ArrayList()
public ArrayList(Collection extends E> c)
public int size() 返回集合的大小
public boolean isEmpty() 判断集合是否为空
public boolean contains(Object o) 判断包含
public int indexOf(Object o) 获得某个元素的第一个位置
public int lastIndexOf(Object o) 获得某个元素的最后一个位置
public E get(int index) 根据索引获取元素
public E set(int index, E element) 替换某个位置的元素
public boolean add(E e) 添加元素
public void add(int index, E element) 在某个位置添加元素
public E remove(int index) 根据索引删除元素
public boolean remove(Object o) 根据元素对象删除元素
public boolean addAll(Collection extends E> c) 将另一个集合添加到当前集合中
源码解析
// ArrayList的默认容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组元素,在实例化新集合时如果传入初始容量为0,则会生成这个空数组对象
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认大小的空集合对象,在实例化新集合时如果使用默认无参构造,则会生成这个数组对象
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 集合元素缓冲区,这个缓冲区的大小等于集合的容量
// 添加第一个元素时,elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的任何空ArrayList都将扩展为DEFAULT_CAPACITY。
transient Object[] elementData;
// 集合的容量
private int size;
// 集合的修改次数
// 当前集合发生意料之外的修改时,程序会触发fail-fast机制抛出ConcurrentModificationException异常
// 当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
protected transient int modCount = 0;
// 集合的最大容量 2的31次方 - 1 - 8
// 一些虚拟机会预留数组头部的大小,所以还要在减8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
1. 初始化大小的构造函数 ArrayList(int initialCapacity)
// 初始化大小的构造函数
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 如果initialCapacity > 0,则将elementData实例化为初始容量为initialCapacity的Object数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 如果initialCapacity == 0,则初始化一个空数组EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 如果initialCapacity < 0,则抛出IllegalArgumentException异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
2. 根据元素获取该元素所在的第一个位置 indexOf(Object o)
public int indexOf(Object o) {
// 如果传入的元素为null
if (o == null) {
for (int i = 0; i < size; i++)
// 从0开始循环,判断哪个元素为null,找到则返回该元素的下标
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
// 同上,因为这边o不为null,所以使用equals
if (o.equals(elementData[i]))
return i;
}
// 找不到对应的元素,则返回-1
return -1;
}
3. 根据集合索引下标获取元素 get(int index)
public E get(int index) {
// 校验下标参数
rangeCheck(index);
// 如果参数合法,则从数组的对应下标中取出对应的元素
return elementData(index);
}
private void rangeCheck(int index) {
// 判断参数是否>=集合的容量,如果是则抛出索引越界的异常
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
4. 添加元素 add(E e)
public boolean add(E e) {
// 确保集合的容量,其实就是该扩容的时候要扩容
ensureCapacityInternal(size + 1);
// 在集合的最后一位添加元素
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
* 计算容量大小
*
* @Param elementData 当前数组
* @Param minCapacity 最小容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果当前elementData是默认大小的数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 返回默认大小(10)或minCapacity的最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// 操作数+1
modCount++;
// overflow-conscious code
// 如果当前数组所需的最小容量 - 当前容量 > 0 说明当前容量不够本次新增的数组所需的容量
if (minCapacity - elementData.length > 0)
// 扩容
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
// 当前数组容量
int oldCapacity = elementData.length;
// 预计扩容后的容量,为当前容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果newCapacity还是比所需容量小,则直接将所需容量赋值给newCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果newCapacity大于集合所能承载的最大容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
则考虑将Integer的最大值赋给newCapacity
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;
}
5. 在指定索引处插入一个元素 add(int index, E element)
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
// 上面的逻辑都是一样的,区别就在这个地方,会将需要插入的索引之后的数组往后复制一位,然后在当前位置插入一个新的元素
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
6. 根据指定索引移除一个元素 remove(int index)
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);
// 将当前位置置为null,后期会被GC
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
从集合中删除元素
List list = new ArrayList<>(Arrays.asList(1, 2, 3, 1, 1, 4, 5, 6));
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == 1) {
list.remove(i);
}
}
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
按照上述代码中的逻辑 ,我们应该把list中值等于1的元素全部删除,但让我看看结果
竟然还有一个1 ?
这种方式的问题在于:删除某个元素后,list的大小发生了变化,而你的索引也在变化,所以会导致你在遍历的时候漏掉某些元素。
比如当你删除第1个元素后,后面的元素都往前移动了一位;继续根据索引访问第2个元素时,实际访问的是原来的第3个元素。
因此,这种方式可以用在删除特定的一个元素时使用,但不适合循环删除多个元素时使用。
如果通过增强for循环来删除元素呢?
List list = new ArrayList<>(Arrays.asList(1, 2, 3, 1, 1, 4, 5, 6));
for (Integer i : list) {
if (list.get(i) == 1) {
list.remove(i);
}
}
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
竟然直接报错了? 通过debug我发现,原来增强for循环实际上就是创建一个Iterator对象,让我们一起来看看源码
// 首先在循环开始时,会先创建一个iterator对象
public Iterator iterator() {
return new Itr();
}
// 然后会调用hasNext方法,这里其实就是比较当前索引是否不等于容器容量
public boolean hasNext() {
// cursor标记了当前循环的索引
return cursor != size;
}
// 然后调用next方法
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];
}
final void checkForComodification() {
// 在ArrayList中实现了一个iterator对象
// 在其中有expectedModCount这个属性
// 在循环之初初始化Itr对象时,会将ArrayList的modCount赋值给expectedModCount
// int expectedModCount = modCount;
// 此处如果modCount != expectedModCount 就会抛出异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
那么在这为什么expectedModCount 和 modCount会不相等呢,我们可以回到上面看看ArrayList的remove方法就能知道,方法中只对modCount做了操作,但是并没有对expectedModCount做任何的操作。说到这是不是就清晰了,大家也明白为什么会这边会抛出ConcurrentModificationException异常了吧~
1. 使用ArrayList内置的iterator对象进行循环,删除操作
再来看看ArrayList中Itr对象的remove方法
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
// 实际上这个方法也是调用list中的remove方法
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
// 区别在这,itr中的remove方法会将modCount 赋值给expectedModCount
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
2. 使用ArrayList的removeIf方法删除元素
这个方法是将要删除的元素的下标先放到BitSet中,然后再进行删除操作,这个操作不会修改modCount的值。
总结
1. ArrayList是底层是由Object数组实现的
2. ArrayList支持动态扩容,每次扩容1.5倍
3. ArrayList支持按照索引下标随机访问,因此查询快
4. 如果初始化ArrayList时不指定容量,则会生成默认容量的空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,在第一次add时才会初始化容量
5. ArrayList删除元素时要使用Iterator或removeIf