List和Set集合中iterator的fail-fast特性之區別

剛纔看到一個問題,關於List和Set集合中iterator的fail-fast特性

先上代碼:

List集合:

public class Test {
	public static void main(String[] args){
		List<String> L = new LinkedList();
		L.add("aaa");
		L.add("bbb");
		L.add("ccc");
		System.out.println(L);
		String delete = "bbb";
		for(Iterator<String> iter=L.iterator();iter.hasNext();){
			String now = iter.next();
			System.out.println(now);
			if(now==delete)
				L.remove(delete);
		}
		System.out.println(L);
	}
}

運行無異常,List在用iterator遍歷過程中,可以用List的remove方法刪除其最後兩個元素任意一個(不能同時刪除吐舌頭

Set集合:

public class Test {
	public static void main(String[] args){
		Set<String> S = new LinkedHashSet();
		S.add("aaa");
		S.add("bbb");
		S.add("ccc");
		System.out.println(S);
		String delete = "bbb";
		for(Iterator<String> iter=S.iterator();iter.hasNext();){
			String now = iter.next();
			System.out.println(now);
			if(now==delete)
				S.remove(delete);
		}
		System.out.println(S);
	}
}

當在用iterator遍歷過程中,用Set的remove方法刪除倒數第二個元素時運行出現異常:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.LinkedHashMap$LinkedHashIterator.nextNode(Unknown Source)
at java.util.LinkedHashMap$LinkedKeyIterator.next(Unknown Source)
at nowcoder.Test.main(Test.java:18)

用Set的remove方法刪除倒數第1個元素

public class Test {
	public static void main(String[] args){
		Set<String> S = new LinkedHashSet();
		S.add("aaa");
		S.add("bbb");
		S.add("ccc");
		System.out.println(S);
		String delete = "ccc";
		for(Iterator<String> iter=S.iterator();iter.hasNext();){
			String now = iter.next();
			System.out.println(now);
			if(now==delete)
				S.remove(delete);
		}
		System.out.println(S);
	}
}
運行正常:

[aaa, bbb, ccc]
aaa
bbb
ccc
[aaa, bbb]

終於到了解決問題的時候了。。。

1、首先找到List和Set各自的iterator實現源代碼

List:LinkedList->AbstractSequentialList找到

/**
     * Returns an iterator over the elements in this list (in proper
     * sequence).<p>
     *
     * This implementation merely returns a list iterator over the list.
     *
     * @return an iterator over the elements in this list (in proper sequence)
     */
    public Iterator<E> iterator() {
        return listIterator();
    }</span>
接着找。。。到AbstractList找到(由於太長,已刪除無關代碼)

    /**
     * {@inheritDoc}
     *
     * <p>This implementation returns {@code listIterator(0)}.
     *
     * @see #listIterator(int)
     */
    public ListIterator<E> listIterator() {
        return listIterator(0);
    }
    public ListIterator<E> listIterator(final int index) {
        rangeCheckForAdd(index);

        return new ListItr(index);
    }

    private class Itr implements Iterator<E> {
        /**
         * Index of element to be returned by subsequent call to next.
         */
        int cursor = 0;

        int expectedModCount = modCount;

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

        public E next() {
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

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

    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            cursor = index;
        }
    }
由上面代碼可以看出當List用iterator遍歷到倒數第二個元素時(已調用next方法),cursor=2,用remove方法刪除會使得size()減1變爲2,於是當iter調用hasNext方法時,cursor==size(),返回false,結束遍歷,無異常。

當List用iterator遍歷到倒數第一個元素時(已調用next方法),cursor=3,用remove方法刪除會使得size()減1變爲2,於是當iter調用hasNext方法時,cursor!=size(),本應返回true,返回的卻是false???結束遍歷,無異常。我也不知爲什麼。。。求解


Set:尋找路徑:LinkHashSet->HashSet

    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

->HashMap$KeySet->HasMap$KeyIterator

final class KeyIterator extends HashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().key; }
    }

->HashMap$HashIterator

abstract class HashIterator {
        Node<K,V> next;        // next entry to return
        Node<K,V> current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot

        HashIterator() {
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                do {} while (index < t.length && (next = t[index++]) == null);//指向第一個不爲空的元素
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);//<span style="font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif;">指向下一個不爲空的元素</span>
            }
            return e;
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

從上面代碼中可以看出,當Set用iterator遍歷到倒數第二個元素時(已調用next方法),next(Node類型)指向倒數一個元素(節點),不爲null,用remove方法刪除會使得modCount加一而expectedModCount不變於是

modCount = expectedModCount+1 =》 modCount != expectedModCount

當iter調用hasNext方法時,next!=null ,返回true,調用next方法,即調用nextNode方法,拋出異常。


如果是在遍歷到倒數第一個元素時,next=null,所以無論你幹什麼都可以,反正下一次hasNext會返回false。

這樣看來,只要不是在遍歷倒數第一個元素時,修改Set,都會使得modCount != expectedModCount,從而拋出異常。










發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章