Java哈希代码提供整数溢出

Java哈希代码提供整数溢出,java,hash,hashcode,integer-overflow,integer-hashing,Java,Hash,Hashcode,Integer Overflow,Integer Hashing,背景信息: 在我的项目中,我将强化学习(RL)应用于马里奥领域。对于我的状态表示,我选择使用带有自定义对象的哈希表作为键。我的自定义对象是不可变的,并且已经覆盖了.equals()和.hashcode()(由IntelliJ IDE生成) 这是生成的.hashcode(),我在注释中添加了可能的值作为额外信息: 问题: 这里的问题是,上述代码中的结果可能超过整数.MAX\u值。我在网上读到过,这不一定是个问题,但就我而言,这是个问题。这部分是由于所使用的算法是Q学习(一种RL方法),并且取决于哈

背景信息:

在我的项目中,我将强化学习(RL)应用于马里奥领域。对于我的状态表示,我选择使用带有自定义对象的哈希表作为键。我的自定义对象是不可变的,并且已经覆盖了.equals()和.hashcode()(由IntelliJ IDE生成)

这是生成的.hashcode(),我在注释中添加了可能的值作为额外信息:

问题:

这里的问题是,上述代码中的结果可能超过
整数.MAX\u值。我在网上读到过,这不一定是个问题,但就我而言,这是个问题。这部分是由于所使用的算法是Q学习(一种RL方法),并且取决于哈希表中存储的正确Q值。基本上,我在检索值时不会有冲突。当运行我的实验时,我发现结果一点也不好,我95%确定问题在于从哈希表中检索Q值。(如果需要,我可以详细说明为什么我对此很确定,但这需要一些与问题无关的项目额外信息。)

问题:

有没有办法避免整数溢出,也许我忽略了什么?或者,是否有其他方法(可能是另一种数据结构)可以合理快速地获得给定自定义键的值

备注:

在阅读了一些评论之后,我意识到我使用哈希表的选择可能不是最好的,因为我想要不引起冲突的唯一键。如果我仍然想使用哈希表,我可能需要正确的编码。

您需要一个专用的密钥字段来保证唯一性 .hashCode()不是为您使用它的目的而设计的
.hashCode()
设计用于在bucketing算法中提供良好的通用结果,该算法可以容忍较小的碰撞。它不是为提供唯一的密钥而设计的。默认的算法是在时间、空间和小碰撞之间进行权衡,不保证唯一性

完全散列 您需要实现的是基于对象内容的一个或一些其他唯一键。这在
int
的范围内是可能的,但我不会使用
.hashCode()
来表示此表达式。我将在对象上使用显式键字段

package com.stackoverflow;

import javax.annotation.Nonnull;
import java.util.Date;
import java.util.UUID;

public class Q23633894
{

    public static class Person
    {
        private final String firstName;
        private final String lastName;
        private final Date birthday;
        private final UUID key;
        private final String strRep;

        public Person(@Nonnull final String firstName, @Nonnull final String lastName, @Nonnull final Date birthday)
        {
            this.firstName = firstName;
            this.lastName = lastName;
            this.birthday = birthday;
            this.strRep = String.format("%s%s%d", firstName, lastName, birthday.getTime());
            this.key = UUID.nameUUIDFromBytes(this.strRep.getBytes());
        }

        @Nonnull
        public UUID getKey()
        {
            return this.key;
        }

        // Other getter/setters omitted for brevity

        @Override
        @Nonnull
        public String toString()
        {
            return this.strRep;
        }

        @Override
        public boolean equals(final Object o)
        {
            if (this == o) { return true; }
            if (o == null || getClass() != o.getClass()) { return false; }
            final Person person = (Person) o;
            return key.equals(person.key);
        }

        @Override
        public int hashCode()
        {
            return key.hashCode();
        }
    }
}
唯一散列 使用标准库中内置的
SHA1
哈希的一种方法,对于小数据集来说,这种方法的冲突几率极低。您发布到
SHA1
的值不会出现巨大的组合爆炸

你应该能够计算出一种方法,用你在问题中显示的有限值生成一个
最小完美散列

最小完美散列函数是映射n的完美散列函数 n个连续整数的键通常为[0..n−1] 或[1..n]。更多 正式的表达方式是:让j和k是一些 有限集K.F是一个最小完美散列函数iff(j)=F(K) 意味着j=k(内射性),并且存在一个整数a,使得 F的范围为a..a+|K|−1.事实证明,通用 最小完美哈希方案要求至少1.44位/密钥。这个 目前最著名的最小完美散列方案大约使用2.6 位/键[3]

如果密钥为零,则最小完美散列函数F是保序的 对于任何键aj和ak,j,按a1、a2、…、an和的顺序给出 如果最小完美散列函数F保持 键的字典顺序。在这种情况下,函数值为 只是每个键在所有键的排序顺序中的位置 钥匙。如果要散列的键本身存储在已排序的 阵列中,每个阵列可以存储少量的附加位 输入可用于计算哈希值的数据结构 快点

解决方案 请注意,当它谈到
URL
时,它可以是从对象计算的任何
字符串的任何
字节[]
表示形式

我通常重写
toString()
方法,使其生成唯一的内容,然后将其输入
UUID.nameuidfrombytes()
方法

版本3 UUID使用一个通过MD5从URL派生UUID的方案 完全限定域名、对象标识符、可分辨 名称(轻型目录访问协议中使用的DN),或 未指定名称空间中的名称。版本3的UUID具有以下形式 xxxxxxxx-xxxx-3xxx-yxxx-xxxxxxxxxx,其中x是任何十六进制数字 y是8、9、A或B中的一个

要确定给定名称的版本3 UUID 命名空间(例如,域的6ba7b810-9dad-11d1-80b4-00c04fd430c8)为 转换为与其十六进制对应的字节字符串 数字,与输入名称连接,用MD5散列,生成128 位。六位替换为固定值,其中四位 表示版本,0011表示版本3。最后,修改固定哈希 转换回十六进制形式,用连字符分隔 与其他UUID版本相关的部件

我的首选解决方案是类型5 UUID(类型3的SHA版本) 版本5 UUID使用带有SHA-1哈希的方案;否则它就是 与版本3中的想法相同。RFC 4122规定首选版本5 超过基于名称的UUID版本3,因为MD5的安全性 妥协的。请注意,160位SHA-1哈希被截断为128位 把长度算出来。勘误表说明了中的示例 RFC 4122的附录B

关键对象应该是不可变的 通过这种方式,您可以计算
toString()
.hashCode()
,并在
构造函数中生成一个唯一的主键,将它们设置一次,而不是反复计算

我在这里
package com.stackoverflow;

import javax.annotation.Nonnull;
import java.util.Date;
import java.util.UUID;

public class Q23633894
{

    public static class Person
    {
        private final String firstName;
        private final String lastName;
        private final Date birthday;
        private final UUID key;
        private final String strRep;

        public Person(@Nonnull final String firstName, @Nonnull final String lastName, @Nonnull final Date birthday)
        {
            this.firstName = firstName;
            this.lastName = lastName;
            this.birthday = birthday;
            this.strRep = String.format("%s%s%d", firstName, lastName, birthday.getTime());
            this.key = UUID.nameUUIDFromBytes(this.strRep.getBytes());
        }

        @Nonnull
        public UUID getKey()
        {
            return this.key;
        }

        // Other getter/setters omitted for brevity

        @Override
        @Nonnull
        public String toString()
        {
            return this.strRep;
        }

        @Override
        public boolean equals(final Object o)
        {
            if (this == o) { return true; }
            if (o == null || getClass() != o.getClass()) { return false; }
            final Person person = (Person) o;
            return key.equals(person.key);
        }

        @Override
        public int hashCode()
        {
            return key.hashCode();
        }
    }
}
@Override
public int hashCode() {
    int result = (stuck ? 1 : 0); // needs 1 bit (2 possible values)
    result += (facing ? 1 : 0) << 1; // needs 1 bit (2 possible values)
    result += marioMode << 2; // needs 2 bits (3 possible values)
    result += (onGround ? 1 : 0) << 4; // needs 1 bit (2 possible values)
    result += (canJump ? 1 : 0) << 5; // needs 1 bit (2 possible values)
    result += (wallNear ? 1 : 0) << 6; // needs 1 bit (2 possible values)
    result += (nearestEnemyX + 16) << 7; // needs 6 bits (33 possible values)
    result += (nearestEnemyY + 16) << 13; // needs 6 bits (33 possible values)
}
@Override
public int hashCode() {
    int result = (stuck ? 1 : 0);                // 2 possible values: 0, 1
    result = 2 * result + (facing ? 1 : 0);      // 2 possible values: 0, 1 
    result = 3 * result + marioMode;             // 3 possible values: 0, 1, 2
    result = 2 * result + (onGround ? 1 : 0);    // 2 possible values: 0, 1 
    result = 2 * result + (canJump ? 1 : 0);     // 2 possible values: 0, 1 
    result = 2 * result + (wallNear ? 1 : 0);    // 2 possible values: 0, 1 
    result = 33 * result + (16 + nearestEnemyX); // 33 possible values: - 16 to 16
    result = 33 * result + (16 + nearestEnemyY); // 33 possible values: - 16 to 16

    return result;
}