目录(本文基于JDK1.8)
1. 前言
我们在日常编程中,常常需要集中存放多个数据。这时候,数组是我们一个很好的选择,但是数组在初始化的时候指定了长度之后,这个数组长度就是不可变的,所以使用数组的话我们需要实现知道需要保存对象的数量。如果我们需要保存一个可以动态增长的数据,这个时候我们就需要集合类,这里我们就来学习一下集合类中的List。
2. List基本概念
List继承自Collection接口,用来存储一组相同类型的元素,List集合代表一个元素有序、可重复的集合,它和Set的区别是:元素有放入顺序,元素可重复 。有顺序,即先放入的元素排在前面。
List主要有
ArrayList
、LinkedList
、Vector
几种实现方式,这三者都实现了List接口。关于它们我们在,Java集合类里面有做简单的介绍。
2.1 ArrayList部分源码详解
2.1.1 ArrayList类的定义
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- ArrayList继承AbstractList抽象父类
- 实现List接口规定了List的操作规范。
- 实现RandomAccess(标记接口)代表ArrayList是支持快速随机访问。
- 实现Cloneable(标记接口)代表ArrayList是可以拷贝。
- 实现Serializable(标记接口)代表ArrayList是可以序列化的。
2.1.2 ArrayList字段属性
/**
* 默认初始容量.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用于空实例的共享空数组实例(当用户指定该 ArrayList 容量为 0 时,返回该空数组).
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 默认大小空实例的共享空数组实例
* 当用户使用无参构造函数创建ArrayList实例的时候,返回的是该数组。
* 当用户第一次添加元素的时候,该数组将会扩容,变成容量为默认初始容量的一个数组。
* 它与 EMPTY_ELEMENTDATA的区别就是:该数组是不指定容量返回的,而后者是在用户指 定容量为 0 时返回。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* ArrayList的容量是这个数组缓冲区的长度
* 任何空数组(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
* 在新增第一个元素的时候将会把容量扩充到DEFAULT_CAPACITY即10
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* ArrayList的大小(它包含的元素的数量).
*
* @serial
*/
private int size;
2.1.3 ArrayList的构造方法
/**
*
* 用指定的初始容量构造一个空的ArrayList.
*
* @param initialCapacity ArrayList的初始容量
* @throws 如果指定的初始容量的值为负值则抛出 IllegalArgumentException
*
*/
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);
}
}
/**
* 无参构造函数
*
* 创建一个 空的 ArrayList,此时其内数组缓冲区 elementData = {}, 长度为 0,
* 当元素第一次被添加的时候,扩容到默认容量10。
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造一个包含指定元素的列表的列表集合,按照集合的顺序返回迭代器
*
* @param c 要放入 ArrayList 中的集合,其内元素将会全部添加到新建的 ArrayList 实例中
* @throws 如果指定的集合为null则抛出 NullPointerException
*/
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;
}
}
因为ArrayList的初始容量比较小,所以如果能预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。
2.1.4 其他方法
/**
* 将这个ArrayList实例的容量变成ArrayList当前的大小
*
* 因为容量常常会大于实际元素的数量。内存紧张时,可以调用该方法删除预留的位置,调整容量为元素实际数量。
* 如果确定不会再有元素添加进来时也可以调用该方法来节约空间。
*/
public void trimToSize() {
//modCount是在其父类AbstractList中定义的,指list结构变化的次数,其中
//结构变化指list的大小,即list包含元素的个数的变化
modCount++;
//先判断ArrayList里面的元素数量是不是小于容量
if (size < elementData.length) {
//如果size < 容量,判断size是不是等于0,如果等于0,容量为EMPTY_ELEMENTDATA
//如果size不为0,容量为Arrays.copyOf(elementData, size)。
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
/**
* 使用指定参数设置ArrayList容量
*
* @param minCapacity 所需最小容量
*/
public void ensureCapacity(int minCapacity) {
//如果数组为空,容量预取0,否则取默认的初始容量10。
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
//如果指定容量大于预设容量,则用指定容量设置数组容量
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
/**
* 得到最小扩容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果elementData为空数组的时候,则返回初始容量和minCapacity其中大的一个
//不为空则返回 minCapacity
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
/**
* 扩容
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
* 私有方法:明确 ArrayList 的容量
* -用于内部优化,保证空间资源不被浪费:在 add() 方法添加时起效
* @param minCapacity 指定的最小容量
*/
private void ensureExplicitCapacity(int minCapacity) {
将“list结构变化的次数”+1,该变量主要是用来实现fail-fast机制的
modCount++;
// 防止溢出代码:确保指定的最小容量 > 数组缓冲区当前的长度
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 数组最大容量,如果视分配更大可能会导致OOM
*
* 2^31 = 2,147,483,648,数组本身要占用 8 bytes存储大小,所以 2^31 -8
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 扩容,以确保 ArrayList 至少能存储 minCapacity 个元素
* 扩容计算:newCapacity = oldCapacity + (oldCapacity >> 1); 扩充当前容量的1.5倍.
*
* @param minCapacity 指定的最小容量
*/
private void grow(int minCapacity) {
// overflow-conscious code
// 获取到ArrayList中elementData数组的内存空间长度
int oldCapacity = elementData.length;
//右移一位等于自身一半,所以扩容到原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 若 newCapacity 依旧小于 minCapacity,则用minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 若 newCapacity 大于最大存储容量,则返回最大整数值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间
// 并将elementData的数据复制到新的内存空间
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 检查是否溢出,若没有溢出,返回最大整数值(java中的int为4字节,所以最大为0x7fffffff)或默认最大值.
*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/**
* 返回ArrayList实际存储的元素数量.
*/
public int size() {
return size;
}
/**
* ArrayList是否有元素
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 判断是否包含一个元素(根据 indexOf()判断)
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
/**
* 返回一个值在数组首次出现的位置,会根据是否为null使用不同方式判断。不存在就返回-1。时间复杂度为O(N)
*/
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使用不同方式判断。不存在就返回-1。时间复杂度为O(N)
*/
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
/**
* 返回这个ArrayList实例的一个浅副本。(元素本身不会被复制。)
*/
public Object clone() {
try {
// 调用父类(翻看源码可见是Object类)的clone方法得到一个ArrayList副本
ArrayList<?> v = (ArrayList<?>) super.clone();
// 调用Arrays类的copyOf,将ArrayList的elementData数组赋值给副本的elementData数组
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
// 返回副本v
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
/**
* 转换为Object数组,使用Arrays.copyOf()方法.
*
* @return 一个数组包含所有列表中的元素, 且顺序正确
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
/**
* 如果a的长度小于ArrayList的长度,直接调用Arrays类的copyOf,返回一个比a数组长度要大的新数组,里面元素就是ArrayList里面的元素;
*
*如果a的长度比ArrayList的长度大,那么就调用System.arraycopy,将ArrayList的elementData数组赋值到a数组,然后把a数组的size位置赋值为空
*
* @param a 如果它的长度大的话,列表元素将存储在这个数组中; 否则,将为此分配一个相同运行时类型的新数组。
* @return 一个包含ArrayList元素的数组
* @throws 将与数组类型不兼容的值赋值给数组元素时抛出的异常ArrayStoreException
* @throws 如果指定的数组为空则抛出NullPointerException
*/
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
// 若数组a的大小 < ArrayList的元素个数,则新建一个T[]数组,
// 数组大小是"ArrayList的元素个数",并将“ArrayList”全部拷贝到新数组中
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
// 若数组a的大小 >= ArrayList的元素个数,则将ArrayList的全部元素都拷贝到数组a中。
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
/**
* 返回指定位置的值
*
* @param index
* @return
*/
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
/**
* 返回ArrayList指定位置的元素.但是会先检查这个位置数否超出数组长度
*
* @param index 返回元素的索引
* @return 在这个ArrayList中指定位置的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
// 检查是否越界
rangeCheck(index);
// 返回ArrayList的elementData数组index位置的元素
return elementData(index);
}
/**
* 设置指定位置为一个新值,并返回之前的值,会检查这个位置是否超出数组长度.
*
* @param index 要替换的元素的索引
* @param element 现在要存储在指定位置的元素
* @return 之前在指定位置的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
// 检查是否越界
rangeCheck(index);
// 调用elementData(index)获取到当前位置的值
E oldValue = elementData(index);
// 将element赋值到ArrayList的elementData数组的第index位置
elementData[index] = element;
return oldValue;
}
/**
* 增加指定的元素到ArrayList的最后位置
*
* @param e 要添加的元素
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//确定ArrayList的容量大小
//若list是第一次添加元素则,初始容量为默认的10
//若不是第一次添加元素,则先扩容自身的1.5倍容量和初始容量比较
//如果比初始容量小则取初始容量,如果比初始容量大则返回最大整数值
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将e赋值给elementData的size+1的位置
elementData[size++] = e;
return true;
}
/**
* 在这个ArrayList中的指定位置插入指定的元素,.
*
* @param index 指定元素将被插入的索引
* @param element 要插入的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
// 判断index是否越界
rangeCheckForAdd(index);
//确定ArrayList的容量大小
ensureCapacityInternal(size + 1); // Increments modCount!!
//src:源数组; srcPos:源数组要复制的起始位置; dest:目的数组; destPos:目的数组放置的起始位置; length:复制的长度
// 将elementData从index位置开始,复制到elementData的index+1开始的连续空间
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 在elementData的index位置赋值element
elementData[index] = element;
// ArrayList的大小加一
size++;
}
/**
*在ArrayList的移除index位置的元素,会检查添加的位置,返回之前的值
*
* @param index 要删除的元素的索引
* @return 从ArrayList中删除的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
// 判断是否越界
rangeCheck(index);
modCount++;
//获取要删除元素的索引上的值
E oldValue = elementData(index);
//获取ArrayList内元素要移动的长度
int numMoved = size - index - 1;
if (numMoved > 0)
// 将elementData数组index+1位置开始拷贝到elementData从index开始的空间
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 使size-1 ,设置elementData的最后一个位置为空,让GC来清理内存空间
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
/**
* 在ArrayList的移除对象为O的元素,跟indexOf方法思想基本一致
*
* @param o 要从该列表中删除的元素(如果存在)
* @return true 如果这个列表包含指定的元素
*/
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++;
//获取ArrayList内元素要移动的长度
int numMoved = size - index - 1;
if (numMoved > 0)
// 将elementData数组index+1位置开始拷贝到elementData从index开始的空间
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 使size-1 ,设置elementData的最后一个位置为空,让GC来清理内存空间
elementData[--size] = null; // clear to let GC do its work
}
/**
* 清空数组,把每一个值设为null,方便垃圾回收(不同于reset,数组默认大小有改变的话不会重置)
*/
public void clear() {
//这个地方改变了modCount的值了
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
//便于垃圾回收器回收
elementData[i] = null;
//把size设置为0,以便我们不会浏览到null值的内存空间
size = 0;
}
/**
* 添加一个集合的元素到末端,若要添加的集合为空返回false
*
* @param c 包含要添加到此列表中的元素的集合
* @return list 元素个数有改变时,成功:失败
* @throws 如果指定的集合为null 则抛出NullPointerException
*/
public boolean addAll(Collection<? extends E> c) {
// 将c转换为数组a
Object[] a = c.toArray();
// 获取a占的内存空间长度赋值给numNew
int numNew = a.length;
//扩容原ArrayList的容量为size + numNew
ensureCapacityInternal(size + numNew); // Increments modCount
// 将a的第0位开始拷贝至elementData的size位开始,拷贝长度为numNew
System.arraycopy(a, 0, elementData, size, numNew);
//将size的长度增加numNew
size += numNew;
// 如果c为空,返回false,c不为空,返回true
return numNew != 0;
}
/**
* 从第index位开始,将c全部拷贝到ArrayList,若要添加的集合为空返回false
*
* @param index 在哪个索引处插入指定集合中的第一个元素
* @param c 包含要添加到此列表中的元素的集合
* @return list 元素个数有改变时,成功:失败
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws 如果指定的集合是空的则抛出 NullPointerException
*/
public boolean addAll(int index, Collection<? extends E> c) {
// 判断index大于size或者是小于0,如果是,则抛出IndexOutOfBoundsException异常
rangeCheckForAdd(index);
// 将c转换为数组a
Object[] a = c.toArray();
int numNew = a.length;
//扩容原ArrayList的容量为size + numNew
ensureCapacityInternal(size + numNew); // Increments modCount
//获取ArrayList内元素要移动的长度
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
/**
* 删除指定范围元素。参数为开始删的位置和结束位置
*
* @throws IndexOutOfBoundsException if {@code fromIndex} or
* {@code toIndex} is out of range
* ({@code fromIndex < 0 ||
* fromIndex >= size() ||
* toIndex > size() ||
* toIndex < fromIndex})
*/
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
//获取ArrayList内元素要移动的长度
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
//便于垃圾回收期回收
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
/**
* 检查index是否超出数组长度 用于添加元素时
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 判断index大于size或者是小于0,如果是,则抛出IndexOutOfBoundsException异常
*/
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 抛出的异常的详情
*/
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
/**
* RArrayList移除集合c中的所有元素
*/
public boolean removeAll(Collection<?> c) {
// 如果c为空,则抛出空指针异常
Objects.requireNonNull(c);
return batchRemove(c, false);
}
/**
* 仅保留指定集合c中的元素
*/
public boolean retainAll(Collection<?> c) {
// 如果c为空,则抛出空指针异常
Objects.requireNonNull(c);
return batchRemove(c, true);
}
/**
* 根据complement值,将ArrayList中包含c中元素的元素删除或者保留
*
* @param c
* @param complement true时从数组保留指定集合中元素的值,为false时从数组删除指定集合中元素的值。
* @return 数组中重复的元素都会被删除(而不是仅删除一次或几次),有任何删除操作都会返回true
*/
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
// 如果c中不包含elementData[r]这个元素
if (c.contains(elementData[r]) == complement)
// 则直接将r位置的元素赋值给w位置的元素,w自增
elementData[w++] = elementData[r];
} finally {
// 防止抛出异常导致上面r的右移过程没完成
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
// 将r未右移完成的位置的元素赋值给w右边位置的元素
System.arraycopy(elementData, r,
elementData, w,
size - r);
// 修改w值增加size-r
w += size - r;
}
// 如果有被覆盖掉的元素,则将w后面的元素都赋值为null
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
//新的大小为保留的元素的个数
size = w;
modified = true;
}
}
return modified;
}
/**
* 保存数组实例的状态到一个流(即序列化)。写入过程数组被更改会抛出异常
*
* @serialData The length of the array backing the <tt>ArrayList</tt>
* instance is emitted (int), followed by all of its elements
* (each an <tt>Object</tt>) in the proper order.
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
//执行默认的反序列化/序列化过程。将当前类的非静态和非瞬态字段写入此流
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
// 写入大小
s.writeInt(size);
// Write out all elements in the proper order.
// 按顺序写入所有元素
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
/**
* 从流中重构ArrayList实例(即反序列化)
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
// 执行默认的序列化/反序列化过程
s.defaultReadObject();
// Read in capacity
// 读入数组长度
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
// 像clone()方法 ,但根据大小而不是容量分配数组
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
//读入所有元素
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
/**
* 返回一个从index开始的ListIterator对象
*
* <p>The returned list iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
/**
* 返回一个ListIterator对象,ListItr为ArrayList的一个内部类,其实现了ListIterator<E> 接口口
*
* <p>The returned list iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @see #listIterator(int)
*/
public ListIterator<E> listIterator() {
return new ListItr(0);
}
/**
* 返回一个Iterator对象,Itr为ArrayList的一个内部类,其实现了Iterator<E>接口
*
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
return new Itr();
}
2.2 什么是fail-fast,什么是fail-safe
上面源码里面我们提到了modcount++ 是为了实现fail-fast,现在我们看一下什么是 fail-fast。
2.2.1 fail-fast (快速失败)
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出ConcurrentModificationException
fail-fast
会在以下两种情况下抛出ConcurrentModificationException
1、单线程环境 集合被创建后,在遍历它的过程中修改了结构。
2、多线程环境 当一个线程在遍历这个集合,而另一个线程对这个集合的结构进行了修改。
2.2.1.1 fail-fast机制是如何检测的?
迭代器在遍历过程中是直接访问内部数据的,因此内部的数据在遍历的过程中无法被修改。为了保证不被修改,迭代器内部维护了一个
modCount
变量 ,当集合结构改变(添加删除或者修改),modCount
会被修改,而迭代器每次的hasNext()
和next()
方法都会检查modCount
t变量是否为expectedmodCount
,如果是的话就返回遍历,如果检测到被修改时,抛出Concurrent Modification Exception
。
注意:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
2.2.2 fail—safe (安全失败)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历,因此不会抛出
ConcurrentModificationException
2.2.2.1 fail—safe的原理
由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发
ConcurrentModificationException
。
2.2.2.1 fail—safe的缺点
- 1、因为迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的,也就是说无法保证读取的数据是目前原始数据结构中的数据。
- 2、需要复制集合,产生大量的无效对象,开销大
注意:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
2.2.3 如何在遍历的同时删除ArrayList中的元素
Java中遍历集合的方法有好几种,例如普通for循环、增强for循环、迭代器以及lambda表达式的forecah,但是通过上面提到的fail-fast机制,我们知道遍历过程修改数据是会触发fail-fast的,那么如何如何在遍历的同时删除ArrayList中的元素呢?这里推荐使用迭代器遍历。
Iterator<People> peo = peoples.iterator();
while (peo.hasNext()) {
People people = peo.next();
if (people.getId() == 2)
people.remove();//这里要使用Iterator的remove方法移除当前对象,如果使用List的remove方法,则同样会出现ConcurrentModificationException
}
2.3 LinkedList部分源码详解
2.3.1 LinkedList类的定义
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
- LinkedList继承了AbstractSequentialList,它可以被当作堆栈、队列或双端队列进行操作。
- 实现List接口规定了List的操作规范。
- 实现 Deque 接口代表LinkedList可以当作双端队列使用。
- 实现Cloneable(标记接口)代表LinkedList是可以拷贝。
- 实现Serializable(标记接口)代表LinkedList是可以序列化的。
2.3.2 LinkedList字段属性
/**
* size是双向链表中节点实例的个数,transient关键字表示size不会被序列化
*/
transient int size = 0;
/**
* 链表头节点。
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* 链表尾节点。
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
/**
* Node内部类
*/
private static class Node<E> {
//当前节点的值
E item;
// 后一个节点
Node<E> next;
// 前一个节点
Node<E> prev;
// 构造函数元素顺序分别为前一个节点,自己,后一个节点。
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
2.3.3 LinkedList的构造方法
/**
* 无参构造方法.
*/
public LinkedList() {
}
/**
* 构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回的顺序排列的
*
* @param c 包含用于去构造 LinkedList 的元素的 collection
* @throws 如果指定的集合为 null 则抛出 NullPointerException
*/
public LinkedList(Collection<? extends E> c) {
// 调用无参构造函数
this();
// 添加集合中所有元素
addAll(c);
}
2.3.4 其他方法
/**
* 头插入,即将节点值为e的节点设置为链表首节点,内部使用
*/
private void linkFirst(E e) {
//获取当前首节点引用
final Node<E> f = first;
//构建一个prev值为null,节点值为e,next值为f的新节点newNode
final Node<E> newNode = new Node<>(null, e, f);
//将newNode作为首节点
first = newNode;
//如果原首节点为null,即原链表为null,则链表尾节点也设置为newNode
//否则,原首节点的prev设置为newNode
if (f == null)
last = newNode;
else
f.prev = newNode;
//长度加+1
size++;
//修改次数+1
modCount++;
}
/**
* 尾插入,即将节点值为e的节点设置为链表的尾节点.
*/
void linkLast(E e) {
// 获取当前尾结点引用
final Node<E> l = last;
//构建一个prev值为l,节点值为e,next值为null的新节点newNode
final Node<E> newNode = new Node<>(l, e, null);
//将newNode作为尾节点
last = newNode;
//如果原尾节点为null,即原链表为null,则链表首节点也设置为newNode
//否则,原尾节点的next设置为newNode
if (l == null)
first = newNode;
else
l.next = newNode;
//长度加+1
size++;
//修改次数+1
modCount++;
}
/**
* 在非空节点succ之前插入元素e.
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//获取succ前一个节点
final Node<E> pred = succ.prev;
//构建一个prev值为pred,节点值为e,next值为succ的新节点newNode
final Node<E> newNode = new Node<>(pred, e, succ);
//把newNode作为prev的前一个节点
succ.prev = newNode;
//如果succ.prev为null,即如果succ为首节点,则将newNode设置为首节点
//否则原来succ前一个节点的下一个节点为newNode
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
/**
* 删除首节点并返回该元素,内部使用
*/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
//获取首节点的值
final E element = f.item;
//获取首节点的下一个节点
final Node<E> next = f.next;
//删除首节点
f.item = null;
f.next = null; // help GC
//原来首节点的下一个节点设置为首节点
first = next;
// 如果原来首结点的后继结点为空,则尾结点设为null
// 否则,原来首结点的后继结点的前驱结点设为null
if (next == null)
last = null;
else
next.prev = null;
//长度 - 1
size--;
//修改次数 +1
modCount++;
return element;
}
/**
* 删除尾节点并返回该元素,内部使用
*/
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
//获取为尾节点的值
final E element = l.item;
//获取尾节点上一个节点
final Node<E> prev = l.prev;
//删除尾节点
l.item = null;
l.prev = null; // help GC
//原来尾节点上一个节点设置为尾节点
last = prev;
// 如果原来尾结点的前驱结点为空,则首结点设为null
// 否则,原来尾结点的前驱结点的后继结点设为null
if (prev == null)
first = null;
else
prev.next = null;
//长度 - 1
size--;
//修改次数 +1
modCount++;
return element;
}
/**
* 删除指定非空节点并返回该元素
*/
E unlink(Node<E> x) {
// assert x != null;
//获取指定节点的值
final E element = x.item;
//获取指定节点下一个节点
final Node<E> next = x.next;
//获取指定节点前一个节点
final Node<E> prev = x.prev;
//如果指定节点前一个节点为null,则首节点为指定节点的下一个节点
//否则指点节点的前节点的下一个节点为指定节点的下一个节点
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
//如果指定节点下一个节点为null,则尾节点为指点节点的前一个节点。
//否则指点节点的前一个节点为,指点节点的前一个节点
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
//删除指定节点的值
x.item = null;
//长度减1
size--;
//修改次数加1
modCount++;
return element;
}
/**
* 获list 取首节点存储的值.
*
* @return 这个列表中的第一个元素
* @throws 如果这个list是空的则抛出 NoSuchElementException
*/
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
/**
* 获取list 尾节点存储的值
*
* @return 这个列表中的最后一个元素
* @throws 如果这个list是空的则抛出 NoSuchElementException
*/
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
/**
* 删除首节点并返回首节点存储的值
*
* @return 首结点存储的值
* @throws 如果这个list是空的则抛出 NoSuchElementException
*/
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
/**
* 删除尾节点并返回尾节点存储的值
*
* @return 尾节点存储的值
* @throws 如果这个list是空的则抛出 NoSuchElementException
*/
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
/**
* 在list的开头插入指定的元素.
*
* @param e 需要添加的元素
*/
public void addFirst(E e) {
linkFirst(e);
}
/**
* 在列表的尾部添加指定元素,该方法等价于add()
*
* @param e 需要添加的元素
*/
public void addLast(E e) {
linkLast(e);
}
/**
* 判断是否包含指定元素
*
* @param o 判断链表是否包含的元素
* @return {@code true} 如果链表包含指定的元素
*/
public boolean contains(Object o) {
return indexOf(o) != -1;
}
/**
* 返回list中的元素数量.
*
* @return 列表中元素的数量
*/
public int size() {
return size;
}
/**
* 插入指定元素,返回操作结果,默认添加到末尾作为最后一个元素
*
* @param e 要添加到此链表中的元素
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
// 通过尾插法来插入指定元素
linkLast(e);
return true;
}
/**
* 删除指定元素,默认从first节点开始,删除第一次出现的那个元素
*
* @param o 要从该列表中删除的元素(如果存在)
* @return {@code true} 如果这个列表包含指定的元素
*/
public boolean remove(Object o) {
//会根据是否为null分开处理。若值不是null,会用到对象的equals()方法
if (o == null) {
// 遍历链表,查找到指定元素后删除该结点,返回true
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
/**
* 将集合插入到链表尾部,即开始索引位置为size
*
* @param c 包含要添加到此链表中的元素的集合
* @return {@code true} 如果该链表因添加而改变
* @throws 如果指定的集合是空的则抛出NullPointerException
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
/**
* 将集合从指定位置开始插入
*
* @param index 在哪个索引处前插入指定集合中的第一个元素
*
* @param c 包含要添加到此链表中的元素的集合
* @return {@code true} 如果该链表因添加而改变
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws 如果指定的集合是空的则抛出 NullPointerException
*/
public boolean addAll(int index, Collection<? extends E> c) {
//检查该索引是否超过了list里面元素的数量,如果超出了则抛出 IndexOutOfBoundsException
checkPositionIndex(index);
// 将集合转换为Object数组
Object[] a = c.toArray();
// 获取数组长度
int numNew = a.length;
//若没有元素要添加,直接返回false
if (numNew == 0)
return false;
//succ指向当前需要插入节点的位置,pred指向其前一个节点
Node<E> pred, succ;
//如果是在末尾开始添加,当前节点后一个节点初始化为null,前一个节点为尾节点
//否则当前位置的节点为指定位置的节点,前一个节点为要添加的节点的前一个节点
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
//遍历数组并添加到列表中
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//将元素值e,前继节点pred“封装”为一个新节点newNode
Node<E> newNode = new Node<>(pred, e, null);
//如果原链表为null,则新插入的节点作为链表首节点
//否则前节点会向后指向新加的节点
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
//如果是从最后开始添加的,则最后添加的节点成为尾节点
if (succ == null) {
last = pred;
} else {
//如果不是从最后开始添加的,则最后添加的节点向后指向之前得到的后续第一个节点
pred.next = succ;
//当前,后续的第一个节点也应改为向前指向最后一个添加的节点
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
/**
* 删除所有元素
*/
public void clear() {
//遍历链表,删除所有结点,方便gc回收垃圾
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
//删除首尾节点
first = last = null;
//元素数量置为0
size = 0;
modCount++;
}
// Positional Access Operations
/**
* 获取指定位置的元素
*
* @param index 要返回的元素的索引
* @return 该链表中指定位置的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
// 判断指定位置是否合法
checkElementIndex(index);
//返回指定位置的值
return node(index).item;
}
/**
* 修改指定位置的元素,返回之前元素
*
* @param index 要替换的元素的索引
* @param element 要存储在指定位置的元素
* @return 先前在指定位置的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
//判断位置是否合法
checkElementIndex(index);
//获取指定位置的节点
Node<E> x = node(index);
//获取指定位置的值
E oldVal = x.item;
//将指定位置的值更新为新值
x.item = element;
return oldVal;
}
/**
* 在指定位置前插入指定元素
*
* @param index 指定元素将被插入的索引
* @param element 要插入的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//判断位置是否合法
checkPositionIndex(index);
//如果指定位置在尾部,则通过尾部插入法插入元素
//否则通过中间插入法插入元素
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* 删除指定位置的元素,返回之前元素
*
* @param index 要删除的元素的索引
* @return 之前在该位置的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//判断位置是否合法
checkElementIndex(index);
//删除指定非空节点并返回该元素
return unlink(node(index));
}
/**
* 判断指定位置是否合法.
*/
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
/**
* 判断迭代器遍历时或插入元素时指定位置是否合法
*/
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
/**
* Constructs an IndexOutOfBoundsException detail message.
* Of the many possible refactorings of the error handling code,
* this "outlining" performs best with both server and client VMs.
*/
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}