HashMap计数器的高效实现
原始的计数器
在实际应用中,我们经常把HashMap作为一个计数器使用.例如统计一篇文章中单词 'the' 出现了多少次.于是我们很轻松地就能写出下面的程序:
Map<String, Integer> counter = new HashMap<String, Integer>(); for(String s : strs) { if(counter.containsKey(s)) { Integer times = counter.get(s); times = times + 1; } else { counter.put(s, 1); } }
这个计数器确实可以完成我们所需的功能,但在速度上却慢了点.仔细分析不难发现,由于Integer是immutable的,即不可变的,所以在执行times = times + 1这行代码时实际上会创建一个新的Integer对象,然后再赋给times.这样一来,每次将次数 +1 时都会创建一个Integer,等循环执行完内存中可能会存在成千上万个垃圾Integer对象.所以,如果能将不可变的Integer改成可变的(mutable),能提高程序性能.
改进后的计数器
我们可以自己定义一个名为MutableInteger 的类来实现可变:
class MutableInteger { int value; public MutableInteger(int val) { this.value = val; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } }
改进后的程序代码如下:
Map<String, MutableInteger> counter = new HashMap<String, MutableInteger>(); for(String s : strs) { if(counter.containsKey(s)) { MutableInteger times = counter.get(s); times.setValue(times.getValue() + 1); // 不会每次都创建新对象了 } else { counter.put(s, new MutableInteger(1)); } }
高效的计数器
代码如下:
Map<String, MutableInteger> counter = new HashMap<String, MutableInteger>(); for(String s : strs) { MutableInteger newValue = new MutableInteger(1); MutableInteger oldValue = counter.put(s, newValue); if(null != oldValue) { newValue.setValue(oldValue.getValue() + 1); } }
这段代码看进来好像有些不好理解,我们来分析一下:
1. 执行
MutableInteger newValue = new MutableInteger(1);
时,会创建一个新的MutableInteger对象,初始化为1.
2. 执行
MutableInteger oldValue = counter.put(s, newValue);
时,程序会将刚刚创建的newValue放到HashMap中,同时返回执行完put()方法之后当前的健所对应的值.
3. 如果oldValue为不为空,则说明原来的HashMap中没有这个键.而此时我们已经将没有的键put进去了,且其对应的值恰好是newValue所引用的对象.因此我们只需要通过newValue调用setValue()方法就能修改到值了.
我编写了一个测试程序,对这3个counter进行了性能对比,结果如下:
大概是8 : 7 : 6.