ArrayList中元素的删除操作
ArrayList中元素的删除操作
在聊 ArrayList
的删除删除操作之前,先来说说它的遍历方法。
一个 list 的遍历方法主要有三种:
- Iterator 迭代器遍历
- 遍历下标 for 循环遍历
- forEach 遍历
对于这三种遍历方法,产生的删除操作 (remove
) 结果也会不一样。我们主要将 forEach
遍历删除会出现的问题。
我们先来看一段代码
public static void main(String[] args) {List<String> list1 = new ArrayList<>(3);list1.add("name");list1.add("age");list1.add("phone");for (String str : list1){if ("age".equals(str)){list1.remove(str);}}
}
这样子进行删除操作是不会出现问题的,然而当我们把删除的判断条件换成 phone
后就会报异常。
Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)at java.util.ArrayList$Itr.next(ArrayList.java:851)at com.wiceflow.collection.List.ListRemove.main(ListRemove.java:18)
分析源码揭开它的秘密
我们知道,forEach
循环其实是走 list
的迭代器进行遍历的,我们先看 ArrayList
内部的 forEach
方法。
在 ArrayList
中有一个内部类 Itr
实现了 Iterator
,还有一个 ListItr
继承了 Itr
(这个类初始化的时候会将 ArrayList 对象的 modCount 属性的值赋值给 expectedModCount)。
先看迭代器的 next
方法
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];
}
/**
* 在对一个集合对象进行跌代操作的同时,并不限制对集合对象的元素进行操作
* 这些操作包括一些可能引起跌代错误的add()或remove()等危险操作。
* 在AbstractList中,使用了一个简单的机制来规避这些风险。
* 这就是modCount和expectedModCount的作用所在
*/
final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();
}
关于 checkForComodification() 方法更多的可以看这篇博文:
Java 集合中常见 checkForComodification()方法的作用
我们可以看到,list
每次获取下一个对象前都要去检查一下光标是否越界。在 ArrayList
的所有涉及结构变化的方法中都增加 modCount
的值,包括:add()、remove()、addAll()、removeRange()
及 clear()
方法。这些方法每调用一次,modCount
的值就加 1。而变量 expectedModCount
在迭代开始时便会被赋值成 modCount
的值。所以在循环遍历中,改变结构变化的方法,例如 add()、remove()
都会是 modCount
增长 1 ,而 expectedModCount
却不会变化。
注意,以上讲的涉及到结构变化的方法是 ArrayList 的方法,不是其内部类 Itr 的方法。
来看一下 ArrayList
的 remove
方法
public E remove(int index) {rangeCheck(index);modCount++;E oldValue = elementData(index);int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // clear to let GC do its workreturn oldValue;
} public boolean remove(Object o) {if (o == null) {for (int index = 0; index < size; index++)if (elementData[index] == null) {fastRemove(index);return true;}} else {for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}return 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; // clear to let GC do its work
}
从上面源码中我们不难发现,ArrayList
中两个 remove()
方法都对 modCount
进行了自增,那么我们在用迭代器迭代的时候,若是删除 末尾 的元素,则会造成 modCount
和 expectedModCount
的不一致导致异常抛出。
为什么对倒数第二个元素进行删除不会报异常,而对其他位置的删除会报异常?
我们来看一下 ArrayList
中的内部类 Itr
。我们在调用迭代器的 Next()
方法之前会先调用 hasNext()
方法。
public boolean hasNext() {return cursor != size;
}
从代码上我们可以看出判断条件是当 cursor != size
的时候,才会进行下一次循环,而 cursor
参数是我们迭代循环的下标,在我们删除倒数第二个元素后,此时 list
的大小减了 1,再进入下一次循环后会出现 cursor == size
,也就是 hasNext()
便会返回 false
终止了循环。实际上 modCount
的数值也增加了 1,只不过循环没发执行到那里,所以异常也就不会被抛出来了。
for 下标遍历删除
从源码上我们可以看出,在利用 for
下标进行遍历的时候,并不会触发 checkForComodification()
方法,所以此时只要要删除的位置比列表大小小时都不会出错。
public E remove(int index) {rangeCheck(index);modCount++;E oldValue = elementData(index);int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // clear to let GC do its workreturn oldValue;
}
在 ArrayList
源码介绍中,作者是推荐使用 for ( int i; i < size; i++)
方式去遍历,而不是 foreach
或者迭代,这个主要是因为 list
接口实现了 RandomAccess
接口。 实现这个接口的集合是随机无序的,所以遍历的时候一般使用上述的 for
,记住一点就可以了所有实现了 RandomAccess
接口的集合都是用一般 for
就可以了(可以通过 api
查看那些集合实现了 RandomAccess
)。
Iterator 迭代遍历删除
这里我们将的 Iterator
遍历删除调用的方法不是 ArrayList
的 remove
方法,而是其内部类的 remove
方法
我们看源码不难发现,在 Itr
类中,属性 expectedModCount
在调用外部的 remove()
方法后再次被赋值,此时 expectedModCount
是等于 modCount
的。
public void remove() {if (lastRet < 0)throw new IllegalStateException();// 这里检查时候还没有进行删除操作checkForComodification();try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;// 先进行了 remove 操作后 再重新对 expectedModCount 进行赋值expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}
}
所以在使用 Iterator
进行遍历删除时不会出现 ConcurrentModificationException
异常。
ArrayList中元素的删除操作相关推荐
- Python列表(获取列表中指定元素的索引、获取列表中的多个元素、判断指定元素是否在列表中存在、列表元素的遍历、列表元素的增加操作、 列表元素的删除操作、列表元素的修改操作、列表元素的排序操作)
1.获取列表中指定元素的索引 eg1:未指定索引范围查找索引 zyr=['憨憨','憨宝'] print(zyr.index('憨宝')) print(zyr[1]) eg2:在指定索引范围内查找元素 ...
- 3.5链表----链表中元素的删除(只删除一个元素情况)
3.5链表----链表中元素的删除(只删除一个元素情况) 该部分与上一节是息息相关的,关于如何在链表中删除元素,我们一步一步来分析: 一.图示删除逻辑 假设我们需要在链表中删除索引为2位置的元素,此时 ...
- java list 元素排序_对arraylist中元素进行排序实例代码
rrayList中的元素进行排序,主要考查的是对util包中的Comparator接口和Collections类的使用. 实现Comparator接口必须实现compare方法,自己可以去看API帮助 ...
- elementui在table中使用el-popover删除操作
elementui在table中使用el-popover删除操作 .vue <el-table-column label="操作" width="120" ...
- ArrayList中remove()方法删除元素之后下标重定位的问题
需求: 有一个ArrayList数组,要求删除长度大于5的字符串,如:arr = {"ab1","123ad","bca","da ...
- map中的迭代删除操作注意问题
如果map中包含若干个key为10的倍数,要删除key为10的倍数. 错误形式 #include <iostream> #include <map> #include < ...
- vector 中元素的删除
vector 中删除元素的方法是:b.erase(it); //b是vector, it 是 vector::iterator 但是删除vector 中的元素有些诡异(^_^),稍不注意,就会出错. ...
- vector中元素的删除
如果想要删除vector中值为val的元素,最容易想到的方法就是对vector进行遍历,然后遇到值为val时就将其删除.比较好的是vector容器有erase操作. 但是这儿需要注意,与list中的e ...
- java list 获取索引_java – 获取arrayList中元素的索引
我试图在arrayList minuteList中获得466的索引 [288, 318, 346, 376, 406, 436, 466, 1006, 1036, 1066, 1096, 1126, ...
最新文章
- Asp.net 序列化应用实例(转载)
- Spring MVC中获取当前项目的路径
- G2. 唐纳德与子串 (Hard)kmp
- linux 镜像自动安装,制作能自动安装的CentOS镜像文件
- 《四世同堂》金句摘抄(十七)
- hibernate 别名_Hibernate:在sqlRestriction上使用联接表别名
- Modify HTTP Headers (Examples)
- 1个平方大概多少立杆_1斤草坪种子播撒多少平方/四季青
- 【重点】LeetCode 143. Reorder List
- 学习STM32F769DK-OTA例程之APP中断向量表重映射
- DFA极简化和NFA确定化
- 白鹭发布html5,白鹭Egret Engine 1.5发布 HTML5性能大幅提升
- Luarocks: 安装 lyaml 库
- 拯救 中国区 谷歌翻译 解决方案
- Python爆力破解rar密码并对比多线程的效率
- 拒酒词,社交必备!!!
- 用Python去优惠券,看到结果我惊呆了!
- putty小键盘输入_putty或xshell上用vi/vim小键盘无法使用的解决方法-阿里云开发者社区...
- 英寸和厘米的交互python_matplotlib 设置图形大小时 figsize 与 dpi 的关系
- 关于正则表达式里含有空格的问题
热门文章
- 生于七八十年代,我们的童年是这样的
- 自走棋显示连接服务器就断了,赤潮自走棋无法连接服务器是什么原因
- oracle删除两个月以前的数据,Oracle恢复删除数据
- VS编译时一些常见错误积累LNK,比如LNK2019、LNK2001(实时更新)
- IDEA启动VUE项目的方法
- tftp命令使用详解
- 国内股票KDJ指标计算,Python实现KDJ指标计算,Talib实现KDJ指标计算
- Android textview设置ttf字体库本地库和网络库使用的两种方式
- AWS KVS(Kinesis Video Streams)之WebRTC移植编译(三)
- 南大科院大数据Hadoop工程实训