关于TreeMap存储null问题

一、HashMap存储null

HashMap键或者值都可以null,只不过允许一个key为null,多个value为null,key相同覆盖先前的值。

二、那么TreeMap能否存储null值呢?

public static void main(String[] args) {
      TreeMap<String, String> map = new TreeMap<>();
      map.put("name", "andy");
      map.put("age", "17");
      //map.put(null, "byx");  // 编译正常、运行报空指针异常
      map.put("address", null); // 运行正常
      System.out.println(map.get("address"));
}
说明TreeMap的value可以是null,(没有自定义比较器)key不能为null。

三、分析map.put(null, "byx")空指针异常原因

调用栈:put方法-------------------->compare方法
final int compare(Object k1, Object k2) {
       return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2) : comparator.compare((K)k1, (K)k2);
}
compare中调用了String的compareTo方法,k1为null,可想而知会报空指针异常。

上述异常解决办法:自定义Comparator,重写compare方法
TreeMap<String, String> map = new TreeMap<>(new Comparator<String>() {
        public int compare(String s1, String s2) { // s1待存储数据,s2已存储数据
              if (s1 == null) {
                  return 1;
              } else {
                  return s2.charAt(0) - s1.charAt(0); }
              }
        }
);
map.put("name", "andy");
map.put("age", "17");
map.put(null, "byx"); // 运行正常
System.out.println(map.get(null));  // get获取不到值,返回null

// 但是如下方式遍历可以获取键值对
Set<Map.Entry<String, String>> s = map.entrySet();
for (Map.Entry<String, String> entry : s) {
     System.out.println(entry);
}

返回结果如下:
name=andy
age=17
null=byx

四、分析上述map.get(null)返回null原因

调用栈:get方法-------------------->getEntry方法-------------------->getEntryUsingComparator方法(自定义了Comparator 时)
public V get(Object key) {
       Entry<K,V> p = getEntry(key);
       return (p==null ? null : p.value);
}

final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        //以下代码省略
}

final Entry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
            K k = (K) key;
        Comparator<? super K> cpr = comparator; // 此处使用了自定义比较器
        if (cpr != null) {
            Entry<K,V> p = root;
            while (p != null) {
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
    }

// 再举个例子,强制改变比较器compare方法返回值
Map<Integer, String> map = new TreeMap<>(new Comparator<Integer>() {
       public int compare(Integer i1, Integer i2) {
                int num = i1 - i2;
                return num == 0 ? 1 : num;
            }
        }
);
map.put(11, "andy");
map.put(22, "ivan");
map.put(33, "eason");
map.put(11, "edison");       
System.out.println(map.get(11));  // 即使key不为null时也会返回null

通过上述源码可知:底层取值使用了自定义比较器的compare方法,TreeMap采用红黑树数据结构存储数据。调用get(key)时,会调用比较器中的compare方法,使用传入的Key逐个比对树中存储节点:<0则往左查找,>0往右查找,只有当=0时才返回该值,而上述调用null是返回1,则一直往右查找,永远找不到最终返回null。

五、TreeSet又能否存储null值呢?

TreeSet底层使用了TreeMap来操作数据, 存储null值同TreeMap的put(null, "XXX")一样会报空指针异常。解决方式同TreeMap一致,自定义Comparator,重写compare方法。
public TreeSet(Comparator<? super E> comparator) {
      this(new TreeMap<>(comparator));
}

猜你喜欢

转载自blog.csdn.net/jiangtianjiao/article/details/88397464
今日推荐