HashMap<String,Integer> 数据结构,但值被添加而不是被替换

HashMap<String,Integer> data structure, but values are added instead of replaced

我正在搜索一个几乎完全是 HashMap<String,Integer> 的数据结构,但是 HashMap 的问题是通过调用 putAll() 方法丢失了存储在键值对中的大部分数据在两个 HashMap 上,由于 java/util/HashMap.java.

line 655putVal() 的替换行为

这基本上就是我想要的改变:

    if (e != null) { // existing mapping for key
         V oldValue = e.value;
         if (!onlyIfAbsent || oldValue == null)
--            e.value = value;
++            e.value = value + oldValue;
         afterNodeAccess(e);
         return oldValue;
    }

是否有一个我忽略的现有数据结构会做这样的事情,或者我如何创建一个 class 基本上是一个更改的 HashMap?

我已经尝试编写一些代码,但没有按照我想要的方式工作...事实上,如果我在 @Override 上设置 put 方法,这样做并不重要,或者完全删除它 - 替换行为当然保持不变,因为 putAll() 使用 putVal() 我无法从外部到达/更改 - 或者我至少不知道如何...

 /**
  * doesn't work, putAll() uses putVal() that I can't reach
  */
 public class SumHashMap<K> extends HashMap<K, Integer> {
    private static final long serialVersionUID = 1L;

    public Integer put(K key, Integer value) {
        Integer oldValue = get(key);
        if (oldValue == null)
            return super.put(key, value);
        return super.put(key, oldValue + value);
    }
}

提前致谢

附加信息:

我认为没有数据结构可以做到这一点。数据结构的目的是存储数据,而不是关联逻辑。 HashMap 可以为您存储键值对,但如果您需要一些更高级的或特定的与某些操作相关的逻辑,您需要自己添加它。

一种方法是将地图包装在具有此逻辑的 class 中。另一个可能是自己实现 Map 接口(也可以在内部使用 HashMap),但我不建议这样做,因为改变行为不是一个好主意。

提供添加功能的最小包装器:

public class AddingMap {
    private final HashMap<String, Integer> map;

    public AddingMap() {
        map = new HashMap<>();
    }

    public void add(String key, Integer value) {
        map.put(key, map.getOrDefault(key, 0) + value);
    }
    
    public Integer get(String key) {
        return map.get(key);
    }
}

编辑

答案应该不会写到一半...

的确,缺少 addAll() 方法:

public void addAll(Map<String, Integer> map) {
    map.entrySet().forEach(e -> this.add(e.getKey(), e.getValue()));
}

我想这就是您要找的。我这样做是为了让钥匙可以是任何类型。如果需要,您可以删除键的泛型,只需扩展 HashMap.

import java.util.HashMap;
import java.util.Map;

public class AddingHashMap<K> extends HashMap<K, Integer> {
    @Override
    public Integer put(K key, Integer value) {
        Integer existingValue = super.get(key);
        if (existingValue == null) {
            existingValue = value;
        } else {
            existingValue = existingValue.intValue() + value.intValue();
        }
        return super.put(key, existingValue);
    }

    @Override
    public void putAll(Map<? extends K, ? extends Integer> m) {
        m.entrySet().forEach(entry -> {
            this.put(entry.getKey(), entry.getValue());
        });
    }
}

这是有效的:

public static void main(String[] argv) {
        AddingHashMap<String> myAddingHashMap = new AddingHashMap<>();
        myAddingHashMap.put("One", 1);
        myAddingHashMap.put("Two", 2);
        myAddingHashMap.put("One", 3);

        myAddingHashMap.entrySet().forEach(entry -> System.out.println(entry.getKey() + " - " + entry.getValue()));
    }

输出:

One - 4
Two - 2

稍后编辑:请记住,这不是线程安全的。

为此您不需要新的数据结构,您甚至不需要继承自 HashMap 的新 class。相反,使用 Map.merge 方法:

newMap.forEach((k, v) -> oldMap.merge(k, v, Integer::sum));

此代码使用 Map.forEach 遍历新地图的条目(您将在 putAll 中作为参数收到的地图)并使用 Map.merge(连同 Integer::sum) 将其条目合并到一个已经存在的地图中(我在这里命名为 oldMap)。