ArrayList源码解析(三)

本文深入解析了ArrayList中迭代器的实现原理,包括通用迭代器和特定迭代器的使用方法,快速失败机制,以及如何通过subList获取子列表并处理局部列表。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

通用迭代器对象:Iterator()

 public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // 下一个要返回元素的下标
        int lastRet = -1; // 上一个要被返回元素的下标,如果没有,则为-1
        int expectedModCount = modCount;//用来做快速失败检测

        //判断是否还有下一个元素
        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        //获取下一个元素
        public E next() {
            checkForComodification();//快速失败检测,检测expectedModCount跟modCount是否相等,
                                     //判断在遍历期间list是否被改变了结构
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;//cursor移向下一个元素
            return (E) elementData[lastRet = i]; //将lastRet设置为被返回元素的下标
        }

       //删除最近一个被next()返回的元素。
        public void remove() {
            if (lastRet < 0) //由此可知,在调用remove之前必须调用next(),只有next()方法会重置lastRet。
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet); //删除元素
                cursor = lastRet; //回退一步
                lastRet = -1; //lastRet重置为-1,不能多次重复调用remove()
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        //暂时先不管,这个是为Lambda表达式而准备的
        @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();
        }

        //快速失败检测
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

ArrayList的迭代器通过一个实现了Iterator接口的内部类实现。这样很好的隐藏了内部实现细节,由给外部提供了遍历list的方法。而且因为ArrayList不是线程安全的,采用了“快速失败”检测机制来防止多线程环境下:遍历过程中别的线程修改了list结构的问题。对于“快速失败”检测,我们后面再讲。

由此,我在这里说一下遍历一个list的几种方式。

  • for循环遍历(ArrayList推荐,它底层是用数组实现,用下标访问最快。但LinkedList还是用iterator比较好)
for(int i=0;i<list.size();i++){
    System.out.print(list.get(i)+" ");  
}
  • 超级for循环遍历(不推荐,虽然实质上是调用迭代器进行循环,但这个转换过程也是需要时间的)
for(Object o : list){
    System.out.print(o+" ");
}
  • 迭代器遍历(所有collection通用,推荐使用)
Iterator iterator = list.iterator();
while(iterator.hasNext()){
    Object o = iterator.next();
    System.out.print(o+" ");
}

特定迭代器:listIterator() 和 listIterator(int i)

public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

 //ArrayList扩展的迭代器,能够向前向后遍历,还可以添加、设置数据
 private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

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

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

获取子列表:subList(int fromIndex, int toIndex)

我们通常用substring来分割字符串,ArrayList也提供了一个subList来获取子列表。

1. subList得到的只是主list的一个视图

public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);// 范围检查
        return new SubList(this, 0, fromIndex, toIndex); 
    }

subListRangeCheck只是做一些范围检查,重点是 return new SubList(this, 0, fromIndex, toIndex); 它将this传递给了SubList,这表示原始list,记住这点非常重要!

 private class SubList extends AbstractList<E> implements RandomAccess {
        private final AbstractList<E> parent;
        private final int parentOffset;
        private final int offset;
        int size;

        //构造函数
        SubList(AbstractList<E> parent,
                int offset, int fromIndex, int toIndex) {
            this.parent = parent;
            this.parentOffset = fromIndex;
            this.offset = offset + fromIndex;
            this.size = toIndex - fromIndex;
            this.modCount = ArrayList.this.modCount;
        }

        //设置方法
        public E set(int index, E e) {
            rangeCheck(index);
            checkForComodification();
            E oldValue = ArrayList.this.elementData(offset + index);
            ArrayList.this.elementData[offset + index] = e;
            return oldValue;
        }

        //获取get方法
        public E get(int index) {
            rangeCheck(index);
            checkForComodification();
            return ArrayList.this.elementData(offset + index);
        }

        //获取元素数量size
        public int size() {
            checkForComodification();
            return this.size;
        }

        //添加元素
        public void add(int index, E e) {
            rangeCheckForAdd(index);
            checkForComodification();
            parent.add(parentOffset + index, e);
            this.modCount = parent.modCount;
            this.size++;
        }

        // 删除元素
        public E remove(int index) {
            rangeCheck(index);
            checkForComodification();
            E result = parent.remove(parentOffset + index);
            this.modCount = parent.modCount;
            this.size--;
            return result;
        }

        //范围删除
        protected void removeRange(int fromIndex, int toIndex) {
            checkForComodification();
            parent.removeRange(parentOffset + fromIndex,
                               parentOffset + toIndex);
            this.modCount = parent.modCount;
            this.size -= toIndex - fromIndex;
        }

    }

SubList是ArrayList的子类,也继承了AbstractList,并且实现了RandomAccess接口。也拥有常规list中的添加、删除、设置值等方法。
但是它的构造函数有点特殊,在该构造函数中有两个地方需要注意:
1、this.parent = parent;而parent就是在前面传递过来的list,也就是说this.parent就是原始list的引用。
2、this.offset = offset + fromIndex;this.parentOffset = fromIndex;。同时在构造函数中它甚至将modCount(fail-fast机制)传递过来了: this.modCount = ArrayList.this.modCount 。

我们再看get方法,在get方法中

 return ArrayList.this.elementData(offset + index);

这段代码可以清晰表明get所返回就是原列表offset + index位置的元素。同样的道理还有add方法里面的:

 parent.add(parentOffset + index, e);

也同样表明SubList的操作实质上是对原始list的操作,都会反映到原始list中。

2. subList生成子列表后,不要试图去操作原列表

SubList中有大量的

 checkForComodification();

在add,remove,set,get,size等方法中。都有对checkForComodification()的调用。
而这个方法具体是干什么呢。

private void checkForComodification() {
            if (ArrayList.this.modCount != this.modCount)
                throw new ConcurrentModificationException();
        }

判断主list的modCount是否与子list的modCount相等。
这都表明在生成子列表之后,如果再去操作主list,并对主list产生了结构性的修改(增加删除元素),那么对子list的所有操作都会抛出ConcurrentModificationException异常。SubList把主ArrayList的”快速失败”机制也一起带过来了。

对于子列表视图,它是动态生成的,生成之后就不要操作原列表了,否则必然都导致视图的不稳定而抛出异常。最好的办法就是将原列表设置为只读状态,要操作就操作子列表:

/通过subList生成一个与list一样的列表 list1
List<Integer> list1 = list1.subList(0, list.size());  

//对list设置为只读状态  
list = Collections.unmodifiableList(list);  
//需要将Collections.unmodifiableList(list);的返回值重新赋值给list,光执行Collections.unmodifiableList(list); 是不起作用的。

3. 推荐使用subList处理局部列表

在开发过程中我们一定会遇到这样一个问题:获取一堆数据后,需要删除某段数据。例如,有一个列表存在1000条记录,我们需要删除100-200位置处的数据,可能我们会这样处理:

for(int i=0;i<list.size();i++){
    if(i>=100 || i<=200){
        list.remove(i);
        //这个代码当然是错误的。当删除第100个元素时,第101个元素会自动往前挪一位,它的索引变成100。
        //下一次循环remove(101)实际上删除的是原来102位置上的数据。
        //这也是在for循环中不要删除元素的原因。这里只是举个例子说明
    }
}

这是大部分人的处理方式。还有一种更简洁有效的方法

list.subList(100,200).clear();

简洁优雅有效!

参考: http://blog.youkuaiyun.com/chenssy/article/details/44102915

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值