ArrayList循环删除元素的方法总结

一、遍历List集合的三种方式

JAVA中循环遍历list有三种方式:for循环、增强for循环(即foreach循环)、iterator遍历

  1. 普通的for循环
  2. foreach循环(增强的for循环)
  3. 迭代器Iterator

二、循环删除元素问题及比较分析

创建ArrayList:

List<String> list = new ArrayList<String>();
list.add("AA");
list.add("BBB");
list.add("CCCC");
list.add("DDDD");
list.add("EEE");

1. for循环索引删除

示例:删除长度为4的字符串元素。

for (int i = 0; i < list.size(); i++) {
    if (list.get(i).length() == 4) {
        list.remove(i);
    }
}

验证输出结果:

for (String s : list) System.out.print(s + ",");

输出结果为:AA,BBB,DDDD,EEE,

错误之处DDDD元素竟然没有删除掉。

问题分析

这种方式的问题在于,删除某个元素后,list的大小size发生了变化,而你的索引也在变化所以会导致你在遍历的时候漏掉某些元素。比如当你删除第1个元素后,继续根据索引访问第2个元素时,因为删除的关系后面的元素都往前移动了一位,所以实际访问的是第3个元素。<见源码分析>;不会报出异常,只会出现漏删的情况;如果只是删除一个元素,就break,可以使用这种方式。

适用场景

因此,这种方式可以用在删除特定的一个元素时使用,但不适合循环删除多个元素时使用。

2. foreach循环删除元素

删除一个元素之后,无论是否还有满足条件的元素,都必须跳出循环break,否则报出java.util.ConcurrentModificationException

因此,也只能用于删除一个元素。

//删除元素后必须break跳出,否则报出异常
for (String s : list) {
    if (s.length() == 4) {
        list.remove(s);
        break;
    }
}
for (String s : list)   System.out.print(s + ",");//AA,BBB,DDDD,EEE,

这种方式的问题在于,删除元素后继续循环会报错误信息ConcurrentModificationException,因为元素在使用的时候发生了并发的修改,导致异常抛出。但是删除完毕马上使用break跳出,则不会触发报错。

3. 迭代器Iterator

使用迭代器删除元素完美。

//迭代器:完美
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
    if(iterator.next().length()==4){
        iterator.remove();
    }
}
for (String s : list)  System.out.print(s + ",");//AA,BBB,EEE,

这种方式可以正常的循环及删除。但要注意的是,使用iterator的remove方法,如果用list的remove方法同样会报上面提到的ConcurrentModificationException错误。

4. 其他方法:新建一个List对象

适合场景:对一个List对象,需要删除大量元素,保留较少元素时,此时可以采用创建一个新的List对象,将需要保留的元素add进新的List对象,然后让旧引用指向新对象即可。

新建一个List对象,将需要保留的元素item添加到新List中,然后原来的引用指向新List即可。

// 4. 创建新对象
ArrayList<String> newList = new ArrayList<String>();
for (int i = 0; i < list.size(); i++) {
    if (list.get(i).length() < 3) {//条件为需要保留元素的条件
        newList.add(list.get(i));
    }
}

list = newList;
for (String s : list) System.out.print(s + ",");//AA,

三、源码分析

1. ArrayList中的size()方法

内部有个size属性,直接返回该size属性。

public int size() {
    checkForComodification();
    return this.size;
}

2. remove(index)与remove(Object)方法

  • remove(index)

原理:将index之后的元素向前移动1个位置;通过native方法-System.arraycopy实现。

并将size减一,并将原list最后一个元素引用置为null,便于垃圾回收GC。

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 work
    return oldValue;
}public static native void arraycopy(Object src,  int  srcPos,

                                                                   Object dest, int destPos, int length);

  • remove(Object)

内部有个for循环遍历,遍历一遍找到该元素的索引,然后再调用remove(index)方法删除。

因此,foreach删除元素性能肯定不如普通的for循环。

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;
}

3. ArrayList的迭代器实现原理

见五、迭代器原理

迭代器内部也是通过调用remove(index)方法实现的,只不是增加了cursor控制了索引,保证删除元素后,cursor不变。

四、总结

如果只是删除一个元素,这4种方法都可以实现,但是普通的for循环和迭代器较好,因为遍历过程中索引是已知的;

若循环删除多个元素,只能使用迭代器和创建新对象存储。根据情况使用,一般情况下使用迭代器最好。

for循环删除与迭代器删除的区别:

迭代器删除:内部也是调用remove(index)方法,只不过是通过cursor控制了索引,在删除元素后cursor不变,不会造成漏删的情况。

与创建新ArrayList相比:创建新ArrayList会消耗更多的内存空间;在删除较多的情况下效率更高些。

五、 ArrayList的迭代器的实现

1. Iterator<E>接口

public interface Iterator<E>

定义了四种方法:hasNextnextremove、forEachRemaining

boolean hasNext();

E next();

default void remove() {

        throw new UnsupportedOperationException("remove");

}

default void forEachRemaining(Consumer<? super E> action) {

        Objects.requireNonNull(action);

        while (hasNext())

            action.accept(next());

}

迭代器的使用方法:hasNext和next方法结合使用

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    if (iterator.next().length() == 4) {
        iterator.remove();
    }
}

2. ListIterator<E> extends Iterator<E>

由于List列表这种特殊的集合,可以前后遍历、添加、删除等,继承了Iterator<E>接口,并扩展了几个接口。

boolean hasNext();

E next();

 

boolean hasPrevious();

E previous();

 

int nextIndex();

int previousIndex();

 

void remove();

void set(E e);

void add(E e);

3. 实现方式:Iterator与ListIterator

Iterator<E>一般在集合类中作为内部类实现,由于方法很少,一般实现起来比较简单。

一般会设置几个索引属性cursorlastRet、expectedModCount等结合集合的size属性实现。

int cursor;       // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;

下面以ArrayList的Iterator实现为例:

ArrayList提供了Iterator的实现类Itr();也提供了ListIterator的ListItr()。

  1. 对外API方法获取迭代器

  • 获取Iterator迭代器:
public Iterator<E> iterator() {
    return new Itr();
}
  • 获取listIterator列表迭代器:
public ListIterator<E> listIterator() {
    return new ListItr(0);
}
public ListIterator<E> listIterator(int index) {

    if (index < 0 || index > size)

        throw new IndexOutOfBoundsException("Index: "+index);

    return new ListItr(index);

}

2. 具体实现—循环删除元素的实现

(1)Itr类

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() {}
  • hasNext方法:只需要判断当前索引cursor是否到达末尾。
public boolean hasNext() {

        return cursor != size;

    }
  • next方法:对光标cursor的判断,如果合法,返回当前元素,并cursor指向下一个

cursor自动增1,因此调用next方法之后,光标指向下一个元素。

    @SuppressWarnings("unchecked")

    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];

    }
  • remove方法:

真正删除的操作还是通过调用ArrayList的remove方法,改变的是索引,对外不暴露索引,删除之后,仍然保持cursor=lastRet不变,这就是与for循环删除的区别。

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();
        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
  • forEachRemaining方法
@Override

@SuppressWarnings("unchecked")

public void forEachRemaining(Consumer<? super E> consumer) {

    Objects.requireNonNull(consumer);
    final int size = ArrayList.this.size;
    int i = cursor;
    if (i >= size) {
        return;
    }
    final Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length) {
        throw new ConcurrentModificationException();
    }
    while (i != size && modCount == expectedModCount) {
        consumer.accept((E) elementData[i++]);
    }
    // update once at end of iteration to reduce heap write traffic
    cursor = i;
    lastRet = i - 1;
    checkForComodification();
}

(2). ListItr类

ListItr可以提供一个参数index,表示从哪个位置开始迭代;缺省情况下是从0开始。

/**

 * An optimized version of AbstractList.ListItr

 */

private class ListItr extends Itr implements ListIterator<E> {
    ListItr(int index) {
        super();
        cursor = index;

    }
  • hasPrevious方法:
    public boolean hasPrevious() {

        return cursor != 0;

    }
  • nextIndex方法:
public int nextIndex() {
        return cursor;
    }
  • previousIndex
public int previousIndex() {
        return cursor - 1;
    }
  • previous
 @SuppressWarnings("unchecked")
    public E previous() {
        checkForComodification();
        int i = cursor - 1;
        if (i < 0)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i;
        return (E) elementData[lastRet = i];
    }
  • set方法
public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();
        try {
            ArrayList.this.set(lastRet, e);
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
  • add方法
public void add(E e) {
        checkForComodification();
        try {
            int i = cursor;
            ArrayList.this.add(i, e);
            cursor = i + 1;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

感悟:之前总觉得迭代器很神秘,查看源码之后,发现so so easy。

 

 

 

 

 

 

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值