Java集合迭代器、Fail-Fast、Fail-Safe机制

Iterator是Java集合中迭代器的顶级接口,在此接口中定义了遍历集合的方法。

注意:Iterable与Iterator不是同一个概念
Iterable是可迭代的意思,实现了该接口就代表这个集合是可以利用迭代器和forEach()方法进行遍历的。
因此Iterable是所有集合都实现的接口,在此接口中定义了获取迭代器的方法和forEach()方法
这是Iterable的源码

public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}

Iterator是迭代器的顶级接口,在Iterator接口中三个方法:

package java.util;
import java.util.function.Consumer;
public interface Iterator<E> {
// 判断集合中是否还有元素可以迭代
boolean hasNext();
// 返回下一个元素
E next();
// 删除最后一个遍历过的元素
default void remove() {
throw new UnsupportedOperationException("remove");
}
}

迭代器的本质就是指针,我们知道Java中有各种各样的集合,所以针对不同的集合,也会有实现的不同的迭代器
不同集合的结构是不同的,所以不同集合的迭代器的实现也是不同的
一般每个集合利用私有内部类的方式来实现这个Iterator接口

来看看ArrayList的迭代器

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// ...
// 私有内部类,私有的迭代器
private class Itr implements Iterator<E> {
int cursor; // 下一个元素的指针
int lastRet = -1; // 上一个元素的指针
int expectedModCount = modCount;
Itr() {}
// 判断下一个元素是否为空
public boolean hasNext() {
return cursor != size;
}
// 获取下一个元素
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();
}
}
// ...
}

来看一个普通的使用

ArrayList<String> list = new ArrayList<>();
list.add("zs");
list.add("liu");
list.add("lisi");
list.add("wangwu");
// 获取此集合的迭代器
Iterator<String> iterator = list.iterator();
// 开始迭代
while (iterator.hasNext()) {
String data = iterator.next();
System.out.println(data);
if ("zs".equals(data)) {
iterator.remove();
}
}
System.out.println(list);
// zs
// liu
// lisi
// wangwu
// [liu, lisi, wangwu]

注意:每调用一个next()方法,迭代器的指针就会向下移动一次,所以在一次迭代的过程中,只能调用一次next()方法

ListIterator和Iterator

在Iterator的继承、实现体系中,
ListIterator接口继承自Iterator,在Iterator的基础上,增加了针对List集合进行操作的迭代器的方法,例如针对List集合的增删改的操作
来看看这个接口

package java.util;
public interface ListIterator<E> extends Iterator<E> {
// 判断是否存在下一个元素
boolean hasNext();
// 返回下一个元素
E next();
// 判断是否存在前驱元素,一般在反向遍历时使用
boolean hasPrevious();
// 返回前驱元素,一般是在反向遍历使用
// 可以与next()搭配使用
E previous();
// 返回下一个元素的索引
int nextIndex();
// 返回前驱元素的索引
int previousIndex();
//删除刚才遍历过的元素
void remove();
// 修改刚才遍历过的元素
void set(E e);
// 在刚才遍历过的元素的后面插入一个元素
void add(E e);
}

在ArrayList也有针对ListIterator实现的私有迭代器,实现了针对ArrayList更具有操作性的方法

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}}
}

来演示一下

ArrayList<String> list = new ArrayList<>();
list.add("zs");
list.add("liu");
list.add("lisi");
list.add("wangwu");
// 获取此集合的迭代器
ListIterator<String> iterator = list.listIterator();
// 开始迭代
while (iterator.hasNext()) {
String data = iterator.next();
if("zs".equals(data)) {
// 修改元素
iterator.set("ZhangSan");
}
if ("wangwu".equals(data)) {
// 插入元素
iterator.add("kk");
}
if("liu".equals(data)){
// 删除元素
iterator.remove();
}
}
System.out.println(list);
// [ZhangSan, lisi, wangwu, kk]

Iterator和listIterator的对比

  • 遍历方式:Iterator只能单向遍历集合中的元素,即正序遍历;而ListIterator是可以双向遍历List中的集合的,即正序、逆序两种遍历方式
  • 操作元素:Iterator只能对元素进行遍历,不能对元素进行修改操作;而ListIterator可以对元素进行增删改操作
  • 集合类型:因为Iterator是所有集合的顶级接口,所有的集合都实现了Iterator接口,所以可以用来遍历所有的集合。但是ListIterator是针对List集合实现的一个迭代器,只能对List类型的集合有效
    如需要双向遍历集合,并且需要对List中的元素进行添加、修改、删除的操作,可以使用ListIterator,如果只是单纯的遍历集合,可以使用Iterator
    大部分的List的实现类都实现了ListIterator这个接口,所以可以根据需求选择不同的迭代器。
    当我们使用增强for循环来遍历集合时,底层就会调用Iterator来进行迭代,所以在使用for增强循环进行遍历时,不能对元素做出增删改操作

Fail-Fast

快速失败机制,当使用迭代器进行遍历时,如果同时有其他的线程修改了的集合中的元素,就会立马抛出异常。
Fail-Fast机制就是在遍历时不能对元素做出修改
ArrayList就是一个典型的Fail-Fast代表
来看ArrayList中的迭代器的源码实现:

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
// !!!
// 使用expectedModCount记录初始遍历时的元素数量
// modCount是ArrayList中用来操作元素次数的变量
// 每次对集合元素进行增删改,此变量就会 +1
// 使用expectedModCount 来记录创建迭代器时集合的操作次数
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
// 在获取下一个元素时,会先检查集合的modCount是否与expectedModCount相等
// 如果不相等,说明有其他线程并发对集合元素进行了操作
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() {
// 如果modeCount不等于expectedModCount
// 代表其他线程并发对集合元素做出了修改,
// 直接抛出异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

来看ArrayList中一个普通的add()方法

public boolean add(E e) {
// 操作次数 + 1
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

**总结:

  • 在ArrayList中,使用一个变量modCount来记录对集合元素的总操作次数
  • 每次对集合元素进行add()、remove()操作时,modCount就会 + 1
  • 在初始化迭代器时,用一个expectedModCount变量来记录此时的元素操作次数
  • 迭代器每次获取下一个元素时,会检查目前的集合操作次数与刚开始创建迭代器时的元素操作是否相等
  • 如果两者不相等,意味着有其他线程并发对集合做出了add()、remove()操作,会对接下来的遍历产生影响,所以直接抛出异常**

Fail-Safe

当进行迭代时,如果同时有其他线程修改了集合中的元素,就会牺牲一致性,来保证本次遍历的正常结束。
CopyOnWriteArrayList就是一个典型代表
CopyOnWriteArrayList实现Fail-Safe的机制就是实现了读写分离。
每次对集合进行增删操作时,都会将原数组Copy一份,对Copy出来的数组进行修改,最后合并到原数组中。
CopyOnWriteArrayList在遍历时,遍历的是原数组,即创建迭代器那一刻的数组
而如果其他线程集合元素做出增删操作,都会Copy一份成为一个新的数组,因此其他线程对集合的操作不会影响原线程中的迭代器,以此来保证Fail-Safe

来看CopyOnWriteArrayList的源码实现:
来看CopyOnWriteArrayList中一个普通的add()方法

public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取目前的数组
Object[] elements = getArray();
int len = elements.length;
// 对数组进行Copy
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 对Copy出来的新数组进行操作
newElements[len] = e;
// 合并到新的数组中
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

再来看CopyOnWriteArrayList中的迭代器

static final class COWIterator<E> implements ListIterator<E> {
// 此刻的集合的元素
// 此时集合元素的一个快照
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
public boolean hasPrevious() {
return cursor > 0;
}
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
@SuppressWarnings("unchecked")
public E previous() {
if (! hasPrevious())
throw new NoSuchElementException();
return (E) snapshot[--cursor];
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor-1;
}
/**
* Not supported. Always throws UnsupportedOperationException. * @throws UnsupportedOperationException always; {@code remove}
* is not supported by this iterator. */ public void remove() {
throw new UnsupportedOperationException();
}
/**
* Not supported. Always throws UnsupportedOperationException. * @throws UnsupportedOperationException always; {@code set}
* is not supported by this iterator. */ public void set(E e) {
throw new UnsupportedOperationException();
}
/**
* Not supported. Always throws UnsupportedOperationException. * @throws UnsupportedOperationException always; {@code add}
* is not supported by this iterator. */ public void add(E e) {
throw new UnsupportedOperationException();
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
Object[] elements = snapshot;
final int size = elements.length;
for (int i = cursor; i < size; i++) {
@SuppressWarnings("unchecked") E e = (E) elements[i];
action.accept(e);
}
cursor = size;
}
}
posted @   秋天Code  阅读(12)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示