用Java增加Map值的最有效方法

我希望这个问题不被视为这个论坛的基础,但我们会看到。 我想知道如何重构一些代码以获得更好的性能,而这些代码正在运行很多次。

假设我使用一个Map(可能是一个HashMap)创建一个词频列表,其中每个键都是一个字符串,并且该字符被计数,并且该值是一个整数,每当找到该单词的一个标记时该值就会递增。

在Perl中,递增这样一个值将非常简单:

$map{$word}++;

但在Java中,它更复杂。 这是我目前正在做的事情:

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

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

更新:我已经做了几个答案的测试。 见下文。


一些测试结果

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

  • 我在问题中提出的“ContainsKey”方法
  • Aleksandar Dimitrov建议的“TestForNull”方法
  • Hank Gay提出的“AtomicLong”方法
  • jrudolph建议的“Trove”方法
  • phax.myopenid.com建议的“MutableInt”方法
  • 方法

    这就是我所做的...

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

    我将首先介绍结果以及下面的代码,以供有兴趣的人士参考。

    正如所料, ContainsKey方法是最慢的,所以我会将每种方法的速度与该方法的速度进行比较。

  • ContainsKey: 30.654秒(基线)
  • AtomicLong: 29.780秒(1.03倍)
  • TestForNull: 28.804秒(1.06倍)
  • 特洛夫 26.313秒(1.16倍)
  • MutableInt: 25.747秒(1.19倍)
  • 结论

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

    请注意,我没有在不同情况下分析内存使用情况。 我很乐意听到任何对MutableInt和Trove方法可能会影响内存使用情况的人有很好的见解。

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

    代码

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

    的containsKey

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

    的AtomicLong

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

    MutableInt

    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();
    }
    

    好的,可能是一个老问题,但Java 8有一个较短的方法:

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

    它做什么:如果密钥不存在,则将1作为值,否则将1与链接到密钥的值相加。 更多信息在这里


    2016年的一点研究:https://github.com/leventov/java-word-count,基准源代码

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

                     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
    

    时间空间结果:

    链接地址: http://www.djcxy.com/p/46353.html

    上一篇: Most efficient way to increment a Map value in Java

    下一篇: Get selected element's outer HTML