我希望这个问题对于本论坛来说不是太基本了,但是我们会看到的。 我想知道如何重构一些代码以获得更好的性能,而这些性能已经运行了很多次。
假设我正在使用地图(可能是HashMap)创建一个单词频率列表,其中每个键是一个带有要计算单词的String,并且值是一个Integer,每次找到该单词的标记时都会增加。
在Perl中,增加这样的值非常容易:
$map{$word}++;
但是在Java中,它要复杂得多。 这是我目前的操作方式:
int count = map.containsKey(word) ? map.get(word) : 0;
map.put(word, count + 1);
当然,哪个依赖于较新的Java版本中的自动装箱功能。 我想知道您是否可以建议一种更有效的递增此值的方法。 避开Collections框架并改用其他东西,甚至有良好的性能原因吗?
更新:我已经测试了几个答案。 见下文。
#1楼
一些测试结果
对于这个问题,我已经得到了很多很好的答案-谢谢大家-因此,我决定进行一些测试,找出哪种方法实际上最快。 我测试的五种方法是:
- 我在问题中介绍的“ ContainsKey”方法
- Aleksandar Dimitrov建议的“ TestForNull”方法
- Hank Gay建议的“ AtomicLong”方法
- jrudolph建议的“激励”方法
- 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);
}
原子长
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();
}
#2楼
MutableInt方法的一种变体是使用单元素int数组,如果稍作改动,它可能会更快一些:
Map<String,int[]> map = new HashMap<String,int[]>();
...
int[] value = map.get(key);
if (value == null)
map.put(key, new int[]{1} );
else
++value[0];
如果您可以使用此版本重新运行性能测试,那将很有趣。 它可能是最快的。
编辑:上面的模式对我来说很好用,但是最终我改变为使用Trove的集合来减少我正在创建的一些非常大的地图中的内存大小-而且,它还更快。
一个很好的特点是, TObjectIntHashMap
类有一个adjustOrPutValue
呼叫,取决于是否已经有在该键的值,要么把一个初始值,或增加现有的值。 这非常适合递增:
TObjectIntHashMap<String> map = new TObjectIntHashMap<String>();
...
map.adjustOrPutValue(key, 1, 1);
#3楼
Google Guava是您的朋友...
...至少在某些情况下。 他们有这个漂亮的AtomicLongMap 。 特别好,因为您正在处理地图中的长期价值。
例如
AtomicLongMap<String> map = AtomicLongMap.create();
[...]
map.getAndIncrement(word);
也可以将值加1以上:
map.getAndAdd(word, 112L);
#4楼
如果您使用的是Eclipse Collections ,则可以使用HashBag
。 就内存使用而言,这将是最有效的方法,并且在执行速度方面也将表现良好。
HashBag
由HashBag
支持,该MutableObjectIntMap
存储原始int而不是Counter
对象。 这样可以减少内存开销并提高执行速度。
HashBag
提供了所需的API,因为它是一个Collection
,它还允许您查询项目的出现次数。
这是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的提交者。
#5楼
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
时间/空间结果:
来源:oschina
链接:https://my.oschina.net/u/3797416/blog/3186487