黑马程序员----学习集合遇到的两个问题
来源:互联网 发布:php程序员的简历 编辑:程序博客网 时间:2024/05/20 18:45
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ------
问题1:
例如名字为主要,年龄为次要,那么我们返回的都不过是名字相比的值或者年龄相减的值,跟主次要有什么关系呢?
原因分析:
如果相等,那么比较年龄确定在根节点的左侧还是右侧
如果不相等,跟左或右的节点继续比较姓名
也就是说如果跟当前元素的姓名一致,那么待添加元素距离当前元素的距离肯定比跟当前元素姓名不一致的元素要近。
例如二叉树有三个元素,分别为s1="helong",20s2="helong",18s3="helong",22其中s1为根节点,s2在左侧,s3在右侧,如果我们要添加两个元素s4="helong",17和s5="ldy",22,我们会发现添加s4时,由于姓名与根节点一样,那么它距离跟节点不会远,其实就是说同名的元素肯定挨着,而s5就不同了,由于它跟根节点不同名,因此会被移动到一侧跟那一侧的元素继续比较姓名,如果移动到了还是跟根节点同名的元素那一侧,那么依然不同名,会被再次移动,直到跟它比较的元素不是跟根节点同名为止。
这就是主要和次要条件的分别,关键在于主要条件是先考虑的,而次要条件是后考虑的。
问题2:
对于比较器方法或者compareTo方法的调用问题:
当我们向TreeMap中只添加一个元素时,会调用两次比较方法??
而且这种调用都是元素自己与自己的比较???
在TreeMap中添加元素的情况:
public V put(K key, V value) { <strong><span style="color:#ff0000;">Entry<K,V> t = root; if (t == null) { compare(key, key); // type (and possibly null) check</span></strong> 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; 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); 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; }查阅put方法源代码我们发现,即便是插入第一个元素时,也就是root==null,也就是t==null时,依然会调用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方法内部是使用了key的compareTo方法或者比较器的compare方法。实现了第一个元素自己与自己比较的情况,这也就解释了为什么我们只添加了一个元素,却也会调用了compare方法的问题。
下面我们来看看导致第二次调用compare或者compareTo方法的地方,get方法源代码:
public V get(Object key) { Entry<K,V> p = <strong><span style="color:#ff0000;">getEntry(key)</span></strong>; return (p==null ? null : p.value); }
当我们使用keySet方法获得TreeMap中key的集合时,我们最终会通过map的get方法通过传入key来获取value,查阅get方法源代码我们发现,在get方法内部是通过调用getEntry方法来得到映射关系p的。
final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance <strong><span style="color:#ff0000;">if (comparator != null) return getEntryUsingComparator(key);</span></strong> if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") <strong><span style="color:#ff0000;">Comparable<? super K> k = (Comparable<? super K>) key;</span></strong> Entry<K,V> p = root; while (p != null) { int cmp = <strong><span style="color:#ff0000;">k.compareTo(p.key)</span></strong>; if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; }
通过get方法调用了getEntry方法,查阅getEntry方法我们发现,到此分为两种情况
1.使用自然排序的,也就是comparator==null,没有比较器传入进来的。
对于这种情况,源代码中将key转为Comparable接口对象,然后调用它的compareTo方法。
2.使用比较器排序的,也就是comparator!=null,也就是在构造函数中传入了比较器的。
对于这种情况,又调用了getEntryUsingComparator方法传入key,参看下面的getEntryUsingComparator方法源代码final Entry<K,V> getEntryUsingComparator(Object key) { @SuppressWarnings("unchecked") K k = (K) key; <strong><span style="color:#ff0000;">Comparator<? super K> cpr = comparator;</span></strong> if (cpr != null) { Entry<K,V> p = root; while (p != null) { int cmp =<strong><span style="color:#ff0000;"> cpr.compare(k, p.key);</span></strong> if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } } return null; }
查阅源代码我们发现,其内部最终还是调用了compare方法。
这就解释了为什么我们在TreeMap集合中添加一个元素,并用keySet方式获取时最终调用了两次compare方法,本质就是put方法时调用一次,而get方法时,由get调用getEntry方法,而getEntry方法调用了getEntryUsingComparator方法,该方法内部又调用了一次compare方法。
TreeMap中即便只是添加一个元素,也要有可比性,因为根据源码可知,它会调用compare或者compareTo方法,因此必须具有可比性。
在TreeSet中添加元素的情况:
import java.util.*;class Demo{}class TreeSetTest {public static void main(String[] args) {TreeSet set=new TreeSet();<strong><span style="color:#ff0000;">set.add(new Demo());//12行</span></strong>set.add(new Demo());//13行}}
我们可以看到出错的是12行,也就是说添加第一个元素时就出错了。这与我在看毕向东老师的视频时的情况不同。
查阅add方法源码发现了问题所在:
public boolean add(E e) { return m.put(e, PRESENT)==null; }这个是TreeSet的add方法,我们发现其实它内部是调用的put方法
public V put(K key, V value) { Entry<K,V> t = root; <strong><span style="color:#ff0000;"> if (t == null) { compare(key, key); // type (and possibly null) check</span></strong> 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; 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); 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; }可以看到,即便root为null,依然会调用compare方法
final int compare(Object k1, Object k2) { return comparator==null ? ((Comparable<? super K>)k1).<strong><span style="color:#ff0000;">compareTo</span></strong>((K)k2) : comparator.<strong><span style="color:#ff0000;">compare</span></strong>((K)k1, (K)k2); }可以看到TreeMap的compare内部还是使用了compareTo或者比较器的compare方法,因此即便我们只添加一个元素,依然会报错。
而其实本质是m是一个本地的map对象,也就是说TreeSet底层其实是用TreeMap实现的,而我们知道TreeMap的put方法在即便只有一个元素时也会调用compare或者compareTo方法的,因此会报错。
根据这个我估计应该是JDK的版本问题,因为现在在使用的是JDK8,而毕老师视频中的貌似是JDK6。
三、我的心得
-------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
- 黑马程序员----学习集合遇到的两个问题
- 黑马程序员:集合的学习
- 黑马程序员 + 在学习中遇到的一些问题1
- 黑马程序员 + 在学习中遇到的一些问题2
- 黑马程序员 集合学习
- 黑马程序员_关于集合的学习
- 黑马程序员——集合的学习
- 黑马程序员_学习笔记5多线程在的应用遇到的主要问题及解决方法。
- MFC学习中遇到的问题集合
- 黑马程序员__Java 集合学习
- 黑马程序员-学习笔记-集合
- 黑马程序员-sqlhelper 传入多parameter参数遇到的问题
- 黑马程序员——总结第一次面试遇到的问题
- 黑马程序员 日记六:集合的学习总结
- 黑马程序员--12集合类的学习 Map
- 黑马程序员-集合框架的学习笔记-2
- 黑马程序员----集合类(ArrayList)的学习整理
- 黑马程序员 集合的复习
- hdu 1874 畅通工程续
- android入门学习-java数据类型和运算符
- 使用AnyChat实现基本视频聊天的步骤
- 开启博客--
- Android 智能问答机器人的实现
- 黑马程序员----学习集合遇到的两个问题
- Rust中文翻译25
- 博客第一天
- 初始化
- StickyListview 源码简读
- 一个简单的排序问题
- Eclipse快捷键大全
- 十二、最小生成树
- [深入学习C#]C#实现多线程的方法:线程(Thread类)和线程池(ThreadPool)