【问题标题】:Efficiently "modifying" an ImmutableMap有效地“修改”一个 ImmutableMap
【发布时间】:2012-02-01 05:55:31
【问题描述】:

我们目前正在将 Guava 用于其不可变集合,但我惊讶地发现他们的地图没有方法可以轻松创建新地图,只需稍作修改。最重要的是,他们的构建器不允许为键分配新值或删除键。

因此,如果我只想修改一个值,我希望能够这样做:

ImmutableMap<Guid, ImmutableMap<String, Integer>> originalMap = /* get the map */;
ImmutableMap<Guid, ImmutableMap<String, Integer>> modifiedMap =
    originalMap.cloneAndPut(key, value);

Guava 期待我这样做:

ImmutableMap<Guid, ImmutableMap<String, Integer>> originalMap = /* get the map */;
Map<Guid, ImmutableMap<String, Integer>> mutableCopy = new LinkedHashMap<>(originalMap);
mutableCopy.put(key, value);
originalMap = ImmutableMap.copyOf(mutableCopy);
/* put the map back */

通过这样做,我得到了一份带有我想要的修改的新地图副本。原始副本未受影响,我将使用原子引用将其放回原处,因此整个设置是线程安全的。

只是很慢。

这里有很多在幕后进行的浪费复制。假设地图中有 1,024 个桶。那是 1,023 个桶,您不必要地重新创建(每个也两次),而您可以按原样使用这些不可变的桶并只克隆其中一个。

所以我猜:

  1. 有没有为这类事情埋在某处的 Guava 实用方法? (它不在地图或 ImmutableMap.Builder 中。)

  2. 还有其他的 Java 库可以做到这一点吗?我的印象是 Clojure 有这种东西,但我们还没有准备好切换语言......

【问题讨论】:

  • 如果您愿意,请提交功能请求,但您可能真的在寻找合适的功能数据结构库。
  • 是的。这是可以做到的——这就是函数式语言所做的——但这不是 Guava 的用途。番石榴的例如ImmutableMap 是基于散列的,不支持高效更新,查询速度也有很大的代价。
  • 改写:Guava 的不可变集合是为快速查询/迭代和最小的内存消耗而构建的。这意味着散列和数组,它们不会让您支持有效的非破坏性更新。
  • @Louis Guava 仍然可以支持 API originalMap.cloneAndPut(key, value)originalMap.asBuilder().put(key, value)
  • 这是线性开销,让用户认为它比实际更快是非常冒险的......你的代码并没有真正让我意识到这是一个缓慢的解决方法。

标签: java map immutability guava


【解决方案1】:

有点意外,map of Functional Java 像 Guava 一样是可变的。尽管正如我所料,该列表是不可变的。

谷歌搜索“持久集合 java”:pcollections。有一个Map implementation

在实际使用任何其他实现之前,我会针对 Guava 对内存和性能特征进行基准测试。如果它仍然更好,我不会感到惊讶。

【讨论】:

    【解决方案2】:

    可以使用以下,只复制地图一次而不是两次

    ImmutableMap<String, Object> originalMap = /* get the map */;
    Map<String, Object> modifiedMap = ImmutableMap.<String, Object>builder().putAll( originalMap )
       .put( "new key", new Object() )
       .build();
    

    但是,在现有地图上进行迭代时,删除一个值看起来不太漂亮,例如像这样:

    ImmutableMap<String, Object> originalMap = /* get the map */;
    if( !originalMap.containsKey( "key to remove" ) ) {
       return;
    }
    ImmutableMap.Builder<String, Object> mapBuilder = ImmutableMap.builder();
    for( Map.Entry<String, Object> originalEntry : originalMap.entrySet() ) {
       if( originalEntry.getKey().equals( "key to remove" ) ) {
          continue;
       }
       mapBuilder.put( originalEntry );
    }
    Map<String, Object> modifiedMap = mapBuilder.build();
    

    【讨论】:

    • 我很确定这会复制地图两次——一次在putAll 调用,一次在build()——但这确实取决于构建器的实现方式。可以实现构建器,使其在您构建它之前实际上不会添加地图中的条目,但我不认为这就是它目前的实现方式。如果您想将其减少到一次,有一种方法 - 流式传输地图条目,附加到流中,将流收集回地图。
    猜你喜欢
    • 2017-10-04
    • 2014-08-25
    • 1970-01-01
    • 2011-10-21
    • 1970-01-01
    • 2015-02-12
    • 1970-01-01
    • 1970-01-01
    • 2014-12-23
    相关资源
    最近更新 更多