在地图中未检测到对象

时间:2016-06-04 07:28:11

标签: java nlp

我有这个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中已有对象?

以下是NGramtoString()hashCode()的代码:

equals()

1 个答案:

答案 0 :(得分:0)

以下是您的3种方法应该是什么的示例 这就是所谓的MCVE (最小,完整和可验证的示例)

主要概念是通过调用toString()来构建新的组合字符串不是实现equals()hashCode()的正确方法。

这主要是出于性能原因。在18 million objects添加到地图后,equals()hashCode()的效果很重要。

另一个不好主意的原因是toString()可能无法显示对象的所有值,或者两个不同的对象可能仍然具有相同的toString()结果,例如如果令牌可以包含空格,那么连接它们将无法区分A BCAB 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);
    }
}