【问题标题】:guava-libraries: Is Objects.hashCode(Object[]) collision safe?番石榴库:Objects.hashCode(Object[]) 碰撞安全吗?
【发布时间】:2011-05-27 23:27:14
【问题描述】:

在查看覆盖 hashCode() 的不同选项时,我被定向到 Google 番石榴库 (javadoc) 中的 Objects.hashCode(Object[])。 javadoc 声明它委托给Arrays.hashCode(Object[])。在许多不同的对象类型中使用此方法是否安全?这不是很容易发生哈希冲突,还是因为容器通常只包含一种类型的对象而不太可能?

作为一个简单的例子,考虑以下类,

public class Student {
    private final String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(name);
    }
}

public class Teacher {
    private final String name;

    public Teacher(String name) {
        this.name = name;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(name);
    }
}

public class HashCodeDriver {
    public static void main(String[] args) {
        final String name = "moe";
        Student s = new Student(name);
        Teacher t = new Teacher(name);

        long studentHash = s.hashCode();
        long teacherHash = t.hashCode();
        System.out.println("studentHash=" + studentHash + " teacherHash=" + teacherHash);
        if(studentHash == teacherHash) {
            System.out.println("hash codes match");
        }
        else {
            System.out.println("hash codes don't match");
        }
    }
}

输出:

studentHash=108322 teacherHash=108322
hash codes match

对象是两种不同的类型,但生成相同的哈希码。这不是问题吗?我应该将类作为第一个参数传入以防止这种冲突吗?例如,

public class Student {
    private final String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(Student.class, name);
    }
}

public class Teacher {
    private final String name;

    public Teacher(String name) {
        this.name = name;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(Teacher.class, name);
    }
}

这就是为什么 javadoc 会警告只向该方法提供单个对象的原因吗?来自 javadoc,

警告:当提供单个对象时,返回的哈希码不等于该对象的哈希码。

【问题讨论】:

    标签: java guava hashcode


    【解决方案1】:

    当 2 种不同类型的 2 个不同对象具有相同的哈希码时,这不是问题。

    希望,当您要构建HashMap 时,您不会将学生和教师混为一谈作为该地图的关键。即使在你想做HashMap<Object, Object>的情况下你也可以,因为

    assertFalse( new Teacher( "John Smith" ).equals( new Student( "John Smith" ) );
    

    这就是为什么同时覆盖hashCodeequals 很重要的原因。

    委派给Arrays.hashCode(Object[]) 的唯一缺点可能是有时从性能的角度来看可能过于昂贵。

    例如,在您的情况下,这对于教师或学生来说都是一种更好的哈希方法。

    @Override
    public int hashCode() {
        return name.hashCode();
    }
    

    【讨论】:

    • 这个对话有点颠倒。通常,开发人员会覆盖equals 而忘记hashcode。在这种情况下,我看不出HashMap<Object, Object> 会怎样。你的assertFalse 是从哪里来的?我在HashMap 中找不到它。据我所知,HashMap 只使用hashCode,而不是equals。所以,两个不同类型的对象可能会发生碰撞,这就是我对Objects.hashCode(Object[]) 持怀疑态度的原因。我同意将 hashCodeequals 作为一对覆盖总是很重要的,但据我了解,如果两个对象具有相同的哈希码,它们应该相等,反之亦然。
    • @nondescript1 - 我猜你误解了 equals 和 hashcode 契约 - 当你覆盖 equals 时你必须覆盖 hashcode 并且两个相等的对象应该产生相同的 hashcode 但反之则不然。 Object#hashcode 的一般约定说 - 如果两个对象根据 equals(java.lang.Object) 方法不相等,则不需要,然后对两个对象中的每一个调用 hashCode 方法必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。
    • @nondescript1 - 关于你的陈述“据我所知,HashMap 只使用 hashCode,而不是 equals” > 你是如何得出结论的?你能指出我的文档/规范链接吗?根据我的理解,HashSet 将使用哈希码值将对象存储在哈希桶中,但会在其上调用 equals 来比较相等性。因此,如果您的教师和学生返回相同的哈希码,它们将被放置在同一个存储桶中,但如果您想从 Map 中获取教师,它将检查等号,然后返回有效的教师或 null,因此不会有任何碰撞。
    • @Falcon - 你是对的。我想我对hashCode 和哈希支持的容器的理解都没有了。我只是在阅读HashMap 的开源代码。有没有更好的文档可以参考?那么,我们是否认为Objects.hashCode(Object[]) 对于大多数用例来说是安全的?
    • @Falcon,@nondescript1。我不是在谈论Arrays.hashCode 的哈希分布性能,这很好。这纯粹是在谈论在此处介绍的一个简单案例中做太多事情。如果您委派给Arrays.hashCode( Object[] ),那么您至少会通过构造一个数组来降低性能。我的观点是,如果您的对象只有一个字段,在大多数情况下,可以委托给该字段的 hashCode
    【解决方案2】:

    警告只说x.hashCode() != Objects.hashCode(x) 是真的。 (好吧,这在大多数情况下都是正确的。对于某些值,它们仍然可能发生冲突。对于 大多数 对象,实际上并不相等。)

    一个有效的 hashCode/equals 实现是:

    public class Teacher {
        private final String name;
    
        public Teacher(String name) {
            this.name = name;
        }
    
        @Override public equals(Object obj){
            if(obj == this) return true;
            if(!(obj instanceof Teacher)) return false;
            return Objects.equal(name, ((Teacher) obj).getName());
        }
    
        @Override public hashCode(){
            return 0;
        }
    }
    

    这是一个有效的,尽管所有的哈希值都发生了冲突。来自 hashCode() javadoc:

    不要求如果两个对象 根据不相等 equals(java.lang.Object) 方法,然后 在每个上调用 hashCode 方法 这两个对象必须产生不同的 整数结果。

    与“正常”实现的不同之处在于这段代码的性能会差很多。例如,HashMaps 会退化为一个列表,比如查找性能。

    即使有这个实现:

    @Override
    public int hashCode() {
        return Objects.hashCode(Teacher.class, name);
    }
    

    不同类的哈希值有可能(但不太可能)发生冲突。如果两个类的类名的哈希值相同,就会出现这种情况。

    当集合中使用hashCode( ) 内部。整体效果将是有限的:如果您有 n 种类型,由于这种情况,您最多有 n 次冲突。其他因素可能会主导性能特征。

    【讨论】:

    • 在您的 equals 实现中的行 - if(!(obj instanceof Teacher)) return true;应该返回 false 而不是 true
    【解决方案3】:

    如果您在同一个地图的键集中混合了许多不同的具体类型,您仍然可以使用 Objects.hashCode() 并通过将输出与每种具体类型的不同值进行异或来最小化冲突。

    class Class1 {
      public int hashCode() {
        return Object.hashCode(...) ^ 0x12b7eff8;
      }
    }
    
    class Class2 {
      public int hashCode() {
        return Object.hashCode(...) ^ 0xe800792b;
      }
    }
    

    通过与一个随机选择的值进行异或运算,但每个具体类都是稳定的,您可以消除仅仅因为 Object.hashCode 的参数相等而可能发生的冲突。

    警告:当提供单个对象时,返回的哈希码不等于该对象的哈希码。

    这就是为什么 javadoc 会警告只向该方法提供单个对象的原因吗?来自 javadoc,

    没有。此警告与具有相同成员的不同具体类的实例之间发生冲突的可能性无关。由于假设单个值的哈希值与 singleValue.hashCode() 的哈希值相同,因此它会警告哈希码匹配中的误报。

    例如,看看下面在 incorrect 快速跟踪代码中做出的假设,该代码试图通过使用缓存的哈希码来避免相等性检查:

    class Name {
      int cachedHashCode;
    
      ...
    }
    
    class Person {
      int cachedHashCode;  // 0 if not computed
    
      private final Name name;
    
      public boolean hasName(Name n) {
        return ((cachedHashCode != 0 && n.cachedHashCode != 0) 
                && cachedHashCode == n.cachedHashCode)
            || n.equals(name);
      }
    
      public int hashCode() {
        if (cachedHashCode == 0) { cachedHashCode = Object.hashCode(name); }
        return cachedHashCode;
      }
    }
    

    【讨论】:

    • 我想Object.hashCode(...) ^ 0x12b7eff8 破坏了一种类型的值分布。 Object.hashCode(0x12b7eff8, ...) 应该没问题。 Object.hashCode(..., getClass().getName()) 可能更具可读性。
    • @Thomas,“破坏分布”是什么意思?
    • 我认为所有位操作都会改变分布(最简单的例子 hashCode | Integer.MAX_VALUE)。好吧,exor 是不会改变值分布的。它们仍然是平等分布的。
    • @Thomas Jung,是的。 xor 和 xnor 都不会减少熵的位数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-16
    • 2012-06-04
    • 1970-01-01
    • 2013-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多