我有一个HashMap<Token, Integer>,它计算Token的出现。每次找到Token时,地图中的值都应增加。

Map<Token, Integer> occurrences = new HashMap<Token, Integer>();
// ...

public void tokenFound(Token token) {

    Integer numberOfOccurs = occurrences.get(token);
    Integer newNumberOfOccurs = new Integer((numberOfOccurs == null) ? 1 : numberOfOccurs.intValue() + 1);
    occurrences.put(token, newNumberOfOccurs);
}


是否有更优雅的方法?

评论

函子...如果您不受Java约束...

#1 楼

为此,您有几种不同的选择:

Guava

Google的Guava库引入了一种Multiset的概念,它能够计算出现的次数,并且还提供了其他一些功能。

Java 8

如果您正在使用Java 8(如果有能力,我强烈建议您使用Java 8),则您的tokenFound方法可以简单地是这样:

occurrences.merge(token, 1, (oldValue, one) -> oldValue + one);


或者这样:

occurrences.compute(token, (tokenKey, oldValue) -> oldValue == null ? 1 : oldValue + 1);


请注意,从Java 7开始,您可以使用“菱形运算符”:

Map<Token, Integer> occurrences = new HashMap<>();


没有Java 8,没有库

如果您无法使用Java 8并且不想添加Guava作为您项目的第三方库,您可以做一小部分来简化现有代码:

Integer previousValue = occurrences.get(token);
occurrences.put(token, previousValue == null ? 1 : previousValue + 1);


更具体地说:
new Integer构造函数,Java会自动使用装箱来执行此操作。对于接近零的Integer值,实际上会节省一点时间,因为Java会保留一些整数。
您不需要newNumberOfOccurs变量,因为它仅使用一次。


评论


\ $ \ begingroup \ $
作为整数缓存部分的补充:docs.oracle.com/javase/7/docs/api/java/lang/…(-128至127)
\ $ \ endgroup \ $
– h.j.k.
2014年7月15日14:07



\ $ \ begingroup \ $
太棒了! ps。我发现您可以将第一个示例稍微简化为:petitions.merge(token,1,Integer :: sum); (Integer :: sum只是一个BiFunction,将两个整数相加)
\ $ \ endgroup \ $
–伊朗棉兰
16年2月11日在21:40



#2 楼

番石榴的Multiset及其AtomicLongMap专为这种计数而设计。

另请参见:




番石榴的新收藏类型,说明。

有效的Java,第二版,第47项:了解和使用库(作者仅提及JDK的内置库,但我认为其他库也可能适用。)


#3 楼

我觉得非库类的答案可以改善,所以这是我的看法。

对于Java 7:

值类型,允许使用简单的AtomicInteger,而不必覆盖incrementAndGet()中的存储桶。对于Java 8:

private final Map<Token, AtomicInteger> occurrences = new HashMap<>();

public void tokenFound(Token token) {
    if (!occurrences.containsKey(token)) {
        occurrences.put(token, new AtomicInteger(1));
        return;
    }
    occurrences.get(token).incrementAndGet();
}


Map是一种专门用于计数的类型(尤其是在大量并发情况下)。在LongAdder上添加了computeIfAbsent()方法,并添加了lambda,将整个过程变成了单行代码。

如果您使用的是Java 7,我会选择Guava,但是如果您使用8只需使用Map类。

评论


\ $ \ begingroup \ $
我不希望您为此使用AtomicInteger,而仅当您实际上在并发上下文中运行时才使用它。如果我必须维护此代码并发现AtomicInteger被用来避免单行代码,那么我花很长时间试图弄清楚如何同时使用此类时,我会非常恼火。
\ $ \ endgroup \ $
–克里斯·海斯(Chris Hayes)
2014年7月16日在2:56



\ $ \ begingroup \ $
@ChrisHayes,您还可以使用increment()方法将自己的包装器简单地围绕int滚动。我的解决方案中的“优雅”不是来自于使用AtomicInteger本身,而是来自于在映射中使用可变值类型。如果确实使用AtomicInteger,则可以使用JCIP注释@NotThreadSafe注释该类,以避免混淆。
\ $ \ endgroup \ $
– Bowmore
2014年7月16日4:45



\ $ \ begingroup \ $
如果我看到一个类在自身内部使用线程安全类型并且被注释为不安全的话,我会感到更加困惑。 ;)当然,我喜欢简单地能够调用增量的优雅。
\ $ \ endgroup \ $
–克里斯·海斯(Chris Hayes)
14年7月16日在4:47

\ $ \ begingroup \ $
LongAdder ?!永不停止学习。您的纯java8示例确实很整洁。我希望我可以投票10次。
\ $ \ endgroup \ $
–GhostCat
18年11月20日在7:28

#4 楼

代替使用Map<Token, Integer>,请使用Map<Token, int[]>

当您要
修改现有值时,可以用来避免调用put()

HashMap<String, int[]> m=new HashMap<>();
m.put("a", new int[]{0});
m.get("a")[0]++;
System.out.println("m="+m.get("a")[0]);


输出:

m=1


#5 楼

如果您使用的是Java 8:虽然merge中的computeMap方法可用于此目的,但Map.getOrDefault(Object key, V defaultValue)向我更清楚地显示了代码的意图。