在下面的代码中,我创建了一个hashmap来存储名为Datums的对象,它包含一个String(位置)和一个count。不幸的是,代码给出了非常奇怪的行为。
FileSystem fs = FileSystem.get(new Configuration());
Random r = new Random();
FSDataOutputStream fsdos = fs.create(new Path("error/" + r.nextInt(1000000)));
HashMap<String, Datum> datums = new HashMap<String, Datum>();
while (itrtr.hasNext()) {
Datum next = itrtr.next();
synchronized (datums) {
if (!datums.containsKey(next.location)) {
fsdos.writeUTF("INSERTING: " + next + "\n");
datums.put(next.location, next);
} else {
} // skit those that are already indexed
}
}
for (Datum d : datums.values()) {
fsdos.writeUTF("PRINT DATUM VALUES: " + d.toString() + "\n");
}
hashmap将字符串作为键。
以下是我在错误文件中获得的输出(示例):
INSERTING: (test.txt,3)
INSERTING: (test2.txt,1)
PRINT DATUM VALUES: (test.txt,3)
PRINT DATUM VALUES: (test.txt,3)
The correct output for the print should be:
INSERTING: (test.txt,3)
INSERTING: (test2.txt,1)
PRINT DATUM VALUES: (test.txt,3)
PRINT DATUM VALUES: (test2.txt,1)
以test2.txt作为位置的Datum发生了什么?为什么它会被test.txt取代?
基本上,我不应该两次看到相同的位置。 (这就是!datums.containsKey正在检查的内容)。不幸的是,我的行为非常奇怪。
顺便说一下,这是在Hadoop上的一个reducer中。
我尝试将同步放在这里,以防它在多个线程中运行,据我所知,事实并非如此。不过,同样的事情发生了。
答案 0 :(得分:2)
这不是地图的问题,而是代码的问题 datums.put(next.location,next);插入作为后面的chnaged值引用:) 这就是为什么最后地图中的所有值都等于地图中最后处理的数据
答案 1 :(得分:2)
根据this answer Hadoop的迭代器总是返回相同的对象,而不是每次都围绕循环创建一个新对象。
因此,保持对迭代器返回的对象的引用是无效的,并且会产生令人惊讶的结果。您需要将数据复制到新对象:
while (itrtr.hasNext()) {
Datum next = itrtr.next();
// copy any values from the Datum to a fresh instance
Datum insert = new Datum(next.location, next.value);
if (!datums.containsKey(insert.location)) {
datums.put(insert.location, insert);
}
}
以下是对Hadoop Reducer documentation的引用,它确认了这一点:
框架将重用传递的键和值对象 进入reduce,因此应用程序应克隆对象 他们想保留一份。