我有这个HashMap:HashMap<NGram, Integer> countMap = new HashMap<>();
我试图填写它(片段在循环中):
NGram ng = new NGram(param);
if (countMap.containsKey(ng)){ // or if(countMap.get(ng) != null){
int counter = countMap.get(ng);
countMap.put(ng, ++counter);
}
else{
countMap.put(ng, 1);
}
但是countMap.containsKey(ng)
永远不会返回true(当它应该),所以else语句将永远执行!我覆盖了equals()
的{{1}}和hashCode()
,他们工作得很好。使用原始数据类型,代码段将起作用。
所以,我的问题是:我如何说服Java,HashMap中已有对象?
以下是NGram
,toString()
和hashCode()
的代码:
equals()
答案 0 :(得分:0)
以下是您的3种方法应该是什么的示例 这就是所谓的MCVE (最小,完整和可验证的示例)。
主要概念是通过调用toString()
来构建新的组合字符串不是实现equals()
和hashCode()
的正确方法。
这主要是出于性能原因。在18 million objects添加到地图后,equals()
和hashCode()
的效果很重要。
另一个不好主意的原因是toString()
可能无法显示对象的所有值,或者两个不同的对象可能仍然具有相同的toString()
结果,例如如果令牌可以包含空格,那么连接它们将无法区分A B
,C
与A
,B C
。
对于示例,String[]
用于previousToken
字段,varargs构造函数用于简化测试。不允许使用空令牌。你的可能略有不同,但这显示了概念。
public class NGram {
private String token;
private String[] previousToken; // could also be List<String>
public NGram(String ... allTokens) {
if (allTokens.length == 0)
throw new IllegalArgumentException("At least one token is required");
for (String s : allTokens)
if (s == null)
throw new NullPointerException();
this.token = allTokens[allTokens.length - 1];
this.previousToken = Arrays.copyOfRange(allTokens, 0, allTokens.length - 1);
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
for (String s : this.previousToken)
buf.append(s).append(' ');
return buf.append(this.token).toString();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
NGram that = (NGram) obj;
return this.token.equals(that.token) &&
Arrays.equals(this.previousToken, that.previousToken);
}
@Override
public int hashCode() {
return this.token.hashCode() + 31 * Arrays.hashCode(this.previousToken);
}
}
<强> TEST 强>
这显示了一个更快,更紧凑的代码版本,可以将新对象添加到地图中。它更快,因为它只对每个添加执行一次查找,并且通过使用三元运算符而不是if
语句,结合两个put()
调用来实现更紧凑。
它以辅助方法隔离,便于测试。
public static void main(String[] args) {
Map<NGram, Integer> countMap = new HashMap<>();
add(countMap, new NGram("Foo"));
add(countMap, new NGram("Bar"));
add(countMap, new NGram("Foo", "Bar"));
add(countMap, new NGram("This", "is", "a", "test"));
add(countMap, new NGram("Bar"));
System.out.println(countMap);
}
private static void add(Map<NGram, Integer> countMap, NGram ng) {
Integer counter = countMap.get(ng);
countMap.put(ng, (counter != null ? counter + 1 : 1));
}
<强>输出强>
{Bar=2, Foo=1, This is a test=1, Foo Bar=1}
如您所见,重复Bar
值计算两次。
如果最终有大量重复对象,即具有高计数器值,则使用不可变Integer
对象作为计数器对性能不是很好。您可以使用int[1]
,但可变助手类更好,因为它可以提供良好的toString()
实现。
private static void add(Map<NGram, Counter> countMap, NGram ng) {
Counter counter = countMap.get(ng);
if (counter == null)
countMap.put(ng, new Counter(1));
else
counter.increment();
}
private static final class Counter {
private int count;
public Counter(int count) {
this.count = count;
}
public void increment() {
this.count++;
}
public int get() {
return this.count;
}
@Override
public String toString() {
return Integer.toString(this.count);
}
}