文档章节

【java集合框架源码剖析系列】java源码剖析之TreeMap

htq
 htq
发布于 2016/07/26 09:39
字数 2673
阅读 3
收藏 0
点赞 0
评论 0

注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本。本博客将从源码角度带领大家学习关于TreeMap的知识。

一TreeMap的定义:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
可以看到TreeMap是继承自AbstractMap同时实现了NavigableMap,Cloneable,Serializable三个接口,其中Cloneable,Serializable这两个接口基本上是java集合框架中所有的集合类都要实现的接口。

二TreeMap类中的一些重要属性:

<strong> </strong>private final Comparator<? super K> comparator;
 private transient Entry<K,V> root;
 private transient int size = 0;
 private transient int modCount = 0;
第一个属性是Comparator<? super K> comparator比较器,从这里就可以知道TreeMap会运用比较器接口来对插入的元素进行排序。而第二个成员属性为Entry<K,V>即为红黑树,红黑树是一种数据结构,它和AVL树一样是一种自平衡二叉查找树,该数据结构具备非常高的插入,删除,查找的效率。Entry被定义为TreeMap的一个内部类,代码如下:

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;

        /**
         * Make a new cell with given key, value, and parent, and with
         * {@code null} child links, and BLACK color.
         */
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        /**
         * Returns the key.
         *
         * @return the key
         */
        public K getKey() {
            return key;
        }

        /**
         * Returns the value associated with the key.
         *
         * @return the value associated with the key
         */
        public V getValue() {
            return value;
        }

        /**
         * Replaces the value currently associated with the key with the given
         * value.
         *
         * @return the value associated with the key before this method was
         *         called
         */
        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;

            return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
        }

        public int hashCode() {
            int keyHash = (key==null ? 0 : key.hashCode());
            int valueHash = (value==null ? 0 : value.hashCode());
            return keyHash ^ valueHash;
        }

        public String toString() {
            return key + "=" + value;
        }
    }
可以看到Entry红黑树的代码一点也不复杂,和普通的二叉树差不多,仅仅多了一个判断颜色的属性boolean color,该属性默认值为黑色,即BLACK,关于红黑树的具体知识,在此不做过多介绍,博主打算在数据结构与算法那块进行详细介绍。可以先点此 红黑树查看百度百科做初步了解。


三TreeMap内部的实现原理:我们首先看一下其构造器

public TreeMap() {// 构造方法一,默认的构造方法,comparator为空,即采用自然顺序维持TreeMap中节点的顺序
        comparator = null;
    }

 public TreeMap(Comparator<? super K> comparator) {// 构造方法二,提供指定的比较器
        this.comparator = comparator;
    }

public TreeMap(Map<? extends K, ? extends V> m) {// 构造方法三,采用自然序维持TreeMap中节点的顺序,同时将传入的Map中的内容添加到TreeMap中
        comparator = null;
        putAll(m);
    }
/** 
*构造方法四,接收SortedMap参数,根据SortedMap的比较器维持TreeMap中的节点顺序, 同时通过buildFromSorted(int size, Iterator it, java.io.ObjectInputStream str, V defaultVal)方法将SortedMap中的内容添加到TreeMap中
*/
public TreeMap(SortedMap<K, ? extends V> m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

重点关注构造器二和三,即提供指定的比较器和将传入的Map参数采用自然序维持节点的顺序,因为很多情况下,不同对象的比较大小的方法是不一样的,所以很多时候我们需要自己指定比较器。另外可以看到在构造器三种调用了putAll方法,我们来看一下其源码:

public void putAll(Map<? extends K, ? extends V> map) {
        int mapSize = map.size();
        if (size==0 && mapSize!=0 && map instanceof SortedMap) {
            Comparator<?> c = ((SortedMap<?,?>)map).comparator();
            if (c == comparator || (c != null && c.equals(comparator))) {
                ++modCount;
                try {
                    buildFromSorted(mapSize, map.entrySet().iterator(),
                                    null, null);
                } catch (java.io.IOException cannotHappen) {
                } catch (ClassNotFoundException cannotHappen) {
                }
                return;
            }
        }
        super.putAll(map);
    }

 public void putAll(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }
我们可以看到在putAll方法中调用了buildFromSorted(int size, Iterator<?> it, java.io.ObjectInputStream str,V defaultVal),该方法的作用即是在线性时间内对数据进行排序(Linear time tree building algorithm from sorted data),看到这里我们就明白TreeMap排序的原理了,即当使用一个Map集合作为参数构造一个TreeMap的时候,TreeMap会将Map中的元素先排序,然后排序后的元素put到TreeMap中。其中在TreeMap的putAll方法的最后会调用其父类AbstractMap的putAll方法,在其父类的putAll方法中才会调用put方法。

四TreeMap中的重要函数:

1put方法

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;
        if (cpr != null) {//如果比较器 cpr 不为 null,即表明采用自定义的排序
            do {// do while作用是在以root为根节点的红黑树中根据传入的key值寻找待插入的位置
                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);// 如果两个 key 相等,新的 value 覆盖原有的 value, 然后返回原 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);//当没找到key值相同的节点,则创建新的节点存储该key值
        if (cmp < 0)
            parent.left = e;// 如果新插入 key 小于 parent 的 key,则 e 作为 parent 的左子节点
        else
            parent.right = e;// 如果新插入 key 小于 parent 的 key,则 e 作为 parent 的右子节点
        fixAfterInsertion(e);// 修复红黑树,当往TreeMap中插入新的节点之后可能破坏了红黑树的性质,所以得调用该函数将其调整为红黑树
        size++;
        modCount++;
        return null;
    }

从上面的代码可以看到put方法的本质就是构造排序二叉树的过程,即当往TreeMap中添加一个节点元素时,首先会寻找待插入的位置,如果在寻找的过程中在TreeMap中找到了与待插入节点的key值相同的节点,则替换然后返回该原来的vlaue,如果没找到,则创建一个新的节点,在恰当的位置处插入该结点,插入完之后会调用fixAfterInsertion(e);来重新修复TreeMap,使其始终满足红黑树的性质。因此可以看到对于相同的key只存在唯一的value值与之对应,因为原来的会被新的替换。


2get方法

public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }

 final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)// 如果比较器不为空,返回getEntryUsingComparator(Object key)的结果
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            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;
    }
可以看到在get方法中会调用getEntry()方法,getEntry()方法会根据传入的key值寻找相应的value然后返回,get的过程也包含两种情况即依据比较器是否为空分别进行get操作,get寻找的过程事实上与构造二叉排序树的过程非常相似,代码也很简单,在此不做赘述。

3remove方法

public V remove(Object key) {
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        deleteEntry(p);
        return oldValue;
    }

  private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;

            // Fix replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
            if (p.color == BLACK)
                fixAfterDeletion(p);

            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }
可以看到在remove方法中调用了deleteEntry方法,即用来从红黑树中删除某一个节点,在这个过程中同样会调用fixAfterDeletion(p);方法,即涉及到树的调整过程。

4clear()方法

public void clear() {
        modCount++;
        size = 0;
        root = null;
    }
代码非常简洁,主要就是将size置为0,同时将根节点root置为null,这样就不能通过root访问其它的节点,这样GC就会回收该TreeMap的内存空间。

5containsKey(Object key)/containsValue(Object value)

public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }

public boolean containsValue(Object value) {
        for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
            if (valEquals(value, e.value))
                return true;
        return false;
    }
其中containsKey非常简单,不做赘述,在containsValue(Object value)中可以看到调用了getFirstEntry()方法和successor(e)方法,我们来看一下其源码:

final Entry<K,V> getFirstEntry() {
        Entry<K,V> p = root;
        if (p != null)
            while (p.left != null)
                p = p.left;
        return p;
    }

 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()方法是用来取整个红黑树中的第一个节点,实际是获取的整棵树中“最左”的节点,因为红黑树是排序的树,所以“最左”的节点也是值最小的节点。而successor(e)方法是返回节点e的继承者,如果e的左孩子非空则返回其左孩子,因此在for循环中配合使用getFirstEntry()方法和successor(Entry<K,V> e)及e!=null是遍历树的一种方法。

五总结:

1TreeMap中的元素是排序的,其内部是通过Comparator接口来实现的,可以通过Comparator接口自定义排序规则。

2TreeMap内部是采用红黑树Entry来实现的,当使用一个Map集合作为参数构造一个TreeMap的时候,TreeMap会将Map中的元素先排序,然后排序后的元素put到TreeMap中,put的过程本质上是构造二叉排序树的过程,插入完之后会调用fixAfterInsertion(e);来重新修复TreeMap,使其始终满足红黑树的性质。

3TreeMap中的元素的key值是唯一的且对于相同的key只存在唯一的value值与之对应,因为在put的过程中原来的会被新的替换。

4TreeMap不是线程同步的,因为TreeMap中的方法都未使用synchronized关键字修饰,即TreeMap是非同步的。

本文转载自:http://blog.csdn.net/htq__/article/details/51055261

共有 人打赏支持
htq

htq

粉丝 19
博文 67
码字总数 1007
作品 3
武汉
Java开发者不会这些永远都只能是三流程序员,细数一下你是不是?

源码系列 手写spring mvc框架 基于Spring JDBC手写ORM框架 实现自己的MyBatis Spring AOP实战之源码分析 Spring IOC高级特性应用分析 ORM框架底层实现原理剖析 手写Spring MVC框架实现 手把手...

美的让人心动 ⋅ 04/16 ⋅ 0

sharding-jdbc源码分析—准备工作

原文作者:阿飞Javaer 原文链接:https://www.jianshu.com/p/7831817c1da8 接下来对sharding-jdbc源码的分析基于tag为源码,根据sharding-jdbc Features深入学习sharding-jdbc的几个主要特性...

飞哥-Javaer ⋅ 05/03 ⋅ 0

【死磕Sharding-jdbc】—–路由&执行

原文作者:阿飞Javaer 原文链接:https://www.jianshu.com/p/09efada2d086 继续以模块中的为基础,剖析分库分表简单查询SQL实现--,即如何执行简单的查询SQL,接下来的分析以执行SQL语句为例...

飞哥-Javaer ⋅ 05/03 ⋅ 0

三流程序员与一流程序员之间的区别,看看你是属于哪一类?

源码系列 手写spring mvc框架 基于Spring JDBC手写ORM框架 实现自己的MyBatis Spring AOP实战之源码分析 Spring IOC高级特性应用分析 ORM框架底层实现原理剖析 手写Spring MVC框架实现 手把手...

茶轴的青春 ⋅ 04/17 ⋅ 0

[Java 并发编程] 集合框架之 同步容器类 & 并发容器类

吾生也有涯,而知也无涯。———《庄子》 通过上一篇文章,我们已经知道设计一个线程安全类的原则和步骤,以及在设计过程中我们应当注意的细节。实际上,Java 的集合库包含了线程安全集合和非...

seaicelin ⋅ 05/25 ⋅ 0

【小马哥】Spring Cloud系列讲座

这里为大家推荐一个不错的Spring Cloud系列讲座,讲师介绍如下: 小马哥,阿里巴巴技术专家,从事十余年Java EE 开发,国内微服务技术讲师。目前主要负责微服务技术推广、架构设计、基础设施...

杜琪 ⋅ 03/02 ⋅ 0

sharding-jdbc源码解析全集

sharding-jdbc源码解析之词法解析 sharding源码解析之api分析 sharding-jdbc源码解析之spring集成 sharding-jdbc源码解析之spring集成分片构造实现 sharding-jdbc源码解析之jdbc规范重写 sh...

天河2018 ⋅ 05/03 ⋅ 0

使用Dubbo中需要注意的事项

一、前言 Dubbo作为高性能RPC框架,已经进入Apache卵化器项目,虽然官方给出了dubbo使用的用户手册,但是大多是一概而过,使用dubbo时候要尽量了解源码,不然会很容易入坑。 二 、服务消费端...

加多 ⋅ 01/02 ⋅ 0

大型互联网架构必备技术——性能调优专题

性能调优 深入内核,直击故障 ,拒绝蒙圈 性能优化如何理解 1、性能基准 2、什么是性能优化 3、衡量标准 JVM调优 1、Jvm虚拟机内存剖析 2、垃圾收集器 3、实战调优案例与解决方案 4、Jvm运行...

Java高级架构 ⋅ 04/15 ⋅ 0

Dubbo剖析-Dubbo协议

一、前言 TCP协议栈中,每层模型都有自己的协议报文格式,TCP协议是网络七层模型中的传输层,在TCP上层是应用层,应用层协议常见的有telnet等,Dubbo协议作为建立在TCP协议之上的一种协议,自...

加多 ⋅ 01/04 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Thrift RPC实战(二) Thrift 网络服务模型

TServer类层次体系 TSimpleServer/TThreadPoolServer是阻塞服务模型 TNonblockingServer/THsHaServer/TThreadedSelectotServer是非阻塞服务模型(NIO) 1 TServer抽象类的定义 内部静态类Args的...

lemonLove ⋅ 16分钟前 ⋅ 0

vim命令用法

第五章 vim命令 vim和vi几乎是一样的,唯一的区别就是当编辑一个文本时,使用vi不会显示颜色,而使用vim会显示颜色。 vim有三个模式:一般模式,编辑模式,命令模式。 系统最小化安装时没有安...

弓正 ⋅ 17分钟前 ⋅ 0

MyBatis源码解读之配置

1. 目的 本文主要介绍MyBatis配置文件解析,通过源码解读mybatis-config.xml(官方默认命名)、Mapper.xml 与Java对象的映射。 2. MyBatis结构 查看大图 MyBatis结构图,原图实在太模糊了,所以...

无忌 ⋅ 21分钟前 ⋅ 0

Ignite的jdbc与网格的连接方式的查询性能对比

环境: 数据量100万 Ignite2.5 Windows10 8g jdbc方式连接 import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; i......

仔仔1993 ⋅ 36分钟前 ⋅ 0

收集自网络的wordpress 分页导航的代码教程(全网最全版)

wordpress 分页导航是用来切换文章的一个功能,添加了 wordpress 分页导航后,用户即可自由到达指定的页面数浏览分类文章,而这样的一个很简单功能却有很多朋友在用插件:WP-PageNavi,插件的...

Rhymo-Wu ⋅ 51分钟前 ⋅ 0

微服务 WildFly Swarm 入门

Hello World 就像前面章节中的其他框架一样,我们希望添加一些基本的 Hello-world 功能,然后在其上逐步添加更多的功能。让我们从在我们的项目中创建一个 HolaResources 开始。您可以使用您的...

woshixin ⋅ 58分钟前 ⋅ 0

Maven的安装和Eclipse的配置

1. 下载Maven 下载地址 2. 解压压缩包,放到自己习惯的硬盘中 此处我将其放到了 D:\Tools 目录下。 3. 配置环境变量 右键此电脑 -> 属性 -> 高级系统设置 -> 环境变量。 在系统变量中新建,变...

影狼 ⋅ 今天 ⋅ 0

python pip使用国内镜像的方法

国内源 清华:https://pypi.tuna.tsinghua.edu.cn/simple 阿里云:http://mirrors.aliyun.com/pypi/simple/ 中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/ 华中理工大学:http://......

良言 ⋅ 今天 ⋅ 0

对于url变化的spa应该如何使用微信jssdk

使用vue单页面碰上微信jssdk config验证失败的坑。第一次成功 之后切换页面全部失败,找到了解决方法,第一次验证成功后保存验证信息 切换页面时验证信息直接拿来用,加一个wx.error() 失败时...

孙冠峰 ⋅ 今天 ⋅ 0

Spring Cloud Gateway 一般集成

SCF发布,带来很多新东西,不过少了点教程,打开方式又和以前的不一样,比如这个SCG,压根就没有入门指导,所以这里写一个,以备后用。 一、集成 pom.xml <dependency> <groupI...

kut ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部