概述
一、遍历List集合的三种方式
JAVA中循环遍历list有三种方式:for循环、增强for循环(即foreach循环)、iterator遍历。
- 普通的for循环
- foreach循环(增强的for循环)
- 迭代器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>
定义了四种方法:hasNext、next、remove、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>一般在集合类中作为内部类实现,由于方法很少,一般实现起来比较简单。
一般会设置几个索引属性cursor、lastRet、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()。
-
对外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。
最后
以上就是俊逸菠萝为你收集整理的ArrayList循环删除元素的方法总结一、遍历List集合的三种方式二、循环删除元素问题及比较分析三、源码分析四、总结for循环删除与迭代器删除的区别:五、 ArrayList的迭代器的实现的全部内容,希望文章能够帮你解决ArrayList循环删除元素的方法总结一、遍历List集合的三种方式二、循环删除元素问题及比较分析三、源码分析四、总结for循环删除与迭代器删除的区别:五、 ArrayList的迭代器的实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复