Java集合类-TreeMap

Map集合.jpg

TreeMap和HashMap的区别和共同点

TreeMap HashMap
实现 SortMap接口,基于红黑树 基于哈希散列表实现
存储 默认按键的升序排序 随机存储
遍历 Iterator遍历是排序的 Iterator遍历是随机的
键值对 键、值都不能为null 只允许键、值均为null
安全 非并发安全Map 非并发安全Map
效率

TreeMap 简介

  • TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
  • TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
  • TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
  • TreeMap 实现了Cloneable接口,意味着它能被克隆
  • TreeMap 实现了java.io.Serializable接口,意味着它支持序列化

TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。

TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。
另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。

TreeMap的构造函数

// 默认构造函数。使用该构造函数,TreeMap中的元素按照自然排序进行排列。
TreeMap()

// 创建的TreeMap包含Map
 public TreeMap(Map<? extends K, ? extends V> m)

// 指定Tree的比较器
public TreeMap(Comparator<? super K> comparator)

// 创建的TreeSet包含m
 public TreeMap(SortedMap<K, ? extends V> m)

TreeMap数据结构

java.lang.Object
   ↳     java.util.AbstractMap<K, V>
         ↳     java.util.TreeMap<K, V>

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable

TreeMap与Map关系如下图:

TreeMap关系图

从图中可以看出:

    1. TreeMap实现继承于AbstractMap,并且实现了NavigableMap接口。
    1. TreeMap的本质是R-B Tree(红黑树),它包含几个重要的成员变量: root, size, comparator。root 是红黑数的根节点。它是TreeMapEntry类型,TreeMapEntry是红黑数的节点,它包含了红黑数的6个基本组成成分:key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色)。TreeMapEntry节点根据key进行排序,Entry节点包含的内容为value。

红黑数排序时,根据TreeMapEntry中的key进行排序;TreeMapEntry中的key比较大小是根据比较器comparator来进行判断的。size是红黑数中节点的个数。

关于红黑数的具体算法,请参考"红黑树(一) 原理和算法详细介绍"。

TreeMap源码解析(基于JDK1.8)

插入操作

public V put(K key, V value) {
        TreeMapEntry<K,V> t = root;// 获取根节点
        if (t == null) {
        // 如果根节点为空,则将该元素设置为根节点 
            if (comparator != null) {//如果比较器不为空,比较key值
                if (key == null) {
                    comparator.compare(key, key);//
                }
            } else {
                if (key == null) {
                    throw new NullPointerException("key == null");
                } else if (!(key instanceof Comparable)) {
                    //如果key值没有实现Comparable接口,这里会报异常
                    throw new ClassCastException(
                            "Cannot cast" + key.getClass().getName() + " to Comparable.");
                }
            }
            // END Android-changed: Work around buggy comparators. http://b/34084348
            root = new TreeMapEntry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        TreeMapEntry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        //如果比较器对象不为空,表示key已经实现了Comparable接口
        if (cpr != null) {
            do {// 循环比较并确定元素应插入的位置(也就是找到该元素的父节点)
                parent = t; // t就是root
              // 调用比较器对象的compare()方法,该方法返回一个整数
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)          // 待插入元素的key"小于"当前位置元素的key,则查询左子树
                    t = t.left;
                else if (cmp > 0)    // 待插入元素的key"大于"当前位置元素的key,则查询右子树
                    t = t.right;
                else                // "相等"则替换其value。
                    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)  // 待插入元素的key"小于"当前位置元素的key,则查询左子树
                    t = t.left;
                else if (cmp > 0)  // 待插入元素的key"大于"当前位置元素的key,则查询右子树
                    t = t.right;
                else   // "相等"则替换其value。
                    return t.setValue(value);
            } while (t != null);
        }
        // 根据key找到父节点后新建一个节点
        TreeMapEntry<K,V> e = new TreeMapEntry<>(key, value, parent);
        if (cmp < 0)  // 根据比较的结果来确定放在左子树还是右子树
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;  // 集合大小+1
        modCount++;  // 集合结构被修改次数+1
        return null;
    }

获取元素操作

  //获取指定key的value
 public V get(Object key) {
        TreeMapEntry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }

  //根据指定的key获取节点
 final TreeMapEntry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        TreeMapEntry<K,V> p = root;
         //循环找k
        while (p != null) {
            //从p节点开始比较,
            int cmp = k.compareTo(p.key);
            if (cmp < 0)    //如果当前节点的key,比p节点的key小,移动到左孩子
                p = p.left;
            else if (cmp > 0)    //如果当前节点的key,比p节点的key大,移动到右孩子
                p = p.right;
            else     //如果相等,返回p。
                return p;
        }
        return null;
    }
    //有比较器时,获取节点
    final TreeMapEntry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
            K k = (K) key;
        Comparator<? super K> cpr = comparator;  //获取比较器
        if (cpr != null) {
            TreeMapEntry<K,V> p = root;
            while (p != null) {
                int cmp = cpr.compare(k, p.key);  //从p节点开始比较,
                if (cmp < 0)
                    p = p.left;    //如果当前节点的key,比p节点的key小,移动到左孩子
                else if (cmp > 0)    //如果当前节点的key,比p节点的key大,移动到右孩子
                    p = p.right;
                else     p = p.right;
                    return p;
            }
        }
        return null;
    }

删除元素的操作

 public V remove(Object key) {
        //根据key值查找节点
        TreeMapEntry<K,V> p = getEntry(key);
        if (p == null)
            return null;
        //获取该节点的值,作为返回值
        V oldValue = p.value;
        //删除节点
        deleteEntry(p);
        return oldValue;
    }
private void deleteEntry(TreeMapEntry<K,V> p) {
        modCount++;
        size--;
        //如果p有两个孩子
        if (p.left != null && p.right != null) {
            TreeMapEntry<K,V> s = successor(p);  //获取p的继承节点
            p.key = s.key;     //将s的key设置为p的key
            p.value = s.value;    //将s的value设置为p的value
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
         //开始修复被移除节点的树结构
        //如果p有左孩子,获取左孩子,没有就获取右孩子
        TreeMapEntry<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)     //如果p没有父亲,p就是root节点
                root = replacement;     //将replacement设置为root节点
            else if (p == p.parent.left)     //如果p是父节点的左孩子
                p.parent.left  = replacement;     //将replacement设置为p的父亲的左孩子
            else
                p.parent.right = replacement;   //否则,将replacement设置为p的父亲的右孩子

            // Null out links so they are OK to use by fixAfterDeletion.
             //解除p节点的父亲和p节点的左右孩子的引用
            p.left = p.right = p.parent = null;

            // Fix replacement
            if (p.color == BLACK)     //颜色修复
                fixAfterDeletion(replacement);
                //p的父亲为null,说明p只有自己一个节点
        } 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)   //如果p是黑色
                fixAfterDeletion(p);    //调整

            if (p.parent != null) {
                if (p == p.parent.left)      //p是父亲的左孩子
                    p.parent.left = null;     //删除引用
                else if (p == p.parent.right)  //p是父亲的右孩子
                    p.parent.right = null;    //删除引用
                p.parent = null;    /删除p对父亲的引用
            }
        }
    }

使用案例

public static void main(String[] args) {
        
        TreeMap<String, String> treeMap = new TreeMap<String,String>();
        treeMap.put("m", "梅西");
        treeMap.put("c", "C罗");
        treeMap.put("n", "内马尔");
        treeMap.put("b", "布冯");
        
         Iterator<Entry<String, String>> it = treeMap.entrySet().iterator();
            while(it.hasNext()) {
                Entry<String, String> entry = it.next();
                System.out.println(entry.getKey() + " : " + entry.getValue());
            }

    }
//输出结果
b : 布冯
c : C罗
m : 梅西
n : 内马尔

上面在贴出的插入元素源码中说过,使用TreeMap,k值要实现Comparable 接口,这里的Key值是String类型,下面代码可以看到,String类,已经实现了Comparable接口

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence 

如果key值是一个对象,使用方式如下

 //注意这里实现了Comparable接口
public class People implements Comparable<People> {
    private String name;
    private int age;
    
    public People(String name,int age){
        this.name = name;
        this.age = age;
    }
    
    //当compareTo方法返回0的时候集合中只有一个元素
   //当compareTo方法返回正数的时候集合会怎么存就怎么取
  //当compareTo方法返回负数的时候集合会倒序存储
    @Override
    public int compareTo(People o) {
        int num = this.name.compareTo(o.name);        //姓名是主要条件
        return num == 0 ? this.age - o.age : num;    //年龄是次要条件
        
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}
public static void main(String[] args) {
        
        TreeMap<People, String> treeMap = new TreeMap<People,String>();
        treeMap.put(new People("MeiXi",18), "梅西");
        treeMap.put(new People("BuFeng",18), "布冯18");
        treeMap.put(new People("CLuo",15), "C罗");
        treeMap.put(new People("NeiMaEr",15), "内马尔");
        treeMap.put(new People("BuFeng",15), "布冯");
        
         Iterator<Entry<People, String>> it = treeMap.entrySet().iterator();
            while(it.hasNext()) {
                Entry<People, String> entry = it.next();
                People people = entry.getKey();
                System.out.println(people.getName()+" : "+people.getAge() + " : " + entry.getValue());
            }

    }
//输出结果
BuFeng : 15 : 布冯
BuFeng : 18 : 布冯18
CLuo : 15 : C罗
MeiXi : 18 : 梅西
NeiMaEr : 15 : 内马尔

使用自定义比较器

public static void main(String[] args) {

 TreeMap<String, String> tMap = new TreeMap<String,String>(new Comparator<String>(){
                public int compare(String o1,String o2){
                    //用正负表示大小值,这里用的是o2.compareTo(o1),表示倒序
                   //如果是o1.compareTo(o2),会正序排序
                    return  o2.compareTo(o1); 
                }
            });
            tMap.put("MeiXi", "梅西");
            tMap.put("CLuo", "C罗");
            tMap.put("NeiMaEr", "内马尔");
            tMap.put("BuFeng", "布冯");
            
            Iterator<Entry<String, String>> iter = tMap.entrySet().iterator();
            while(iter.hasNext()) {
                Entry<String, String> entry = iter.next();
               
                System.out.println(entry.getKey() + " : " + entry.getValue());
            }
}
//输出结果
NeiMaEr : 内马尔
MeiXi : 梅西
CLuo : C罗
BuFeng : 布冯

参考文章:
TreeMap
红黑树(一)之 原理和算法详细介绍
Comparable和Comparator的区别

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容

  • Java集合类可用于存储数量不等的对象,并可以实现常用的数据结构如栈,队列等,Java集合还可以用于保存具有映射关...
    小徐andorid阅读 1,944评论 0 13
  • 一、基本数据类型 注释 单行注释:// 区域注释:/* */ 文档注释:/** */ 数值 对于byte类型而言...
    龙猫小爷阅读 4,266评论 0 16
  • 一、集合入门总结 集合框架: Java中的集合框架大类可分为Collection和Map;两者的区别: 1、Col...
    程序员欧阳阅读 11,569评论 2 61
  • 有一些情绪 像深沉的大海 无言把所有灌溉 后来,我试着卖弄文采 幻想了一切精彩 却写不出未来 有一些挣扎 总伴随着...
    诗人可貌相阅读 306评论 3 2
  • 文/陈安若 灵感 要么没有 要么脑中暴走 争着叫着同时闪现 就像无数根短毛线 被揉成一团 杂乱交错的毛线球 看着都...
    陈安若阅读 534评论 5 3