简体   繁体   English

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

[英]Create a HashMap copy in Java - What's the most efficient way?

I have a HashMap that needs to be copied ~100 000 times and the copies will be expanded individually. 我有一个HashMap需要复制~100000次,副本将单独扩展。 Since 100 000 copies are a lot (and this is not the only time this happens in my code) this is currently a major bottleneck in my implementation (in fact, it happens so often that it takes up 45% of the runtime, and there's unfortunately no way to limit that number), so I'm looking for the most efficient way to do this. 由于100 000个副本很多(这不是我的代码中唯一发生的时间),这是我实现的一个主要瓶颈(事实上,它经常发生,占用了45%的运行时间,并且有不幸的是没有办法限制这个数字),所以我正在寻找最有效的方法来做到这一点。

I found the following options to create a shallow copy of the HashMap original: 我找到了以下选项来创建HashMap原始的浅表副本:

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

and

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

and

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

In your experience, what is the most efficient way to copy a HashMap? 根据您的经验,复制HashMap最有效的方法是什么? Are there options that I missed (other than iteration through the original, but I guess that isn't really an option)? 有没有我错过的选项(除了通过原始的迭代,但我想这不是一个真正的选项)?

Consider whether you really need copies. 考虑一下你是否真的需要副本。

You say that "I just need maps with the same objects that I can add other objects to individually without affecting the other maps". 你说“我只需要具有相同对象的地图,我可以单独添加其他对象而不影响其他地图”。 With this in mind, you could create a composite implementation of Map : 考虑到这一点,您可以创建Map的复合实现:

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

Now, you can implement your methods. 现在,您可以实现您的方法。 For example: 例如:

  • Your containsKey method can first check mapThatYouAddThingsTo to see if the key is present there; 你的containsKey方法可以先检查mapThatYouAddThingsTo以查看该键是否存在; if so, it returns the value from mapThatYouAddThingsTo . 如果是这样,它将从mapThatYouAddThingsTo返回值。 Otherwise, it checks mapThatIsShared . 否则,它会检查mapThatIsShared
  • The put method only ever puts things into mapThatYouAddThingsTo , never into mapThatIsShared . put方法只会把东西放到mapThatYouAddThingsTo ,永远不会放入mapThatIsShared

There are some tricky aspects to the implementation (like deduplicating the keys and values in keySet() and entrySet() ), but provided that mapThatYouAddThingsTo is much smaller than mapThatIsShared , you will get away with using a lot less memory. 实现有一些棘手的方面(比如重复删除keySet()entrySet()的键和值),但是如果mapThatYouAddThingsTomapThatIsShared ,那么你将使用更少的内存。

1 - it is worst. 1 - 这是最糟糕的。 2 and 3 are almost the same. 2和3几乎相同。 You are using Map and it is also considered a collection. 您正在使用Map,它也被视为一个集合。 And why is the clone bad practice you can read here: Why people are so afraid of using clone() (on collection and JDK classes)? 为什么克隆不好的做法你可以在这里阅读: 为什么人们如此害怕使用clone()(在集合和JDK类上)?

I would choose this: 我会选择这个:

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

, because when an API is giving you the ability to write it more elegant - usually the api is taking care of the other things behind the scene in the most appropriate way. ,因为当API让你能够更优雅地编写它时 - 通常api会以最恰当的方式处理场景背后的其他事情。

This is an old question but I think there is something else to mention. 这是一个老问题,但我认为还有其他事情要提。

If you just want to create a shallow copy of the map then the option number 3 is the most recommended. 如果您只想创建地图的浅表副本,则最推荐使用选项编号3。

However, if you need to make a copy of a map defined as HashMap<Integer, List<Item>> but you want the original map to stay the same while you change something in the copy. 但是,如果您需要制作定义为HashMap<Integer, List<Item>>的地图副本,但您希望在更改副本中的内容时原始地图保持不变。 ie if you remove something from the List in the copy the List in the original should maintain the value. 即,如果从副本中的List中删除某些内容,则原始中的List应保持该值。

I have two solutions for this a deep copy function. 我有两个解决方案,这是一个深度复制功能。 Right now Java 8 does not provide a native implementation. 目前Java 8不提供本机实现。 We can use Guava or Apache Commons Lang . 我们可以使用GuavaApache Commons Lang But we can find a work around creating a method to create new instances using foreach method or Stream.collect() method. 但是我们可以找到一个创建使用foreach方法或Stream.collect()方法创建新实例的方法的工作。 The former is simple we use a foreach to create a new Instance of the object we want to copy in this case a List<T> Check the generic function here: 前者很简单,我们使用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;
}

If you don't want to deal with generics then we will use Stream.collect() . 如果您不想处理泛型,那么我们将使用Stream.collect() In this case we use the stream to extract the data and we wrapped as a map and create a new instance 在这种情况下,我们使用流来提取数据,并将其包装为地图并创建新实例

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())));
}

Note 注意

Please, notice that I didn't use <K,V> for the generics because this is not a proper deep copy method, that will work with nested clones of each level. 请注意,我没有使用<K,V>作为泛型,因为这不是一个适当的深层复制方法,它将适用于每个级别的嵌套克隆。 This approach is based on the idea that we have a HashMap<Integer, List<Item>> where the Item class does not contains attributes that requires cloning. 这种方法基于我们有一个HashMap<Integer, List<Item>>的想法,其中Item类不包含需要克隆的属性。

You need to loop over the items. 你需要遍历项目。 Easiest way is a Stream. 最简单的方法是Stream。 I made the key for the map a String and made a "Pojo" class for your "T"... 我为地图创建了一个字符串,并为你的“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());
}

Simple class to represent your "T" 简单的类代表你的“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;
    }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM