【问题标题】:How to deep clone a hash table如何深度克隆哈希表
【发布时间】:2015-07-08 10:30:11
【问题描述】:

我有一个项目需要非常具体地完成,我需要一些帮助。我基本上到处都在寻找答案,但找不到它,甚至在 Stack Overflow 上也找不到。它与克隆哈希表有关。 (既浅又深。)

我已经粘贴了下面的代码,但简而言之,我有一个名为 EHashtable 的类,它扩展了 Hashtable。然后我添加了一些字符串键和各种自定义类类型的值。在 EHashtable 类中,有称为 Clone() 和 dClone() 的方法。 Clone() 应该创建其 Hashtable 的浅表克隆 - 这意味着 Hashtable 被克隆但它的值不是。 dClone() 应该创建一个新的哈希表并将每个原始哈希表的值克隆到它(意味着每个值指向与第一个不同的内存引用)。如果自定义对象不可克隆,它也应该抛出异常。

现在,即使在浅克隆(Clone() 方法)上,也会更改一个 Hashtable 中的一个值,而不会更改另一个 Hashtable 中的值。似乎每个值都指向不同的引用。我不明白如何让 Clone() 和 dClone() 方法做我希望他们做的两件不同的事情。另一件事,哈希表不能有泛型。它必须是Hashtable 而不是Hashtable<K, V>

我已经查找了如何循环遍历哈希表。这仅适用于 Object 类型,并且由于方法的受保护状态,Object 类型无法 clone()。下面是我的代码,从 main 方法开始。我意识到这是非常具体的,非常感谢所有帮助。

import java.util.Hashtable;
import java.util.Iterator;

public class _Test {
    public static void main(String[] arguments) {
        Circle cr1 = new Circle(1);
        Circle cr2 = new Circle(2);
        Point po1 = new Point(10, 10);
        Point po2 = new Point(20, 20);
        PlaneCircle pcr1 = new PlaneCircle(po1, 11f);
        PlaneCircle pcr2 = new PlaneCircle(po2, 12f);

        EHashtable eh = new EHashtable(20);
        eh.add(new String("C1"), cr1);
        eh.add(new String("C2"), cr2);
        eh.add(new String("PC1"), pcr1);
        eh.add(new String("PC2"), pcr2);

        try {
            EHashtable ehCloned = (EHashtable) eh.Clone();

            System.out.println("/***--Before Alteration--***/");
            System.out.println("eh:");
            System.out.println(eh);
            System.out.println();
            System.out.println("ehCloned:");
            System.out.println(ehCloned);
            System.out.println();

            Circle cr3 = new Circle(99);
            Point po3 = new Point(99, 99);
            PlaneCircle pcr3 = new PlaneCircle(po3, 9999f);

            eh.add(new String("C1"), cr3);
            eh.add(new String("PC1"), pcr3);

            System.out.println("/***--After Alteration--***/");
            System.out.println("eh:");
            System.out.println(eh);
            System.out.println();
            System.out.println("ehCloned:");
            System.out.println(ehCloned);
            System.out.println();
        }

        catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        } 

        catch (ClassCastException e) {
            System.out.println(e.toString());
        }

        catch (Exception e) {
            System.out.println("\nError Message:" + e.getMessage());
        }
    }
}


import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

public class EHashtable extends Hashtable {
    public EHashtable() {

    }

    public EHashtable(int capacity) {

    }

    // Done
    public boolean add(Object key, Object value) {
        if (!(containsKey(key) && containsValue(value))) {
            put(key, value);
            return true;
        }

        else {
            return false;
        }
    }

    // Done
    public void Clear() {
        clear();
    }

    // Done
    public Object Clone() throws CloneNotSupportedException {
        EHashtable eh = (EHashtable) this.clone();
        return eh;
    }

    public Object dClone() {
        EHashtable eh = new EHashtable();
        for (Object key : keySet())
            eh.put(key, get(key));
        return eh;
    }

    // Done
    public boolean isNotEmpty() {
        return !isEmpty();
    }

    // Done
    public Iterator iterate() {
        return entrySet().iterator();
    }
}


public class Point {
    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public String toString() {
        return "[x=" + x + ", y=" + y + "]";
    }
}


public class PlaneCircle {
    private Point p;
    private float radius;

    public PlaneCircle (Point p, float radius) {
        this.p = p;
        this.radius = radius;
    }

    public String toString() {
        return "[p=" + p.toString() + ", radius=" + radius + "]";
    }
}


public class Circle {
    private float radius;

    public Circle(float radius) {
        this.radius = radius;
    }

    public String toString() {
        return "[radius=" + radius + "]";
    }
}

【问题讨论】:

    标签: java hashtable clone


    【解决方案1】:

    最简单的方法是对HashMap进行序列化和反序列化。但是,这需要在所有键和值类上实现 Serializable。不管它是 Cloneable 还是 Serializable .. 无论如何都需要做一些工作来深度复制对象图

    最好使用像http://code.google.com/p/fast-serialization/ 这样的快速序列化程序:-)。这种方式性能应该没问题

    【讨论】:

    • 这是否要求所有的键和值都实现Serializable?
    • 是的(就像指出的那样)。然而,在大多数情况下,这应该不是那么难。
    【解决方案2】:

    在有效的java中发现了这个

    public class HashTable implements Cloneable {
        private Entry[] buckets = ...;
    
        private static class Entry {
            final Object key;
            Object value;
            Entry next;
            Entry(Object key, Object value, Entry next) {
                this.key = key;
                this.value = value;
                this.next = next;
            }
    
            // Recursively copy the linked list headed by this Entry
            Entry deepCopy() {
                return new Entry(key, value,
                        next == null ? null : next.deepCopy());
            }
        }
        @Override public HashTable clone() {
            try {
                HashTable result = (HashTable) super.clone();
                result.buckets = new Entry[buckets.length];
                for (int i = 0; i < buckets.length; i++)
                    if (buckets[i] != null)
                        result.buckets[i] = buckets[i].deepCopy();
                return result;
            } catch (CloneNotSupportedException e) {
                throw new AssertionError();
            }
        }
    

    另一种方法是:

    SerializationUtils.clone() 序列化和反序列化 Map 引用的整个对象图,这就是为什么它需要这么长时间。但是,它会创建一个真正的深层副本(前提是您的课程中没有有趣的序列化内容)。

    【讨论】:

    • 哼..这不是深拷贝,不是吗?
    【解决方案3】:

    如果所有元素都是Serializable,则序列化hashMap并通过ObjectInputStream反序列化,这将是一个深拷贝。

    请参考this

    此链接中的一些重点:

    通过序列化克隆

    解决这些问题的一种方法是使用序列化进行克隆。 通常,序列化用于将对象发送到某处(进入 文件或通过网络),以便其他人可以重建它们 之后。但是你可以滥用它来自己重建对象 立即地。如果对象完全是可序列化的,那么 重建应该是忠实的副本。在正常使用中 序列化,原来的对象远不及;它可能在 位于网络连接远端的世界另一端。那么你 可以确定更改副本不会影响 原创。

    在继续之前,我应该提醒一下,这种技术不是 要轻而易举地使用。首先,序列化非常昂贵。 它很容易比 clone() 贵一百倍 方法。其次,并非所有对象都是可序列化的。第三,制作一个 类可序列化很棘手,并非所有类都可以依赖 修正它。 (您可以假设系统类是正确的, 不过。)

    【讨论】:

      【解决方案4】:

      除了序列化的想法,如果你有现有的代码来将你的 Map 持久化到文件、数据库、XML/json 等,这对你来说将是一个“快速而肮脏”的选择。性能方面不是很快,但编码速度很快。

      【讨论】:

        【解决方案5】:

        请注意,仅使用大小写不同的方法(清除、清除;克隆、克隆)是一个糟糕的主意。这不是必需的,因为您可以使用 super 关键字来调用方法的超类实现。

        Hashtable 本身是可克隆的,因此不需要覆盖克隆;现有的 clone 方法已经完成了浅层克隆所需的操作,尽管为方便起见,您可以覆盖它以提供 EHashtable 的返回类型而不是 Object:

        public EHashtable clone() {
            return (EHashtable)super.clone();
        }
        

        对于深度克隆,你是对的,因为 Object.clone 是受保护的,所以你不能调用它。 It's a historical design mistake in Java. 周围有很多混乱的方法(序列化,或反射,或定义使方法公开的ActuallyCloneable 接口),但我认为您不需要做这些事情,因为(除非您将 mutator 方法排除在外)粘贴问题时要简洁)您存储在表中的所有类型似乎都是不可变的。如果它们无法更改,则克隆它们是没有用的。

        如果它们在您的实际代码中不是不可变的,那么将它们设为不可变对于这些简单的值类型实际上是一个非常好的主意。它使您能够简化其他代码,而不必担心何时需要复制它们。

        现在,即使在浅克隆(Clone() 方法)上,更改一个 Hashtable 中的一个值,并且不会更改另一个中的值。

        没有改变价值观;你在放新的。更改值类似于:

        cr1.radius = 123;
        

        如果您的对象是不可变的,这是不可能的,但它会改变从原始哈希表及其浅克隆中看到的对象。

        几个小建议: (1) 打印异常的toString() 只会打印其名称和消息;堆栈跟踪信息将丢失。要获取堆栈跟踪,请使用printStackTrace() 方法。这还具有确保它被打印到 stderr 而不是 stdout 的好处(这可能使它更明显;它在 Eclipse 中是鲜红色的)。例如,

        catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        

        或者,如果对特定的已检查异常没有任何用处,最好将其作为未检查异常重新抛出,这样即使出现错误,周围的代码也不会继续执行:

        catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
        

        (2) 执行eh.add(new String("C1"), cr1);(创建一个新字符串)是一种愚蠢的反模式,它没有任何好处,而且会增加不必要的对象创建和输入开销。您可以将其更改为 eh.add("C1", cr1);

        【讨论】:

        • 现在,你的回答是我的最爱。一个请求:尽管你说过可变与不可变,但我仍然需要进行深度克隆。我的一些课程是可变的。您介意解释如何克隆每个值吗?谢谢。
        • @user1567060 如前所述,Java 的 clone() 方法是 API 中的一个弱点,但请参阅 stackoverflow.com/questions/64036/… 和所有相关问题,了解许多替代想法。
        猜你喜欢
        • 2011-02-07
        • 1970-01-01
        • 2012-03-30
        • 2023-04-03
        • 1970-01-01
        • 1970-01-01
        • 2019-03-25
        • 2017-09-17
        • 2017-09-01
        相关资源
        最近更新 更多