HashSet.add()方法解析

这篇文章主要是记录学习HashSet时关于add()方法的解析,以便个人理解HashSet大概的工作原理,仅作参考。

 

范例一:

TestObj类的任意实例对象a, b, a.equals(b)方法都返回true,并且a.hashcode() == b.hashcode(),即hashcode值一样。

/**
 * TestObj类的任意实例对象a, b, a.equals(b)方法都返回true
 * 并且a.hashcode() == b.hashcode(),即hashcode值一样。
 */
public class TestObj{
	String value ;
	public TestObj(String val) {
		value = val;
	}
	@Override
	public boolean equals(Object obj) {
		return true;
	}
	@Override
	public int hashCode() {
		
		return 1;
	}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return value;
	}
}

测试代码块:

TestObj obj = new TestObj("abc");
TestObj obj2 = new TestObj("def");
Set<TestObj> hashSetObj = new HashSet<>(); 
hashSetObj.add(obj);
hashSetObj.add(obj2);
System.out.println(hashSetObj.size());

将obj1和obj2放入HashSet容器中,容器的大小是1,而不是2。

 

范例二

TestObj类的任意实例对象a, b, a.equals(b)方法表现与a.value.equals(b.value)一致并且a.hashcode() == b.hashcode(),即hashcode值一样。

/**
 * TestObj类的任意实例对象a, b, a.equals(b)方法表现与a.value.equals(b.value)一致
 * 并且a.hashcode() == b.hashcode(),即hashcode值一样。
 */
public class TestObj{
	String value ;
	public TestObj(String val) {
		value = val;
	}
	@Override
	public boolean equals(Object obj) {
        if (obj == null) return false;
        if (obj instanceOf TestObj) {
            TestObj instance = (TestObj)obj;
            return value.equals(instance.value);
        }
		return false;
	}
	@Override
	public int hashCode() {
		
		return 1;
	}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return value;
	}
}

测试代码:

TestObj obj = new TestObj("abc");
TestObj obj2 = new TestObj("def");
Set<TestObj> hashSetObj = new HashSet<>(); 
hashSetObj.add(obj);
hashSetObj.add(obj2);
System.out.println(hashSetObj.size());

将obj1和obj2放入HashSet容器中,容器的大小是2,而不是1。

 

解析

在没有深入了解HashSet的源码之前,错误的认为:向HashSet容器添加一个新对象B,如果能够在容器中找到一个对象A满足条件A.hashcode == B.hashcode, 那么就不会往容器中放入B。下面来看看HashSet add()方法的源码。

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

这里的mapPRESENT是什么东西呢?

map是在HashSet中声明的,即 HashSet容器的底层是用HashMap来存储数据的,向容器中添加的元素e (泛型E的实例)作为key,  PRESENT作为value。这里的PRESENT是一个虚拟的值,用来关联一个对象,即PRESENT是用来帮助填充value的,没有实际意义。map容器中所有节点的value都指向 PRESENT。

 private transient HashMap<E,Object> map;

 // Dummy value to associate with an Object in the backing Map
 private static final Object PRESENT = new Object();

继续看看HashMap的 put(K key, V value)方法

 public V put(K key, V value) {
     return putVal(hash(key), key, value, false, true); // hash(key)得到Key的hash值
 }


 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
         // 如果容器为空,就初始化这个容器(给容器分配一个默认的大小DEFAULT_INITIAL_CAPACITY)
            n = (tab = resize()).length; 
        // (n - 1) & hash 是根据key的hash值来判断,元素放到哪个桶(Node)中
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            // 如果对应的桶不为空
            // p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))
            // 上面的代码是比较p节点的hash值与需要放入的元素e的hash,以及e与p.key是否相等(e            
            // 在map容器节点中作为键(key))
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                // 两个条件同时满足就不会将e放入容器中
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                // 二者其一不满足,就创建一个新节点,放在p节点后即p.next = newNode
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // 如果容器中的节点数达到了容器设定的阈值,就需要扩容。
                        // 这样设定是让容器有更好的性能。
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

结论:

向HashSet中放入元素e时,不仅需要比较hash值,而且需要比较e是否等于对应节点的key。两者同时满足时,e不会放入容器中,否则将e放入容器中。参考代码:p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))

同理,HashMap也是如此。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
解释这段代码 String docDatumId = docDatumDTO.getDocDatumId(); // 所有的ai结果都为通过 List<TAiStdAuditResult> tAiStdAuditResults = aiStdAuditResultDao.listByDocDatumIds(Lists.newArrayList(docDatumId)); log.info("tAiStdAuditResults查询结果:{}",JSONObject.toJSONString(tAiStdAuditResults)); //不通过的 if (CollectionUtils.isEmpty(tAiStdAuditResults)) { return false; } List<TAiStdAuditResult> finalNoPassAiStdAuditResultList = new ArrayList<>(); Set<String> notThisFileSet = new HashSet<>(); Set<String> finalNoPassAiStdAuditResultSet=new HashSet<>(); tAiStdAuditResults.stream().forEach(item -> { String auditResultStr = item.getAuditResult(); Map auditResultMap = null; try { auditResultMap = JsonUtils.toObject(auditResultStr, Map.class); } catch (Exception e) { log.error(e.getMessage(), e); } if (!MapUtils.isEmpty(auditResultMap) && auditResultMap.get(AiStdCommonCons.AUDIT_RESULT) != null && AIAuditResultValueEnum.SPLIT_PASS.getCode().equals(auditResultMap.get(AiStdCommonCons.AUDIT_RESULT))) { otherPassDocFileIdList.add(item.getDocFileId()); }else if(!MapUtils.isEmpty(auditResultMap) && auditResultMap.get(AiStdCommonCons.AUDIT_RESULT) != null && AIAuditResultValueEnum.NOT_THIS_FILE.getCode().equals(auditResultMap.get(AiStdCommonCons.AUDIT_RESULT))){ finalNoPassAiStdAuditResultList.add(item); notThisFileSet.add(item.getDocFileId()); } else if (MapUtils.isEmpty(auditResultMap) || !AIAuditResultValueEnum.PASS.getCode().equals(auditResultMap.get(AiStdCommonCons.AUDIT_RESULT))) { finalNoPassAiStdAuditResultList.add(item); finalNoPassAiStdAuditResultSet.add(item.getDocFileId()); } });
最新发布
07-14

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值