给定哈希图中的键,如何更新值?


624

假设我们有一个HashMap<String, Integer>Java语言。

如何为找到的每个字符串更新(递增)字符串键的整数值?

一个可以删除并重新进入一对,但是开销会是一个问题。
另一种方法是只放置新的一对,而旧的将被替换。

在后一种情况下,如果哈希码与我尝试插入的新密钥发生冲突怎么办?哈希表的正确行为是为其分配一个不同的位置,或在当前存储桶中列出该列表。

Answers:


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

应该没事。它将更新现有映射的值。请注意,这使用自动装箱。在map.get(key)我们的帮助下,您可以获得相应密钥的值,然后您可以根据需要进行更新。在这里,我将值增加1。


21
实际上,这是最健壮和可扩展的企业解决方案。
紫罗兰色拉维尔

12
@Lavir,这不是一个糟糕的解决方案,但是看不到它最强大和可扩展的方式。相反,atomicinteger具有更大的可伸缩性。
约翰·维特

13
假设密钥存在,对吗?我没有得到nullPointer异常。
2014年

84
使用Java 8,可以很容易地避免使用getOrDefault,例如:map.put(key, count.getOrDefault(key, 0) + 1);
Martin

2
@Martin .. map.put(key,map.getOrDefault(key,0)+ 1)
Sathesh

110

Java 8方式:

您可以使用computeIfPresentmethod并为其提供一个映射函数,该函数将被调用以基于现有值来计算新值。

例如,

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"));

或者,您可以使用mergemethod,其中1为默认值,函数将现有值增加1:

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

此外,有一堆其他有用的方法,例如putIfAbsentgetOrDefaultforEach等。


3
我刚刚测试了您的解决方案。第二个方法引用方法有效。当您的地图的任何值是null(例如words.put("hello", null);)时,第一个lambda表达式one不能始终如一地工作,结果仍然null不是1我期望的那样。
张涛

3
在Javadoc中:“如果指定键的值存在且非空,则尝试计算新的映射”。您可以改用compute()它,它也会处理null值。
丹卢尔

我想将值增加1. .merge是我的解决方案Integer::sum
S_K

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

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


55
不,它不会创造,它会给nullPointer Exception
smttsp 2015年

13
该代码是给定问题的正确答案,但是在接受的答案中发布了完全相同的代码一年后才发布。区分此答案的是声明put可以创建一个新条目,但本示例中不能。如果您将hashmap.get(key)用于不存在的键/值,则将获得null并在尝试递增时,如@smttsp所说的那样,它将NPE。-1
Zach 2015年

8
这个答案是错误的。不存在的密钥的NullPointerException
Eleanore

@smttp NullpointterException仅在您未初始化值时(您知道不能递增null)
Mehdi

复制和不正确的说明... ,如果不存在则会创建它。您不能这样做,null + 1因为这将尝试将inbox拆null成整数以进行增量。
AxelH


30

替换IntegerAtomicInteger并在其上调用incrementAndGet/ getAndIncrement方法之一。

一种替代方法是将一个包装int到您自己的MutableInteger具有increment()方法的类中,您只需要解决线程安全问题。


37
AtomicInteger是可变整数,但内置。我严重怀疑编写自己的MutableInteger是一个更好的主意。
彼得·劳瑞

自定义MutableInteger更好,因为AtomicInteger使用volatile会产生开销。我会用int[1]代替MutableInteger
Oliv

@Oliv:不是并发的。
BalusC

@BalusC但是,易失性写入更加昂贵。它会使缓存无效。如果没有差异,则所有变量都是易变的。
奥利夫(Oliv)

@Oliv:问题明确提到了哈希码冲突,因此并发对于OP很重要。
BalusC

19

一线解决方案:

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

4
那不会对现有答案添加任何新内容,是吗?
罗伯特

1
是的,它确实。如果键不存在,则正确标记的答案将引发NullPointerException。此解决方案将正常工作。
Hemant Nagpal

18

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

如果您需要高性能,则AtomicInteger是@BalusC更好的解决方案。

但是,一种更快的解决方案(提供的线程安全性不是问题)是使用TObjectIntHashMap,它提供了增量(密钥)方法,并且比创建AtomicIntegers使用的原语和对象更少。例如

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

13

您可以像下面这样递增,但需要检查是否存在,以便不引发NullPointerException

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

9

哈希是否存在(值为0)还是在第一个增量上“放入”映射?如果在第一个增量上“放入”,则代码应类似于:

if (hashmap.containsKey(key)) {
    hashmap.put(key, hashmap.get(key)+1);
} else { 
    hashmap.put(key,1);
}

7

可能还不晚,但这是我的两分钱。

如果您使用的是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。从文档中:

An 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的预期吞吐量会显着提高,但要消耗更多的空间。还要检查这个问题



2

由于信誉欠佳,我无法评论一些答案,因此我将发布一个已应用的解决方案。

for(String key : someArray)
{
   if(hashMap.containsKey(key)//will check if a particular key exist or not 
   {
      hashMap.put(hashMap.get(key),value+1);// increment the value by 1 to an already existing key
   }
   else
   {
      hashMap.put(key,value);// make a new entry into the hashmap
   }
}

1

使用for循环来增加索引:

for (int i =0; i<5; i++){
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("beer", 100);

    int beer = map.get("beer")+i;
    System.out.println("beer " + beer);
    System.out ....

}

3
那只会在每次迭代时覆盖Map。有关正确方法,请参见马修的答案。
Leigh 2012年


1
Integer i = map.get(key);
if(i == null)
   i = (aValue)
map.put(key, i + 1);

要么

Integer i = map.get(key);
map.put(key, i == null ? newValue : i + 1);

整数是原始数据类型http://cs.fit.edu/~ryan/java/language/java-data.html,因此您需要将其取出,进行一些处理,然后再放回去。如果您拥有的值不是原始数据类型,则只需将其取出,处理它,而无需将其放回哈希图中。


1
感谢您提供此代码段,它可能会立即提供帮助。正确的解释显示为什么这是解决问题的好方法,从而大大提高其教育价值,并且对将来有相似但不相同的问题的读者来说更有用。请编辑您的答案以添加解释,并指出适用的限制和假设。
Toby Speight

0

尝试:

HashMap hm=new HashMap<String ,Double >();

注意:

String->give the new value; //THIS IS THE KEY
else
Double->pass new value; //THIS IS THE VALUE

您可以在哈希图中更改键或值,但不能同时更改两者。


0

使用内置的功能'computeIfPresent'的Java8

例:

public class ExampleToUpdateMapValue {

    public static void main(String[] args) {
        Map<String,String> bookAuthors = new TreeMap<>();
        bookAuthors.put("Genesis","Moses");
        bookAuthors.put("Joshua","Joshua");
        bookAuthors.put("Judges","Samuel");

        System.out.println("---------------------Before----------------------");
        bookAuthors.entrySet().stream().forEach(System.out::println);
        // To update the existing value using Java 8
        bookAuthors.computeIfPresent("Judges", (k,v) -> v = "Samuel/Nathan/Gad");

        System.out.println("---------------------After----------------------");
        bookAuthors.entrySet().stream().forEach(System.out::println);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.