Java集合系列-Map系列-TreeMap源码解析

    xiaoxiao2022-07-13  145

    本文主要内容包括如下:

    1:TreeMap的demo 2:TreeMap的源码解析 第一节:TreeMap的demo public static void main(String[] args) { Map<Integer, Integer> map = new TreeMap<>(); map.put(3, 1); map.put(1, 2); map.put(12, 1); map.put(2, 10); map.put(20, 20); map.forEach((key, value) -> System.out.println(key + “:::” + value)); } 运行结果如下: Java集合系列-Map系列-TreeMap源码解析 从运行结果上可以看出,TreeMap的迭代输出顺序是按照key的顺序输出的,所以TreeMap是一个具有排序功能的Map,接下来我们从源码角度分析它是怎样具有排序功能的。

    第二节:TreeMap的源码解析 1.比较重要的属性说明

    //比较器,主要用于key的排序,如果此属性为null,则默认按照key的自然顺序 private final Comparator<? super K> comparator; //TreeMap的数据结构,因为TreeMap底层用红黑树实现的,所以这代表红黑树的根节点 private transient Entry<K,V> root; //TreeMap的大小 private transient int size = 0; 2.底层数据结构Entry的定义

    static final class Entry<K,V> implements Map.Entry<K,V> { //key-value中的key K key; //key-value中的value V value; //当前节点的左子树 Entry<K,V> left; //当前节点的右子树 Entry<K,V> right; //当前节点的父节点 Entry<K,V> parent; //当前节点的颜色 boolean color = BLACK; ///构造函数 Entry(K key, V value, Entry<K,V> parent) { this.key = key; this.value = value; this.parent = parent; } 3.TreeMap的构造函数

    //无参构造函数,comparator=null,说明用此构造函数是利用key的自然顺序排序的。 public TreeMap() { comparator = null; } //此构造函数传递了一个比较器comparator,说明用此构造函数是利用比较器来比较key的顺序的 public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } 4.TreeMap底层数据结构是利用红黑树存储的,如果对红黑树不了解请看红黑树一和红黑树二这两篇文章,这里就不再重复说明了。那我们看看TreeMap是怎样对数据进行增删改查的,以及它是怎样对key进行排序的

    首先我们看一下最重要的增加方法put

    public V put(K key, V value) { Entry<K,V> t = root; if (t == null) { //走到这一步:说明此时红黑树还是一颗空树。 //compare方法首先判断comparator是否为null,如果为null,则默认key的自然顺序,此时key必须实现comparable接口,如果不实现,则会抛出异常:(Comparable<? super K>)k1,如果comparator!=null,则进行比较。 compare(key, key); // type (and possibly null) check //把key-value封装成Entry赋值给根节点 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) { //走到这一步:说明comparator!=null,则调用comparator.compare比较key,此段代码是遍历红黑树,红黑树是一个二叉搜索树,任何左子树都小于它的父节点,任何右子树都大于它的父节点 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 { //走到这一步:说明comparatornull,从下面一段代码可以看出如果comparatornull的话,TreeMap不允许为空,如果comparator!=null的话则没有这个要求。 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); } //把key-value封装成Entry。 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; } 如果你对红黑树非常的了解的话,上面TreeMap的put方法非常的好理解,不懂的话看我对红黑树解读的两篇文章,现在总结一下TreeMap的put方法

    1:TreeMap的put操作,底层其实就是对红黑树的元素添加 2:如果comparator!=null,TreeMap允许key为null,否则会抛出异常 3:如果comparatornull,TreeMap不允许key为null,并且key必须实现Comparable接口。 示例1:comparatornull

    public static void main(String[] args) { Map<String,Integer>map = new TreeMap<>(); map.put(null,1); } 此时key==null,运行结果如下: Java集合系列-Map系列-TreeMap源码解析 示例2:comparator!=null

    public class TreeMapComparatorNotNullDemo { public static void main(String[] args) { Map<String,Integer>map = new TreeMap<>(new MyComparator()); map.put(null,1); System.out.println(map.get(null)); } } class MyComparator implements Comparator{ @Override public int compare(Object o1, Object o2) { return 0; } } 此时key==null,运行结果如下: Java集合系列-Map系列-TreeMap源码解析 接下来我们分析一下TreeMap输出时怎样进行排序的:

    public Set<Map.Entry<K,V>> entrySet() { EntrySet es = entrySet; return (es != null) ? es : (entrySet = new EntrySet()); } 如果你看了我分析HashMap的这一篇文章,你一定会很熟悉这样的代码,那么我们继续跟踪到EntrySet中去。

    class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() { //重写了迭代器的方法,首先通过getFirstEntry获取key最小的节点。 return new EntryIterator(getFirstEntry()); } 继续跟踪到getFirstEntry方法中 final Entry<K,V> getFirstEntry() { Entry<K,V> p = root; //因为红黑树是二叉查找树,任何左子树都小于它的父节点,任何右子树都大于它的父节点,下面这个循环就是找到做小的哪一个节点。 if (p != null) while (p.left != null) p = p.left; return p; } 接下来看一下EntryIterator类 final class EntryIterator extends PrivateEntryIterator<Map.Entry<K,V>> { EntryIterator(Entry<K,V> first) { super(first); } public Map.Entry<K,V> next() { return nextEntry(); } } 迭代就需要看它的next方法了,继续跟踪到nextEntry方法中 final Entry<K,V> nextEntry() { Entry<K,V> e = next; if (e == null) 走到这一步:说明我们迭代时没有判断hashNext,就开始调用next了,可能会导致异常。 throw new NoSuchElementException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); //走到这一步:获取后继者 next = successor(e); lastReturned = e; return e; } 所以我们继续跟踪successor方法,查询后继者一定是比当前节点要大,所以先判断它自己的右子树是否存在。 1:自己的右子树存在:就开始以当前节点为根的子树进行查找 2:自己的右子树不存在:那么就开始以当前节点的父节点为根的子树上进行查找。 static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) { if (t == null) return null; else if (t.right != null) { //右子树存在:则查找自己为根节点的子树。 Entry<K,V> p = t.right; while (p.left != null) p = p.left; return p; } else { //右节点不存在:则查找自己父节点为根的子树。 Entry<K,V> p = t.parent; Entry<K,V> ch = t; while (p != null && ch == p.right) { ch = p; p = p.parent; } return p; } } 上面的流程图示如下:

    通过getFirstEntry()获取红黑树key最小的节点。图示如下: Java集合系列-Map系列-TreeMap源码解析 通过上面的步骤后next=first=1,所以继续获取后继者successor()获取后继者

    从上面红黑树可以看出next=first=1没有右子树,所以查找以父节点8为根节点的子树,因为除了1外最小的数据一定在这棵子树上。 Java集合系列-Map系列-TreeMap源码解析 下面总结一下TreeMap:

    1:TreeMap是按照key排序的Map集合 2:TreeMap底层数据结构是红黑树 3:如果比较器comparator==null的话,key不能为null,且key需要继承Comparable接口,value没有要求 4:如果比较器comparator!=null的话,key可以为null.value也没有要求 通过上面的分析,大家对TreeMap有了一定的了解,其他方法的代码非常的简单,大家可以自行观看,TreeMap虽然使用的不太多,但是还是非常重要的,接下来几篇文章我会接着介绍一个非常重要的Map集合:ConcurrentHashMap,如果感兴趣,请持续关注。 还有就是这我总结出了一些架构视频资料和互联网公司java程序员面试涉及到的绝大部分面试题和答案做成了文档和架构视频资料还有完整高清的java进阶架构学习思维导图免费分享给大家(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术资料),希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。 料领取方式:加群:374308445填写【 资料】即可免费获取!!! 如果您喜欢本文章,可以点击关注,每天将有更多精彩文章与您分享!

    最新回复(0)