关于ArrayList源码的一些自我理解以及解析(四):开始对迭代器Iterator展开进攻

前面将ArrayList的基本一些功能的源码都看了一遍,现在到了核心的迭代器方面了,在这之前可以看看极客学院关于Iterator的讲解这里面的东西,讲的详细,我在略微懂了一点之后,继续将源码看下去。

文章说ArrayList里面自己定义了一个内部类,于是我立马找到了它

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        // Android-changed: Add "limit" field to detect end of iteration.
        // The "limit" of this iterator. This is the size of the list at the time the
        // iterator was created. Adding & removing elements will invalidate the iteration
        // anyway (and cause next() to throw) so saving this value will guarantee that         the
        // value of hasNext() remains stable and won't flap between true and false when elements
        // are added and removed from the list.
        protected int limit = ArrayList.this.size;
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;
        public boolean hasNext() {
            return cursor < limit;
        }
        @SuppressWarnings("unchecked")
        public E next() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            int i = cursor;
            if (i >= limit)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
                limit--;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
        @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;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

首先,这个类实现了一个接口Iterator,而这个接口存在于java.util.Iterator中。里面总共有四个方法:

default voidforEachRemaining(Consumer<? super E> action)

Performs the given action for each remaining element until all elements have been processed or the action throws an exception.

abstract booleanhasNext()

Returns true if the iteration has more elements.

abstract Enext()

Returns the next element in the iteration.

default voidremove()

Removes from the underlying collection the last element returned by this iterator (optional operation).

除了第一个方法之外,其他三个方法是使用最多的,也是ArrayList里面这个内部类自己实现了的方法。这下我们知道后面有哪些重要的方法。这个类的注释已经解释了,是AbstractList.Itr的优化版本,那么关注的点就来了:优化了什么?以及三个方法的实现。首先我们看它定义了什么:

        // Android-changed: Add "limit" field to detect end of iteration.
        // The "limit" of this iterator. This is the size of the list at the time the
        // iterator was created. Adding & removing elements will invalidate the iteration
        // anyway (and cause next() to throw) so saving this value will guarantee that the
        // value of hasNext() remains stable and won't flap between true and false when elements
        // are added and removed from the list.
        protected int limit = ArrayList.this.size;

定义了一个int类型的变量,在注释中给我们解释了,这是为Iteration增加了一个limit字段来检测迭代是否结束。这可能也算作是改进吧,我们得知的消息是limit记录了ArrayList的size大小。

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

随后就是正常的两个变量的定义了,以及之前多次见到过的modCount,果然没有猜错它的功能。

        public boolean hasNext() {
            return cursor < limit;
        }
        @SuppressWarnings("unchecked")
        public E next() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            int i = cursor;
            if (i >= limit)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

首先看hasNext方法的实现,直接判断当前指向的元素下标是否超过了ArrayList的size大小,如果没超过,返回true表明后续还有元素。返回false表示后面已经没有元素了。

再看next方法,返回的是一个Element对象。首先通过比较modCount,如果modCount发生了改变,说明调用next方法的时候,ArrayList里面的数据被改动了,不是最新的数据,于是抛出错误异常。然后,我们获取cursor的值,然后和limit进行比较,也就是和ArrayList的size比较,如果大于等于size,那么表示后面没有更多的元素了,于是抛出错误异常。第三个if判断也很容易理解,从抛出的异常也很容易理解,如果是数组越界的话就会抛出IndexOutof.....的异常。这里的异常是ConcurrentModificationException,和第一个if的效果是一样的。设想,如果前面第一重if检测完毕没问题之后刚好ArrayList里面的数组发生了变动突然变得很短,此时这个i也就是cursor已经很大了,超过了数组的length,根本就什么都取不出来。

在经历过三重if的检测过后,确保数据已经是最新的了,于是将数据取出来,cursor和lastRet都相应的增大进行挪动,从这里我们知道cursor永远比lastRet大1.

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
                limit--;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

然后我们再看remove方法,首先是第一重if检测判断,为什么使用lastRet来比较,而不是使用cursor来比较?因为实际上的所有操作,都是操作lastRet指向的元素。可以这么理解cursor的关系,cursor是向导的作用,脏活累活都是lastRet去做,获取的时候也是获取的lastRet指向的元素,删除也是删除lastRet指向的元素。

第二重if检测就是我们常见的检测了,为了确保这个数据是最新的,没有改动过的。我们再看trycatch里面的代码块。首先调用ArrayList自身的remove操作去移除lastRet位置的元素,此时一直走到lastRet前面一个位置的cursor自然要退回来,于是lastRet的值赋给了cursor。然后lastRet就重新变成了一个负数,不指向任何的元素,直到cursor移动就又跟在了cursor后面。expectedModCount和modCount同步,因为ArrayList自己的remove操作会改变modCount的值。由于remove了一个元素,所以limit自己也要减小1.

为什么会捕获IndexOutOfBoundsException异常呢?原因很简单,就和之前的next方法一样,如果在我尝试remove的时候,ArrayList里面的数据发生了改动,也就是要移除的元素跑走了。你指着一个10长度的第8个位置,你正打算要把它remove掉,突然它缩短了,原先的10长度只剩下4长度了。而你任然还指着第8个位置要移除它,当然把你看作数组越界了。

从next方法和remove方法中我们发现了一个弊端,虽然它可以很好的避免在操作的时候ArrayList的数据突然变短,但如果ArrayList的数据突然变长呢?接着上面举的例子,当这个10长度变成了15长度,有5个元素被添加到了最前面,而你原先想要操作的第8个位置的元素,突然跑到了第13个位置。那么就不是我们想要的操作结果了。

        @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;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

前面我们就说过Iterator有四个方法,有三个方法是需要实现的,而有一个forEachRemaining方法是已经实现了的。这个方法的作用官方也解释了,对剩余的元素进行指定的操作,传递的是一个Consumer。而这个Consumer是在API24才添加进来的,它的内部是这样的:

Public methods

abstract voidaccept(T t)

Performs this operation on the given argument.

default Consumer<T>andThen(Consumer<? super T> after)

Returns a composed Consumer that performs, in sequence, this operation followed by the after operation.

 

而官方对于它的解释是这样的一句话:

Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects.

This is a functional interface whose functional method is accept(Object).

我们可以这样理解,consumer就是一个指令动作,它有可能只是单个动作”左“,也有可能是一系列的复杂动作”左右右“,所以它才是一种集合的方式。好了,我们知道consumer是啥东西之后就可以开始理解代码了。

首先是判断传递进来的参数是不是仅仅只有一个名字而没有内容,然后获取ArrayList数据的size,用i存储当前指针cursor指向的位置,然后将i和size进行对比,如果i大于等于size,则可以认为已经遍历了所有的数了,没有剩余需要操作的数了,因为所有的元素都指了个遍,相当于所有元素都有被操作到。

然后定义一个变量将ArrayList的数据赋值给它,蛋疼的是名字依然取得是一样的名字elemengData。然后就是和之前两个方法一样的再次检测了,然后进入了while循环,不断的给剩余的元素赋予指令,让它们按照指令动起来。 

当所有的元素都被指过之后,cursor更新自己的位置,将其指在正确的位置上,同时也刷新小跟班lastRet的位置。当所有事情做完之后,再确认一下之前操作的时候是不是最新的数据,如果是最新的数据那就可以放心了,如果不是就直接抛出异常。

总结:

Itr这个内部类并不是最后的迭代器,它还有一个子类,定义在ArrayList内部。所以Itr一些不完善的地方很有可能在子类中得到修复。

除了hasNext方法,其他的方法核心代码其实只有那么几行,这些方法做的最多的就是确保当前操作的数据是最新的数据。

cursor和lastRet的关系很微妙,cursor一开始并没有给它赋值,而lastRet一开始就默认赋予了-1,-1就相当于lastRet的出生点。我们可以通过判断lastRet是否为-1从而判断它是否跟在了cursor后面。remove可以理解为lastRet和它指向的元素同归于尽了,然后lastRet就跑到了出生点去了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值