Java ArrayList实现删除指定位置的元素
作者:海棠路 发布时间:2023-11-25 05:34:13
目标:list中有0到39共40个元素,删除其中索引是10、20、30的元素
方案一:使用普通for循环从前往后遍历再删除
//初始化List列表
List<String> list = new ArrayList<>();
for (int i = 0; i < 40; i++) {
list.add("element" + i);
}
首先当我们删除第10位元素时,List会将后面的元素向前补位,之后再查第10位元素就会输出第11位元素
for (int i = 0; i < list.size(); i++) {
if (i == 10) {
list.remove(i);
}
}
System.out.println(list.get(10));
输出:
element11
那么删除了一个元素以后,后面需要删除的元素位置就向前提1位
/**
* 如果明确需要删除元素的位置
* 那么可以这样,每删除一个元素后就把下一个要删除元素的位置减1
* 注意这么做有个需要注意的点,那就是每次删除完节点后遍历指针i需要减一,这样在删除两个临近节点时才不会出现问题
* 比如要删除10和11
*/
for (int i = 0; i < list.size(); i++) {
if (i == 10) {
list.remove(i);
i--;
}
if (i == 19) {
list.remove(i);
i--;
}
if (i == 28) {
list.remove(i);
i--;
}
}
System.out.println(list.contains("element10"));
System.out.println(list.contains("element20"));
System.out.println(list.contains("element30"));
输出:
false
false
false
当然我们可以用一个数组或列表从小到大存储需要删除的位置,然后再for循环中进行运算和取值
方案二:使用普通for循环从后往前遍历再删除
从后向前遍历的好处是我们不需要再像方案一一样每删除一个元素都需要去考虑后面元素向前补位的问题
for (int i = list.size() - 1; i >= 0; i--) {
if (i == 30) {
list.remove(i);
}
if (i == 20) {
list.remove(i);
}
if (i == 10) {
list.remove(i);
}
}
System.out.println(list.contains("element10"));
System.out.println(list.contains("element20"));
System.out.println(list.contains("element30"));
输出:
false
false
false
从后向前,即使后面进行元素进行向前补位操作也不会影响前面需要删除的元素
这里也可以用一个数组或列表存储需要删除的元素,从大到小排列,取出一个删除一个
方案三:使用迭代器删除
Iterator<String> iterator = list.iterator();
int i = 0;
while (iterator.hasNext()) {
String next = iterator.next();
if (i == 10) {
iterator.remove();
}
i++;
}
System.out.println(list.get(10));
System.out.println(list.contains("element10"));
输出:
element11
false
在迭代器中维护一个数字i标识遍历的位置
如果我们在迭代器中继续删除另外20和30位置元素
Iterator<String> iterator = list.iterator();
int i = 0;
while (iterator.hasNext()) {
String next = iterator.next();
if (i == 10) {
iterator.remove();
}
if (i == 20) {
iterator.remove();
}
if (i == 30) {
iterator.remove();
}
i++;
}
System.out.println(list.get(10));
System.out.println(list.contains("element10"));
System.out.println(list.get(20));
System.out.println(list.contains("element20"));
System.out.println(list.get(30));
System.out.println(list.contains("element30"));
输出:
element11
false
element22
false
element33
false
首先我们在迭代过程中指定的是删除10、20、30三个位置的元素,可以看到输出contains时都是false表示正确删除,但是最终输出列表的值发现对应索引位置已经进行了补位。
我们debug分析一下为什么
先简单介绍一下Iterator和Iterable
Iterable是一个迭代接口,实现了这个接口代表该类可以迭代
可以看到我们的集合Collection接口就是它的子类
它有一个主要方法:
// 返回一个实现了Iterator接口的对象,我们也是用这个对象去进行迭代
Iterator<T> iterator();
Iterator,它主要有三个方法:
// 返回是否还有下一个元素
boolean hasNext();
// 返回下一个元素
E next();
// 删除该元素
default void remove() {
throw new UnsupportedOperationException("remove");
}
每个不同的集合类都会有不同的Iterator接口实现,在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
int expectedModCount = modCount;
Itr() {}
}
我们通过list.iterator()拿到的就是这个内部类的对象实例,这个类中有两个字段cursor和lastRet,这两个字段就是我们能在迭代器中正确删除对应位置的元素的关键。
有关expectedModCount和modCount的问题后面会补充,我们先不用关注
cursor初始化是0 lastRet初始化是-1
分析next和remove方法的源码
/**
* 可以先不关注这两个Exception
* 每次调用next() cursor都会+1 而lastRet就会变成之前cursor的值
* cursor初始化是0
* lastRet初始化是-1
* 调用一次以后 cursor是1 lastRet变成0
**/
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 {
// 调用本身的remove方法
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
关键是这个remove方法对cursor和lastRet的修改
假如正在删除第10个元素
执行remove方法前cursor应该是11,lastRet是10
执行了以后lastRet变成了-1,cursor变成了10
下次执行next()方法返回的元素其实还是elementData[10]
也就是List补位后正确的下一个元素,cursor变成了11,lastRet是10
总结:使用迭代器遍历时ArrayList会用lastRet和cursor两个变量来维护当前遍历的元素索引和下一次需要遍历元素的索引,通过这两个变量就可以实现迭代中正确的删除某个位置的元素。
来源:https://juejin.cn/post/7185771019028660261


猜你喜欢
- 将map集合存数据与取出数据全部放在一个类MapTest中,方便阅读与查看随便创建一个包,在包中新建一个class文件,(也可以不建包,直接
- 1.c#里面的TabControl控件没有关闭按钮,而且很难看。2.有一些已经做好的第三方控件,但是收费。3.由于我的故障树推理诊断项目在绘
- 1、线程数使用开发规约阿里巴巴开发手册中关于线程和线程池的使用有如下三条强制规约【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时
- 申请百度地图密钥以及查看百度API网址:http://lbsyun.baidu.com/apiconsole/key#/home网址:htt
- 今天介绍一个实用的小知识点,如何将List转为Map<Object, List<Object>>1. 基本写法最开始
- unity通过GetVector,GetColor,GetFloat等获取。SetVector,SetColor,SetFloat等设置。这
- 前文本章是关于Java流程控制语句的最全汇总,本篇为汇总上篇。流程是人们生活中不可或缺的一部分,它表示人们每天都在按照一定的流程做事。比如出
- 本文实例讲述了C#统计C、C++及C#程序代码行数的方法。分享给大家供大家参考。具体如下:本文中的两个函数1)用于统计扩展名为 .h .c
- 本文实例讲述了本文章介绍了在c#,vb.net,sql中来判断指定日期是否为闰年的方法。分享给大家供大家参考。具体实现方法如下:C#代码如下
- 本文实例为大家分享了Java实现简易俄罗斯方块的具体代码,供大家参考,具体内容如下一、将对象抽象为类首先考虑俄罗斯方块游戏中含有哪些具体的对
- 本篇介绍基于SSM框架(Spring4.0+SpringMVC+Mybatis)组合的Javamail应用,邮箱的话基于腾讯的QQ邮箱,其实
- Kotlin中函数都是头等的,这意味着它可以存储在变量与数据结构中、作为参数传递给其他高阶函数以及从其他高阶函数返回。可以向操作任何其他非函
- springboot部署项目在linux的两种方式 可以选择 war包方式或者jar包方式(个人推荐使用jar方式)1.springboot
- 前言进入正题之前还是先回顾一个会被问无数遍的问题:stop和interrupt的区别是什么?简单的说就是,stop的话,那线程就真的结束了。
- 条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让
- 这篇文章主要介绍了Mybatis一对多关联关系映射实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,
- <script>//验证身份证号方法var test=function(idcard){var Errors=new Array
- //********************************************************** //******主
- 目录引言API介绍1、Optional(),empty(),of(),ofNullable()2、orElse(),orElseGet()和
- Java连接SQLServer 2008数据库的步骤:1.到微软官方下载jdbc 并解压,得到sqljdbc.jar和sqljdbc4.ja