今天在写reduce函数的时候用HashMap老是出问题。reduce的输入是<query,一串url>,reduce主要是要统计各个url的次数,最后输出"query url1 count1 url2 count2......"的形式。于是我的相关部分代码如下:(最开始没有第5行)
Map<Text,IntWritable>urlCount=new HashMap<Text,IntWritable>(); //存放query对应的url的计数
IntWritable one=new IntWritable(1);
int count=0;
for(Text t:values){
Text tmp=new Text(t.toString()); //这一行坑了我接近一天啊,简直丧心病狂
if(!urlCount.containsKey(tmp)){
urlCount.put(tmp, one);
}else{
count=urlCount.get(tmp).get();
urlCount.put(tmp, new IntWritable(count+1));//urlCount里有的url计数加1
}
}
StringBuffer sb =new StringBuffer();
for(Text t:urlCount.keySet()){
sb.append(t.toString()+"\t"+urlCount.get(t).toString()+"\t");
}
//输出"query url1 count1 url2 count2..."
context.write(key, new Text(sb.toString()));
主要思路就是用一个HashMap存放<url,count>对,每来一个url先判断HashMap里有没有,有就计数加1,没有就put(url,1)。思路上没有问题,但是最后输出的结果不对。比如说跟查询q相关的url们是<q,u1><q,u2><q,u3>,即reduce的输入为<q,(u1,u2,u3)>(表述不够准确,能理解就行),照理说应该输出"qu11u21u31",但实际上输出的却是“q u3 1 u3 1 u3 1”。深深滴费解了好久,各种尝试无果,最后突然联想到一年前遇到的一个有点点类似的问题,然后在代码里加入红色那行,问题得到解决。
我分析了一下,是不是HashMap在put的时候put的是对象的引用,入下图所示:
当没有加上红色那行的时候,如图中①所示,for循环中由t依次遍历u1,u2,u3。三次put操作put的都是t这个引用,而t指向的对象一直在改变,最后HashMap中存储的三个key都指向了t最终指向的那个对象,即u3,所以最后会得到“q u3 1 u3 1 u3 1”这样错误的结果。而当我加上第5行后,如图②所示,每次put操作都重新创建了一个对象,并且将该对象的引用put进去,这样的话HashMap里的三个key均不随t的改变而改变。从而得出正确结果。
这样解释可以解释得通,但是又有个问题,那就是就算put的是引用也不应该出现这样的问题啊?因为t变量分配在栈空间,指向分配在堆空间的对象,在for循环内部每一次put操作的时候,值传递,会在栈空间再分配一个变量,将t的内容拷给它,于是这个变量也指向堆空间的该对象。如果这样理解的话不应该出现①那样的问题。哎,又费解了。。。坐等大神解答