HashMap计数器的高效实现

来源:互联网 发布:java常用的容器 编辑:程序博客网 时间:2024/06/05 16:50

原始的计数器

在实际应用中,我们经常把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));}}

做完这步工作以后,再进行分析,也不难发现,在第一个if分支中,HashMap实际上会被查找2次.即,containsKey()会做一次查找,put()又会进行一次查找.显然这样也会降低程序性能.我们可以进一步优化程序,使其只进行一次查找就能完成所有的操作.

高效的计数器

代码如下:
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.
原创粉丝点击