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

假设我正在使用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框架而使用其他框架呢?

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


当前回答

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

HashBag由MutableObjectIntMap支持,MutableObjectIntMap存储基本整数而不是Counter对象。这减少了内存开销并提高了执行速度。

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

下面是一个来自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的提交者。

其他回答

使用流和getOrDefault计数:

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

查看谷歌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<E, AtomicInteger>,所以您不会产生装箱成本。

各种原语包装器(例如Integer)都是不可变的,因此实际上没有更简洁的方法来实现您的要求,除非您可以使用AtomicLong之类的东西来实现。我可以在一分钟内进行更新。顺便说一下,哈希表是集合框架的一部分。

希望我正确理解了你的问题,我从Python来到Java,所以我可以同情你的挣扎。

如果你有

map.put(key, 1)

你会这么做

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

希望这能有所帮助!

可以使用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。

另一方面,如果你遇到多个线程更新一个公共和的情况,你可以看看LongAdder类。在高争用情况下,该类的预期吞吐量显著高于AtomicLong,但代价是更高的空间消耗。