Java集合分析(1):Iterator(迭代器)

集合框架在开发中经常用到,但一般情况下,我们只限于会用,却不知道其原理,用的过程会发生什么异常,我们也不知所然。所以我觉得花点时间研究研究集合框架还是很好的。

首先我们先看看集合框架的类图:

Java集合分析(1):Iterator(迭代器)_第1张图片

从上面的集合框架图可以看到,Java集合框架主要包括两种类型的容器:一种是Collection,另一种是Map,而 我们又能看到Collection 又继承了 Iterable< E > 接口,所以本节我们先学习下Iterable的用法以及原理。

Iterator(迭代器)

迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。

这里要知道迭代器指向的位置是元素之前的位置,如下图所示:

Java集合分析(1):Iterator(迭代器)_第2张图片

当使用语句Iterator it=List.Iterator()时,迭代器it指向的位置是Iterator1指向的位置,当执行语句it.next()之后,迭代器指向的位置后移到Iterator2指向的位置。

 Java中的Iterator功能比较简单,并且只能单向移动:

  (1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。

  (2) 使用next()获得序列中的下一个元素。

  (3) 使用hasNext()检查序列中是否还有元素。

  (4) 使用remove()将迭代器新返回的元素删除。
  
下面我们看这个例子:

import java.util.ArrayList;
import java.util.Iterator;
public class Test {
        public static void main(String[] args) {
            ArrayList list = new ArrayList();
            list.add("a");
            list.add("b");
            list.add("c");
            Iterator it = list.iterator();
            while(it.hasNext()){
                String content = (String) it.next();
                System.out.println(content);
            }
        }
}

输出结果是:a b c

可以看到,Iterator通过next()遍历整个List,并取出存储的值。

但是,具体是怎么实现的呢?

这里我们来看看Java里ArrayList实现Iterator的部分源代码:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

  ...

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return true (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }  

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

     public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        modCount++;
        E oldValue = (E) 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;
    }

    /**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     */
     public void clear() {
            modCount++;

            // clear to let GC do its work
            for (int i = 0; i < size; i++)
                elementData[i] = null;

            size = 0;
        }

    /**
     * Returns an iterator over the elements in this list in proper sequence.
     *
     * 

The returned iterator is fail-fast. * * @return an iterator over the elements in this list in proper sequence */ public Iterator iterator() { return new Itr(); } /** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { // 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(Consumersuper 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(); } } }

从上面源码可以看到,我们在调用迭代器的 next,remove 方法时,都会比较 expectedModCount 和 modCount 是否相等,如果不相等就会抛出 ConcurrentModificationException ,另外 modCount 在 add, clear, remove 时也都会被修改。

下面我们在第一个例子基础上添加一条语句:

import java.util.ArrayList;
import java.util.Iterator;
public class Test {
        public static void main(String[] args) {
            ArrayList list = new ArrayList();
            list.add("a");
            list.add("b");
            list.add("c");
            Iterator it = list.iterator();
            while(it.hasNext()){
                String content = (String) it.next();
                System.out.println(content);
                list.add("as");
            }

        }
}

运行结果:
Java集合分析(1):Iterator(迭代器)_第3张图片

我们可以看到程序抛出java.util.ConcurrentModificationException异常,而根据上面的分析,导致异常产生的原因很可能就是modCount 和 expectedModCount不相等导致的。这从而就产出了fail-fast机制。

fail-fast机制,它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。

因此我们知道了 fail-fast 即 ConcurrentModificationException 出现的原因,怎么解决呢?

方案一:在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList,这样就可以解决。但是不推荐,因为增删造成的同步锁可能会阻塞遍历操作。

方案二:使用CopyOnWriteArrayList来替换ArrayList。

CopyOnWriteArrayList所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。

该类产生的开销比较大,但是在两种情况下,它非常适合使用。1:在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时。2:当遍历操作的数量大大超过可变操作的数量时。

CopyOnWriterArrayList根本就不会产生ConcurrentModificationException异常,也就是它使用迭代器完全不会产生fail-fast机制。

 public synchronized boolean add(E e) {
        Object[] newElements = new Object[elements.length + 1];
        System.arraycopy(elements, 0, newElements, 0, elements.length);
        newElements[elements.length] = e;
        elements = newElements;
        return true;
    }

因为CopyOnWriterArrayList在写入时copy了原来的array,然后在这个 copy 的数组上进行add操作,这样就不会影响原来的迭代。不过这样造成的代价就是产生大量的对象,同时数组的copy也是相当有损耗的.

最后我们看看 Iterator 的使用:
其实上面列子已经给出了,
通常情况我们都会用for循环来遍历集合:

    for(int i=0; i< ..;i++){  
        // ... 
    } 

如果用Iterator 遍历集合:

  Iterator iterator = list.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }

参考:
http://www.cnblogs.com/hasse/p/5024193.html

http://www.cnblogs.com/pipi-style/p/4738099.html

你可能感兴趣的:(java集合)