Saftkeks
Saftkeks

Reputation: 181

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. 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

Answers (4)

Teocci
Teocci

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

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. 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

Matt
Matt

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

Andy Turner
Andy Turner

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:

  • Your 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.
  • The 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

strash
strash

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

Related Questions