深入浅出 ArrayList:从基础用法到底层原理的全面解析(中)

四、ArrayList 常用方法实战 —— 从添加到遍历的全场景覆盖

ArrayList 提供了数十个方法,但日常开发中常用的只有 10 个左右,我们按 “元素操作”“集合查询”“遍历方式” 三类来梳理,每个方法都附带示例和注意事项。

4.1 元素添加:add () 与 addAll ()

添加元素是 ArrayList 最基础的操作,核心方法有两个:add(E e)(添加单个元素到尾部)和addAll(Collection<? extends E> c)(添加另一个集合的所有元素到尾部)。

4.1.1 add (E e):尾部添加单个元素

源码核心逻辑:先检查是否需要扩容,再将元素添加到elementData[size],最后size++

public boolean add(E e) {// 确保容量足够(size+1:因为要添加1个元素)ensureCapacityInternal(size + 1);// 将元素添加到数组的size位置(此时size是当前元素个数,索引从0开始)elementData[size++] = e;return true;
}

示例:

List<Integer> list = new ArrayList<>();
list.add(10); // 添加成功,返回true
list.add(20);
System.out.println(list); // 输出:[10, 20]
System.out.println(list.size()); // 输出:2(元素个数)

注意:add()方法永远返回true(因为 ArrayList 允许添加任意元素,不会因 “元素已存在” 而返回 false,这与 Set 的add()不同)。

4.1.2 add (int index, E element):指定索引插入元素

除了尾部添加,还可以在指定索引位置插入元素,但需要注意:插入位置会导致后续元素 “向后移动”,时间复杂度为 O (n),频繁使用会影响性能。

示例:

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
// 在索引1的位置插入"c"(原索引1的"b"向后移动到索引2)
list.add(1, "c");
System.out.println(list); // 输出:[a, c, b]

注意:如果指定的索引index超出[0, size]范围(即index < 0index > size),会抛出IndexOutOfBoundsException(索引越界异常)。

4.1.3 addAll (Collection<? extends E> c):添加另一个集合的所有元素

示例:

List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");List<String> list2 = new ArrayList<>();
list2.add("c");
list2.add("d");// 将list2的所有元素添加到list1尾部
list1.addAll(list2);
System.out.println(list1); // 输出:[a, b, c, d]

addAll()返回boolean:如果集合因添加操作发生了变化(即 c 不为空),返回true;如果 c 为空,返回false

4.2 元素删除:remove () 与 clear ()

删除元素是 ArrayList 的高频操作,但需要注意 “删除指定索引” 和 “删除指定元素” 的区别,以及删除后元素的移动问题。

4.2.1 remove (int index):删除指定索引的元素

源码核心逻辑:先检查索引是否合法,再计算需要移动的元素个数,通过System.arraycopy将后续元素向前移动 1 位,最后将数组末尾的元素置为 null(帮助 GC 回收)。

public E remove(int index) {// 检查索引是否越界(index >= size则抛出异常)rangeCheck(index);modCount++;// 获取要删除的元素(用于返回)E oldValue = elementData(index);// 计算需要移动的元素个数:size - index - 1(比如删除索引2,size=5,需要移动5-2-1=2个元素)int numMoved = size - index - 1;if (numMoved > 0) {// 将elementData中index+1到size-1的元素,复制到index到size-2的位置(向前移动1位)System.arraycopy(elementData, index+1, elementData, index, numMoved);}// 将数组末尾的元素置为null(帮助GC回收,避免内存泄漏)elementData[--size] = null;// 返回被删除的元素return oldValue;
}

示例:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
// 删除索引2的元素(即"c")
String removed = list.remove(2);
System.out.println("被删除的元素:" + removed); // 输出:被删除的元素:c
System.out.println(list); // 输出:[a, b, d]

注意:remove(int index)返回的是 “被删除的元素”,而不是boolean,这一点容易和remove(Object o)混淆。

4.2.2 remove (Object o):删除指定元素(第一个匹配项)

remove(int index)不同,remove(Object o)是根据 “元素值” 删除,且只删除 “第一个匹配的元素”(如果元素重复)。

源码核心逻辑:先判断 o 是否为 null(null 元素用 == 判断,非 null 元素用 equals () 判断),找到第一个匹配的索引后,调用fastRemove()删除(逻辑与remove(int index)一致)。

public boolean remove(Object o) {if (o == null) {// 遍历数组,找到第一个null元素for (int index = 0; index < size; index++)if (elementData[index] == null) {fastRemove(index);return true;}} else {// 遍历数组,找到第一个equals(o)为true的元素for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}// 没找到元素,返回falsereturn false;
}// 快速删除:不检查索引,不返回被删除的元素(内部使用)
private void fastRemove(int index) {modCount++;int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index, numMoved);elementData[--size] = null;
}

示例:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "b"));
// 删除第一个"b"
boolean isRemoved = list.remove("b");
System.out.println("是否删除成功:" + isRemoved); // 输出:true
System.out.println(list); // 输出:[a, c, b](只删除了第一个"b")

注意:

  1. 如果集合中存在多个相同元素,remove(Object o)只删除第一个;
  2. 如果存储的是自定义对象,必须重写equals()方法,否则无法正确匹配元素(默认用==判断地址,而非内容)。
4.2.3 clear ():清空所有元素

clear()会删除集合中的所有元素,但不会清空数组容量,只是将数组中的元素置为 null,帮助 GC 回收,size置为 0。

源码:

public void clear() {modCount++;// 将所有元素置为null(帮助GC)for (int i = 0; i < size; i++)elementData[i] = null;// 元素个数置为0size = 0;
}

示例:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
list.clear();
System.out.println(list); // 输出:[](空集合)
System.out.println(list.size()); // 输出:0

如果需要 “清空元素并缩小数组容量”,可以在clear()后调用trimToSize()(后文会讲)。

4.3 元素查询与修改:get ()、set ()、contains ()

4.3.1 get (int index):获取指定索引的元素

get()是 ArrayList 的核心查询方法,基于索引随机访问,时间复杂度 O (1),源码:

public E get(int index) {// 检查索引是否越界rangeCheck(index);// 返回数组中index位置的元素return elementData(index);
}// 索引越界检查
private void rangeCheck(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

示例:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
String element = list.get(1);
System.out.println(element); // 输出:b

注意:index必须在[0, size-1]范围内,否则抛出IndexOutOfBoundsException(比如list.get(3),size=3,索引最大为 2,会报错)。

4.3.2 set (int index, E element):修改指定索引的元素

set()用于替换指定索引的元素,返回 “被替换的旧元素”,源码:

public E set(int index, E element) {rangeCheck(index); // 索引越界检查E oldValue = elementData(index); // 获取旧元素elementData[index] = element; // 替换为新元素return oldValue; // 返回旧元素
}

示例:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
// 将索引1的元素改为"d"
String oldElement = list.set(1, "d");
System.out.println("被替换的旧元素:" + oldElement); // 输出:b
System.out.println(list); // 输出:[a, d, c]
4.3.3 contains (Object o):判断元素是否存在

contains()用于判断集合中是否包含指定元素,返回boolean,底层通过indexOf()实现:

public boolean contains(Object o) {return indexOf(o) >= 0;
}// 查找元素的第一个索引,不存在返回-1
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;
}

示例:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
boolean hasB = list.contains("b");
boolean hasD = list.contains("d");
System.out.println("是否包含b:" + hasB); // 输出:true
System.out.println("是否包含d:" + hasD); // 输出:false

注意:contains()的时间复杂度是 O (n)(需要遍历数组),如果集合元素较多,频繁调用会影响性能。

4.4 ArrayList 的 4 种遍历方式

遍历是集合操作的高频场景,ArrayList 支持多种遍历方式,不同方式的效率和适用场景不同,需根据需求选择。

4.4.1 普通 for 循环(基于索引)

适合需要 “获取索引” 的场景,效率较高(直接随机访问):

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (int i = 0; i < list.size(); i++) {System.out.println("索引" + i + ":" + list.get(i));
}

输出:

索引0:a
索引1:b
索引2:c
4.4.2 增强 for 循环(foreach)

语法简洁,无需关心索引,适合 “只遍历元素,不关心索引” 的场景:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String element : list) {System.out.println(element);
}
a
b
c

注意:foreach 底层是通过迭代器(Iterator)实现的,遍历过程中不能修改集合结构(如 add/remove),否则会抛出ConcurrentModificationException

4.4.3 迭代器(Iterator)

适合 “需要在遍历过程中删除元素” 的场景(必须用迭代器的remove()方法,而非集合的remove()):

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {String element = iterator.next();if ("b".equals(element)) {// 用迭代器的remove()删除,不会抛出异常iterator.remove();}System.out.println(element);
}
System.out.println("删除后的集合:" + list); // 输出:[a, c]

注意:

  1. iterator.next()必须在iterator.hasNext()之后调用,否则会抛出NoSuchElementException
  2. 遍历过程中,只能用iterator.remove()删除元素,不能用list.remove(),否则会破坏迭代器的modCountexpectedModCount一致性,导致异常。
4.4.4 Stream 流遍历(JDK 1.8+)

适合 “需要对元素进行过滤、映射等复杂操作” 的场景,语法简洁,支持链式调用:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
// 过滤出长度为1的元素,转为大写后遍历输出
list.stream().filter(element -> element.length() == 1).map(String::toUpperCase).forEach(System.out::println);

输出:

A
B
C
D

Stream 流遍历的优势在于 “功能性编程”,能快速实现复杂的元素处理逻辑,是 JDK 1.8 + 后的推荐方式之一。

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

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

相关文章

java后端如何实现下载功能

后端需要把要下载的若干文件 按 ZIP 格式编码成一段二进制字节流&#xff0c;然后以 Content-Type: application/zip Content-Disposition: attachment; filenamexxx.zip 的形式写进 HTTP 响应体。浏览器收到这段“ZIP 格式的字节流”后&#xff0c;就会弹出保存对话框&#xf…

AI生成技术报告:GaussDB与openGauss的HTAP功能全面对比

GaussDB 与 openGauss 的 HTAP 功能比较 前言 GaussDB集中式版本从505.2版本开始引入了HTAP混合负载功能&#xff0c;openGauss也从7.0.0 RC1版本开始引入了HTAP行列融合功能&#xff0c;加强了行存转列存的使用友好度&#xff0c;但两者的实现似乎存在不小的差异。 虽然文档…

小程序开发指南(四)(UI 框架整合)

✍讲解了微信小程序 UI 框架的使用方法和特点&#xff0c;根据项目需求选择合适的组件库。附有相应的组件库预览码&#xff0c;也是将所有的微信小程序原生组件库整合在一起方便后续开发的使用。如果有不好或者有错误的地方请告知&#xff01;希望可以与大家相互的交流学习&…

golang 1.25.0 安装

wget https://golang.google.cn/dl/go1.25.0.linux-amd64.tar.gz tar -C /usr/local/ -xzf go1.25.0.linux-amd64.tar.gz ln -s /usr/local/go/bin/* /usr/bin/ go env -w GO111MODULEon go env -w GOPROXYhttps://goproxy.cn,direct

基于深度学习的人脸表情识别系统:YOLOv5/v6/v7/v8/v10模型实现与UI界面集成

基于YOLOv5/v7/v8的智能人脸表情识别系统:从算法原理到应用实现 表情识别的技术价值与挑战 人脸表情识别(Facial Expression Recognition, FERYOLOv5/v7/v8等深度学习算法构建高效的表情识别系统,并设计直观的UI界面集成方案。无论你是深度学习初学者还是有经验的开发者,…

初步了解多线程

系列文章目录 目录 系列文章目录 前言 一、进程 二、线程 1. 线程解决资源开销的方式 2. 线程和进程的联系和区别 三、多线程编程 1. 直观了解多线程 2. 线程的创建方式 1. 继承 Thread 重写 run() 方法 2. 实现 Runable 接口&#xff0c;重写 run() 方法 3. 继承 …

安卓Android低功耗蓝牙BLE连接异常报错133

安卓Android低功耗蓝牙BLE连接异常报错133 之前连接一直好好的,不知道为什么今天突然就连接不了蓝牙了,报错133,按照 找网上的说明总是说清除GATT缓存,其实并不是我的问题,最后看到这里https://softs.im/android-ble-%e8%bf%9e%e6%8e%a5%e9%94%99%e8%af%af133/ 有如下说明: 情…

【分治】快排与归并专题

分治思想 分&#xff08;Divide&#xff09;&#xff1a;将待排序数组不断拆分为两个等长&#xff08;或近似等长&#xff09;的子数组&#xff0c;直到子数组长度为 1&#xff08;天然有序&#xff09;。 治&#xff08;Conquer&#xff09;&#xff1a;递归排序每个子数组。 …

[Linux]学习笔记系列 -- mm/page_alloc

文章目录mm/page_alloc.c 伙伴系统内存分配器(Buddy System Memory Allocator) 内核物理内存管理的核心历史与背景这项技术是为了解决什么特定问题而诞生的&#xff1f;它的发展经历了哪些重要的里程碑或版本迭代&#xff1f;目前该技术的社区活跃度和主流应用情况如何&#xf…

3秒传输大文件:cpolar+Localsend实现跨网络秒传

文章目录前言1. 在Windows上安装LocalSend2. 安装Cpolar内网穿透3. 公网访问LocalSend4. 固定LocalSend公网地址用 cpolar 让 Localsend 突破距离限制就是这么简单&#xff01;三步轻松搞定&#xff1a;在手机和电脑上都安装 Localsend&#xff0c;在其中一台设备上运行 cpolar…

基于STM32单片机智能RFID刷卡汽车位锁桩设计

1 系统功能介绍 本系统是一个 基于 STM32 单片机的智能 RFID 刷卡车位锁桩控制系统&#xff0c;其设计理念来源于现实中智能停车场的车位锁桩管理。通过 RFID 刷卡认证、LCD1602 显示、继电器控制以及按键辅助操作&#xff0c;实现对车位的安全管理。该系统不仅模拟了车辆驶入与…

SQL185 试卷完成数同比2020年的增长率及排名变化

描述现有试卷信息表examination_info&#xff08;exam_id试卷ID, tag试卷类别, difficulty试卷难度, duration考试时长, release_time发布时间&#xff09;&#xff1a;试卷作答记录表exam_record&#xff08;uid用户ID, exam_id试卷ID, start_time开始作答时间, submit_time交…

网络编程中的TCP——TCP的连接的建立、关闭、状态转移

网络编程中的TCP——TCP的连接的建立、关闭、状态转移 TCP连接的建立和关闭wireshark捕获数据&#xff1a;TCP三次握手四次挥手的时序图&#xff1a;三次握手&#xff1a; 报文段1包含SYN标志&#xff0c;这是一个同步报文段&#xff0c;表示发起连接请求&#xff0c;包含自己起…

SQL 语句拼接在 C 语言中的实现与安全性分析

代码解析 // 构建SQL插入语句 char *sql_insert (char *)malloc(sizeof(char) * 200); // 分配200字节内存 strcpy(sql_insert, "INSERT INTO user(username, passwd) VALUES("); // 复制基础SQL语句 strcat(sql_insert, ""); // 添加单引号 strcat(sq…

`lock()` 和 `unlock()` 线程同步函数

1) 函数的概念与用途 lock() 和 unlock() 不是特定的标准库函数&#xff0c;而是线程同步原语的一般概念&#xff0c;用于在多线程环境中保护共享资源。在不同的编程环境和库中&#xff0c;这些函数有不同的具体实现&#xff08;如 POSIX 线程的 pthread_mutex_lock() 或 C 的 …

升级openssh后ORACLE RAC EM 安装失败处理

升级过程中由于SCP传输时目标目录/tmp/tempRACTrans_2025_08_22--18-25-44-032/ractrans 不存在导致的OC4J配置失败&#xff1a;WARNING: /usr/bin/scp: dest open "/tmp/tempRACTrans_2025_08_22--18-25-44-032/ractrans": No such file or directory/usr/bin/scp…

ADB 调试工具的学习[特殊字符]

一、ADB 的工作原理 1.1 ADB 概念 ADB (Android Debug Bridge)&#xff1a;Android 调试桥&#xff0c;是开发/测试 Android 应用必备的调试工具。作用&#xff1a;通过 电脑终端命令 操作 安卓手机/模拟器。 1.2 ADB 构成与原理 ADB 由三部分组成&#xff1a; Client 端&#…

用一根“数据中枢神经”串起业务从事件流到 Apache Kafka

1. 为什么是“事件流”&#xff1f; 在一个软件定义、自动化、永远在线的世界里&#xff0c;系统之间最需要的是&#xff1a;把发生了什么这件事&#xff0c;第一时间、按正确顺序、可靠地传到该知道的人/系统那里。 事件流就像企业的中枢神经&#xff1a;它把数据库更新、设备…

【RAGFlow代码详解-4】数据存储层

数据库基础设施 RAGFlow 使用关系数据库&#xff08;MySQL 或 PostgreSQL&#xff09;作为主要元数据存储&#xff0c;通过具有连接池和重试机制的 Peewee ORM 进行管理。 连接管理 数据库连接通过 service_conf.yaml 和环境变量进行配置。该系统支持具有可配置连接池的 MySQL …

ES_映射

一、 映射&#xff08;Mapping&#xff09;是什么&#xff1f; 简单来说&#xff0c;映射就像是关系型数据库中的表结构定义&#xff08;Schema&#xff09;。它定义了索引&#xff08;Index&#xff09;中的文档&#xff08;Document&#xff09;可以包含哪些字段&#xff08;…