【问题标题】:Most efficient way to increment a Map value in Java在 Java 中增加 Map 值的最有效方法
【发布时间】:2010-09-10 00:41:12
【问题描述】:

我希望这个问题对于这个论坛来说不是太基本,但我们会看到的。我想知道如何重构一些代码以获得更好的性能,这些代码正在运行很多次。

假设我正在使用 Map(可能是 HashMap)创建一个词频列表,其中每个键是一个字符串,其中包含正在计数的单词,值是一个整数,每次单词的标记时递增找到了。

在 Perl 中,增加这样的值非常容易:

$map{$word}++;

但在 Java 中,它要复杂得多。这是我目前的做法:

int count = map.containsKey(word) ? map.get(word) : 0;
map.put(word, count + 1);

这当然依赖于较新 Java 版本中的自动装箱功能。我想知道您是否可以提出一种更有效的方法来增加这样的值。避免使用 Collections 框架并改用其他东西是否有更好的性能原因?

更新:我已经对几个答案进行了测试。见下文。

【问题讨论】:

  • 我认为 java.util.Hashtable 也是一样的。
  • 当然如果是一样的,因为Hashtable实际上是一个Map。
  • Java 8:computeIfAbsent 示例:stackoverflow.com/a/37439971/1216775
  • 看看Map::computeIfAbsent和朋友们。

标签: java optimization collections


【解决方案1】:

部分测试结果

我已经得到了很多关于这个问题的好答案——谢谢大家——所以我决定运行一些测试并找出哪种方法实际上是最快的。我测试的五种方法是:

  • 我在the question 中介绍的“ContainsKey”方法
  • Aleksandar Dimitrov 建议的“TestForNull”方法
  • Hank Gay 建议的“AtomicLong”方法
  • jrudolph 建议的“Trove”方法
  • phax.myopenid.com 建议的“MutableInt”方法

方法

这就是我所做的......

  1. 创建了五个相同的类,除了下面显示的差异。每个类都必须执行我提出的场景的典型操作:打开一个 10MB 的文件并读入它,然后对文件中的所有单词标记执行频率计数。由于这平均只需要 3 秒,因此我让它执行了 10 次频率计数(而不是 I/O)。
  2. 对 10 次迭代的循环进行计时,但不是 I/O 操作,并基本上使用 Ian Darwin's method in the Java Cookbook 记录所花费的总时间(以时钟秒为单位)。
  3. 连续执行了所有五项测试,然后又执行了 3 次。
  4. 对每种方法的四个结果进行平均。

结果

我先把结果和下面的代码展示给有兴趣的人。

ContainsKey 方法正如预期的那样是最慢的,所以我将给出每种方法的速度与该方法的速度的比较。

  • ContainsKey: 30.654 秒(基线)
  • AtomicLong: 29.780 秒(快 1.03 倍)
  • TestForNull: 28.804 秒(快 1.06 倍)
  • Trove: 26.313 秒(快 1.16 倍)
  • MutableInt: 25.747 秒(快 1.19 倍)

结论

似乎只有 MutableInt 方法和 Trove 方法明显更快,因为只有它们提供了超过 10% 的性能提升。但是,如果线程是一个问题,AtomicLong 可能比其他的更有吸引力(我不太确定)。我还使用 final 变量运行了 TestForNull,但差异可以忽略不计。

请注意,我没有分析不同场景中的内存使用情况。如果有人对 MutableInt 和 Trove 方法可能如何影响内存使用有深入的了解,我将很高兴听到他们的意见。

就我个人而言,我发现 MutableInt 方法最有吸引力,因为它不需要加载任何第三方类。所以除非我发现它有问题,否则我最有可能这样做。

代码

这是每个方法的关键代码。

包含密钥

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
int count = freq.containsKey(word) ? freq.get(word) : 0;
freq.put(word, count + 1);

TestForNull

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
Integer count = freq.get(word);
if (count == null) {
    freq.put(word, 1);
}
else {
    freq.put(word, count + 1);
}

原子长

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
...
final ConcurrentMap<String, AtomicLong> map = 
    new ConcurrentHashMap<String, AtomicLong>();
...
map.putIfAbsent(word, new AtomicLong(0));
map.get(word).incrementAndGet();

宝藏

import gnu.trove.TObjectIntHashMap;
...
TObjectIntHashMap<String> freq = new TObjectIntHashMap<String>();
...
freq.adjustOrPutValue(word, 1, 1);

可变整数

import java.util.HashMap;
import java.util.Map;
...
class MutableInt {
  int value = 1; // note that we start at 1 since we're counting
  public void increment () { ++value;      }
  public int  get ()       { return value; }
}
...
Map<String, MutableInt> freq = new HashMap<String, MutableInt>();
...
MutableInt count = freq.get(word);
if (count == null) {
    freq.put(word, new MutableInt());
}
else {
    count.increment();
}

【讨论】:

  • 干得好,干得好。一个小注释 - AtomicLong 代码中的 putIfAbsent() 调用将实例化一个新的 AtomicLong(0),即使一个已经在地图中。如果您调整它以使用 if (map.get(key) == null) 代替,您可能会在这些测试结果中得到改进。
  • 我最近用类似于 MutableInt 的方法做了同样的事情。我很高兴听到这是最佳解决方案(我只是假设它是,没有做任何测试)。
  • 虽然 MutableInt 增量方法非常快,但它一般不是线程安全的,因为 ++ 运算符不是原子的。
  • 在 Atomic Long 的情况下,一步完成会不会更有效(所以你只有 1 个昂贵的 get 操作而不是 2 个)"map.putIfAbsent(word, new AtomicLong(0 )).incrementAndGet();"
  • @gregory 你考虑过 Java 8 的 freq.compute(word, (key, count) -&gt; count == null ? 1 : count + 1) 吗?在内部,它比containsKey 进行更少的散列查找,看看它与其他人的比较会很有趣,因为 lambda。
【解决方案2】:

现在有一种使用Map::merge 的Java 8 更短的方法。

myMap.merge(key, 1, Integer::sum)

它的作用:

  • 如果key不存在,则将1作为值
  • 否则 sum 1 到链接到 key 的值

更多信息here

【讨论】:

  • 这似乎对我不起作用,但 map.merge(key, 1, (a, b) -&gt; a + b); 起作用
  • @Tiina 原子性特征是特定于实现的,参见。 the docs:“默认实现不保证此方法的同步或原子性属性。任何提供原子性保证的实现都必须覆盖此方法并记录其并发属性。特别是,子接口 ConcurrentMap 的所有实现必须记录是否应用了该函数仅当值不存在时原子地一次。"
  • 对于 groovy,它不会接受 Integer::sum 作为 BiFunction,并且不喜欢 @russter 以它的编写方式回答。这对我有用Map.merge(key, 1, { a, b -&gt; a + b})
  • @russter,我知道你的评论是一年多以前的事了,但你还记得为什么它不适合你吗?您是否收到编译错误或值未增加?
  • @Hatefiend 对于前 126 个增量(每个键!),将不会创建对象,因为重用 Integer 实例是强制性的。对于超出此范围的值,是否创建新对象取决于运行时。相反,使用可变对象总是需要为每个键创建一个不同的新对象。所以,不要对性能做出笼统的陈述。性能取决于键的数量和每个键的平均增量数。
【解决方案3】:

2016年的一点研究:https://github.com/leventov/java-word-count,benchmark source code

每种方法的最佳结果(越小越好):

                 time, ms
kolobokeCompile  18.8
koloboke         19.8
trove            20.8
fastutil         22.7
mutableInt       24.3
atomicInteger    25.3
eclipse          26.9
hashMap          28.0
hppc             33.6
hppcRt           36.5

时间\空间结果:

【讨论】:

  • 谢谢,这真的很有帮助。将 Guava 的 Multiset(例如 HashMultiset)添加到基准测试中会很酷。
【解决方案4】:
Map<String, Integer> map = new HashMap<>();
String key = "a random key";
int count = map.getOrDefault(key, 0); // ensure count will be one of 0,1,2,3,...
map.put(key, count + 1);

这就是您使用简单代码递增值的方式。

好处:

  • 无需添加新类或使用可变 int 的其他概念
  • 不依赖任何库
  • 很容易理解到底发生了什么(不要太抽象)

缺点:

  • 哈希映射将被搜索两次以获得 get() 和 put()。所以它不会是性能最高的代码。

理论上,一旦你调用 get(),你就已经知道在哪里 put(),所以你不必再搜索了。但是在哈希映射中搜索通常需要很短的时间,您可以忽略这个性能问题。

但是如果你对这个问题非常认真,你是一个完美主义者,另一种方法是使用合并方法,这(可能)比之前的代码 sn-p 更有效,因为你将(理论上)搜索地图只有一次:(虽然这段代码乍一看并不明显,但它简短而高效)

map.merge(key, 1, (a,b) -> a+b);

建议:在大多数情况下,您应该更关心代码的可读性,而不是很少的性能提升。如果第一个代码 sn -p 对您来说更容易理解,请使用它。但是如果你能理解第二个就好了,那你也可以去!

【讨论】:

  • JAVA 7 中没有 getOfDefault 方法。如何在 JAVA 7 中实现这一点?
  • 那么您可能不得不依赖其他答案。这仅适用于 Java 8。
  • +1 用于合并解决方案,这将是性能最高的功能,因为您只需为哈希码计算支付 1 次费用(如果您使用的 Map 正确支持该方法) ,而不是可能支付 3 次费用
  • 使用方法推断:map.merge(key, 1, Integer::sum)
【解决方案5】:

作为我自己评论的后续行动:Trove 看起来像是要走的路。如果出于某种原因,您想坚持使用标准 JDK,ConcurrentMapAtomicLong 可以使代码tiny 更好一点,尽管 YMMV。

    final ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
    map.putIfAbsent("foo", new AtomicLong(0));
    map.get("foo").incrementAndGet();

1 保留为映射中foo 的值。实际上,增加对线程的友好性就是这种方法所推荐的全部。

【讨论】:

  • putIfAbsent() 返回值。将返回值存储在局部变量中并使用它来 incrementAndGet() 而不是再次调用 get 可能是一个很大的改进。
  • putIfAbsent 如果指定的键尚未与 Map 中的值关联,则可以返回空值,因此我会小心使用返回的值。 docs.oracle.com/javase/8/docs/api/java/util/…
  • @bumbur 解决方案应该很明显。记住传递给putIfAbsent 的新AtomicLong 和方法的返回值,然后使用正确的值作为增量。当然,从 Java 8 开始使用map.computeIfAbsent("foo", key -&gt; new AtomicLong(0)) .incrementAndGet(); 就更简单了……
【解决方案6】:

Google Guava 是你的朋友...

...至少在某些情况下。他们有这个很好的AtomicLongMap。特别好,因为您正在处理 long 作为地图中的值。

例如

AtomicLongMap<String> map = AtomicLongMap.create();
[...]
map.getAndIncrement(word);

还可以将大于 1 的值添加到值:

map.getAndAdd(word, 112L); 

【讨论】:

  • AtomicLongMap#getAndAdd 采用原语 long 而不是包装类; new Long() 没有意义。而AtomicLongMap是参数化类型;你应该把它声明为AtomicLongMap&lt;String&gt;
【解决方案7】:

查看Google Collections Library 总是一个好主意。在这种情况下,Multiset 可以解决问题:

Multiset bag = Multisets.newHashMultiset();
String word = "foo";
bag.add(word);
bag.add(word);
System.out.println(bag.count(word)); // Prints 2

有类似 Map 的方法用于迭代键/条目等。内部实现目前使用HashMap&lt;E, AtomicInteger&gt;,因此您不会产生装箱成本。

【讨论】:

  • 以上答案需要反映tovares的响应。自发布以来,API 发生了变化(3 年前 :))
  • 多重集上的count() 方法是否在 O(1) 或 O(n) 时间内运行(最坏情况)?文档在这一点上不清楚。
  • 我对这种事情的算法: if ( hasApacheLib(thing) ) return apacheLib; else if ( hasOnGuava(thing) ) 返回番石榴。通常我不会通过这两个步骤。 :)
【解决方案8】:

你应该知道你最初的尝试

int count = map.containsKey(word) ? map.get(word) : 0;

在地图上包含两个可能代价高昂的操作,即containsKeyget。前者执行的操作可能与后者非常相似,因此您正在执行相同的工作两次

如果您查看 Map 的 API,当地图不包含请求的元素时,get 操作通常会返回 null

请注意,这将产生类似的解决方案

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

很危险,因为它可能会产生NullPointerExceptions。您应该首先检查null

还要注意,这非常重要,HashMaps 可以包含nulls的定义。所以并不是每个返回的null 都说“没有这样的元素”。在这方面,containsKey 的行为与get 的行为不同,它实际上告诉你是否有这样一个元素。有关详细信息,请参阅 API。

但是,对于您的情况,您可能不想区分存储的 null 和“noSuchElement”。如果您不想允许nulls,您可能更喜欢Hashtable。使用其他答案中已经提出的包装库可能是手动处理的更好解决方案,具体取决于您的应用程序的复杂性。

要完成答案(由于编辑功能,我一开始忘了把它放进去!),最好的原生方法是将get 放入final 变量中,检查@987654337 @ 和 put1 重新输入。该变量应该是final,因为它无论如何都是不可变的。编译器可能不需要这个提示,但这样会更清楚。

final HashMap map = generateRandomHashMap(); 最终对象键 = fetchSomeKey(); 最终整数 i = map.get(key); 如果(我!= null){ map.put(i + 1); } 别的 { // 做一点事 }

如果你不想依赖自动装箱,你应该说类似map.put(new Integer(1 + i.getValue()));

【讨论】:

  • 为了避免 groovy 中初始未映射/空值的问题,我最终做了:counts.put(key, (counts.get(key) ?: 0) + 1) // 过于复杂的版本的 ++
  • 或者,最简单的:counts = [:].withDefault{0} // ++ away
【解决方案9】:

另一种方法是创建一个可变整数:

class MutableInt {
  int value = 0;
  public void inc () { ++value; }
  public int get () { return value; }
}
...
Map<String,MutableInt> map = new HashMap<String,MutableInt> ();
MutableInt value = map.get (key);
if (value == null) {
  value = new MutableInt ();
  map.put (key, value);
} else {
  value.inc ();
}

当然,这意味着创建一个额外的对象,但与创建一个整数(即使使用 Integer.valueOf)相比,开销不应该那么多。

【讨论】:

  • 您不想在第一次将 MutableInt 放入地图时从 1 开始关闭它?
  • Apache 的 commons-lang 已经为你编写了一个 MutableInt。
  • 制作inc final 理论上可以在这里提供可衡量的好处。
【解决方案10】:

您可以在Java 8中提供的Map 接口中使用computeIfAbsent 方法。

final Map<String,AtomicLong> map = new ConcurrentHashMap<>();
map.computeIfAbsent("A", k->new AtomicLong(0)).incrementAndGet();
map.computeIfAbsent("B", k->new AtomicLong(0)).incrementAndGet();
map.computeIfAbsent("A", k->new AtomicLong(0)).incrementAndGet(); //[A=2, B=1]

computeIfAbsent 方法检查指定的键是否已经与值关联?如果没有关联值,则它尝试使用给定的映射函数计算其值。在任何情况下,它都会返回与指定键关联的当前(现有或计算)值,如果计算值为 null,则返回 null。

附带说明,如果您遇到多个线程更新共同总和的情况,您可以查看LongAdder 类。在高争用情况下,该类的预期吞吐量显着高于AtomicLong,代价是更高的空间消耗。

【讨论】:

  • 为什么要使用 concurrentHashmap 和 AtomicLong?
【解决方案11】:

这里的内存轮换可能是一个问题,因为大于或等于 128 的 int 的每个装箱都会导致对象分配(请参阅 Integer.valueOf(int))。尽管垃圾收集器可以非常有效地处理短期对象,但性能会受到一定程度的影响。

如果您知道增量的数量将大大超过键的数量(在本例中为 = 单词),请考虑改用 int 持有者。 Phax 已经为此提供了代码。又是这样,有两个变化(持有者类设为静态,初始值设置为 1):

static class MutableInt {
  int value = 1;
  void inc() { ++value; }
  int get() { return value; }
}
...
Map<String,MutableInt> map = new HashMap<String,MutableInt>();
MutableInt value = map.get(key);
if (value == null) {
  value = new MutableInt();
  map.put(key, value);
} else {
  value.inc();
}

如果您需要极高的性能,请寻找直接针对原始值类型量身定制的 Map 实现。 jrudolph 提到了GNU Trove

顺便说一下,这个主题的一个很好的搜索词是“直方图”。

【讨论】:

    【解决方案12】:

    很简单,如下使用Map.java中的内置函数即可

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

    【讨论】:

    • 这不会增加值,它只是设置当前值,如果没有为键分配值,则设置为 0。
    • 您可以将值增加++... OMG,就是这么简单。 @siegi
    • 记录:++ 在此表达式中的任何地方都不起作用,因为需要一个变量作为其操作数,但只有值。不过,您添加+ 1 可以。现在您的解决方案与off99555s answer 中的相同。
    【解决方案13】:

    与调用 containsKey() 相比,调用 map.get 并检查返回的值是否为空会更快。

        Integer count = map.get(word);
        if(count == null){
            count = 0;
        }
        map.put(word, count + 1);
    

    【讨论】:

      【解决方案14】:

      我建议使用 Java 8 Map::compute()。 它也考虑密钥不存在的情况。

      Map.compute(num, (k, v) -> (v == null) ? 1 : v + 1);
      

      【讨论】:

      • mymap.merge(key, 1, Integer::sum)?
      【解决方案15】:

      您确定这是一个瓶颈吗?你做过性能分析吗?

      尝试使用 NetBeans 分析器(免费且内置于 NB 6.1)查看热点。

      最后,JVM 升级(比如从 1.5->1.6)通常是一种廉价的性能提升器。即使是内部版本号的升级也可以提供良好的性能提升。如果您在 Windows 上运行并且这是一个服务器类应用程序,请在命令行上使用 -server 以使用 Server Hotspot JVM。在 Linux 和 Solaris 机器上,这是自动检测到的。

      【讨论】:

        【解决方案16】:

        有几种方法:

        1. 使用类似于 Google Collections 中包含的集合的 Bag 算法。

        2. 创建可在 Map 中使用的可变容器:

        class My{ String word; int count; }

        并使用 put("word", new My("Word") );然后你可以检查它是否存在并在添加时增加。

        避免使用列表滚动您自己的解决方案,因为如果您进行内循环搜索和排序,您的性能会很糟糕。第一个 HashMap 解决方案实际上相当快,但在 Google Collections 中找到的类似解决方案可能更好。

        使用 Google Collections 计算单词,如下所示:

        HashMultiset s = new HashMultiset(); s.add("word"); s.add("word"); System.out.println(""+s.count("word") );

        使用 HashMultiset 非常优雅,因为袋算法正是您在计算单词时所需要的。

        【讨论】:

          【解决方案17】:

          MutableInt 方法的一种变体可能更快,如果有点 hack,是使用单元素 int 数组:

          Map<String,int[]> map = new HashMap<String,int[]>();
          ...
          int[] value = map.get(key);
          if (value == null) 
            map.put(key, new int[]{1} );
          else
            ++value[0];
          

          如果您可以使用此变体重新运行性能测试,将会很有趣。这可能是最快的。


          编辑:上面的模式对我来说很好,但最终我改用 Trove 的集合来减少我正在创建的一些非常大的地图的内存大小——而且它也更快。

          一个非常好的特性是TObjectIntHashMap 类有一个adjustOrPutValue 调用,根据该键是否已经存在值,将放置一个初始值或增加现有值。这非常适合递增:

          TObjectIntHashMap<String> map = new TObjectIntHashMap<String>();
          ...
          map.adjustOrPutValue(key, 1, 1);
          

          【讨论】:

            【解决方案18】:

            Google Collections HashMultiset:
            - 使用起来非常优雅
            - 但会消耗 CPU 和内存

            最好的方法是:Entry&lt;K,V&gt; getOrPut(K); (优雅、低成本)

            这种方法只会计算一次哈希和索引, 然后我们可以对条目做我们想做的事 (替换或更新值)。

            更优雅:
            - 采取HashSet&lt;Entry&gt;
            - 扩展它,以便 get(K) 在需要时放置一个新条目
            - 条目可以是您自己的对象。
            --> (new MyHashSet()).get(k).increment();

            【讨论】:

              【解决方案19】:

              “put”需要“get”(确保没有重复键)。
              所以直接做一个“放”,
              如果有以前的值,那么做一个加法:

              Map map = new HashMap ();
              
              MutableInt newValue = new MutableInt (1); // default = inc
              MutableInt oldValue = map.put (key, newValue);
              if (oldValue != null) {
                newValue.add(oldValue); // old + inc
              }
              

              如果计数从 0 开始,则加 1:(或任何其他值...)

              Map map = new HashMap ();
              
              MutableInt newValue = new MutableInt (0); // default
              MutableInt oldValue = map.put (key, newValue);
              if (oldValue != null) {
                newValue.setValue(oldValue + 1); // old + inc
              }
              

              注意:此代码不是线程安全的。使用它来构建然后使用地图,而不是同时更新它。

              优化:在一个循环中,保持旧值成为下一个循环的新值。

              Map map = new HashMap ();
              final int defaut = 0;
              final int inc = 1;
              
              MutableInt oldValue = new MutableInt (default);
              while(true) {
                MutableInt newValue = oldValue;
              
                oldValue = map.put (key, newValue); // insert or...
                if (oldValue != null) {
                  newValue.setValue(oldValue + inc); // ...update
              
                  oldValue.setValue(default); // reuse
                } else
                  oldValue = new MutableInt (default); // renew
                }
              }
              

              【讨论】:

                【解决方案20】:

                各种原始包装器,例如,Integer 是不可变的,因此实际上没有更简洁的方法来执行您所要求的操作除非您可以使用 AtomicLong 之类的方法来执行此操作。我可以在一分钟内试一试并更新。顺便说一句,Hashtable Collections Framework 的一部分。

                【讨论】:

                  【解决方案21】:

                  我会使用 Apache Collections Lazy Map(将值初始化为 0)并使用 Apache Lang 的 MutableIntegers 作为该映射中的值。

                  最大的成本是必须在您的方法中搜索两次地图。在我的情况下,您只需执行一次。只需获取值(如果不存在,它将被初始化)并增加它。

                  【讨论】:

                    【解决方案22】:

                    Functional Java 库的TreeMap 数据结构在最新的主干头中有一个update 方法:

                    public TreeMap<K, V> update(final K k, final F<V, V> f)
                    

                    示例用法:

                    import static fj.data.TreeMap.empty;
                    import static fj.function.Integers.add;
                    import static fj.pre.Ord.stringOrd;
                    import fj.data.TreeMap;
                    
                    public class TreeMap_Update
                      {public static void main(String[] a)
                        {TreeMap<String, Integer> map = empty(stringOrd);
                         map = map.set("foo", 1);
                         map = map.update("foo", add.f(1));
                         System.out.println(map.get("foo").some());}}
                    

                    这个程序打印“2”。

                    【讨论】:

                      【解决方案23】:

                      我不知道它的效率如何,但是下面的代码也可以。您需要在开头定义一个BiFunction。另外,使用这种方法,您可以做的不仅仅是增加。

                      public static Map<String, Integer> strInt = new HashMap<String, Integer>();
                      
                      public static void main(String[] args) {
                          BiFunction<Integer, Integer, Integer> bi = (x,y) -> {
                              if(x == null)
                                  return y;
                              return x+y;
                          };
                          strInt.put("abc", 0);
                      
                      
                          strInt.merge("abc", 1, bi);
                          strInt.merge("abc", 1, bi);
                          strInt.merge("abc", 1, bi);
                          strInt.merge("abcd", 1, bi);
                      
                          System.out.println(strInt.get("abc"));
                          System.out.println(strInt.get("abcd"));
                      }
                      

                      输出是

                      3
                      1
                      

                      【讨论】:

                        【解决方案24】:

                        如果您使用的是Eclipse Collections,则可以使用HashBag。就内存使用而言,这将是最有效的方法,并且在执行速度方面也将表现良好。

                        HashBagMutableObjectIntMap 支持,它存储原始整数而不是 Counter 对象。这减少了内存开销并提高了执行速度。

                        HashBag 提供了您需要的 API,因为它是一个 Collection,还允许您查询某个项目的出现次数。

                        这是来自Eclipse Collections Kata 的示例。

                        MutableBag<String> bag =
                          HashBag.newBagWith("one", "two", "two", "three", "three", "three");
                        
                        Assert.assertEquals(3, bag.occurrencesOf("three"));
                        
                        bag.add("one");
                        Assert.assertEquals(2, bag.occurrencesOf("one"));
                        
                        bag.addOccurrences("one", 4);
                        Assert.assertEquals(6, bag.occurrencesOf("one"));
                        

                        注意:我是 Eclipse Collections 的提交者。

                        【讨论】:

                          【解决方案25】:

                          使用流和getOrDefault 进行计数:

                          String s = "abcdeff";
                          s.chars().mapToObj(c -> (char) c)
                           .forEach(c -> {
                               int count = countMap.getOrDefault(c, 0) + 1;
                               countMap.put(c, count);
                            });
                          

                          【讨论】:

                            【解决方案26】:

                            由于很多人在 Java 主题中搜索 Groovy 答案,以下是您可以在 Groovy 中执行此操作的方法:

                            dev map = new HashMap<String, Integer>()
                            map.put("key1", 3)
                            
                            map.merge("key1", 1) {a, b -> a + b}
                            map.merge("key2", 1) {a, b -> a + b}
                            

                            【讨论】:

                              【解决方案27】:

                              希望我正确理解了您的问题,我是从 Python 转向 Java 的,因此我可以理解您的挣扎。

                              如果你有

                              map.put(key, 1)
                              

                              你会的

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

                              希望这会有所帮助!

                              【讨论】:

                                【解决方案28】:

                                java 8中简单易行的方法如下:

                                final ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
                                    map.computeIfAbsent("foo", key -> new AtomicLong(0)).incrementAndGet();
                                

                                【讨论】:

                                  猜你喜欢
                                  • 2016-03-31
                                  • 1970-01-01
                                  • 2022-12-09
                                  • 1970-01-01
                                  • 2021-04-03
                                  • 1970-01-01
                                  • 2011-03-05
                                  • 2012-03-11
                                  相关资源
                                  最近更新 更多