【问题标题】:Create a HashMap copy in Java - What's the most efficient way?在 Java 中创建 HashMap 副本 - 最有效的方法是什么?
【发布时间】:2017-09-14 17:18:11
【问题描述】:

我有一个 HashMap 需要复制约 100 000 次,并且副本将单独扩展。由于 100 000 个副本很多(这不是我的代码中唯一一次发生这种情况),这目前是我实现中的一个主要瓶颈(事实上,它经常发生,占用了 45% 的运行时间,并且有不幸的是没有办法限制这个数字),所以我正在寻找最有效的方法来做到这一点。

我找到了以下选项来创建 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 最有效的方法是什么?有没有我错过的选项(除了迭代原始版本,但我想这不是一个真正的选项)?

【问题讨论】:

  • 尽量避免使用克隆方法...
  • 我能问一下为什么需要 100,000 个 HashMap 副本吗?我无法想象有这种要求的场景
  • Flyweight pattern 可能对您的情况有所帮助。
  • 您的副本是否会被修改?如果实际上有相当大的比例没有修改,那么您可能会受益于推迟复制,直到您真正需要它。有多种方法可以实现这一点,但我立即想到的方法都涉及在地图周围创建一个持有者或包装类。
  • 我倾向于为持久数据结构找到一个库,它可以有效地支持这种操作。

标签: java hashmap copy clone


【解决方案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】:

    考虑您是否真的需要副本。

    您说“我只需要具有相同对象的地图,我可以单独添加其他对象而不影响其他地图”。考虑到这一点,您可以创建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() 中的键和值进行重复数据删除),但如果mapThatYouAddThingsTomapThatIsShared 小得多,您将可以避免使用很多内存少。

    【讨论】:

      【解决方案3】:

      您需要遍历这些项目。最简单的方法是流。我将地图的键设为字符串,并为您的“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;
          }
      
      }
      

      【讨论】:

      • 为什么要使用流而不是仅仅使用Map&lt;String, Pojo&gt; mapCopy = new HashSet&lt;&gt;(orig);?为什么要为String 定义包装器?
      • 啊,谢谢。在回复您时,我注意到我没有复制该值。我认为他希望价值对象不一样。更新了我的回复,使其具有新的地图值值,并更新了 orig 中的 pojo。
      【解决方案4】:

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

      如果您只想创建地图的浅表副本,那么最推荐使用选项 3。

      但是,如果您需要复制定义为 HashMap&lt;Integer, List&lt;Item&gt;&gt; 的地图,但又希望在更改副本中的某些内容时原始地图保持不变。即,如果您从副本中的列表中删除某些内容,则原始列表中的值应保持不变。

      对于这个深拷贝功能,我有两个解决方案。现在 Java 8 不提供本地实现。我们可以使用 GuavaApache Commons Lang。但是我们可以找到一个解决方法来创建一个使用foreach 方法或Stream.collect() 方法创建新实例的方法。前者很简单,我们使用 foreach 来创建我们要复制的对象的新实例,在这种情况下为 List&lt;T&gt; 在此处查看泛型函数:

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

      注意

      请注意,我没有将&lt;K,V&gt; 用于泛型,因为这不是正确的深拷贝方法,它适用于每个级别的嵌套克隆。这种方法基于我们有一个HashMap&lt;Integer, List&lt;Item&gt;&gt; 的想法,其中Item 类不包含需要克隆的属性。

      【讨论】:

        猜你喜欢
        • 2017-08-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多