ArrayList如何正确删除一个元素?

目标:list中有0到39共40个元素,删除其中索引是10、20、30的元素

方案一:使用普通for循环从前往后遍历再删除


   
   
  1. 初始化List列表
  2. List < String > list = new ArrayList <>();
  3. for (int i = 0; i < 40; i + +) {
  4. list. add( "element" + i);
  5. }
  6. 复制代码

首先当我们删除第10位元素时,List会将后面的元素向前补位,之后再查第10位元素就会输出第11位元素


   
   
  1. for (int i = 0; i < list. size(); i + +) {
  2. if (i = = 10) {
  3. list.remove(i);
  4. }
  5. }
  6. System.out.println(list. get( 10));
  7. 复制代码

   
   
  1. 输出:element 11
  2. 复制代码

那么删除了一个元素以后,后面需要删除的元素位置就向前提1位


   
   
  1. / **
  2. * 如果明确需要删除元素的位置
  3. * 那么可以这样,每删除一个元素后就把下一个要删除元素的位置减1
  4. * 注意这么做有个需要注意的点,那就是每次删除完节点后遍历指针i需要减一,这样在删除两个临近节点时才不会出现问题
  5. * 比如要删除10和11
  6. */
  7. for (int i = 0; i < list. size(); i + +) {
  8. if (i = = 10) {
  9. list.remove(i);
  10. i--;
  11. }
  12. if (i = = 19) {
  13. list.remove(i);
  14. i--;
  15. }
  16. if (i = = 28) {
  17. list.remove(i);
  18. i--;
  19. }
  20. }
  21. System.out.println(list. contains( "element10"));
  22. System.out.println(list. contains( "element20"));
  23. System.out.println(list. contains( "element30"));
  24. 复制代码

   
   
  1. 输出:
  2. false
  3. false
  4. false
  5. 复制代码

当然我们可以用一个数组或列表从小到大存储需要删除的位置,然后再for循环中进行运算和取值

方案二:使用普通for循环从后往前遍历再删除

从后向前遍历的好处是我们不需要再像方案一一样每删除一个元素都需要去考虑后面元素向前补位的问题


   
   
  1. for (int i = list. size() - 1; i >= 0; i--) {
  2. if (i = = 30) {
  3. list.remove(i);
  4. }
  5. if (i = = 20) {
  6. list.remove(i);
  7. }
  8. if (i = = 10) {
  9. list.remove(i);
  10. }
  11. }
  12. System.out.println(list. contains( "element10"));
  13. System.out.println(list. contains( "element20"));
  14. System.out.println(list. contains( "element30"));
  15. 复制代码

   
   
  1. 输出:
  2. false
  3. false
  4. false
  5. 复制代码

从后向前,即使后面进行元素进行向前补位操作也不会影响前面需要删除的元素

这里也可以用一个数组或列表存储需要删除的元素,从大到小排列,取出一个删除一个

方案三:使用迭代器删除


   
   
  1. Iterator < String > iterator = list.iterator();
  2. int i = 0;
  3. while (iterator.hasNext()) {
  4. String next = iterator. next();
  5. if (i = = 10) {
  6. iterator.remove();
  7. }
  8. i + +;
  9. }
  10. System.out.println(list. get( 10));
  11. System.out.println(list. contains( "element10"));
  12. 复制代码

   
   
  1. 输出:
  2. element 11
  3. false
  4. 复制代码

在迭代器中维护一个数字i标识遍历的位置

如果我们在迭代器中继续删除另外20和30位置元素


   
   
  1. Iterator < String > iterator = list.iterator();
  2. int i = 0;
  3. while (iterator.hasNext()) {
  4. String next = iterator. next();
  5. if (i = = 10) {
  6. iterator.remove();
  7. }
  8. if (i = = 20) {
  9. iterator.remove();
  10. }
  11. if (i = = 30) {
  12. iterator.remove();
  13. }
  14. i + +;
  15. }
  16. System.out.println(list. get( 10));
  17. System.out.println(list. contains( "element10"));
  18. System.out.println(list. get( 20));
  19. System.out.println(list. contains( "element20"));
  20. System.out.println(list. get( 30));
  21. System.out.println(list. contains( "element30"));
  22. 复制代码

   
   
  1. 输出:
  2. element 11
  3. false
  4. element 22
  5. false
  6. element 33
  7. false
  8. 复制代码

首先我们在迭代过程中指定的是删除10、20、30三个位置的元素,可以看到输出contains时都是false表示正确删除,但是最终输出列表的值发现对应索引位置已经进行了补位。

我们debug分析一下为什么

先简单介绍一下Iterator和Iterable

  • Iterable是一个迭代接口,实现了这个接口代表该类可以迭代

可以看到我们的集合Collection接口就是它的子类

它有一个主要方法:


   
   
  1. / / 返回一个实现了Iterator接口的对象,我们也是用这个对象去进行迭代
  2. Iterator <T > iterator();
  3. 复制代码
  • Iterator,它主要有三个方法:

   
   
  1. // 返回是否还有下一个元素
  2. boolean hasNext();
  3. // 返回下一个元素
  4. E next();
  5. // 删除该元素
  6. default void remove() {
  7. throw new UnsupportedOperationException( "remove");
  8. }
  9. 复制代码

每个不同的集合类都会有不同的Iterator接口实现,在ArrayList中使用了一个内部类来实现


   
   
  1. private class Itr implements Iterator <E > {
  2. int cursor; / / index of next element to return
  3. int lastRet = - 1; / / index of last element returned; - 1 if no such
  4. int expectedModCount = modCount;
  5. Itr() {}
  6. }
  7. 复制代码

我们通过list.iterator()拿到的就是这个内部类的对象实例,这个类中有两个字段cursor和lastRet,这两个字段就是我们能在迭代器中正确删除对应位置的元素的关键。

有关expectedModCount和modCount的问题后面会补充,我们先不用关注

cursor初始化是0 lastRet初始化是-1

分析next和remove方法的源码


   
   
  1. / **
  2. * 可以先不关注这两个 Exception
  3. * 每次调用 next() cursor都会 + 1 而lastRet就会变成之前 cursor的值
  4. * cursor初始化是 0
  5. * lastRet初始化是- 1
  6. * 调用一次以后 cursor1 lastRet变成 0
  7. ** /
  8. public E next() {
  9. checkForComodification();
  10. int i = cursor;
  11. if (i >= size)
  12. throw new NoSuchElementException();
  13. Object[] elementData = ArrayList.this.elementData;
  14. if (i >= elementData. length)
  15. throw new ConcurrentModificationException();
  16. cursor = i + 1;
  17. return (E) elementData[lastRet = i];
  18. }
  19. public void remove() {
  20. if (lastRet < 0)
  21. throw new IllegalStateException();
  22. checkForComodification();
  23. try {
  24. / / 调用本身的remove方法
  25. ArrayList.this.remove(lastRet);
  26. cursor = lastRet;
  27. lastRet = - 1;
  28. expectedModCount = modCount;
  29. } catch (IndexOutOfBoundsException ex) {
  30. throw new ConcurrentModificationException();
  31. }
  32. }
  33. 复制代码

关键是这个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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值