【问题标题】:Key in TreeMap returning nullTreeMap 中的键返回 null
【发布时间】:2012-02-24 19:51:55
【问题描述】:

所以我有一个非常奇怪的错误。当我最初使用 keySet() 迭代大型 TreeMap 的前 10 个键时,我偶然发现了它。其中一个关键是返回 null,据我了解,这应该是不可能的。于是我写了下面的测试代码:

int i = 0;
        for (Map.Entry<String, Integer> es : sortedMap.entrySet()){
            if (i >= 10) {
                break;
            }

            if (sortedMap.containsKey(es.getKey())){
                System.out.println(es.getKey() + ":" + sortedMap.get(es.getKey()));
            } else {
                System.out.println("Key " + es.getKey() + " does not exist, yet...");
                System.out.println("This does work: " + es.getKey() + ":" + es.getValue());
                System.out.println("This does NOT work: " + es.getKey() + ":" + sortedMap.get(es.getKey()));
            }
            i++;
        }

并得到以下结果:

SOAP:967
'excerpt'::679
'type'::679
Key 'author_url': does not exist, yet...
This does work: 'author_url'::679
This does NOT work: 'author_url'::null
'date'::679
Android:437
TLS:295
message:283
server:230
monthly:215
<<<<<<<<<<<<<<<<<<<<DUMPING MAP!
{SOAP=967, 'excerpt':=679, 'type':=679, 'author_url':=679, 'date':=679, Android=437, TLS=295, message=283, server=230, monthly=215...

我在前十名之后切断了地图,因为那里还有很多,但所有这些都是具有价值的键。

所以我的问题是:为什么我在使用键直接从 TreeMap 中获取(键)时得到空值,但 EntrySet 返回正确的键和值?

这是我的比较器,因为我在 Integer 上订购:

class ValueComparator implements Comparator<Object> {

  Map<String, Integer> base;
  public ValueComparator(Map<String, Integer> base) {
      this.base = base;
  }

  public int compare(Object a, Object b) {

    if ((Integer) base.get(a) < (Integer) base.get(b)) {
      return 1;
    } else if ((Integer) base.get(a) == (Integer) base.get(b)) {
      return 0;
    } else {
      return -1;
    }
  }
}

TreeMap 的构建如下:

ValueComparator bvc =  new ValueComparator(allMatches);
TreeMap<String, Integer> sortedMap = new TreeMap<String, Integer>(bvc);
//Sort the HashMap
sortedMap.putAll(allMatches);

其中 allMatches 是 HashMap&lt;String, Integer&gt;

【问题讨论】:

  • 您是否为 TreeMap 使用了不寻常的比较器?如果是这样,我们可以看到它的代码吗?从您的转储中看起来好像您没有使用默认的String 排序...
  • @LouisWasserman 添加了我的比较器。
  • 为什么你在那个类中有一个构造函数和一个成员?通常会为每个元素调用比较器,因此您不应引用生成的映射。你的整个代码会很有用。
  • This question 可能与您的问题有关...
  • 其中一个键返回 null 有什么问题? TreeSet 允许空值

标签: java treemap


【解决方案1】:

从您的TreeMap 显示的迭代顺序来看,您使用自定义Comparator 肯定是这种情况。 [否则迭代将按字典顺序进行]

注意根据javadocs

实施者必须确保 sgn(compare(x, y)) == -sgn(compare(y, x)) 对于所有 x 和 y。 (这意味着 compare(x, y) 必须抛出一个 异常当且仅当 compare(y, x) 抛出异常。)

实现者还必须确保关系是可传递的: ((compare(x, y)>0) && (compare(y, z)>0)) 意味着 compare(x, z)>0。

最后,实现者必须确保 compare(x, y)==0 意味着 所有 z 的 sgn(compare(x, z))==sgn(compare(y, z))。

如果您的 Comparator 不应用这些规则 - 行为未定义,可能会显示奇怪的结果 - 如您所见。

编辑: [作为对已编辑问题的回应]
您的比较器使用身份 [operator==] 来检查两个整数。
请注意,Integer 是一个对象 - 因此,operator== 仅当它是同一个对象时才会返回 true
您应该使用equals() 来检查两个整数是否相同——甚至更好——使用Integer.compareTo()

【讨论】:

  • 使用 compareTo 会导致 TreeMap 在任何相等的值上折叠。 {monthly=215, server=230, message=283, TLS=295, Android=475, excerpt=679, SOAP=967}
  • 确实,这暗示您的代码更加损坏。你根本不能使用TreeMap,不像你正在尝试使用它。
  • @DennisSullivan:除了 Louis 所说的:阅读随附的 java 文档。您必须确保您的 Comparator 满足所写的条款 - 否则行为未定义。
  • 为帮助我的思路得出正确答案投了赞成票。
【解决方案2】:

您最大的问题是您在值比较器中使用== 而不是.equals 会破坏事情,因为不同的键被映射到具有相同intValue() 的不同Integer 对象,这正在抛出更多的事情出人意料。

但是,如果您修复了这个问题,那么您的 TreeMap 将不允许您插入多个具有相同值的键,这几乎肯定也会导致细微的损坏。

更好的解决方案是this,但基本上,你应该填写一个不按值排序的映射,对entrySet进行排序,然后将条目(按顺序)复制到像LinkedHashMap这样的映射不需要比较器,只需按插入顺序保留条目。

可能可以更改比较器,这样如果值相同,它也会比较键。这至少可以让您插入多个具有相同值的键...但它仍然是一个非常老套的解决方案,比上述基于LinkedHashMap 的解决方案风险更大。

【讨论】:

  • 为帮助我的思路得出正确答案投了赞成票。
【解决方案3】:

问题解决了:

class ValueComparator implements Comparator<Object> {

Map<String, Integer> base;

public ValueComparator(Map<String, Integer> base) {
    this.base = base;
}

public int compare(Object a, Object b) {

    if (((Integer) base.get(a)).intValue() < ((Integer) base.get(b)).intValue()) {
        return 1;
    } else if ( ((Integer) base.get(a)).intValue() == ((Integer) base.get(b)).intValue()) {
        return ((String)a).compareTo(((String)b));
    } else {
        return -1;
    }
}
}

这带来了额外的好处,即按字母顺序恢复具有相同值的键。

【讨论】:

    【解决方案4】:

    你应该有:

    class ValueComparator implements Comparator<Integer> {
    
    
      public int compare(Integer a, Integer b) {
          return a.compareTo(b);
      }
    }
    

    接下来,您需要使用比较器初始化树形图并添加所有项目:

    Treemap

    【讨论】:

    • 他用它来比较值,这不适用于TreeMap,不是真的=(
    • 你是对的。我认为他正在对键进行排序(像往常一样),但他正在对值进行排序。我误读了这个问题。我只能说,通常不会在地图中对值进行排序。列表更适合这种情况。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-10
    相关资源
    最近更新 更多