ArrayList源码分析(二)

今天来进行ArrayList中关于迭代器方面的相关分析。

Iterator

初始化以及内部属性详情

复制代码
    public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.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;

        // prevent creating a synthetic constructor
        Itr() {}
    
    //其它代码
    
    }
复制代码

关于迭代器,可以从以上代码看出其内部并没有定义实际的数据之类的,只定义了几种方法,如cursor,lastRet,expectedModCount,用来提供迭代ArrayList对象的机制。

next方法

复制代码
        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]; } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException();    //当被并发修改时抛出异常 }
复制代码

next 方法是实际用来迭代的方法,返回迭代的元素,并且让指针指向下一个元素。在进行一开始的检测后(如上注释所示),“if (i >= size)” 用来判断是否迭代完成,之后的“if (i >= elementData.length)”,因为到这里的前提是i < size,若满足i >= elementData.length这个条件说明size > elementData.length,抛出并发修改异常。之后cursor+1,将之前的cursor值赋给lastRet。

remove方法

 

复制代码
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();
            }
        }
复制代码

 

首先检查lastRet是否小于0,lastRet小于0,要么是还没开始迭代时,为-1,要么是已经调用过一次remove,会被再次设置为-1(后面代码就是)。这也可以发现,没办法连续两次调用remove。接着检查并发修改。之后调用ArrayList对象自己的remove方法来进行删除,然后更新cursor和lastRet的值:cursor设置为lastRet,也就是自减了1,由于ArrayList的remove会把删除元素后面的元素都往前移一位,所以cursor对应的元素仍然没变。最后会设置迭代器的expectedModCount,因为ArrayList的remove会修改modCount值。所以我们发现,如果在用迭代器迭代元素时,想删除元素,可以调用迭代器的remove方法,这不会导致后面的迭代抛出异常;如果调用ArrayList的remove,会导致expectedModCount和modCount值不一致,迭代器就无法再使用了。
 

forEachRemaining方法

复制代码
public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i < size) {
                final Object[] es = elementData;
                if (i >= es.length)
                    throw new ConcurrentModificationException();
                for (; i < size && modCount == expectedModCount; i++)
                    action.accept(elementAt(es, i));
                // update once at end to reduce heap write traffic
                cursor = i;
                lastRet = i - 1;
                checkForComodification();
            }
        }
复制代码

关于此部分代码,关键还是在for循环这块,一开始将cursor赋给i,故此循环的范围为cursor到size-1,循环中进行的操作为action.accept(elementAt(es, i)),我们可以点accept进去看下,其为Consumer函数式接口中的一个抽象方法。

我们可以这样调用 forEachRemaining :iter.forEachRemaining(System.out::println);,这样就可以快速的打印iter里剩下的元素,其实是用方法引用来代替实现了Consumer接口的类的对象,也可以自行编写lambda表达式,当然,也可以定义一个Consumer接口的实现类。

 

ListIterator

初始化以及内部属性详情

复制代码
private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
}

public ListIterator<E> listIterator(int index) {
        rangeCheckForAdd(index);
        return new ListItr(index);
}

public ListIterator<E> listIterator() {
        return new ListItr(0);
}
复制代码

如上所示,ListIterator根据指定的index进行迭代。

previous方法

复制代码
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方法大部分代码相同,本方法返回的是cursor指向元素的前一个元素,正如其名。

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();
            }
}
复制代码

set方法是用来进行替换操作的方法,要求lastRet >= 0,当然也有并发修改的相关的检测。

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();
            }
}
复制代码

调用ArrayList自己的add之后,会让cursor自增一个,由于add 会把index之后的元素都向后挪一位,所以cursor还是指向它之前指向的元素,最后设置expectedModCount。

SubList

SubList的执行结果是获取ArrayList的一部分,返回的是ArrayList的部分视图。

初始化以及内部属性详情

复制代码
public List<E> subList(int fromIndex, int toIndex) {
     //fromIndex---截取元素的起始位置,包含该索引位置元素
     //toIndex-----截取元素的结束位置,不包含该索引位置元素 subListRangeCheck(fromIndex, toIndex, size);
return new SubList<>(this, fromIndex, toIndex); } private static class SubList<E> extends AbstractList<E> implements RandomAccess { private final ArrayList<E> root; private final SubList<E> parent; private final int offset; private int size; /** * 从ArrayList创建SubList. */ public SubList(ArrayList<E> root, int fromIndex, int toIndex) { this.root = root; this.parent = null; this.offset = fromIndex; this.size = toIndex - fromIndex; this.modCount = root.modCount; } /** * 从SubList再创建SubList. */ private SubList(SubList<E> parent, int fromIndex, int toIndex) { this.root = parent.root; this.parent = parent; this.offset = parent.offset + fromIndex; this.size = toIndex - fromIndex; this.modCount = parent.modCount; }
复制代码

由于SubList继承了AbstractList类,所以它自己也有一个modCount属性;可以选择由ArrayList或者subList来创建一个新的subList。首先看由ArrayList初始化subList,毕竟你需要有第一个subList,才能用subList初始化出其它的subList。可以看到,这里把root 设置为这个ArrayList对象本身,parent设置为null,然后分别设置offset,size和modCount。

再看由SubList初始化SubList,可以发现,root还是那个root,不管你subList嵌套了多少层,parent就是此subList上面一层,offset就是此subList相对于原始ArrayList的偏移量,层层叠加,size就是subList的长度,modCount和parent的保持一致。

get方法

public E get(int index) {
         Objects.checkIndex(index, size);
         checkForComodification();
         return root.elementData(offset + index);
}

get方法较为简单,首先还是老样子,进行相关检测,如同步检测,越界检测,然后以offset + index作为索引的下标返回,offset相当于偏移量。

add方法

复制代码
public void add(int index, E element) {
            rangeCheckForAdd(index);
            checkForComodification();
            root.add(offset + index, element);
            updateSizeAndModCount(1);
}
private void updateSizeAndModCount(int sizeChange) {    
SubList<E> slist = this;
   //改变此层及以上的size和modCount
do {
slist.size += sizeChange;
slist.modCount = root.modCount;
slist = slist.parent;
} while (slist != null);
}
 
复制代码

该方法主要还是靠调用root的add即ArrayList本身的add的方法,主要还是看最后的updateSizeAndModCount(1),递归的改变这层和以上层的size和modCount,从这也可以发现,此层以下的subList就不管了,所以如果subList嵌套了许多层,需要用subList进行结构性修改的话,最好用最下面那层来改,不然,下面的subList就都废掉了。

还有其他方法,但大都大同小异,就不具体分析了。最后关于迭代器中的for-each循环, 其实就是新建了一个迭代器,不断进行hasNext()next()的调用。

就这样吧,这些源码分析主要还是自用

 

 

 

 

 

posted @   NOMAD冰  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示