hashmap 自定义类键 && 对象保存/加载

Posted

技术标签:

【中文标题】hashmap 自定义类键 && 对象保存/加载【英文标题】:hashmap custom class key && object saving/loading 【发布时间】:2011-11-08 00:38:30 【问题描述】:

从事一个项目已经有一段时间了,我遇到了一些不同的复杂情况和解决方案,但似乎并没有一起成功。

final public class place implements Serializable 
    private static final long serialVersionUID = -8851896330953573877L;
    String world;
    Double X;
    Double Y;
    Double Z;

HashMap<place, Long> blockmap = new HashMap<place, Long>(); // does not work
HashMap<Location, Long> blockmap = new HashMap<Location, Long>(); //works

首先,我的哈希图是一个哈希图,其中包含一个项目被放置(或添加)到世界的时间。 place 是一个包含字符串 world、double x、double y、double z 的 'class place ';我遇到的问题是它不适用于哈希图。我可以使用它存储一个新的哈希键,但我不能调用来获取它的值。改用 Location 可以解决这个问题(哈希图)并且可以完美运行。

public void SetBlock(Block block) 
    Location loc = new Location(null, block.getLocation().getX(),block.getLocation().getY(),block.getLocation().getZ());
    //...
    Long time = (long) (System.currentTimeMillis() / 60000);
    //...
    if (blockmap.containsKey(loc)) 
            blockmap.remove(loc);
            blockmap.put(loc, time);
            //System.out.println("MyLeveler: Block Existed, Updated");
     else 
            blockmap.put(loc, time);
            //System.out.println("MyLeveler: Block added to " + loc.getX() + ", " + loc.getY() + ", " + loc.getZ());
            //System.out.println("MyLeveler: total blocks saved: " + blockmap.size());
    

这可以正常工作。现在,为此目的,必须在禁用和启用插件时保存并重新加载这些数据。为此,我创建了一个具有保存/加载功能的新 java 类文件。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SLAPI 
    public static void save(Object obj,String path) throws Exception
    
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
        oos.writeObject(obj);
        oos.flush();
        oos.close();
    
    public static Object load(String path) throws Exception
    
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
        Object result = ois.readObject();
        ois.close();
        return result;
    

我通常会收到“不可序列化”错误。使用 'implements Serializable' 和 ois.defaultReadObject() 或 oos.defaultWriteObject() 来检查文件上的序列号只会在对象为空时导致干净的保存/加载!当它包含数据时,我不断得到“java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException”

这显然是个问题!这里的建议之一:ArrayList custom class as HashMap key 未能产生任何更好的结果。事实上,创建自定义类是我开始的第一个问题 >.>

所以我想问题是:

1) 我必须改变什么才能使用自定义类作为键(并且正常工作)

2) 为什么它不能识别我将它设置为可序列化的类/函数/java 类

3) 为什么它适用于空的 hashmap,但不适用于填充的 hashmap?

【问题讨论】:

【参考方案1】:

基本上你需要覆盖place 中的hashCode()equals()。大概Location 已经覆盖了这些方法。

HashMap 使用这些方法首先快速缩小候选键列表(使用哈希码),然后检查它们是否相等(通过调用 equals)。

尚不清楚可序列化问题是什么——我的猜测是虽然place 是可序列化的,但Location 不是。如果您可以发布一个简短但完整的问题来演示该问题,那真的很有帮助。 (开始遵循 Java 命名约定并将您的字段设为私有也是一个好主意...)

编辑:这是一个带有哈希码和相等性的 Place 类的示例。请注意,为了避免在将其用作哈希映射中的键后值发生变化,我将其设置为不可变 - 我不知道它与序列化的效果如何,但希望没关系:

public final class Place implements Serializable 
    private static final long serialVersionUID = -8851896330953573877L;

    private final String world;
    // Do you definitely want Double here rather than double?
    private final Double x;
    private final Double y;
    private final Double z;

    public Place(String world, Double x, Double y, Double z) 
        this.world = world;
        this.x = x;
        this.y = y;
        this.z = z;
    

    @Override public int hashCode() 
        int hash = 17;
        hash = hash * 31 + (world == null ? 0 : world.hashCode());
        hash = hash * 31 + (x == null ? 0 : x.hashCode());
        hash = hash * 31 + (y == null ? 0 : y.hashCode());
        hash = hash * 31 + (z == null ? 0 : z.hashCode());
        return hash;
    

    @Override public boolean equals(Object other) 
        if (!(other instanceof Place)) 
            return false;
        
        Place p = (Place) other;
        // Consider using Guava's "Objects" class to make this simpler
        return equalsHelper(world, p.world) &&
               equalsHelper(x, p.x) &&
               equalsHelper(y, p.y) &&
               equalsHelper(z, p.z);
    

    private static boolean equalsHelper(Object a, Object b) 
        if (a == b) 
            return true;
        
        if (a == null || b == null) 
            return false;
        
        return a.equals(b);
    

    // TODO: Add getters?

值得注意的是,这将比较 Double 的值是否相等,这几乎总是一个坏主意……但你不能真正容忍像 equals 这样的东西。只要值在您查找它们时完全相同相同,它应该可以正常工作。

【讨论】:

当我们计划开始使用测试版代码时,公共/私人清理将在稍后阶段完成。我会看看我能做些什么来创建一个...更小的...项目以显示适合代码的确切复杂性。 - 你能告诉我你所说的覆盖过程的例子吗?我读过一两次,但不知道如何使用它? @James:立即获得像变量访问这样的简单功能确实要好得多。将使用 Place 的散列示例进行编辑... 非常感谢...非常有见地。编写 c++ 这么久,跳入 java 有点学习曲线。您列出的代码有一些缺陷,例如 null long 将始终 == 0,使其不为 null,因此我默认它提供 hashCode() 以消除我遇到的错误。此外,错误输入“return equalsHeler(world, p.world) &&”。认为我现在应该如何实现可序列化:P 并且覆盖将派上用场。我的代码现在可以正常工作了^.^ 猜我现在可以清理它了!!我担心我将不得不在那里将它保存到 SQL 数据库中>.> @James:您所说的“null long 总是 == 0”是什么意思?里面没有长。但它发现哈希为 0。虽然没有单个字段可以使哈希为 0... 处理 Doubles 错误的 3 个“hash = hash...”行,“The operator == is undefined for the argument type(s) double, null”,默认为“hash = z .hashCode();"可能不安全?如果你知道更好的方法,欢迎让我知道

以上是关于hashmap 自定义类键 && 对象保存/加载的主要内容,如果未能解决你的问题,请参考以下文章

Java集合数据结构——HashMap&HashSet

vue视频: 自定义指令 && 拖拽 && 自定义键盘信息

HashMap&& LeetCode 13.罗马数组转换为整数

WPF自定义控件与样式(15)-终结篇 & 系列文章索引 & 源码共享

使用适配器中的不同数据更新不同 ListView 中的常见帖子

阿昌教你自定义拦截器&自定义参数解析器&自定义包装HttpServletRequest