如何为对象生成(几乎)唯一的哈希ID?

时间:2015-10-16 11:36:03

标签: java hash

如何获取对象的ID,以便于将其与其他对象区分开来?

class MyClass {
    private String s;
    private MySecondClass c;
    private Collection<someInterface> coll;
    // ..many more

    public Result calculate() {
        /* use all field values recursively to calculate the result */
        /* takes considerable amount of time. Implemented */
        return result;
    }

    public String hash() {
        /* use all field values recursively to generate a unique identifier */
        // ?????
}

calculate()通常需要约40秒才能完成。因此,我不想多次调用它。

MyClass个对象非常庞大(约60 MB)。计算的Result值仅为~100 KB。

每当我要对一个对象运行计算时,我的程序应该查看是否已经提前一段时间完成,并且递归地使用完全相同的值。如果是这样,它将在(例如)HashMap中查找结果。基本上,MyClass对象本身可以用作键,但HashMap将包含30-200个元素 - 我显然不希望以全尺寸存储所有这些元素。这就是我想要存储30-200 Hash/result值的原因。

所以,我以为我会在MyClass对象中的所有值上生成ID(哈希)。我该怎么做?这样,我可以使用那个非常哈希来查找结果。 我知道像MD5这样的哈希码不能保证100%的唯一性,因为多个对象可能具有相同的哈希值。但是,如果我通过MD5存储(最多)200个元素,我认为两次使用哈希的可能性是可以忽略的。可能存在16^32=3.4e38个不同的哈希码。我很乐意听到任何人对此的评论,或者看到其他方法。

生成哈希后,我不再需要该对象,只需要其各自的result值。

具有完全相同值的两个单独对象必须返回相同的哈希码。就像原始的hashCode()一样,我正试图保持唯一性。具有相同哈希码的两个对象的概率应该是绝对可以忽略的。

我不知道如何用其他的话来描述这个问题。如果需要进一步说明,请询问。

那么如何生成我的MyClass.hash()

问题实际上并不是关于如何或在何处存储哈希值,因为我甚至不知道如何为整个对象生成(几乎)唯一的哈希值,对于相同的值,它总是相同的。

澄清:

谈到尺寸时,我的意思是硬盘上的序列化尺寸。

我不认为将对象放在HashMap中会减小它们的大小。那就是我想要存储一些哈希字符串。 HashMap<hashStringOfMyClassObject, resultValue>

  

当您将对象放入HashMap(作为键或值)时,不会创建它的副本。因此,在HashMap中存储200个大对象比200个对象本身消耗更少的内存。

我自己不存储200个大对象。我只保留200个不同的结果(作为值),这些结果很小,并且MyClass对象的200个相应的hashCodes也非常小。对对象进行“散列”的要点是能够使用散列而不是对象值本身。

4 个答案:

答案 0 :(得分:3)

你在一个对象上调用hash(),你的目标是记住结果,因为计算成本很高,除非某些状态发生变化,结果是不变的吗?

那么为什么不将结果保存在对象的实例变量中呢?有一些像

这样的逻辑
  calculate() {
      if ( m_cachedResult == null ){
          m_cachedResult = origincalCaclulate(); // refactored original
      }
      return m_cachedResult;
  }

然后,如果您可以确保通过此类上的setter修改所有相关状态,请在需要重新计算时清除缓存

  setThing(newValues) {
        m_cachedResult = null;
        //process new state values
  }    

答案 1 :(得分:2)

实际上你有一个名为UUID

的对象
  

表示不可变通用唯一标识符(UUID)的类。 UUID表示128位值。

您可以找到some ideas here,例如:

import java.util.UUID;

public class GenerateUUID {
   public static UUID generate() {
        UUID idOne = UUID.randomUUID();
        return idOne;
   }
}

然后检查是否存在于已创建的对象中(这几乎是不可能的),并在必要时再次调用。

答案 2 :(得分:1)

如果要创建所有数据的哈希值,您需要确保可以从中获取字节格式的所有值。

要做到这一点,如果你能控制所有类(可能除了Java内置类之外),它是最好的,这样你就可以为它们添加一个方法来实现这一点。

鉴于您的对象非常大,将递归收集到一个大字节数组然后计算摘要可能不是一个好主意。创建MessageDigest对象可能更好,并添加如下方法:

void updateDigest( MessageDigest md );

他们每个人。如果您愿意,可以为此声明一个接口。每种这样的方法都将收集该类自己的数据,这些数据参与了大计算&#34;并使用该数据更新md对象。在更新了所有自己的数据之后,它应该递归地调用其中定义了该方法的任何类的updateDigest方法。

例如,如果您有一个包含字段的类:

int myNumber;
String myString;
MyClass myObj;  // MyClass has the updateDigest method
Set<MyClass> otherObjects;

然后它的updateDigest方法应该做这样的事情:

// Update the "plain" values that are in the current object
byte[] myStringBytes = myString.getBytes(StandardCharsets.UTF_8);
ByteBuffer buff = ByteBuffer.allocate(
                        Integer.SIZE / 8    // For myNumber
                        + Integer.SIZE / 8  // For myString's length
                        + myStringBytes.length
                  );
buff.putInt( myNumber );
buff.putInt( myStringBytes.length );
buff.put( myStringBytes );
buff.flip();
md.update(buff);

// Recurse
myObj.updateDigest(md);

for ( MyClass obj : otherObjects ) {
    obj.updateDigest(md);
}

我在摘要中添加字符串长度(实际上是字节表示的长度)的原因是为了避免出现两个String字段的情况:

String field1 = "ABCD";
String field2 = "EF";

如果你只是将他们的字节直接放入摘要中,它将对摘要产生同样的影响:

String field1 = "ABC";
String field2 = "DEF";

这可能会导致为两组不同的数据生成相同的摘要。因此,添加长度将消除歧义。

我使用了ByteBuffer,因为添加内容比intdouble更方便。

如果您的课程无法控制且无法添加方法,那么您必须具有创造性。毕竟,您确实从每个这样的类中获取值以进行计算,因此您可以调用相同的方法并消化其结果。或者,如果它们是可序列化的,你可以消化它们的序列化形式。

因此,在您的班组中,您将使用md或您希望使用的摘要创建MessageDigest.getInstance("SHA")对象。

MessageDigest md = null;
try {
    md = MessageDigest.getInstance("SHA");
} catch (NoSuchAlgorithmException e) {
    // Handle properly
}

// Call md.update with class's own data and recurse using
// updateDigest methods of internal objects

// Compute the digest
byte [] result = md.digest();

// Convert to string to be able to use in a hash map
BigInteger mediator = new BigInteger(1,result);
String key = String.format("%040x", mediator);

(您实际上可以使用BigInteger本身作为密钥)。

答案 3 :(得分:1)

计算一些类似哈希的标识符通常不是这样做的好方法。发生冲突的可能性非常低,但仍然可能发生。请记住,哈希不是100%随机数,它在大多数情况下以某种方式与输入数据相关联,因此,根据您的哈希方法,某些哈希值可能无法访问,或者 - 在更糟糕的情况下为您 - 某些对于相当大的输入对象,它们可能很常见。它可以精确计算,但那就是计算机科学和概率论。

使用某些distest函数(MD5,SHA等)可能会有很大帮助,但仍然无法完全解决问题。

我更喜欢的解决方案类似于 Jordi 。使用一些标识符增强您的类。根据您的项目 - 我将设置,例如创建日期和/或此类任务的名称。 std::string任务的名称或描述可以使调试更容易。

如果theese不够独特,你可以总是添加唯一的数字计数器(或std::string_view实例)。