高效“修改”不可变映射

2022-09-04 22:57:57

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

因此,如果我只想修改一个值,我希望能够执行以下操作:

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

以下是番石榴期待我做的事情:

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. 有没有一种番石榴实用方法埋在什么地方用于这种事情?(它不在 Maps 或 ImmutableMap.Builder 中。

  2. 有没有其他Java库可以正确处理这种事情?我的印象是Clojure在引擎盖下有这种东西,但我们还没有准备好切换语言......


答案 1

有点出乎意料的是,Functional Java的地图是可变的,就像Guava的一样。正如我所期望的那样,该列表是不可变的。

谷歌搜索“持久收集java”提出了:pcollections。有一个 Map 实现

在实际使用任何其他实现之前,我会根据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();