【问题标题】:Is there a data structure that only stores hash codes and not the actual objects?是否存在只存储哈希码而不存储实际对象的数据结构?
【发布时间】:2019-08-07 00:22:05
【问题描述】:

我的用例是我正在寻找 Java 中的数据结构,它可以让我查看内部是否存在具有相同哈希码的对象(通过调用 contains()),但我永远不需要遍历元素或检索实际对象。 HashSet 很接近,但据我了解,它仍然包含对实际对象的引用,这会浪费内存,因为我永远不需要实际对象的内容。我能想到的最佳选择是仅存储哈希码的整数类型的 HashSet,但我想知道是否有一个内置的数据结构可以完成同样的事情(并且只接受一种类型而不是 HashSet类型 Integer 将接受任何对象的哈希码)。

【问题讨论】:

  • 你的哈希函数完美吗?或者你可以有多个具有相同哈希值的对象?
  • 哈希冲突怎么办?
  • HashSet 将包含对您的对象的引用,而不是副本,因此不必担心空间问题。 HashSet<Integer> 可能会占用更多空间,因为它引用了整数。
  • 我同意@Sweeper 的观点,除非你真的需要超级骗子优化。此外,您将哈希码存储为整数的第二个想法不会更有效,因为它会存储哈希+哈希的哈希。
  • @Sweeper HashSet 在内部使用 HashMap。内存空间是一样的。

标签: java hashset


【解决方案1】:

Bloom filter 可以判断一个对象可能是成员,还是绝对不是成员。您可以控制误报的可能性。每个哈希值映射到一个位。

Guava 库提供an implementation in Java

【讨论】:

  • 不错。这似乎是非常低存储开销的解决方案。但是您必须担心假阴性情况。如果你能在统计上消除它,那就太好了!
  • 误报,但你可以控制它们的概率。另一个缺点是您不能删除元素。
  • 问题是只使用预定义的hashCode() 进行检查的数据结构,它可能有 2^31 个值(仅计算正数)。使用具有 2 ^ 31 个可能值的散列函数的布隆过滤器会非常大,因为它基本上只是一个 BitSet。我不明白这如何算作“非常低的存储开销”。
  • @LeoAso - 哈希值集合和位集合之间的关系不是一对一的。
【解决方案2】:

您可以使用像IntSet 这样的原始集合实现来存储哈希码的值。显然,正如其他人所提到的,这假设碰撞不是问题。

【讨论】:

    【解决方案3】:

    如果您想跟踪哈希码是否已经存在并提高内存效率,BitSet 可能适合您的要求。

    看下面的例子:

      public static void main(String[] args) {
        BitSet hashCodes = new BitSet();
        hashCodes.set("1".hashCode());
    
        System.out.println(hashCodes.get("1".hashCode())); // true
        System.out.println(hashCodes.get("2".hashCode())); // false
      }
    

    BitSet"implements a vector of bits that grows as needed."。它是一个 JDK“内置数据结构”,它不包含“对实际对象的引用”。仅当“内部有相同的哈希码”时才存储。

    编辑:
    正如@Steve 在他的评论中提到的那样,BitSet 的实现并不是内存效率最高的。但是还有一些位集的内存效率更高的实现——尽管不是内置的。

    【讨论】:

    • 我不知道 BitSet 如何存储单个位。由于显然这种用法会将位分散到一个非常大的输入域中,你确定这些是有效存储的吗?只是问问而已。天真的假设是该结构将是一个字节数组,其中该数组只是扩展为包括任何位位置和之前的所有位置,这将是非常低效的。但我不知道它实际上是如何表示分散的位。
    • 您的解决方案似乎行不通。见github.com/brettwooldridge/SparseBitSet
    • @Steve 你是对的。还发现了这个post。但是位集的想法基本还不错。而是JDKBitSet的实现。
    • 当使用大约一半的可能值时会非常有效......(在实践中不太可能,但仍然......)
    【解决方案4】:

    没有这样的内置数据结构,因为很少需要这样的数据结构。不过,构建一个很容易。

    public class HashCodeSet<T> {
    
        private final HashSet<Integer> hashCodes;        
    
        public MyHashSet() {
            hashCodes = new HashSet<>();
        }         
    
        public MyHashSet(int initialCapacity) {
            hashCodes = new HashSet<>(initialCapacity);
        }         
    
        public HashCodeSet(HashCodeSet toCopy) {
            hashCodes = new HashSet<>(toCopy.hashCodes);
        } 
    
        public void add(T element) {
           hashCodes.add(element.hashCode());
        }
    
        public boolean containsHashCodeOf(T element) {
           return hashCodes.contains(element.hashCode());
        }        
    
        @Override
        public boolean equals(o: Object) {
            return o == this || o instanceof HashCodeSet && 
                    ((HashCodeSet) o).hashCodes.equals(hashCodes);
        }        
    
        @Override
        public int hashCode() {
            return hashCodes.hashCode(); // hash-ception
        } 
    
        @Override
        public String toString() {
            return hashCodes.toString();
        }
    }
    

    【讨论】:

    • 我认为 OP 的问题不是关于 API,而是关于内存使用情况。这无济于事,因为结果仍然像 HashSet 一样。
    • 我意识到我在这方面完全不公平。我已经删除了我的反对意见。随意在我的 cmets 上删除您的 cmets。我仍然认为问题还有更多问题,但我不在基地。对不起
    猜你喜欢
    • 2014-04-12
    • 1970-01-01
    • 2023-03-10
    • 2015-10-07
    • 2011-02-27
    • 1970-01-01
    • 2018-02-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多