【问题标题】:How to generate hash of a set to ensure integrity?如何生成集合的哈希以确保完整性?
【发布时间】:2012-01-16 23:13:16
【问题描述】:

也许以前有人问过这个问题(我没找到)...

我有一个大约 java.util.Set。 50000 个字符串。我想生成某种哈希来检查它是否已更改(比较两个版本的 Set 的哈希)?

如果 Set 发生变化,则哈希必须不同。

如何实现?谢谢!

编辑:
很抱歉这种误导性的措辞。我不想检查“它”是否已更改(同一个实例)。相反,我想检查两个数据库查询,它们生成两个 - 可能相同 - 一组字符串的实例是否相等。

【问题讨论】:

    标签: java hash set


    【解决方案1】:

    我会尝试使用java.util.AbstractSethashCode 方法,如文档中所述:

    返回此集合的哈希码值。一个集合的哈希码是 定义为集合中元素的哈希码之和, 其中空元素的哈希码定义为零。这个 确保 s1.equals(s2) 意味着 s1.hashCode()==s2.hashCode() 对于任何两组 s1 和 s2,根据总合同的要求 Object.hashCode().

    当然,这仅在您的 Set 实现从 AbstractSet 扩展时才有效,我想您使用例如java.util.HashSet与往常一样,存在哈希冲突的可能性。

    或者,您可以扩展现有的 Set 实现并覆盖状态更改方法,如果每个对象的哈希计算变得过于昂贵,这可能是有意义的,例如:

    class ChangeSet<E> extends java.util.HashSet<E> {
        private boolean changed = false;
    
        @Override
        public boolean add(E e) {
            changed = true;
            super.add(e);
        }
    
        public void commit() {
            changed = false;
        }
    
        public boolean isChanged() {
            return changed;
        }
    
        /* and all the other methods (addAll, remove, removeAll, etc.) */
    
    }
    

    【讨论】:

    • 这是错误的。该集合可以更改,但最终仍然具有相同的 hashCode。含义是单向的。当它们相等时,哈希码必须相同。但仅仅因为哈希码相同并不意味着它们是相等的。
    • @Tom:当然,正如我所写的,仍然存在哈希冲突的可能性。如果在所有情况下都必须避免这种情况,那么散列是错误的方法(我突出显示了这句话)。
    • @Tom 没错; OP 特别要求提供哈希值,因此您必须假设他们知道误报的可能性,并且对此感到满意。
    • 删除了我的反对票,因为您补充说有可能发生冲突:-)。抱歉,如果一开始就有,我没注意到。
    • @Tom wh00ps,我没有注意到房间里的大象。所以,我收回我说过的话。
    【解决方案2】:

    基于此声明:

    If the Set changes, the hash has to be different

    这真的无法实现,除非你有更多的限制。通常,哈希是某个固定空间中的值。例如,您的哈希可能是一个 32 位整数,因此有 2^32 个可能的哈希值。一般来说,b 位可以获得 2^b 个可能的哈希值。为了实现您想要的,您必须确保每个可能的集合(即 - 所有集合的集合!)小于或等于 2^b。但我的猜测是你可以有任意字符串,所以这是不可能的。即使有可能,您也必须想出一种映射到哈希空间的方法,这可能具有挑战性。

    但是,使用良好的散列函数,更改集合最终产生相同散列值的可能性不大。所以可以使用hash来判断不等,但是如果hash相同,还是需要检查是否相等。 (这与哈希集或哈希映射背后的想法相同,其中元素根据哈希码映射到存储桶,但您必须检查是否相等)。

    与 Paul 提到的类似但不同:您可以改为创建具有版本号的集合实现,并确保在集合发生变异时始终生成新的版本号。那你可以比较一下版本号吗?我不确定您是否关心不可变集或可变集是否更改回您所见过的版本(即 - 如果它应该始终获得相同的版本)。

    希望这会有所帮助。

    【讨论】:

    • 是的,这很有帮助,因为它表明我的方法没有朝着正确的方向发展。谢谢!
    • @Mulmoth - 太棒了!请记住,哈希仍然很棒,它们也可以缓存。您可能会看到性能大幅提升。要建议任何其他方法,我需要更好地了解您的访问模式,以了解如何优化事物,但这是一个好的开始。
    【解决方案3】:

    如果您需要提高 hashCode 的性能(因为它对于大型 Set 来说相当昂贵),您可以缓存它并随时更新。

        class MyHashSet<E> extends LinkedHashSet<E> {
        int hashCode = 0;
        @Override
        public boolean add(E e) {
            if (super.add(e)) {
                hashCode ^= e.hashCode();
                return true;
            }
            return false;
        }
    
        @Override
        public boolean remove(Object o) {
            if(super.remove(o)) {
                hashCode ^= o.hashCode();
                return true;
            }
            return false;
        }
    
        @Override
        public void clear() {
            super.clear();
            hashCode = 0;
        }
    
        @Override
        public int hashCode() {
            return hashCode;
        }
    }
    

    【讨论】:

    • +- 应该是相同的,即使有上溢和下溢,但^ 更容易看出它是如何工作的。
    【解决方案4】:

    有时越简单越好。我建议编写自己的 Set 实现。在其中,覆盖 addremove 方法,以便在修改 Set 时设置一个标志。为标志添加一个 getter,isModified,您不必担心哈希开销或冲突。只需致电MyCustomSet.isModified

    或者,您可以调用 Collections.unmodifiableSet 以获取无法修改的 Set 的包装。如果代码尝试修改集合,将引发异常。

    【讨论】:

    • 也许“两个版本的套装”具有误导性。我喜欢比较两个不同的实例。
    • +1:类似的方法是使用 modicationCount。当 modifcationCount 与您上次检查时不同时,Set 已更改。
    • @Mulmoth - 集合开始是一样的吗?然后,您可以捕获更改并进行比较。也许重新考虑需要比较两组 50,000 根琴弦的设计会更好。如果您无法避免,也许嵌入式数据库可能是更好的选择?我认为您将很难在性能与避免碰撞之间取得平衡。
    • 谢谢你,Paul,你的想法不适合我的情况,但它很有用 - 我会记住的。
    猜你喜欢
    • 2016-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-03
    • 2020-12-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多