用Java创建HashMap副本 - 最有效的方法是什么?

时间:2017-04-18 18:14:16

标签: java hashmap copy clone

我有一个需要复制~10万次的HashMap,副本将被单独扩展。由于100 000个副本很多(而且这不是我的代码中唯一发生这种情况),这在我的实现中是一个主要的瓶颈(事实上,它经常发生,它占用了运行时的45%,并且#39;很遗憾没有办法限制这个数字),所以我正在寻找最有效的方法。

我找到了以下选项来创建HashMap原始的浅表副本:

//1
 HashMap<T> map = (HashMap<T>) original.clone()

//2
HashMap<T> map = new HashMap<T>();
map.putAll(original);

//3
HashMap<T> map = new HashMap<T>(original);

根据您的经验,复制HashMap的最有效方法是什么?有没有我错过的选项(除了原版的迭代,但我想这不是一个真正的选择)?

4 个答案:

答案 0 :(得分:3)

考虑一下你是否真的需要副本。

你说“我只需要具有相同对象的地图,我可以单独添加其他对象而不影响其他地图”。考虑到这一点,您可以创建Map的复合实现:

class MyCompositeMap<K, V> implements Map<K, V> {
  final Map<K, V> mapThatYouAddThingsTo;
  final Map<K, V> mapThatIsShared;
}

现在,您可以实现您的方法。例如:

  • 您的containsKey方法可以先检查mapThatYouAddThingsTo以查看该密钥是否存在;如果是,则返回mapThatYouAddThingsTo的值。否则,它会检查mapThatIsShared
  • put方法只会将内容放入mapThatYouAddThingsTo,永远不会放入mapThatIsShared

实施中有一些棘手的方面(例如重复删除keySet()entrySet()中的键和值),但前提是mapThatYouAddThingsTo远小于mapThatIsShared,你将使用更少的内存来逃避。

答案 1 :(得分:1)

1 - 这是最糟糕的。 2和3几乎相同。 您正在使用Map,它也被视为一个集合。 为什么克隆不好的做法你可以在这里阅读:Why people are so afraid of using clone() (on collection and JDK classes)?

我会选择这个:

HashMap<T> map = new HashMap<T>(original);

,因为当API让你能够更优雅地编写它时 - 通常api会以最合适的方式处理场景背后的其他事情。

答案 2 :(得分:0)

这是一个古老的问题,但我认为还有其他事情要提及。

如果您只想创建地图的浅表副本,则最建议使用编号3。

但是,如果您需要复制定义为HashMap<Integer, List<Item>>的地图,但是您希望原始地图在更改副本中的内容时保持原样。也就是说,如果您从副本中的列表中删除了某些内容,则原始列表中的列表应保持该值。

为此,我有两种解决方案。目前,Java 8不提供本机实现。我们可以使用 Guava Apache Commons Lang 。但是我们可以找到一种使用foreach方法或Stream.collect()方法创建方法来创建新实例的方法。前者很简单,我们使用foreach来创建要复制的对象的新实例,在这种情况下,请List<T>在此处检查通用函数:

public static <T> HashMap<Integer, List<T>> deepCopy(HashMap<Integer, List<T>> original)
{
    HashMap<Integer, List<T>> copy = new HashMap<>();
    for (Map.Entry<Integer, List<T>> entry : original.entrySet()) {
        copy.put(entry.getKey(), new ArrayList<>(entry.getValue()));
    }

    return copy;
}

如果您不想使用泛型,那么我们将使用Stream.collect()。在这种情况下,我们使用流提取数据,然后将其包装为地图并创建一个新实例

public static <T> Map<Integer, List<T>> deepCopyStream(Map<Integer, List<T>> original)
{
    return original
            .entrySet()
            .stream()
            .collect(Collectors.toMap(Map.Entry::getKey, valueMapper -> new ArrayList<>(valueMapper.getValue())));
}

注意

请注意,我没有对泛型使用<K,V>,因为这不是正确的深度复制方法,该方法可用于每个级别的嵌套克隆。此方法基于以下想法:我们有一个HashMap<Integer, List<Item>>,其中Item类不包含需要克隆的属性。

答案 3 :(得分:-1)

您需要遍历这些项目。最简单的方法是Stream。我为地图创建了一个字符串,并为你的“T”制作了一个“Pojo”类......

public void testMapCopy() {

    // build the orig map
    Map<String, Pojo> orig = new HashMap();
    for (int i = 0; i < 10; i++) {
        orig.put("k" + i, new Pojo("v"+i));
    }

    // make a copy
    Map<String, Pojo> mapCopy = orig.entrySet().stream()
            .collect(Collectors.toMap(e -> e.getKey(), new Pojo(e.getValue().getValue())));

    // change orig
    Pojo pojo = orig.get("k0");
    pojo.setValue("v0-updated!"); 

    // check values
    System.out.println("orig k0: " + orig.get("k0").getValue());
    System.out.println("copy k0: " + mapCopy.get("k0").getValue());
}

表示“T”的简单类

private class Pojo {

    private String value;

    public Pojo(String value) {
        this.value = value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

}