Reputation: 181
I have a HashMap that needs to be copied ~100 000 times and the copies will be expanded individually. 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.
I found the following options to create a shallow copy of the HashMap original:
//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? Are there options that I missed (other than iteration through the original, but I guess that isn't really an option)?
Upvotes: 4
Views: 6245
Reputation: 8895
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.
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. i.e if you remove something from the List in the copy the List in the original should maintain the value.
I have two solutions for this a deep copy function. Right now Java 8 does not provide a native implementation. We can use Guava or Apache Commons Lang. But we can find a work around creating a method to create new instances using foreach
method or Stream.collect()
method. 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:
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()
. 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())));
}
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. 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.
Upvotes: 0
Reputation: 492
You need to loop over the items. Easiest way is a Stream. I made the key for the map a String and made a "Pojo" class for your "T"...
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"
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;
}
}
Upvotes: -1
Reputation: 140318
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
:
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:
containsKey
method can first check mapThatYouAddThingsTo
to see if the key is present there; if so, it returns the value from mapThatYouAddThingsTo
. Otherwise, it checks mapThatIsShared
.put
method only ever puts things into mapThatYouAddThingsTo
, never into 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.
Upvotes: 3
Reputation: 1321
1 - it is worst. 2 and 3 are almost the same. You are using Map and it is also considered a collection. And why is the clone bad practice you can read here: Why people are so afraid of using clone() (on collection and JDK classes)?
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.
Upvotes: 1