💻
QMMMS的笔记
博客
  • QMMMS的笔记
  • agent
    • MCP的背景、原理和开发
    • Agent 历史与背景
    • Agentic Workflows
    • 环境检查与基础工具
    • Tool Call
    • 工具与运行时的值
    • temp
    • 处理 Tool Call error
    • trick
  • algorithm
    • 线性结构
    • 二叉树
    • 图
    • 查找
    • 排序
    • 动态规划
    • 优化方法
    • 数学
    • 迁移至Java
  • computer_composition
    • 系统总线
    • 存储器
    • 输入输出系统
    • 计算机的运算方法
    • 指令系统
    • 补充
  • computer_network
    • 引入
    • 应用层
    • 传输层
    • 网络层(数据平面)
    • 网络层(控制平面)
    • 链路层
    • 常见问答
    • 实验
  • database
    • SQL实战
    • 关系代数
    • 数据库设计
    • 规范化
    • 数据库基本概念
    • 查询原理
    • 数据库恢复技术
    • 并发控制
  • dev_tools
    • Git
    • Nginx
    • Spring
    • LangChain
    • PyTorch Cheat Sheet
    • MyBatis
    • MySQL Cheat Sheet
    • MySQL 补充
    • Redis
    • Docker
    • RocketMQ
    • Chrome
  • linux
    • Linux基础命令与使用
    • 文件与权限
    • 文件与目录操作
    • 权限属性高级
    • 命令与文件的查找
    • 文件压缩和打包
    • vim编辑器
    • shell变量
    • 命令补充
    • 数据流重定向
    • 管道命令
    • shell脚本
    • 用户管理
    • 用户间交流
    • 计划任务
    • 进程管理
    • 软件管理
    • 认识系统服务
    • 运维常用命令
    • 常用命令
  • llm
    • 大规模语言模型概述
    • 分布式训练概述
    • 有监督微调概述
    • 强化学习与LLM
    • LLM评估概述
    • 大模型应用
    • 理解大模型
    • 量化
    • 预训练
    • 上下文学习
  • machine_learning
    • 引入
    • 大致正确学习
    • 一致收敛
    • 偏差还是过拟合?
    • 可学习的充要条件
    • 非均匀可学习性
    • 计算复杂性
  • mathematics
    • 概率与统计基础
    • 线性代数基础
  • operating_system
    • 操作系统基本概念
    • 进程和线程
    • 同步,互斥与死锁
    • 内存管理
    • 文件系统
    • I/O系统
    • 保护与安全
    • 《现代操作系统》
  • statistical_learning
    • 统计学习引入
    • 线性回归
    • 分类
    • 重抽样方法
    • 线性模型选择与正则化
    • 非线性模型
    • 基于树的方法
    • 支持向量机
    • 无指导学习
    • 马尔科夫链和蒙托卡罗方法简明理解
    • R语言速查
  • deep_learning
    • basic_concepts
      • 逻辑回归与损失函数
      • 神经网络
      • 正则化、预处理、权重初始化
      • 优化算法
      • 机器学习策略
      • 复习:从计算机视觉的角度
      • 卷积神经网络
      • 深度卷积网络示例
      • 计算机视觉任务
      • 循环神经网络
      • 自然语言处理任务
      • 注意力
      • Transformers 家族
      • 显卡扫盲
      • 强化学习概述
    • semi-supervise
      • 半监督学习简介
      • Consistency Regularization
      • Proxy-label Methods
      • Holistic Methods
      • Generative Models
      • Graph-Based SSL
      • Self-Supervision for SSL
      • Other SSL methods
  • programming
    • cpp
      • STL
      • C++基础
      • 内存管理
      • 面向对象
    • java
      • 环境和介绍
      • 注释
      • String
      • 面向对象思想
      • Object
      • 包
      • 访问权限修饰符
      • 初始化块
      • 接口
      • 内部类
      • 注解
      • 枚举
      • 集合框架
      • List
      • Map
      • 泛型
      • 迭代
      • IO与流
      • 序列化
      • 异常
      • Lambda
      • Stream流
      • Socket
      • 缓冲
      • 命名规范
      • 拆箱装箱
      • 值传递
      • 深拷贝
      • 反射
      • JVM
      • 并发编程基础
    • python
      • 并发编程
      • 环境管理
  • software_engineering
    • basic_concepts
      • 系统分析与设计概述
      • 规划
      • 需求分析与原型设计
      • 项目管理
      • 建模
      • 数据库设计
      • 架构
      • 配置管理
      • 测试管理
      • 安全
      • 编码原则
      • 微服务
      • 补充内容
    • software_testing
      • CMMI基础
      • PPQA与SQA
      • 软件测试基础
      • 黑盒测试
      • 白盒测试
      • 集成测试
      • 系统测试
      • 测开面试补充
由 GitBook 提供支持
在本页
  • 遍历方式
  • Iterator
  • Iterable
  • 实现方式
  • for-each陷阱
  • 正确地删除元素
在GitHub上编辑
  1. programming
  2. java

迭代

遍历方式

第一种:for 循环。

for (int i = 0; i < list.size(); i++) {
    System.out.print(list.get(i) + ",");
}

第二种:迭代器。

Iterator it = list.iterator();
while (it.hasNext()) {
    System.out.print(it.next() + ",");
}

第三种:for-each。

for (String str : list) {
    System.out.print(str + ",");
}

第一种我们略过,第二种用的是 Iterator,第三种看起来是 for-each,其实背后也是 Iterator,看一下反编译后的代码(如下所示)就明白了。

Iterator var3 = list.iterator();

while(var3.hasNext()) {
    String str = (String)var3.next();
    System.out.print(str + ",");
}

for-each 只不过是个语法糖,让我们开发者在遍历 List 的时候可以写更少的代码,更简洁明了。

Iterator

Iterator 是个接口,用来改进 Enumeration 接口:

public interface Iterator<E> {
    boolean hasNext();  // 判断集合中是否存在下一个对象
    E next();  // 返回集合中的下一个对象,并将访问指针移动一位
    
    // 删除集合中调用next()方法返回的对象
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}

Iterable

JDK 1.8 时,Iterable 接口中新增了 forEach 方法。该方法接受一个 Consumer 对象作为参数,用于对集合中的每个元素执行指定的操作。该方法的实现方式是使用 for-each 循环遍历集合中的元素,对于每个元素,调用 Consumer 对象的 accept 方法执行指定的操作。

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);  //非空检查
    for (T t : this) {
        action.accept(t);
    }
}

由于 Iterable 接口是 Java 集合框架中所有集合类型的基本接口,因此该方法可以被所有实现了 Iterable 接口的集合类型使用。它对 Iterable 的每个元素执行给定操作,具体指定的操作需要自己写Consumer接口通过accept方法回调出来。

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list.forEach(integer -> System.out.println(integer));

写得更浅显易懂点,就是:

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list.forEach(new Consumer<Integer>() {
    @Override
    public void accept(Integer integer) {
        System.out.println(integer);
    }
});

如果我们仔细观察ArrayList 或者 LinkedList 的“户口本”就会发现,并没有直接找到 Iterator 的影子。反而找到了 Iterable!

事实上,List 的关系图谱中并没有直接使用 Iterator,而是使用 Iterable 做了过渡。

public interface Iterable<T> {
    Iterator<T> iterator();
}

正因此,我们可以使用迭代器遍历List:Iterator it = list.iterator();

为什么要套一层?为什么不直接将 Iterator 中的核心方法 hasNext、next 放到 Iterable 接口中呢?

从英文单词的后缀语法上来看,(Iterable)able 表示这个能不能迭代,而 (Iterator)tor 表示如何迭代。能不能和怎么做显然不能混在一起,否则就乱的一笔。还是各司其职的好。

原则上,只要一个 List 实现了 Iterable 接口,那么它就可以使用 for-each 这种方式来遍历,那具体该怎么遍历,还是要看它自己是怎么实现 Iterator 接口的。

实现方式

对于ArrayList ,它重写了 Iterable 接口的 iterator 方法。返回的对象 Itr 是个内部类,实现了 Iterator 接口,并且按照自己的方式重写了 hasNext、next、remove 等方法。

public Iterator<E> iterator() {
    return new Itr();
}

private class Itr implements Iterator<E> {
    // ...
}

Map 就没办法直接使用 for-each,因为 Map 没有实现 Iterable 接口,只有通过 map.entrySet()、map.keySet()、map.values() 这种返回一个 Collection 的方式才能 使用 for-each。

LinkedList 并没有直接重写 Iterable 接口的 iterator 方法,而是由它的父类 AbstractSequentialList 来完成。

public Iterator<E> iterator() {
    return listIterator();
}

LinkedList 重写了 listIterator 方法:

public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}

这里又套了一层迭代器 ListIterator,它继承了 Iterator 接口,在遍历List 时可以从任意下标开始遍历,而且支持双向遍历。ListIterator 就只支持 List

public interface ListIterator<E> extends Iterator<E> {
    boolean hasNext();
    E next();
    boolean hasPrevious();
    E previous();
}

for-each陷阱

for-each 删除元素报错代码:

List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");

for (String str : list) {
	if ("沉默王二".equals(str)) {
		list.remove(str);
	}
}

System.out.println(list);

for-each 本质上是个语法糖,底层是通过迭代器 Iterator 配合 while 循环实现的,来看一下反编译后的字节码。

List<String> list = new ArrayList();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
Iterator var2 = list.iterator();

while(var2.hasNext()) {
    String str = (String)var2.next();
    if ("沉默王二".equals(str)) {
        list.remove(str);
    }
}

System.out.println(list);

再挖一层ArrayList 的 iterator 方法:

public Iterator<E> iterator() {
    return new Itr();
}

private class Itr implements Iterator<E> {
    int cursor;             // 下一个元素的索引
    int lastRet = -1;       // 上一个返回元素的索引;如果没有则为 -1
    int expectedModCount = modCount; // ArrayList 的修改次数

    Itr() { }  // 构造函数

    public boolean hasNext() { // 判断是否还有下一个元素
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() { // 返回下一个元素
        checkForComodification(); // 检查 ArrayList 是否被修改过
        int i = cursor; // 当前索引
        Object[] elementData = ArrayList.this.elementData; // ArrayList 中的元素数组
        if (i >= elementData.length) // 超出数组范围
            throw new ConcurrentModificationException(); // 抛出异常
        cursor = i + 1; // 更新下一个元素的索引
        return (E) elementData[lastRet = i]; // 返回下一个元素
    }
}

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

new Itr() 的时候 expectedModCount 被赋值为 modCount,而 modCount 是 ArrayList 中的一个计数器,用于记录 ArrayList 对象被修改的次数。ArrayList 的修改操作包括添加、删除、设置元素值等。每次对 ArrayList 进行修改操作时,modCount 的值会自增 1。

list 执行了 3 次 add 方法:

  • add 方法调用 ensureCapacityInternal 方法

  • ensureCapacityInternal 方法调用 ensureExplicitCapacity 方法

  • ensureExplicitCapacity 方法中会执行 modCount++

所以 modCount 的值在经过三次 add 后为 3,于是 new Itr() 后 expectedModCount 的值也为 3(回到前面去看一下 Itr 的源码)。

执行 list.remove(str)时:

  • remove 方法调用 fastRemove 方法

  • fastRemove 方法中会执行 modCount++

modCount 的值变成了 4。

第二次遍历时,会执行 Itr 的 next 方法(String str = (String) var3.next();),next 方法就会调用 checkForComodification 方法。该方法对 modCount 和 expectedModCount 进行了比较,发现两者不等,就抛出了 ConcurrentModificationException 异常。这种机制可以保证迭代器在遍历 ArrayList 时,不会遗漏或重复元素,同时也可以在多线程环境下检测到并发修改问题。

结论:不要在 for-each 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式。

正确地删除元素

List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");

Iterator<String> itr = list.iterator();
// 区别于Iterator var2 = list.iterator();

while (itr.hasNext()) {
	String str = itr.next();
    // 区别于String str = (String)var2.next();
	if ("沉默王二".equals(str)) {
		itr.remove();
        // 区别于list.remove(str);
	}
}

之前执行 list.remove(str)时:

  • remove 方法调用 fastRemove 方法

  • fastRemove 方法中会执行 modCount++

现在执行itr.remove()时,加了一步,删除完会执行 expectedModCount = modCount,保证了 expectedModCount 与 modCount 的同步。

上一页泛型下一页IO与流

最后更新于10个月前