TreeMap 中的get(Object)方法调用是 Entry<K,V> getEntry(Object key)
该方法如果 key == null 会抛出异常,所以这个要注意,在添加的时候,如果比较器不为空的话,就根据比较器比较结果进行插入,如果比较为空,如果插入Key为空的
话,就直接异常,为什么会这样做呢,因为TreeMap是一个红黑树结构,其结构是根据键值的排序进行建立的,之所以比较器不为空的情况不抛异常,是因为
比较器中可能会有与NULL比较的情况,所以可能会有这种树中为结点Key为空的情况,还有就是如果Key=NULL作为根结点会直接插入,不会进行比较
但这里要注意了,如果树中有Key为NULL情况,在get(Object) 方法执行的时候是调用 getEntry(Object),我们来看一下getEntry(Object)源码:
final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = root; while (p != null) { int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; }
final Entry<K,V> getEntryUsingComparator(Object key) { 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; }
可见对于取方法也是分为两种情况,一种如果比较器为空,另一种比较器不为空,如果比较器为空的情况下,get(NULL)会直接异常,
所以这个要说的是,在比较器为空,且NULL作为根结点插入后,就不可能再取出这个Key=null 的值
下面是插入算法,至于红黑树的插入这里就不再细说了
public V put(K key, V value) { Entry<K,V> t = root; if (t == null) { // TBD: // 5045147: (coll) Adding null to an empty TreeSet should // throw NullPointerException // // compare(key, key); // type check root = new Entry<K,V>(key, value, null); size = 1; modCount++; return null; } int cmp; Entry<K,V> parent; // split comparator and comparable paths Comparator<? super K> cpr = comparator; if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } Entry<K,V> e = new Entry<K,V>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; return null; }
TreeMap tree = new TreeMap(); tree.put(null,1); System.out.println(tree.size()); System.out.println(tree.get(null));// 会抛异常
自定义Comparator
package Trees; import static java.lang.System.out; import java.util.TreeMap; import java.util.Comparator; public class TreeMapTest { public static void main(String args[]) { TreeMap<Integer,Integer> tree = new TreeMap<Integer,Integer>(new Com()); tree.put(1, 1); tree.put(null, 2); System.out.println(tree.size()); // 2 System.out.println(tree.get(null)); // 不会异常 } } class Com implements Comparator<Integer>{ @Override public int compare(Integer o1, Integer o2) { if(o1==null&& o2==null) return 0; if(o1!=null) return 1; if(o2!=null)return -1; return o1.compareTo(o2); } }
因此,在使用 TreeSet 过程中,如果是自定义的比较器中
在比较器方法 int compare(o1,o2)中,如果没有判断 o1,o2 与 null 所有关系的话,可能会造成 tree.put(null,1) 存入后无法 get(null) 的情况
如:我们只是简单的 if(o1==null || o2 == null) return -1 ;
那么 tree.put(1,1); tree.put(null,2)后, tree.get(null) 得到的值将会是空
说了这么多,就一句话,
如果要在 treeSet 中存放 Key = null 的键值,就要自定义 comparactor ,并且在自定义的 comparactor 中要指明 o1 , o2 与 null 的所有关系
如果不自定义 Comparactor 就不要存放 键值为 null 的值