我希望这个问题对这个论坛来说不是太基本的,但我们会看到的。我想知道如何重构一些代码以获得更好的性能,这是运行了很多次。
假设我正在使用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框架而使用其他框架呢?
更新:我已经对几个答案做了测试。见下文。
内存旋转在这里可能是一个问题,因为对大于或等于128的int进行装箱都会导致对象分配(参见Integer.valueOf(int))。尽管垃圾收集器非常有效地处理存在时间很短的对象,但性能会在一定程度上受到影响。
如果您知道增量的数量将大大超过键的数量(在本例中为=words),请考虑使用int holder。Phax已经为此提供了代码。这里又是一次,有两个变化(holder类是静态的,初始值设置为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。
顺便说一下,这个主题的一个很好的搜索词是“直方图”。
如果您正在使用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的提交者。
有几种方法:
使用袋子算法,比如谷歌集合中包含的集合。
创建可变容器,你可以在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是非常优雅的,因为在计数单词时,袋算法正是您所需要的。