Java-ArrayList集合的遍历方式详解

Java-ArrayList集合的遍历方式详解

    • 二、ArrayList概述
    • 三、ArrayList的遍历方式
      • 1. 普通for循环遍历
      • 2. 增强for循环遍历
      • 3. 迭代器遍历
      • 4. ListIterator遍历
      • 5. Java 8 Stream API遍历
    • 四、性能对比与分析
      • 性能测试结果分析
    • 五、遍历方式的选择建议
    • 六、常见遍历陷阱与注意事项
      • 1. 并发修改异常(ConcurrentModificationException)
      • 2. 迭代器失效问题
      • 3. 并行流的线程安全问题
    • 总结

Java中ArrayList是常用的数据结构之一,它基于动态数组实现,允许我们存储和操作对象集合。对ArrayList进行遍历是日常开发中频繁使用的操作,但遍历方式多种多样,不同场景下选择合适的遍历方式至关重要。本文我将详细介绍ArrayList的各种遍历方式、性能对比及适用场景,帮你在实际项目中做出最优选择。

二、ArrayList概述

ArrayList是Java集合框架中List接口的一个实现类,位于java.util包下。它的底层是基于动态数组实现的,这意味着它可以根据元素的数量自动调整容量。与传统数组相比,ArrayList的容量可以动态增长,提供了更灵活的元素存储方式。

下面是一个简单创建和使用ArrayList的示例:

import java.util.ArrayList;
import java.util.List;public class ArrayListDemo {public static void main(String[] args) {// 创建ArrayList实例List<String> list = new ArrayList<>();// 添加元素list.add("Java");list.add("Python");list.add("C++");// 访问元素System.out.println("第一个元素:" + list.get(0));// 修改元素list.set(1, "JavaScript");// 删除元素list.remove(2);// 打印ArrayListSystem.out.println("ArrayList内容:" + list);}
}

ArrayList的特点包括:

  • 允许存储null元素
  • 元素有序且可重复
  • 动态扩容,无需手动管理容量
  • 支持随机访问,通过索引快速访问元素

三、ArrayList的遍历方式

1. 普通for循环遍历

普通for循环是最基本的遍历方式,通过控制索引来访问ArrayList中的每个元素。

import java.util.ArrayList;
import java.util.List;public class ForLoopTraversal {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("Apple");list.add("Banana");list.add("Cherry");// 普通for循环遍历for (int i = 0; i < list.size(); i++) {System.out.println("元素" + i + ":" + list.get(i));}}
}

优点

  • 可以精确控制遍历的起始和结束位置
  • 支持双向遍历(修改索引的递增方式)
  • 可以在遍历过程中修改元素(通过set方法)

缺点

  • 代码相对繁琐,需要手动管理索引
  • 如果不注意索引范围,容易出现IndexOutOfBoundsException异常

2. 增强for循环遍历

增强for循环(也称为foreach循环)是Java 5引入的语法糖,用于简化集合和数组的遍历。

import java.util.ArrayList;
import java.util.List;public class EnhancedForLoopTraversal {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("Apple");list.add("Banana");list.add("Cherry");// 增强for循环遍历for (String fruit : list) {System.out.println("水果:" + fruit);}}
}

优点

  • 代码简洁,减少了样板代码
  • 提高了代码的可读性
  • 避免了索引越界的风险

缺点

  • 无法获取当前元素的索引(除非使用额外的计数器)
  • 不支持在遍历过程中修改集合结构(如添加、删除元素)
  • 只能单向遍历

3. 迭代器遍历

迭代器(Iterator)是Java集合框架提供的一种标准遍历机制,所有实现了Collection接口的集合类都支持迭代器。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class IteratorTraversal {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("Apple");list.add("Banana");list.add("Cherry");// 获取迭代器Iterator<String> iterator = list.iterator();// 使用迭代器遍历while (iterator.hasNext()) {String fruit = iterator.next();System.out.println("水果:" + fruit);// 可以安全地删除当前元素if ("Banana".equals(fruit)) {iterator.remove();}}System.out.println("删除后的列表:" + list);}
}

优点

  • 提供了统一的遍历接口,适用于所有实现了Collection接口的集合
  • 支持在遍历过程中安全地删除元素(通过Iterator的remove方法)

缺点

  • 代码相对冗长
  • 只能单向遍历
  • 性能略低于普通for循环

4. ListIterator遍历

ListIterator是Iterator的子接口,专门用于遍历List集合,提供了比Iterator更丰富的功能。

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;public class ListIteratorTraversal {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("Apple");list.add("Banana");list.add("Cherry");// 获取ListIterator(从列表开头开始)ListIterator<String> listIterator = list.listIterator();// 正向遍历System.out.println("正向遍历:");while (listIterator.hasNext()) {System.out.println("水果:" + listIterator.next());}// 反向遍历(需要先将指针移到末尾)System.out.println("\n反向遍历:");while (listIterator.hasPrevious()) {System.out.println("水果:" + listIterator.previous());}// 在遍历过程中添加元素listIterator = list.listIterator();while (listIterator.hasNext()) {String fruit = listIterator.next();if ("Banana".equals(fruit)) {listIterator.add("Orange");}}System.out.println("\n添加后的列表:" + list);}
}

优点

  • 支持双向遍历(向前和向后)
  • 支持在遍历过程中添加、修改和删除元素
  • 可以获取当前元素的索引位置

缺点

  • 只能用于List集合,通用性不如Iterator
  • 代码相对复杂,使用场景相对有限

5. Java 8 Stream API遍历

Java 8引入的Stream API提供了一种函数式编程风格的集合处理方式,可以更简洁地实现集合的遍历和处理。

import java.util.ArrayList;
import java.util.List;public class StreamApiTraversal {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("Apple");list.add("Banana");list.add("Cherry");// 使用Stream API的forEach方法遍历System.out.println("使用Stream API遍历:");list.stream().forEach(fruit -> {System.out.println("水果:" + fruit);});// 过滤并遍历(只打印长度大于5的水果)System.out.println("\n过滤后的结果:");list.stream().filter(fruit -> fruit.length() > 5).forEach(fruit -> System.out.println("水果:" + fruit));// 并行流遍历(适用于大数据量并行处理)System.out.println("\n使用并行流遍历:");list.parallelStream().forEach(fruit -> {System.out.println("水果:" + fruit + "(线程:" + Thread.currentThread().getName() + ")");});}
}

优点

  • 代码简洁,支持链式操作
  • 支持函数式编程风格,提高代码可读性
  • 可以方便地进行过滤、映射、聚合等操作
  • 并行流支持多线程并行处理,提高大数据量下的处理效率

缺点

  • 对于简单的遍历场景,可能显得过于重量级
  • 并行流在某些场景下可能引入线程安全问题和额外的开销
  • 调试相对困难

四、性能对比与分析

不同的遍历方式在性能上可能存在差异,特别是在处理大量数据时。下面通过一个简单的性能测试来比较各种遍历方式的执行时间。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;public class PerformanceComparison {public static void main(String[] args) {// 创建一个包含100万个元素的ArrayListList<Integer> list = new ArrayList<>();for (int i = 0; i < 1000000; i++) {list.add(i);}// 测试普通for循环遍历long startTime = System.currentTimeMillis();for (int i = 0; i < list.size(); i++) {list.get(i);}long endTime = System.currentTimeMillis();System.out.println("普通for循环遍历耗时:" + (endTime - startTime) + "ms");// 测试增强for循环遍历startTime = System.currentTimeMillis();for (Integer num : list) {// 空循环,仅测试遍历时间}endTime = System.currentTimeMillis();System.out.println("增强for循环遍历耗时:" + (endTime - startTime) + "ms");// 测试迭代器遍历startTime = System.currentTimeMillis();Iterator<Integer> iterator = list.iterator();while (iterator.hasNext()) {iterator.next();}endTime = System.currentTimeMillis();System.out.println("迭代器遍历耗时:" + (endTime - startTime) + "ms");// 测试ListIterator遍历startTime = System.currentTimeMillis();ListIterator<Integer> listIterator = list.listIterator();while (listIterator.hasNext()) {listIterator.next();}endTime = System.currentTimeMillis();System.out.println("ListIterator遍历耗时:" + (endTime - startTime) + "ms");// 测试Stream API遍历startTime = System.currentTimeMillis();list.stream().forEach(num -> {// 空处理,仅测试遍历时间});endTime = System.currentTimeMillis();System.out.println("Stream API遍历耗时:" + (endTime - startTime) + "ms");// 测试并行流遍历startTime = System.currentTimeMillis();list.parallelStream().forEach(num -> {// 空处理,仅测试遍历时间});endTime = System.currentTimeMillis();System.out.println("并行流遍历耗时:" + (endTime - startTime) + "ms");}
}

性能测试结果分析

在不同的测试环境下,各种遍历方式的性能表现可能有所不同,但通常可以得出以下结论:

  1. 普通for循环在处理ArrayList时性能最优,因为它直接通过索引访问元素,没有额外的开销。

  2. 增强for循环迭代器遍历的性能接近,它们在底层实现上是相似的。增强for循环实际上是迭代器的语法糖。

  3. ListIterator遍历的性能略低于普通迭代器,因为它提供了更多的功能。

  4. Stream API遍历的性能在处理小数据量时与增强for循环接近,但在大数据量下可能略慢。

  5. 并行流遍历在多核处理器上处理大数据量时性能优势明显,但在小数据量或单核处理器上可能表现更差,因为并行流需要创建和管理线程池,带来额外的开销。

五、遍历方式的选择建议

根据不同的场景和需求,可以选择合适的遍历方式:

  1. 如果需要在遍历过程中修改集合结构(添加、删除元素),可以使用迭代器(Iterator或ListIterator)。特别是ListIterator支持在遍历过程中添加、修改和删除元素。

  2. 如果需要双向遍历,只能使用ListIterator。

  3. 如果代码简洁性是首要考虑,增强for循环是不错的选择。它适用于简单的遍历场景,不需要索引和修改集合结构的情况。

  4. 如果需要对元素进行复杂的处理或聚合操作,Stream API提供了更强大和灵活的功能。它支持过滤、映射、排序、聚合等操作,可以使代码更加简洁和可读。

  5. 如果处理的数据量很大且在多核处理器上运行,可以考虑使用并行流提高性能。但要注意并行流可能引入的线程安全问题。

  6. 如果追求极致性能,特别是在处理大量数据时,普通for循环是最佳选择。

六、常见遍历陷阱与注意事项

1. 并发修改异常(ConcurrentModificationException)

在使用增强for循环或迭代器遍历集合时,如果在遍历过程中修改集合结构(添加、删除元素),会抛出ConcurrentModificationException异常。

import java.util.ArrayList;
import java.util.List;public class ConcurrentModificationExample {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("Apple");list.add("Banana");list.add("Cherry");// 错误示例:使用增强for循环删除元素try {for (String fruit : list) {if ("Banana".equals(fruit)) {list.remove(fruit); // 会抛出ConcurrentModificationException}}} catch (Exception e) {e.printStackTrace();}// 正确示例:使用迭代器删除元素list = new ArrayList<>();list.add("Apple");list.add("Banana");list.add("Cherry");java.util.Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String fruit = iterator.next();if ("Banana".equals(fruit)) {iterator.remove(); // 安全删除元素}}System.out.println("删除后的列表:" + list);}
}

2. 迭代器失效问题

在使用迭代器遍历集合时,如果通过集合本身的方法(而不是迭代器的方法)修改集合结构,会导致迭代器失效。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class IteratorInvalidationExample {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("Apple");list.add("Banana");list.add("Cherry");Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String fruit = iterator.next();if ("Banana".equals(fruit)) {// 错误:使用集合的remove方法,而不是迭代器的remove方法list.remove(fruit); // 会导致迭代器失效}}}
}

3. 并行流的线程安全问题

在使用并行流处理集合时,如果操作共享资源,需要注意线程安全问题。

import java.util.ArrayList;
import java.util.List;public class ParallelStreamThreadSafety {public static void main(String[] args) {List<Integer> list = new ArrayList<>();for (int i = 0; i < 1000; i++) {list.add(i);}// 非线程安全的累加器int sum = 0;// 错误示例:并行流中修改非线程安全的共享变量list.parallelStream().forEach(num -> {sum += num; // 线程不安全的操作});System.out.println("错误的累加结果:" + sum); // 结果可能不正确// 正确示例:使用线程安全的累加器java.util.concurrent.atomic.AtomicInteger safeSum = new java.util.concurrent.atomic.AtomicInteger(0);list.parallelStream().forEach(num -> {safeSum.addAndGet(num); // 线程安全的操作});System.out.println("正确的累加结果:" + safeSum.get());}
}

总结

本文我详细介绍了Java中ArrayList集合的多种遍历方式:普通for循环、增强for循环、迭代器、ListIterator、Stream API和并行流。每种遍历方式都有其特点和适用场景,应根据具体需求选择合适的遍历方式。

在实际开发中,除了考虑功能需求外,还应关注代码的性能和可读性。对于简单的遍历场景,建议优先使用增强for循环或Stream API;对于需要高性能的大数据量处理,普通for循环或并行流是更好的选择;而在需要灵活控制遍历过程的场景下,迭代器或ListIterator则更为合适。

若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/81359.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为网路设备学习-23(路由器OSPF-LSA及特殊详解 二)

OSPF动态路由协议要求&#xff1a; 1.必须有一个骨干区域&#xff08;Area 0&#xff09;。有且仅有一个&#xff0c;而且连续不可分割。 2.所有非骨干区域&#xff08;Area 1-n&#xff09;必须和骨干区域&#xff08;Area 0&#xff09;直接相连&#xff0c;且所有区域之间…

基于大模型的急性腐蚀性胃炎风险预测与诊疗方案研究报告

目录 一、引言 1.1 研究背景与意义 1.2 研究目的 1.3 国内外研究现状 二、急性腐蚀性胃炎概述 2.1 定义与发病机制 2.2 病因分析 2.3 临床表现与分型 2.4 诊断方法 三、大模型技术介绍 3.1 大模型原理 3.2 常用大模型及在医疗领域应用案例 3.3 选择用于急性腐蚀性…

泰迪杯特等奖案例深度解析:基于三维点云与深度学习的复杂零件装配质量检测系统设计

一、案例背景与行业痛点 1.1 工业装配质检的现状与挑战 在精密制造领域(如航空航天发动机、新能源汽车电池模组),复杂零件的装配质量直接影响产品性能与安全性。传统人工质检存在效率低(单件检测耗时>3分钟)、漏检率高(约15%)等问题,而现有自动化方案面临以下技术…

离散傅里叶变换DFT推导及理解

DTFT到DFT的推导 关于DTFT的相关推导已经做过总结&#xff0c;详见《DTFT及其反变换的直观理解》&#xff0c;每一个离散的频率分量都是由时域中的复指数信号累加得到的&#xff0c;DTFT得到的频谱时频率的连续函数 。 离散时间傅里叶变换公式&#xff0c;式1&#xff1a; 将…

欣佰特科技|工业 / 农业 / AR 场景怎么选?Stereolabs ZED 双目3D相机型号对比与选型建议

Stereolabs ZED 相机系列为视觉感知领域提供了多种创新解决方案&#xff0c;适用于不同应用场景。选择合适的 ZED 相机型号&#xff0c;需综合考虑分辨率、深度感知范围、接口类型等因素。 Stereolabs ZED 相机产品系列概览 ZED&#xff1a;首款立体视觉相机&#xff0c;专为高…

黑马点评Reids重点详解(Reids使用重点)

目录 一、短信登录&#xff08;redisseesion&#xff09; 基于Session实现登录流程 &#x1f504; 图中关键模块解释&#xff1a; 利用seesion登录的问题 设计key的具体细节 整体访问流程 二、商户查询缓存 reids与数据库主动更新的三种方案 缓存穿透 缓存雪崩问题及…

【Pandas】pandas DataFrame add_suffix

Pandas2.2 DataFrame Reindexing selection label manipulation 方法描述DataFrame.add_prefix(prefix[, axis])用于在 DataFrame 的行标签或列标签前添加指定前缀的方法DataFrame.add_suffix(suffix[, axis])用于在 DataFrame 的行标签或列标签后添加指定后缀的方法 pandas…

解锁MCP:AI大模型的万能工具箱

摘要&#xff1a;MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;是由Anthropic开源发布的一项技术&#xff0c;旨在作为AI大模型与外部数据和工具之间沟通的“通用语言”。它通过标准化协议&#xff0c;让大模型能够自动调用外部工具完成任务&a…

nginx性能调优与深度监控

目录 nginx性能调优 更改进程数与连接数 进程数 连接数 静态缓存功能设置 日志切割 配置网页压缩 nginx 的深度监控 GoAccess 简介 GoAccess安装 ​编辑 配置中文环境 GOAccess生成中文报告 测试访问 nginx vts 简介 nginx vts 安装 nginx配置开启vts 测试访问…

【时时三省】Python 语言----牛客网刷题笔记

目录 1,常用函数 1,input() 2,map() 3,split() 4,range() 5, 切片 6,列表推导式 山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 1,常用函数 1,input() 该函数遇到 换行停止接收,返回类型为字符串 2,map() 该函数出镜率较高,目的是将一个可迭…

docker compose yml 启动的容器中,如何使用linux环境变量赋值

在 Docker Compose 中&#xff0c;可以通过环境变量&#xff08;${VAR} 或 $VAR&#xff09;来动态配置容器。以下是几种常见的使用方式 - 使用 env_file 加载变量文件 可以单独定义一个环境变量文件&#xff08;如 app.env&#xff09;&#xff0c;然后在 docker-compose.y…

深入解析Kafka JVM堆内存:优化策略与监控实践

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storms…

git常用操作命令

本文介绍git常用的操作命令&#xff0c;供大家参考。 1、开始 # 初始化本地git git init# 在初始化的目录中&#xff0c;创建readme.txt&#xff0c;添加到git库中 git add readme.txt git commit -m "写了一个readme.txt文件"2、版本回退 2.1、git reset git lo…

解锁 MCP 中的 JSON-RPC:跨平台通信的奥秘

你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益: 了解大厂经验拥有和大厂相匹配的技术等希望看什么,评论或者私信告诉我! 文章目录 零、 背景一、RPC vs HTTP1.1 什么是RPC1.2 为什么需要 RPC?1.3 RPC 解决了什么…

【Redis】第1节|Redis服务搭建

一、Redis 基础概念 核心功能 内存数据库&#xff0c;支持持久化&#xff08;RDB/AOF&#xff09;、主从复制、哨兵高可用、集群分片。常用场景&#xff1a;缓存、分布式锁、消息队列、计数器、排行榜等。 安装环境 依赖 GCC 环境&#xff08;C语言编译&#xff09;&#xff0…

GitLab-CI简介

概述 持续集成&#xff08;CI&#xff09;和 持续交付(CD) 是一种流行的软件开发实践&#xff0c;每次提交都通过自动化的构建&#xff08;测试、编译、发布&#xff09;来验证&#xff0c;从而尽早的发现错误。 持续集成实现了DevOps, 使开发人员和运维人员从繁琐的工作中解…

FFmpeg解码器配置指南:为什么--enable-decoders不能单独使用?

FFmpeg解码器配置指南 在FFmpeg的编译配置过程中&#xff0c;许多开发者会遇到关于解码器配置的困惑。特别是--enable-decoders这个选项&#xff0c;很多人误以为启用它就能自动包含所有解码器。本文将深入解析FFmpeg解码器配置的机制&#xff0c;并通过实际测试展示正确的配置…

C++多态与虚函数

C++多态与虚函数详解 多态(Polymorphism)是 C++ 面向对象编程的重要特性,通过统一的接口实现不同的行为。虚函数(Virtual Function)是实现运行时多态的核心机制。以下从多态的构成条件、意义、析构函数的虚函数化、纯虚函数和抽象类,以及虚函数表的底层实现依次介绍。 1.…

游戏引擎学习第313天:回到 Z 层级的工作

回顾并为今天的内容定下基调 昨天我们新增了每个元素级别的排序功能&#xff0c;并且采用了一种我们认为挺有意思的方法。原本计划采用一个更复杂的实现方式&#xff0c;但在中途实现的过程中&#xff0c;突然意识到其实有个更简单的做法&#xff0c;于是我们就改用了这个简单…

ODBC简介

ODBC&#xff08;Open Database Connectivity&#xff09;是一个由 Microsoft 制定的标准接口&#xff0c;允许不同的应用程序通过统一的方式访问各种数据库系统。 &#x1f9e0; 简单理解&#xff1a; ODBC 就像是 “翻译官”&#xff0c;在应用程序&#xff08;如 Excel、Py…