序列化任意 Java 对象

Posted

技术标签:

【中文标题】序列化任意 Java 对象【英文标题】:Serializing arbitrary Java objects 【发布时间】:2017-01-07 19:13:52 【问题描述】:

我目前需要,因为我想使用 Hash 作为哈希表的键。在我阅读了各种警告说默认的hashCode 经常会产生冲突之后,我想通过MessageDigest 切换到散列以使用据说允许更多条目而不会发生冲突的替代算法(例如 SHA1,...)。 [作为旁注:我知道即使在这里碰撞也可能在早期发生,但我想增加保持无碰撞的可能性。]

为了实现这一点,我尝试了this *** post 中提出的一种方法。它使用以下代码获取MessageDigest 所需的byte[]

public static byte[] convertToHashableByteArray(Object obj) 
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutput out = null;
    byte[] byteOutput = null;

    try 
        out = new ObjectOutputStream(bos);
        out.writeObject(obj);
        byteOutput = bos.toByteArray();
     catch (IOException io) 
        io.printStackTrace();
     finally 
        try 
            if(out != null)  out.close(); 
         catch(IOException io) 
            io.printStackTrace();
        

        try 
            bos.close();
         catch(IOException io) 
            io.printStackTrace();
        
    

    return byteOutput;

然而,这会导致只有实现serializable 接口的对象才会被序列化/转换为byte[] 的问题。为了规避这个问题,我在catch 子句中将toString() 应用于给定的obj,以强制在所有情况下都获得byte[]

public static byte[] convertToHashableByteArray(Object obj) 
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutput out = null;
    byte[] byteOutput = null;

    try 
        out = new ObjectOutputStream(bos);
        out.writeObject(obj);
        byteOutput = bos.toByteArray();
     catch (IOException io) 
        String stringed = obj.toString();
        byteOutput = stringed.getBytes();
     finally 
        try 
            if(out != null)  out.close(); 
         catch(IOException io) 
            io.printStackTrace();
        

        try 
            bos.close();
         catch(IOException io) 
            io.printStackTrace();
        
    

    return byteOutput;

但是,这对我来说仍然是完全错误的。所以我的问题是,是否有更好的选择将任意对象转换为byte[] 以便能够计算散列。最好是一种无需使用其他库即可工作的解决方案,或者使用诸如 Apache Commons 之类的成熟库的解决方案。 (除此之外,我还接受其他方法来获取任意 Java 对象的 SHA1/SHA512 哈希值。)

【问题讨论】:

如果需要获取对象的 Hash (hashCode?!?) 为什么需要序列化对象? 据我所知,不能序列化不实现serializable的对象 “默认哈希码太频繁地产生冲突”——这是在哪里说的? @OliverCharlesworth:我在这里读到:Link 至少对我来说,推理听起来很合理。 好吧,我误解了这里的意图——“使用哈希作为键”几乎肯定是错误的,并且会导致悲伤的时刻;即使是理想的散列函数也会受到生日悖论的影响(这就是那篇文章所指的)。为什么不使用 object 作为键? 【参考方案1】:

也许您可以将对象的 UUID 用作不可变的唯一标识符?

【讨论】:

【参考方案2】:

这里有很多问题......

    您应该使用正确的键类,实现 equals 和 hashCode,而不是使用随机对象。 序列化性能开销很容易意味着这样的映射将比琐碎的迭代搜索更慢。 在大多数情况下不应使用默认哈希码,因为从业务角度来看,对于“相等”的对象,它可能会有所不同。您应该与 equals 一起重新实现哈希码(回到第 1 点)。每当由于指针别名而发生冲突时,如果它不能正常工作,则无关紧要 关闭内存流的方法过于复杂。一个接一个地关闭它们,它不是外部资源——如果它失败了,就让它失败,你不需要在失败的情况下100%关闭所有东西。您还可以使用可关闭的实用程序之一(或尝试/捕获资源)以避免一些开销 您不需要对该字节数组进行复杂的摘要 - 使用 Arrays.hashCode,它对于您的用例来说已经足够了(记住 - 无论如何都不要这样做,第 1 点)

如果您仍在阅读但仍不愿意实施第 1 点,请返回第 1 点。再重复一遍。又一次。

最后回答你的问题,使用 hessian 序列化。

http://hessian.caucho.com/doc/hessian-overview.xtp

它与 java 非常相似,只是更快,更短的输出,并允许序列化不实现 Serializable 接口的对象(冒着搞砸的风险,你需要设置特殊标志来允许)。

【讨论】:

【参考方案3】:

如果你想序列化一个给定的对象,我建议你像这样改变你的方法:

public static byte[] convertToHashableByteArray(Serializable obj)
     ..........
     ..........

【讨论】:

Objects.hashCode 基本上只是在对象上调用hashCode 好吧,Objects.hashCode(o) 方法只是 Object.hashCode 的 null 安全版本...如果您谈论的是 java.util.Objects 类。 (Serializable obj) 的第二部分将防止任何关于非可序列化对象的异常是真的。但是,它仍然无法解决将不可序列化对象转换为byte[] 的问题。 类 Hashtable 实现了 Serializable。所以哈希表是一个可序列化的对象 (tutorialspoint.com/java/util/java_util_hashtable.htm)。 正如 René Link 所说,java.util.Objects.hashCode() 是 Object.hashCode() 的 null 安全版本。

以上是关于序列化任意 Java 对象的主要内容,如果未能解决你的问题,请参考以下文章

Java面试之反射

JAVA面试题:反射

Java反射面试题(2020)

java反射面试题(2020)

Fastjson1.2.24 反序列化任意命令执行

Fastjson1.2.24 反序列化任意命令执行