栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java集合篇之第()幕——Iterator与ListIterator

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java集合篇之第()幕——Iterator与ListIterator

在阅读linkedList源码时,看到大量的ListIterator,那就写一篇来专门说说它吧。

一、Iterator 1、初始化

继承实现关系很简单,那也顺便说说Iterator吧。
Iterator就是迭代器,常见于集合操作,而ListIterator就像它名字组成传达出来的意思一样,专供List用的。
初始化的时候:

	Iterator it = List.Iterator();

指针其实就是指向List中的第一个元素,是不是觉得这句有点废话,等会儿讲ListIterator就知道是要对比两者差异了。

2、ArrayList中的实现

Iterator本身是个接口,那我们就用ArrayList中的内部实现类来加以说明吧:

    
    private class Itr implements Iterator {
        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() 

        // next()

        // remove()

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer 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();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

首先,看到其中有几个主要字段:

(1)cursor

就是下个要返回元素的索引,默认是0,即表示:初始化的时候,默认返回的是第一个元素;

(2)lastRet

上一个返回的元素索引,默认是-1,这不难理解;

(3)expectedModCount

预期修改次数;

(4)modCount

实际修改次数,这俩不一样的时候就会抛出ConcurrentModificationException。

(5)hasNext()
 	public boolean hasNext() {
            return cursor != size;
        }

没什么特别的,就是判断下一个要访问的元素索引是否等于集合长度,等于说明已经结束了(返回false),即表示没有下一个元素了。
注意它并没有移动指针。

(6)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];
    }

一来就检查修改次数有没有问题;
然后检查索引值是不是超过集合大小了,即越界了;
接着把当前集合所有元素拿到,就是一个数组,看看要访问的元素索引是不是超出数组了,然后让指针指向下一个元素,并将当前元素返回。
相比hasNext(),next()会移动指针。

(7)remove()

一般跟next()结合使用,且删除的是next()返回的那个元素,它是唯一一个在迭代过程中删除元素却又安全的方法,但是每次迭代只能调用一次(不然就会抛出异常),后面会说为什么。

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

从第一个if判断得知,如果我们在对迭代器进行remove()操作时,必须确保lastReturn不为-1,也就是至少要执行一次next(),因为初始化的时候,默认lastRet是-1,将会抛出IllegalStateException异常:

		while(iterator.hasNext()){

            iterator.remove();
            Integer integer = iterator.next();
            if(integer.intValue() == 5){
                iterator.remove();
            }
        }


下面这种就不会报错:

		int count = 0;
        while(iterator.hasNext()){
            if(count > 0){
                iterator.remove();
            }
            Integer integer = iterator.next();
            if(integer.intValue() == 5){
                iterator.remove();
            }
        }

好了,接着往下看,然后就是检查修改次数有没有问题;
然后是又一个可能抛出ConcurrentModificationException异常的try块,第一句就是调用ArrayList的删除指定索引位置元素的方法,看到是带有index的,那么这个lastRet刚开始我们分析了,因为先执行了next(),所以它至少是0,结合后两句我们发现,它把自己的0赋给了cursor,然后还把自己置为-1了,换句话说,就是每次remove的其实都是第一个元素,可以通过一段代码验证下:

我们看到arrayIntegerList的第一个元素(1)没了。

顺便这里提个平时不太注意的点,我们看到经过第一次遍历,元素变少的不止arrayIntegerList,还有followArrayList,Why,我没有操作followArrayList,为什么它也变少了,注意,我们操作的迭代器是arrayIntegerList,而我们是直接把arrayIntegerList赋给了followArrayList(Java语言中,对象赋值实际上是同一个对象具有两个不同的名字,因为它们都是同一个地址值。),我们看到下方圈出来的,两者hashcode一样,都是ArrayList@537,所以改了arrayIntegerList,followArrayList也会受影响。

而initialArrayList跟arrayIntegerList没有任何关系,所以无论迭代多少次,都没有变化。

最后,回到remove()源码try块里的最后一句,非常关键,这也是为什么不能在ArrayList的遍历里调用它的remove(),却可以在Iterator的遍历里调用其remove(),因为它相比ArrayList的那个只修改了modCount,却没修改expectedMODCount,它做了,

	expectedModCount = modCount;

这么一个操作,所以就不会像前者那样抛出ConcurrentModificationException。
当然,这在单线程下没有问题,但如果想在多线程里整,还是要用java.util.concurrent包下的类,或者加一下其他可以确保线程安全的手段才行。

二、ListIterator

相比Iterator,更加强大了,但是呢,像名字里的构成一样,它只能用在各种List的操作,至于使用呢,就是调用.listIterator()方法,产生一个指向List的ListIterator,还可以调用listIterator(n)创建一个从索引n开始的ListIterator。
值得一提的是,cursor并不指向元素,而是元素之前,指向的是两个元素之间:

我们依然用ArrayList的源码来分析:

private class ListItr extends Itr implements ListIterator {
        ListItr(int index) {
            super();
            cursor = index;
        }
		
		// hasPrevious()

		// nextIndex()

        // previousIndex()

		// previous()

		// set()
        
		// add()
        
    }
(1)hasPrevious()
		public boolean hasPrevious() {
            return cursor != 0;
        }

我们看到它就一个表达式,为什么这么写,因为这个双向链表只有头节点没有前置节点,逆序遍历的时候,。

(2)nextIndex()
		public int nextIndex() {
            return cursor;
        }
(3)previousIndex()
		public int previousIndex() {
            return cursor - 1;
        }
(4)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];
        }

跟next()有点像,注意区分,
一来也是先检查expectedModCount跟modCount是否一致;
然后用个临时变量i,获取游标前移一位,如果小于0,说明当前已经是第一个元素了,那么就会抛出NoSuchElementException异常;
然后获取元素数组,如果值比数组长度还大,那就有问题了;
经过上述检查后确定没问题,才确实把游标前移,返回该元素并修改上一返回元素索引。

(5)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();
            }
        }

		
	    
	    public E set(int index, E element) {
	        rangeCheck(index);
	
	        E oldValue = elementData(index);
	        elementData[index] = element;
	        return oldValue;
	    }

跟我们前面说的remove()有相似之处,如果lastRet小于0,那就是第一次遍历的时候,因为刚初始化,lastRet这时还是-1;
然后就是调用ArrayList自己的set了,检查一下索引的合法性,获取索引位置上的元素,把新元素换上去,返回旧元素的值。

(6)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();
            }
        }
		
		
	    
	    public void add(int index, E element) {
	        rangeCheckForAdd(index);
	
	        ensureCapacityInternal(size + 1);  // Increments modCount!!
	        System.arraycopy(elementData, index, elementData, index + 1,
	                         size - index);
	        elementData[index] = element;
	        size++;
	    }

添加呢,拿到当前游标位置,调用ArrayList自己的add,检查索引的合法性,数组扩容,同时modCount增加了,数组拷贝过去,然后设置好相应位置上的新元素,size增加。
完成后回到ListIterator的add(),游标向前移一位,lastRet被置为-1,expectedModCount也被设置成跟modCount一样的值,还是那句话,就是因为这句,才能让iterator在遍历时增加、删除元素,而不至于抛出ConcurrentModificationException异常。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/531301.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号