如何获取对象的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也非常小。对对象进行“散列”的要点是能够使用散列而不是对象值本身。
答案 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
,因为添加内容比int
和double
更方便。
如果您的课程无法控制且无法添加方法,那么您必须具有创造性。毕竟,您确实从每个这样的类中获取值以进行计算,因此您可以调用相同的方法并消化其结果。或者,如果它们是可序列化的,你可以消化它们的序列化形式。
因此,在您的班组中,您将使用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
实例)。