java map按照value排序_Java集合系列-Map系列-TreeMap源码解析

本人是工作7年的老程序员,在头条分享我对Java运用和源码、各种框架运用和源码的认识和理解,如果对您有所帮助,请持续关注。

声明:所有的文章都是自己工作之余一个字一个字码上去的,希望对学习Java的同学有所帮助,如果有理解不到位的地方,欢迎交流。

本文主要内容包括如下:

1:TreeMap的demo2:TreeMap的源码解析

第一节:TreeMap的demo

public static void main(String[] args) { Map 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));}

运行结果如下:

c43c05fb6ef18947849d39b59949f0d4.png

从运行结果上可以看出,TreeMap的迭代输出顺序是按照key的顺序输出的,所以TreeMap是一个具有排序功能的Map,接下来我们从源码角度分析它是怎样具有排序功能的。

第二节:TreeMap的源码解析

1.比较重要的属性说明

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

2.底层数据结构Entry的定义

static final class Entry implements Map.Entry { //key-value中的key K key;//key-value中的value V value; //当前节点的左子树 Entry left;//当前节点的右子树 Entry right;//当前节点的父节点 Entry parent;//当前节点的颜色 boolean color = BLACK; ///构造函数 Entry(K key, V value, Entry 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 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 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 { //走到这一步:说明comparator==null,从下面一段代码可以看出如果comparator==null的话,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 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:如果comparator==null,TreeMap不允许key为null,并且key必须实现Comparable接口。

示例1:comparator==null

public static void main(String[] args) { Mapmap = new TreeMap<>(); map.put(null,1);}

此时key==null,运行结果如下:

c73c0b72102e3ff8410600d7a1104b27.png

示例2:comparator!=null

public class TreeMapComparatorNotNullDemo { public static void main(String[] args) { Mapmap = 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,运行结果如下:

10682c0543397fca64e7422a7840d17c.png

接下来我们分析一下TreeMap输出时怎样进行排序的:

public Set> entrySet() { EntrySet es = entrySet; return (es != null) ? es : (entrySet = new EntrySet());}

如果你看了我分析HashMap的这一篇文章,你一定会很熟悉这样的代码,那么我们继续跟踪到EntrySet中去。

class EntrySet extends AbstractSet> { public Iterator> iterator() { //重写了迭代器的方法,首先通过getFirstEntry获取key最小的节点。 return new EntryIterator(getFirstEntry()); }继续跟踪到getFirstEntry方法中final Entry getFirstEntry() { Entry p = root;//因为红黑树是二叉查找树,任何左子树都小于它的父节点,任何右子树都大于它的父节点,下面这个循环就是找到做小的哪一个节点。 if (p != null) while (p.left != null) p = p.left; return p;}接下来看一下EntryIterator类final class EntryIterator extends PrivateEntryIterator> { EntryIterator(Entry first) { super(first); } public Map.Entry next() { return nextEntry(); }}迭代就需要看它的next方法了,继续跟踪到nextEntry方法中final Entry nextEntry() { Entry 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  TreeMap.Entry successor(Entry t) { if (t == null) return null; else if (t.right != null) {//右子树存在:则查找自己为根节点的子树。 Entry p = t.right; while (p.left != null) p = p.left; return p; } else {//右节点不存在:则查找自己父节点为根的子树。 Entry p = t.parent; Entry ch = t; while (p != null && ch == p.right) { ch = p; p = p.parent; } return p; }}

上面的流程图示如下:

通过getFirstEntry()获取红黑树key最小的节点。图示如下:

06ef8cf34dfde0663f6b7e4e42cca8cc.png

通过上面的步骤后next=first=1,所以继续获取后继者successor()获取后继者

从上面红黑树可以看出next=first=1没有右子树,所以查找以父节点8为根节点的子树,因为除了1外最小的数据一定在这棵子树上。

0eb7759ed23873cc54f9679cd3e47d58.png

下面总结一下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,如果感兴趣,请持续关注。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值