【问题标题】:Accuracy of a Set with a HashMap backing's contains() method?具有 HashMap 支持的 contains() 方法的 Set 的准确性?
【发布时间】:2012-09-01 21:33:44
【问题描述】:

您好,我正在使用由 HashMap 支持的 Set 来跟踪我已经在图中遍历了哪些边。 我正计划通过添加存储在每个边缘中的数据的哈希码的结果来键入集合。

v.getData().hashCode() + wordV.getData().hashCode()

但是当使用 contains 来检查边缘是否在集合中时,这有多可靠?难道我不能假设得到误报吗?有没有办法克服这个问题?

让我担心的确切说法是:

edgeSet.contains(v.getData().hashCode() + wordV.getData().hashCode())

谢谢!

哦,顺便说一句,我正在使用 Java。

编辑:

我应该在问题中说明这一点。在我的图中,没有边对象,有顶点对象,每个顶点对象都包含更多顶点对象的列表,这就是边。因此,我想结合您的回答得出的问题是:

我可以使用 Set 来存储对信息的引用而不是对象......吗? 即我可以存储为顶点的数据对象添加两个哈希码的结果吗?

EDIT2:

我确实在为我的 hashmap 使用 Java 库,我声明如下:

Set<Integer> edgeSet = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());

【问题讨论】:

    标签: java hashmap set hashcode hash-code-uniqueness


    【解决方案1】:

    注意:根据您的问题,我无法判断您使用的是HashSet,还是您自己的家庭滚动实现。请注意,Java 的 HashSet 只是 HashMap 的包装器,其中的值被忽略。 HashSet.contains 只是在内部地图上调用containsKey

    HashMap.containsKey 使用与get 相同的查找。这将计算哈希并使用它来找到正确的存储桶。从那里它将遍历桶并使用equals,直到找到完全匹配。假设元素类型正确实现了hashCodeequals,那么使用containsKey 就不可能得到误报,因为最终使用equals 进行确认。

    relevant source code 在包私有方法getEntry 中,containsKeyget 都使用该方法:

    final Entry<K,V> getEntry(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }
    

    编辑:

    我可以使用 Set 来存储对信息的引用,而不是 对象……?即我可以存储添加两个哈希码的结果吗 对于顶点的数据对象?

    不,您需要实现一个表示此信息的新类并将其实例存储在Set 中。这可能是一个简单的 POJO,每条信息都有一个字段,并且 hashCodeequals 被正确覆盖。

    【讨论】:

    • 好的,明白了。我想我需要让自己成为一个包装类......谢谢!
    • @Ethan - 是的,没错。以防万一:不要试图通过在实例化后更改它们来重用这些对象。 HashMap 不会知道重新计算哈希值。最佳做法是创建这个新课程immutable
    【解决方案2】:

    根据定义,哈希码会发生冲突。将它们加在一起没有任何帮助。

    你应该让你的图的边支持 hashCode 和 equals,然后简单地把边放在一个哈希集中。

    class Edge { ... equals and hashCode ... }
    
    HashSet<Edge> traversed = new HashSet<Edge>();
    traversed.add(edge);
    ...
    if(traversed.contains(edge)) ...
    

    如果您要对边进行编号,则 Integer 已经具有良好的哈希码和等于,因此请使用它:

    HashSet<Integer> traversed = new HashSet<Integer>();
    if(traversed.contains(edgeNumber)) ...
    traversed.add(3);
    

    【讨论】:

    • 我实际上没有 Edge 对象,我在每个顶点都有一个顶点列表
    • 答案仍然有效...您可以选择创建一个 Edge 类,或者您可以尝试以其他方式组合您的顶点...一般来说,为了清晰起见。如果你的空间很紧,你可以把东西打包成一个长的,但这将是一种罪过,除非你实际测试了它与一个很好的 Edge 抽象的工作情况……它当然很容易编写。跨度>
    • BTW gnu.trove 有一些不错的高性能集合,如果你最终想要存储原语。
    【解决方案3】:

    只要同时覆盖hashCode()equals(),就可以了。哈希码永远不能保证是唯一的。也就是说,您有点滥用 Set。通过使用正确实现的hashCode()equals() 方法存储类,contains()' 等方法将具有完美的准确性。但是,这不是您在这里使用它的方式。听起来您几乎正在构建自己的数据结构/集合,因此您需要考虑以与“哈希”集合相同的方式执行此操作-使用 HashMap 存储哈希的存储桶-哈希值作为键,然后是要比较的值的集合。这将允许您快速查看父映射是否甚至具有您正在寻找的哈希值。如果没有,你就完成了(假)。如果是,那么您需要确认其“存储桶”具有您正在寻找的特定值(true)。

    【讨论】:

      猜你喜欢
      • 2012-10-31
      • 1970-01-01
      • 1970-01-01
      • 2018-02-14
      • 2021-08-26
      • 2014-07-29
      • 1970-01-01
      • 2018-08-30
      • 1970-01-01
      相关资源
      最近更新 更多