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

假设我正在使用Map(可能是HashMap)创建一个词频列表,其中每个键都是一个包含要统计的单词的String,值是一个Integer,该Integer在每次找到单词的标记时递增。

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

$map{$word}++;

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

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

这当然依赖于新Java版本中的自动装箱特性。我想知道您是否可以建议一种更有效的方法来增加这个值。是否有更好的性能理由避开Collections框架而使用其他框架呢?

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


当前回答

很简单,只需使用Map.java中的内置函数,如下所示

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

其他回答

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

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”。

有几种方法:

使用袋子算法,比如谷歌集合中包含的集合。 创建可变容器,你可以在Map中使用:


    class My{
        String word;
        int count;
    }

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

避免使用列表来滚动您自己的解决方案,因为如果您使用内循环搜索和排序,您的性能将会非常糟糕。第一个HashMap解决方案实际上相当快,但在谷歌Collections中找到的合适的解决方案可能更好。

使用谷歌集合计数单词,看起来像这样:



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


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

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

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

我不知道它有多高效,但下面的代码也可以工作。你需要在开头定义一个bifuncfunction。另外,你可以用这个方法做更多的增量。

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
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(),您就已经知道在哪里放置(),因此不需要再次搜索。但是在哈希映射中搜索通常只需要很短的时间你可以忽略这个性能问题。

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

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

建议:在大多数情况下,你应该更关心代码的可读性,而不是性能的提高。如果第一个代码片段更容易理解,那么就使用它。但如果你能很好地理解第二个,那么你也可以去做!