记录一次HashSet的iterator.remove()方法不生效,不能删除元素的原因。

首先说明业务场景:

    对公司数据的driver增加离线节点上线重连的功能。大致功能需求,在线连接中有 节点1、节点2、节点3······,这时节点2离线了,将节点2移除在线连接池,加入离线节点池,然后定时访问离线节点池,尝试重新连接。由于driver连接自身没有保存连接的uri信息,所以我对driver连接自己封装了一层,大致如下:

原driver连接新建方式

Driver driver = XXX.driver(uri, db, username, password);

封装的Driver:

package com.xxx.xxx.xxx;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
 * <p> 对xxx封装一层,增加uri属性 <p>
 *
 * @author: chen
 * @create: 2021-04-27
 **/
@Slf4j
@Data
public class DriverAgent {

    /**
     * bolt地址
     */
    private String uri;

    private Driver driver;

    private String db;

    private String username;

    private String password;

    public DriverAgent () {
    }

    public DriverAgent (String uri, String db, String username, String password) {
        this.uri = uri;
        this.db= db;
        this.username = username;
        this.password = password;
        this.driver= xxx.driver(uri, db, username, password);
    }

    public Boolean reconnect(){
        try {
            this.graph =  xxx.driver(uri, db, username, password);
            log.info(uri + " has reconnected");
        }catch (Exception e){
            log.error("reconnection Failed:" + e.getMessage());
            return false;
        }
        return true;
    }

}

坑就出现在这里,首先这里我使用了lombok的@Data注解,它会生成以下方法

  • 所有属性的get和set方法
  • toString 方法
  • hashCode方法
  • equals方法

注意,重写了hashCode方法和equals方法。

出现的问题:使用HashSet作为离线节点存储的容器,重连时使用迭代器遍历HashSet,调用reconnect方法,返回成功后调用iterator.remove()将当前节点从离线节点池中移除。但是测试过程中发现离线的节点重连成功后,iterator.remove()并未将其从HashSet中移除。

singleThreadExecutor.execute(() -> {
                while (true){
                    Iterator<DriverAgent> iterator = offlineNodes.iterator();
                    while (iterator.hasNext()){
                        DriverAgent agent = iterator.next();
                        if(agent.reconnect()){
                            agentList.add(agent);
                            iterator.remove();
                        }
                    }
                    try {
                        TimeUnit.SECONDS.sleep(60);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });

经过阅读源码发现,HashSet实现借助的是HashMap,HashSet的Iterator获取的是HashMap.keySet().Iterator()。其返回的是一个EntryIterator,而EntryIterator继承自HashIterator,HashIterator的remove()方法是通过调用HashMap的removeNode方法实现的。

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()方法源码

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

可以看到,方法中会通过调用hashCode方法和equals方法去判断当前元素是不是要被删除的元素,而在前面,这两个方法是被重写过的,重写过程中,两个方法的返回值会与各个属性值挂钩,所以这里在我调用了reconnect方法后,如果通过hashCode和equals来比较两个类,其实已经不是同一个类了,因此导致iterator.remove()找不到要删除的元素。

上一篇:vue语法01


下一篇:c++(list.remove(xxx);删除自定义类型)