TreeMap与TreeSet的源码分析
1、TreeMap源码
1、属性部分:
private final Comparator<? super K> comparator;//比较器 private transient Entry<K,V> root;//根节点 private transient int size = 0;//大小 private transient int modCount = 0;//结构修改次数 定义一个静态内部对象用以存储: static final class Entry<K,V> implements Map.Entry<K,V> { K key; V value; Entry<K,V> left; Entry<K,V> right; Entry<K,V> parent; boolean color = BLACK; ... 重写了equals、hashCode、toString方法等
2、构造器部分:
public TreeMap() {//无参构造 comparator = null; } public TreeMap(Comparator<? super K> comparator) {//带比较器构造 this.comparator = comparator; } public TreeMap(Map<? extends K, ? extends V> m) public TreeMap(SortedMap<K, ? extends V> m)
3、put方法:
public V put(K key, V value) { Entry<K,V> t = root; //如果根节点为空,设置该元素为根节点; if (t == null) { compare(key, key); // type (and possibly null) check root = new Entry<>(key, value, null); size = 1; modCount++; return null; } int cmp; Entry<K,V> parent; // split comparator and comparable paths Comparator<? super K> cpr = comparator;//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(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = k.compareTo(t.key);//注意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<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; return null; }
注意:
<1>:首先,如果想往TreeMap中存放Entry<K,V>,那么其K必须是实现compareTo方法的,那么K所对应的类需要implements comparable接口。当然常用类中已经重写了这个方法,所以我们可以直接使用:TreeMap.put(int a,String b)等;
<2>:TreeMap是基于红黑树的数据结构,TreeMap中判断新入元素的存储位置时,只需要通过compareTo方法判断两个对象的Key是否相同:相同,则更新Value,返回旧值Value;不同,则插在左或右子节点上,不同于HashMap通过Key的hashCode判断存储位置,所以即使在TreeMap中没有hashCode和equals方法的重写,对于put也没有影响。
<3>:put入新节点后,TreeMap需要调整整个红黑树的结构。后续学习......;
2、TreeSet的add源码
public boolean add(E e) { return m.put(e, PRESENT)==null; }
看到add只需要接收一个参数e,m是一个 private transient NavigableMap<E,Object> m; ,接口,其实现类是TreeMap,因此知道,TreeSet的add通过调用TreeMap的put方法实现添加节点操作,但是由于add只接收一个形参e,故TreeSet生成了一个PRESENT以构成<K,V>的形式,PRESENT是 private static final Object PRESENT = new Object(); 完全是用来凑得嘛。
但是一定要注意:TreeMap的put中当通过comparedTo判断两个节点的Key相等时,会更新Value。而TreeSet的Value并没有什么作用,故可Key还是原来的Key,并没有发生改变。
总结:如果要插入新元素到TreeMap或TreeSet中:
1、TreeMap中若newKey.compareTo(oldkey)返回值是0,那么Key不变,更新Value的值为newValue,返回oldValue。
2、TreeSet中,简单可以理解为不更新(因为我们只用Key)。
实例:
public class C1 implements Comparable { String attr1="attr"; static int attr2=2;//注意static public C1(String attr1,int attr2){ this.attr1=attr1; this.attr2=attr2; } public String getAttr1() { return attr1; } public void setAttr1(String attr1) { this.attr1 = attr1; } public int getAttr2() { return attr2; } public void setAttr2(int attr2) { this.attr2 = attr2; } @Override public int compareTo(Object that) { // return ((C1)that).attr2-((C1)this).getAttr2(); return 0; } public String toString(){return "attr1="+attr1+",attr2="+attr2+";";} public boolean equals(Object that){ return attr1==((C1)that).getAttr1(); } public int hashCode(){return attr2;} public static void main(String[] args) { Set set=new TreeSet(); C1 c1=new C1("sttr",4); set.add(c1); C1 c2=new C1("sttr",5); set.add(c2); set.add(new C1("sttr",5)); set.add(new C1("sttr",3)); set.add(new C1("sttr2",2)); System.out.println(set); } }
[attr1=sttr,attr2=2;] //为什么是2?注意attr2是static的,最后一个new C1(..)会改变该所有对象的attr2值。TreeSet没有不会更新Key(就是这个对象new C1(..))
[attr1=sttr,attr2=4;] //正常,没有更新
//compareTo第一行,attr2不带static [attr1=sttr,attr2=5;, attr1=sttr,attr2=4;, attr1=sttr,attr2=3;, attr1=sttr2,attr2=2;]
//compareTo第一行,attr2带static [attr1=sttr,attr2=2;]