实验踩坑记录:Java迭代器iterator无法remove、remove失败原因以及Iterator源码分析

实验踩坑记录:Java迭代器iterator无法remove、remove失败原因以及Iterator源码分析

第一个坑

在软件构造Lab2实验过程中遇到一个问题,调用Set的iterator.remove函数时无法删除,且未报错,但是单步运行时发现所比较对象时完全相同的,根据重写的equals()函数也可判断相同,以下为原代码:

 	public static boolean removePiece(Player player, Piece piece){
        Iterator iterator = player.pieces.iterator();
        while (iterator.hasNext()){
            if (piece.equals(iterator.next())) {
                iterator.remove();
                return true;
            }
        }
        return false;
    }

经单步运行时注意到一个细节,由于未重载hashCode函数,本该相同的iterator.next()和piece的hashCode不同,根据如下hash规则:
如果hash相同,对象不一定相等;但是如果hash不同,则对象一定不相等。
因此在不重载hashCode的前提下,重新修改该方法为:

    public static boolean removePiece(Player player, Piece piece){
        Iterator iterator = player.pieces.iterator();
        while (iterator.hasNext()){
            Piece piece1 = (Piece) iterator.next();
            if (piece1.equals(piece)) {
                iterator.remove();
                return true;
            }
        }
        return false;
    }

即可正确remove。

第二个坑

此处是复现之前实验踩到的,代码如下:

    public static void main(String[] args){
        Set<Integer> set = new HashSet<Integer>();
        for (int i = 0; i < 10; i++) {
            set.add(i);
        }
        Iterator<Integer> iterator = set.iterator();
        while (iterator.hasNext()) {
            int temp = iterator.next();
            if (temp == 6) {
                //着重注意这一步
                set.remove(temp);
                //正常应该是 iterator.remove()
            }
        }
    }

报错信息如下:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)
	at java.util.HashMap$KeyIterator.next(HashMap.java:1466)
	at Test.main(Animal.java:11)

接下来首先探究iterator实现原理:

首先HashSet内部实现使用了HashMap,而HashMap的实现结构是由数组+链表+红黑树实现的(数据量较大时采用红黑树实现),不做过多深究,主要探究关于Iterator的部分,查看HashSet.remove()源码:

    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

查看removeNode源码:

    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            else if ((e = p.next) != null) {
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                //重点是这一步!!!!!!
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

再查看报错处:Iterator.next的代码:

        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);
            }
            return e;
        }

可以看到是由于modCount和expectedModCount不同而报错,接下来探究为什么使用Iterator.remove()不会报错,而使用object.remove()会报错,Iterator.remove()代码如下:

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

可以看,代码中标注的一部分,在removeNode删除元素后将expectedModCount重新赋值为modCount。

所以表面原因就懂了,同样是调用了removeNode方法,object.remove()方法在调用后将expectedModCount重新赋值,而Iterator.remove()重新赋值了,在Iterator.next()方法中检测两值不等时就会报错。

那么Iterator为什么要使用modCount和expectedModCount这种机制完成Iterator呢?

JDK中对modCount的解释如下:

The number of times this list has been structurally modified. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.

也就是说,modCount记录修改此列表的次数:包括改变列表的结构,改变列表的大小,打乱列表的顺序等使正在进行迭代产生错误的结果。

查找网络,得知这是一种名为“Fail-Fast(快速失败)”的机制,这种机制提供迭代过程中集合的安全性。

从源代码里可以看到增删操作都会使modCount++,通过和expectedModCount的对比,然而使用迭代器进行操作,迭代器内部可知其变化,因此将expectedModCount重新赋值。这种不对称的增删改查方式(其实没有增,Iterator不支持增加)使得迭代过程中对发生不合法的增加删除等操作快速响应报错。

你可能感兴趣的:(软件构造)