在哈希表中如何根据键更新值?

785
假设我们在Java中有一个 HashMap<String,Integer>
如何更新(递增)我找到的每个字符串键的整数值?
一种方法是删除并重新输入该对,但开销会成为一个问题。另一种方法是只放置新的一对,旧的将被替换。
在后一种情况下,如果我正在尝试插入一个新键时发生哈希码冲突会发生什么? 哈希表的正确行为应该是为其分配不同的位置,或者将其制作成当前桶中的列表。
17个回答

1214
map.put(key, map.get(key) + 1);

应该没问题。它将更新现有映射的值。请注意,这使用了自动装箱。通过使用map.get(key),我们可以获取相应键的值,然后您可以根据需求进行更新。这里我更新为将值增加1。


26
事实上,这是最健壮和可扩展的企业解决方案。 - Lavir the Whiolet
13
@Lavir,这不是一个坏的解决方案,但我不认为它是最健壮和可扩展的。相比之下,使用AtomicInteger会更具可扩展性。 - John Vint
22
假设这个关键字存在,对吧?如果不存在,我会得到一个空指针异常。 - Ian
115
使用Java 8,可以很容易地通过使用getOrDefault避免这种情况,例如:map.put(key, count.getOrDefault(key, 0) + 1); - Martin
9
@Martin.. map.put(key, map.getOrDefault(key, 0) + 1)翻译:将key作为键,将其在map中对应的值加一并更新。如果该键不存在,则默认值为0。 - Sathesh
显示剩余10条评论

147

Java 8的方式:

您可以使用computeIfPresent方法,并提供一个映射函数,该函数将基于现有值计算出一个新值。

例如,

Map<String, Integer> words = new HashMap<>();
words.put("hello", 3);
words.put("world", 4);
words.computeIfPresent("hello", (k, v) -> v + 1);
System.out.println(words.get("hello"));

另外,您可以使用merge方法,其中1是默认值,并且该函数将现有值增加1:

words.merge("hello", 1, Integer::sum);

此外,还有一系列其他有用的方法,比如putIfAbsentgetOrDefaultforEach等。


4
我刚刚测试了你的解决方案。第二种方法,即使用方法引用的方法有效。第一种方法,即使用lambda表达式的方法,在你的map中任何一个值为“null”(例如words.put("hello", null);)时不会一致地工作,结果仍然是null而不是我期望的1 - Tao Zhang
5
从 Javadoc 中可以看到:如果指定的键存在且非空,则尝试计算新映射。您可以使用 compute() 方法来替代,它也可以处理 null 值。 - Konstantin Milyutin
1
我想将我的值增加1。.merge是我的解决方案,使用Integer::sum - S_K

79

Java 8的简化写法:

map.put(key, map.getOrDefault(key, 0) + 1);

这里使用了HashMap的方法来通过键(key)检索值(value),但是如果无法检索到键,则返回指定的默认值(在本例中为“0”)。

这种方法在Java核心中得到支持:HashMap<K,V> getOrDefault(Object key, V defaultValue)


3
如果你正在使用Java 1.8,这个更好。 - Hemant Nagpal

54
hashmap.put(key, hashmap.get(key) + 1);

方法put替换现有键的值,如果该键不存在,则会创建该键。


71
不,它不会创建任何东西,而是会导致“空指针异常”。 - smttsp
15
这段代码是对所给问题的正确答案,但是它是在接受的答案发布一年后才发布的,而且与原先的代码完全相同。区别在于这个答案说明了“put”可以创建一个新条目,虽然在这个例子中不是这样的。如果你用HashMap.get(key)查找不存在的键/值,你会得到null,当你尝试增加时,就像@smttsp说的那样,将会发生空指针异常(NPE)。-1 - Zach
13
这个答案是错误的。对于不存在的键会抛出 NullPointerException 异常。 - Eleanore
@smttp 如果您没有初始化值(正如您所知道的,您不能增加null),则会出现NullpointterException。 - Mehdi
重复和错误的解释... 如果不存在则创建 你不能执行 null + 1,因为这会尝试将 null 拆箱成整数来执行增量。 - AxelH

30

AtomicInteger替换Integer,并调用其中之一的incrementAndGet/getAndIncrement方法。

另一种方法是将int封装在您自己的MutableInteger类中,该类具有increment()方法,但您仍需要解决线程安全问题。


37
AtomicInteger 是一个内置的可变整数类型。我认为自己编写一个可变整数类型并不是一个更好的想法。 - Peter Lawrey
自定义MutableInteger更好,因为AtomicInteger使用了有开销的volatile。我会使用int [1]而不是MutableInteger - Oliv
@Oliv:非并发。 - BalusC
@BalusC 但是,volatile写入更昂贵。它会使缓存失效。如果没有区别,所有变量都将是volatile的。 - Oliv
@Oliv:问题明确提到哈希码冲突,因此并发对OP非常重要。 - BalusC
@BalusC,哈希码冲突根本与并发无关。并发是指从多个线程访问值,而这里并不需要。 - Oliv

21

一行代码解决方案:

map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1);

4
这并没有为现有答案增加任何新的内容,对吧? - Robert
1
是的,它会。如果键不存在,则正确标记的答案将抛出NullPointerException。这个解决方案将很好地工作。 - Hemant Nagpal

19

@Matthew的解决方案是最简单的,在大多数情况下表现良好。

如果您需要高性能,AtomicInteger是一个更好的解决方案,就像@BalusC所说。

然而,如果线程安全不是问题,更快的解决方案是使用TObjectIntHashMap,它提供了一个increment(key)方法,并且使用原始类型和比创建AtomicIntegers更少的对象。例如:

TObjectIntHashMap<String> map = new TObjectIntHashMap<String>()
map.increment("aaa");

14

您可以按照以下方式增加,但您需要检查其是否存在,以避免抛出NullPointerException异常

if(!map.containsKey(key)) {
 p.put(key,1);
}
else {
 p.put(key, map.getKey()+1);
}

9

可能有点晚了,但这是我的意见。

如果您使用的是Java 8,则可以利用computeIfPresent方法。如果指定键的值存在且非空,则尝试计算给定键及其当前映射值的新映射。

final Map<String,Integer> map1 = new HashMap<>();
map1.put("A",0);
map1.put("B",0);
map1.computeIfPresent("B",(k,v)->v+1);  //[A=0, B=1]

我们还可以使用另一种方法putIfAbsent来放置一个键。如果指定的键尚未与值相关联(或映射到null),则此方法将其与给定值关联并返回null,否则返回当前值。
如果地图在多个线程之间共享,则可以使用ConcurrentHashMapAtomicInteger。从文档中得知:
“AtomicInteger是一个可以原子更新的int值。AtomicInteger用于应用程序,例如原子递增计数器,并且不能用作Integer的替代品。但是,这个类扩展了Number,以允许工具和基于数字的实用程序统一访问。”
我们可以像下面这样使用它们:
final Map<String,AtomicInteger> map2 = new ConcurrentHashMap<>();
map2.putIfAbsent("A",new AtomicInteger(0));
map2.putIfAbsent("B",new AtomicInteger(0)); //[A=0, B=0]
map2.get("B").incrementAndGet();    //[A=0, B=1]

需要注意的一点是,我们正在调用 get 方法来获取键为B的值,然后在其值上调用 incrementAndGet() 方法,而它的值当然是 AtomicInteger 类型。我们可以进行优化,因为 putIfAbsent 方法会返回该键的值(如果已经存在):

map2.putIfAbsent("B",new AtomicInteger(0)).incrementAndGet();//[A=0, B=2]

顺便提一句,如果我们计划使用AtomicLong,那么根据文档,在高并发情况下,LongAdder的预期吞吐量显着更高,但空间消耗更大。此外,请查看这个问题


9
哈希表中是否存在对应的键值对(值为0),或者该键值对是在第一次增量时“放置”进哈希表的?如果是在第一次增量时“放置”进哈希表的,那么代码应该如下所示:
if (hashmap.containsKey(key)) {
    hashmap.put(key, hashmap.get(key)+1);
} else { 
    hashmap.put(key,1);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接