原始的计数器
在实际应用中,我们经常把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.