精华内容
下载资源
问答
  • TreeMap继承了NavigableMap接口,NavigableMap接口继承了SortedMap接口,可支持一系列的导航定位以及导航操作的方法,当然只是提供了接口,需要TreeMap自己去实现; TreeMap实现了Cloneable接口,可被克隆,实现了...

    一. TreeMap概述

    TreeMap存储K-V键值对,通过红黑树(R-B tree)实现;
    TreeMap继承了NavigableMap接口,NavigableMap接口继承了SortedMap接口,可支持一系列的导航定位以及导航操作的方法,当然只是提供了接口,需要TreeMap自己去实现;
    TreeMap实现了Cloneable接口,可被克隆,实现了Serializable接口,可序列化;
    TreeMap因为是通过红黑树实现,红黑树结构天然支持排序,默认情况下通过Key值的自然顺序进行排序;

    二. 红黑树回顾

    因为TreeMap的存储结构是红黑树,我们回顾一下红黑树的特点以及基本操作,红黑树的原理可参考关于红黑树(R-B tree)原理,看这篇如何。下图为典型的红黑树:
    在这里插入图片描述
    红黑树规则特点:

    节点分为红色或者黑色;
    根节点必为黑色;
    叶子节点都为黑色,且为null;
    连接红色节点的两个子节点都为黑色(红黑树不会出现相邻的红色节点);
    从任意节点出发,到其每个叶子节点的路径中包含相同数量的黑色节点;
    新加入到红黑树的节点为红色节点;

    红黑树自平衡基本操作:

    变色:在不违反上述红黑树规则特点情况下,将红黑树某个node节点颜色由红变黑,或者由黑变红;
    左旋:逆时针旋转两个节点,让一个节点被其右子节点取代,而该节点成为右子节点的左子节点
    右旋:顺时针旋转两个节点,让一个节点被其左子节点取代,而该节点成为左子节点的右子节点

    三. TreeMap构造

    我们先看一下TreeMap中主要的成员变量

    /**
     * 我们前面提到TreeMap是可以自动排序的,默认情况下comparator为null,这个时候按照key的自然顺序进行排
     * 序,然而并不是所有情况下都可以直接使用key的自然顺序,有时候我们想让Map的自动排序按照我们自己的规则,
     * 这个时候你就需要传递Comparator的实现类
     */
    private final Comparator<? super K> comparator;
    
    /**
     * TreeMap的存储结构既然是红黑树,那么必然会有唯一的根节点。
     */
    private transient Entry<K,V> root;
    
    /**
     * Map中key-val对的数量,也即是红黑树中节点Entry的数量
     */
    private transient int size = 0;
    
    /**
     * 红黑树结构的调整次数
     */
    private transient int modCount = 0;
    上面的主要成员变量根节点root是Entry类的实体,我们来看一下Entry类的源码
    
    static final class Entry<K,V> implements Map.Entry<K,V> {
        //key,val是存储的原始数据
        K key;
        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;
        }//需要获取资料的朋友请加Q君样:290194256*
    
        /**
         * 获取节点的key值
         */
        public K getKey() {return key;}
    
        /**
         * 获取节点的value值
         */
        public V getValue() {return value;}
    
        /**
         * 用新值替换当前值,并返回当前值
         */
        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静态内部类实现了Map的内部接口Entry,提供了红黑树存储结构的java实现,通过left属性可以建立左子树,通过right属性可以建立右子树,通过parent可以往上找到父节点。

    大体的实现结构图如下:
    在这里插入图片描述
    TreeMap构造函数:

    //默认构造函数,按照key的自然顺序排列
    public TreeMap() {comparator = null;}
    //传递Comparator具体实现,按照该实现规则进行排序
    public TreeMap(Comparator<? super K> comparator) {this.comparator = comparator;}
    //传递一个map实体构建TreeMap,按照默认规则排序
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }
    //传递一个map实体构建TreeMap,按照传递的map的排序规则进行排序
    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) {
        }
    }
    

    四. put方法

    put方法为Map的核心方法,TreeMap的put方法大概流程如下:
    在这里插入图片描述
    我们来分析一下源码

    public V put(K key, V value) {
        Entry<K,V> t = root;
        /**
         * 如果根节点都为null,还没建立起来红黑树,我们先new Entry并赋值给root把红黑树建立起来,这个时候红
         * 黑树中已经有一个节点了,同时修改操作+1。
         */
        if (t == null) {
            compare(key, key); 
            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        /**
         * 如果节点不为null,定义一个cmp,这个变量用来进行二分查找时的比较;定义parent,是new Entry时必须
         * 要的参数
         */
        int cmp;
        Entry<K,V> parent;
        // cpr表示有无自己定义的排序规则,分两种情况遍历执行
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            /**
             * 从root节点开始遍历,通过二分查找逐步向下找
             * 第一次循环:从根节点开始,这个时候parent就是根节点,然后通过自定义的排序算法
             * cpr.compare(key, t.key)比较传入的key和根节点的key值,如果传入的key<root.key,那么
             * 继续在root的左子树中找,从root的左孩子节点(root.left)开始:如果传入的key>root.key,
             * 那么继续在root的右子树中找,从root的右孩子节点(root.right)开始;如果恰好key==root.key,
             * 那么直接根据root节点的value值即可。
             * 后面的循环规则一样,当遍历到的当前节点作为起始节点,逐步往下找
             *
             * 需要注意的是:这里并没有对key是否为null进行判断,建议自己的实现Comparator时应该要考虑在内
             */
            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);
        }//需要获取资料的朋友请加Q君样:290194256*
        else {
            //从这里看出,当默认排序时,key值是不能为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,节点已经遍历到最后了,我们只需要new一个Entry放到
         * parent下面即可,但放到左子节点上还是右子节点上,就需要按照红黑树的规则来。
         */
        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方法源码中通过fixAfterInsertion(e)方法来进行自平衡处理,我们回顾一下插入时自平衡调整的逻辑。
    在这里插入图片描述

    接下来我们看一看这个方法

    private void fixAfterInsertion(Entry<K,V> x) {
        //新插入的节点为红色节点
        x.color = RED;
        //我们知道父节点为黑色时,并不需要进行树结构调整,只有当父节点为红色时,才需要调整
        while (x != null && x != root && x.parent.color == RED) {
            //如果父节点是左节点,对应上表中情况1和情况2
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                //如果叔父节点为红色,对应于“父节点和叔父节点都为红色”,此时通过变色即可实现平衡
                //此时父节点和叔父节点都设置为黑色,祖父节点设置为红色
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    //如果插入节点是黑色,插入的是右子节点,通过【左右节点旋转】(这里先进行父节点左旋)
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    //设置父节点和祖父节点颜色
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    //进行祖父节点右旋(这里【变色】和【旋转】并没有严格的先后顺序,达成目的就行)
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                //父节点是右节点的情况
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                //对应于“父节点和叔父节点都为红色”,此时通过变色即可实现平衡
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    //如果插入节点是黑色,插入的是左子节点,通过【右左节点旋转】(这里先进行父节点右旋)
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }//需要获取资料的朋友请加Q君样:290194256*
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    //进行祖父节点左旋(这里【变色】和【旋转】并没有严格的先后顺序,达成目的就行)
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        //根节点必须为黑色
        root.color = BLACK;
    }
    

    源码中通过 rotateLeft 进行【左旋】,通过 rotateRight 进行【右旋】。都非常类似,我们就看一下【左旋】的代码,【左旋】规则如下:“逆时针旋转两个节点,让一个节点被其右子节点取代,而该节点成为右子节点的左子节点”。

    private void rotateLeft(Entry<K,V> p) {
        if (p != null) {
            /**
             * 断开当前节点p与其右子节点的关联,重新将节点p的右子节点的地址指向节点p的右子节点的左子节点
             * 这个时候节点r没有父节点
             */
            Entry<K,V> r = p.right;
            p.right = r.left;
            //将节点p作为节点r的父节点
            if (r.left != null)
                r.left.parent = p;
            //将节点p的父节点和r的父节点指向同一处
            r.parent = p.parent;
            //p的父节点为null,则将节点r设置为root
            if (p.parent == null)
                root = r;
            //如果节点p是左子节点,则将该左子节点替换为节点r
            else if (p.parent.left == p)
                p.parent.left = r;
            //如果节点p为右子节点,则将该右子节点替换为节点r
            else
                p.parent.right = r;
            //重新建立p与r的关系
            r.left = p;
            p.parent = r;
        }
    }
    

    就算是看了上面的注释还是并不清晰,看下图你就懂了
    在这里插入图片描述
    五. get 方法

    get方法是通过二分查找的思想,我们看一下源码

    public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }
    /**
     * 从root节点开始遍历,通过二分查找逐步向下找
     * 第一次循环:从根节点开始,这个时候parent就是根节点,然后通过k.compareTo(p.key)比较传入的key和
     * 根节点的key值;
     * 如果传入的key<root.key, 那么继续在root的左子树中找,从root的左孩子节点(root.left)开始;
     * 如果传入的key>root.key, 那么继续在root的右子树中找,从root的右孩子节点(root.right)开始;
     * 如果恰好key==root.key,那么直接根据root节点的value值即可。
     * 后面的循环规则一样,当遍历到的当前节点作为起始节点,逐步往下找
     */
    //默认排序情况下的查找
    final Entry<K,V> getEntry(Object key) {
        
        if (comparator != null)
            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;
    }
    /**
     * 从root节点开始遍历,通过二分查找逐步向下找
     * 第一次循环:从根节点开始,这个时候parent就是根节点,然后通过自定义的排序算法
     * cpr.compare(key, t.key)比较传入的key和根节点的key值,如果传入的key<root.key,那么
     * 继续在root的左子树中找,从root的左孩子节点(root.left)开始:如果传入的key>root.key,
     * 那么继续在root的右子树中找,从root的右孩子节点(root.right)开始;如果恰好key==root.key,
     * 那么直接根据root节点的value值即可。
     * 后面的循环规则一样,当遍历到的当前节点作为起始节点,逐步往下找
     */
    //自定义排序规则下的查找
    final Entry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
        K k = (K) key;
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            Entry<K,V> p = root;
            while (p != null) {
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }//需要获取资料的朋友请加Q君样:290194256*
        }
        return null;
    }
    

    六. remove方法

    remove方法可以分为两个步骤,先是找到这个节点,直接调用了上面介绍的getEntry(Object key),这个步骤我们就不说了,直接说第二个步骤,找到后的删除操作。

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

    通过deleteEntry§进行删除操作,删除操作的原理我们在前面已经讲过

    删除的是根节点,则直接将根节点置为null;
    待删除节点的左右子节点都为null,删除时将该节点置为null;
    待删除节点的左右子节点有一个有值,则用有值的节点替换该节点即可;
    待删除节点的左右子节点都不为null,则找前驱或者后继,将前驱或者后继的值复制到该节点中,然后删除前驱或者后继(前驱:左子树中值最大的节点,后继:右子树中值最小的节点);

    private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;
    	//当左右子节点都不为null时,通过successor(p)遍历红黑树找到前驱或者后继
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p);
            //将前驱或者后继的key和value复制到当前节点p中,然后删除节点s(通过将节点p引用指向s)
            p.key = s.key;
            p.value = s.value;
            p = s;
        } //需要获取资料的朋友请加Q君样:290194256*
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);
        /**
         * 至少有一个子节点不为null,直接用这个有值的节点替换掉当前节点,给replacement的parent属性赋值,给
         * parent节点的left属性和right属性赋值,同时要记住叶子节点必须为null,然后用fixAfterDeletion方法
         * 进行自平衡处理
         */
        if (replacement != null) {
            //将待删除节点的子节点挂到待删除节点的父节点上。
            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;
            p.left = p.right = p.parent = null;
            /**
             * p如果是红色节点的话,那么其子节点replacement必然为红色的,并不影响红黑树的结构
             * 但如果p为黑色节点的话,那么其父节点以及子节点都可能是红色的,那么很明显可能会存在红色相连的情
             * 况,因此需要进行自平衡的调整
             */
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) {//这种情况就不用多说了吧
            root = null;
        } else { 
            /**
             * 如果p节点为黑色,那么p节点删除后,就可能违背每个节点到其叶子节点路径上黑色节点数量一致的规则,
             * 因此需要进行自平衡的调整
             */ 
            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;
            }
        }
    }
    

    操作的操作其实很简单,场景也不多,我们看一下删除后的自平衡操作方法

    fixAfterDeletion
    
    private void fixAfterDeletion(Entry<K,V> x) {
        /**
         * 当x不是root节点且颜色为黑色时
         */
        while (x != root && colorOf(x) == BLACK) {
            /**
             * 首先分为两种情况,当前节点x是左节点或者当前节点x是右节点,这两种情况下面都是四种场景,这里通过
             * 代码分析一下x为左节点的情况,右节点可参考左节点理解,因为它们非常类似
             */
            if (x == leftOf(parentOf(x))) {
                Entry<K,V> sib = rightOf(parentOf(x));
    
                /**
                 * 场景1:当x是左黑色节点,兄弟节点sib是红色节点
                 * 兄弟节点由红转黑,父节点由黑转红,按父节点左旋,
                 * 左旋后树的结构变化了,这时重新赋值sib,这个时候sib指向了x的兄弟节点
                 */
                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }//需要获取资料的朋友请加Q君样:290194256*
    
                /**
                 * 场景2:节点x、x的兄弟节点sib、sib的左子节点和右子节点都为黑色时,需要将该节点sib由黑变
                 * 红,同时将x指向当前x的父节点
                 */
                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    /**
                     * 场景3:节点x、x的兄弟节点sib、sib的右子节点都为黑色,sib的左子节点为红色时,
                     * 需要将sib左子节点设置为黑色,sib节点设置为红色,同时按sib右旋,再将sib指向x的
                     * 兄弟节点
                     */
                    if (colorOf(rightOf(sib)) == BLACK) {
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    /**
                     * 场景4:节点x、x的兄弟节点sib都为黑色,而sib的左右子节点都为红色或者右子节点为红色、
                     * 左子节点为黑色,此时需要将sib节点的颜色设置成和x的父节点p相同的颜色,
                     * 设置x的父节点为黑色,设置sib右子节点为黑色,左旋x的父节点p,然后将x赋值为root
                     */
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);
                    rotateLeft(parentOf(x));
                    x = root;
                }//需要获取资料的朋友请加Q君样:290194256*
            } else {//x是右节点的情况
                Entry<K,V> sib = leftOf(parentOf(x));
    
                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }
    
                if (colorOf(rightOf(sib)) == BLACK &&
                    colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    x = root;
                }
            }
        }
    
        setColor(x, BLACK);
    }
    

    当待操作节点为左节点时,上面描述了四种场景,而且场景之间可以相互转换,如deleteEntry后进入了场景1,经过场景1的一些列操作后,红黑树的结构并没有调整完成,而是进入了场景2,场景2执行完成后跳出循环,将待操作节点设置为黑色,完成。我们下面用图来说明一下四种场景帮助理解,当然大家最好自己手动画一下。

    场景1:

    当x是左黑色节点,兄弟节点sib是红色节点,需要兄弟节点由红转黑,父节点由黑转红,按父节点左旋,左旋后树的结构变化了,这时重新赋值sib,这个时候sib指向了x的兄弟节点。
    在这里插入图片描述
    但经过这一系列操作后,并没有结束,而是可能到了场景2,或者场景3和4

    场景2:

    节点x、x的兄弟节点sib、sib的左子节点和右子节点都为黑色时,需要将该节点sib由黑变红,同时将x指向当前x的父节点
    在这里插入图片描述
    经过场景2的一系列操作后,循环就结束了,我们跳出循环,将节点x设置为黑色,自平衡调整完成。

    场景3:

    节点x、x的兄弟节点sib、sib的右子节点都为黑色,sib的左子节点为红色时,需要将sib左子节点设置为黑色,sib节点设置为红色,同时按sib右旋,再将sib指向x的兄弟节点
    在这里插入图片描述
    并没有完,场景3的一系列操作后,会进入到场景4

    场景4:

    节点x、x的兄弟节点sib都为黑色,而sib的左右子节点都为红色或者右子节点为红色、左子节点为黑色,此时需要将sib节点的颜色设置成和x的父节点p相同的颜色,设置x的父节点颜色为黑色,设置sib右孩子的颜色为黑色,左旋x的父节点p,然后将x赋值为root
    在这里插入图片描述
    四种场景讲完了,删除后的自平衡操作不太好理解,代码层面的已经弄明白了,但如果让我自己去实现的话,还是差了一些,还需要再研究。

    七. 遍历

    遍历比较简单,TreeMap的遍历可以使用map.values(), map.keySet(),map.entrySet(),map.forEach(),这里不再多说。

    八. 总结
    本文详细介绍了TreeMap的基本特点,并对其底层数据结构红黑树进行了回顾,同时讲述了其自动排序的原理,并从源码的角度结合红黑树图形对put方法、get方法、remove方法进行了讲解。
    在这里插入图片描述
    在这里插入图片描述
    最新2020整理收集的一些高频面试题(都整理成文档),有很多干货,包含mysql,netty,spring,线程,spring cloud、jvm、源码、算法等详细讲解,也有详细的学习规划图,面试题整理等,需要获取这些内容的朋友请加Q君样:290194256*

    展开全文
  • Map集合的五种遍历方式及Treemap方法

    万次阅读 多人点赞 2019-02-28 11:31:36
    //循环遍历map的方法 public class MapF { public static void main(String[] args) { Map&amp;lt;String, Integer&amp;gt; tempMap = new HashMap&amp;lt;String, Integer&amp;gt;(); tempMap....

    Map集合:链接: Map集合的五种遍历方式及Treemap方法
    Set集合:链接: Java中遍历Set集合的三种方法
    TreeSet集合:链接: Java深入了解TreeSet,和迭代器遍历方法
    LIst集合:链接: Java中List集合的三种遍历方式(全网最详)
    集合区别:链接: java中list,set,map集合的区别,及面试要点

    //循环遍历map的方法
    public class MapF {
     public static void main(String[] args) {
      Map<String, Integer> tempMap = new HashMap<String, Integer>();
      tempMap.put("a","12");
      tempMap.put("b","34");
      tempMap.put("c","56");
      // JDK1.4中
      // 遍历方法一 hashmap entrySet() 遍历
      Iterator it = tempMap.entrySet().iterator();
      while (it.hasNext()) {
       Map.Entry entry = (Map.Entry) it.next();
       Object key = entry.getKey();
       Object value = entry.getValue();
       System.out.println("key=" + key + " value=" + value);
      }
      System.out.println("");
      // JDK1.5中,应用新特性For-Each循环
      // 遍历方法二
      for (Map.Entry<String, Integer> entry : tempMap.entrySet()) {
       String key = entry.getKey().toString();
       String value = entry.getValue().toString();
       System.out.println("key=" + key + " value=" + value);
      }
      System.out.println("");
      // 遍历方法三 hashmap keySet() 遍历
      for (Iterator i = tempMap.keySet().iterator(); i.hasNext();) {
       Object obj = i.next();
       System.out.println(obj);// 循环输出key
       System.out.println("key=" + obj + " value=" + tempMap.get(obj));
      }
      for (Iterator i = tempMap.values().iterator(); i.hasNext();) {
       Object obj = i.next();
       System.out.println(obj);// 循环输出value
      }
      // 遍历方法四 treemap keySet()遍历
      for (Object o : tempMap.keySet()) {
       System.out.println("key=" + o + " value=" + tempMap.get(o));
      }
      System.out.println("11111");
      // java如何遍历Map <String, ArrayList> map = new HashMap <String,
      // ArrayList>();
      System.out.println("java  遍历Map <String, ArrayList> map = new HashMap<String, ArrayList>();");
      Map<String, ArrayList> map = new HashMap<String, ArrayList>();
      Set<String> keys = map.keySet();
      Iterator<String> iterator = keys.iterator();
      while (iterator.hasNext()) {
       String key = iterator.next();
       ArrayList arrayList = map.get(key);
       for (Object o : arrayList) {
        System.out.println(o);
       }
      }
      Map<String, List> map = new HashMap<String, List>();
      for (Map.Entry entry : map.entrySet()) {
       String key = entry.getKey().toString();
       List<String> list= (List) entry.getValue();
       for (String value : list) {
        System.out.println(key + "====" + value);
       }
      }
     }
    }
    

    Map接口简介

    今天来看一看map集合,map映射接口,用于存放键值对,<key,value>,通过key来查找value,顾名思义key不能为空,唯一且不重复,不然底层怎么查呢!

    可以从图中看出Map为单独的接口,他和Collection有什么区别呢?

    Map和Collection在集合中并列存在。 
    Map集合是双列的,键值对,而Collection是单列集合
    Map存储元素使用put方法,Collection使用Put方法。
    Map遍历没有直接取出元素的方法,而是先转成Set集合,再通过迭代获取元素。
     --Map常用方法
      
    –Map应用
    添加:使用HashMap。立了学生姓名和年龄之间的映射关系。并试图添加重复的键

    复制代码
       public static void main(String[] args) {
            // 定义一个Map的容器对象  
            Map<String, Integer > map1 = new HashMap<String, Integer >();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);
          // map1.put("jack", 30); 在没有hashCode和equals方式   添加重复的键值(值不同),会覆盖掉前面key值相同的值
            System.out.println(map1);  
          
            Map<String, Integer> map2 = new HashMap<String, Integer>();  
            map2.put("张三丰", 100);  
            map2.put("虚竹", 20);  
            System.out.println("map2:" + map2);  
            // 从指定映射中将所有映射关系复制到此映射中。  
            map1.putAll(map2);  
            System.out.println("map1:" + map1);  
        }  
    复制代码
    

    删除:

    复制代码
       public static void main(String[] args) {   
       // 删除:  
            // remove() 删除关联对象,指定key对象  
            // clear() 清空集合对象  
      
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            System.out.println(map1);                 
            // 指定key,返回删除的键值对映射的值。  
            map1.remove("java");
            System.out.println(map1);  
            map1.clear();  
            System.out.println("map1:" + map1);  
        }  
    复制代码
    
    

    获取:

    复制代码
    public static void main(String[] args) {
         // 获取:  
            // V get(Object key) 通过指定的key对象获取value对象  
            // int size() 获取容器的大小  
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            System.out.println(map1);  
            // V get(Object key) 通过指定的key对象获取value对象  
            System.out.println("value:" + map1.get("jack"));  
            // int size() 获取容器的大小
            System.out.println("map.size:" + map1.size()); 
        } 
    复制代码
    
    

    判断:

    复制代码
    public static void main(String[] args) {
            // 判断:  
            // boolean isEmpty() 判断集合是否为空   长度为0返回true否则false  
            // boolean containsKey(Object key) 判断集合中是否包含指定的key  
            // boolean containsValue(Object value)  
      
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            System.out.println(map1);  
            System.out.println("isEmpty:" + map1.isEmpty());  
            System.out.println("containskey:" + map1.containsKey("jack"));  
            System.out.println("containsvalues:" + map1.containsValue(100));  
        }  
    复制代码
    
    

    遍历Map的4中方式:

    第一种:

    复制代码

    public static void main(String[] args) {
            //遍历Map 第一种方式
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            
            //通过 map1.keySet() 获取key  通过key 找到value
            for (String key : map1.keySet()) {
                Integer value = map1.get(key);
                System.out.println("key : "+key+" value : "+value);
            }
        }
    复制代码
    
    第二种:
    复制代码
    public static void main(String[] args) {
            //遍历Map 第二种方式
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            
           //通过Map.Entry(String,Integer) 获取,然后使用entry.getKey()获取到键,通过entry.getValue()获取到值
           for(Map.Entry<String, Integer> entry : map1.entrySet()){
               System.out.println("键 key :"+entry.getKey()+" 值value :"+entry.getValue());
           }
        }
    复制代码
    
    第三种:
    复制代码
    //遍历Map 第三种方式
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            //第三种只遍历键或者值,通过加强for循环
            for(String s1:map1.keySet()){//遍历map的键
               System.out.println("键key :"+s1);
            }
            for(Integer s2:map1.values()){//遍历map的值
                System.out.println("值value :"+s2);
            }
               System.out.println("====================================");    
        }
    复制代码
    
    第四种:
    复制代码
    public static void main(String[] args) {
            //遍历Map 第一种方式
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            
            //第四种Iterator遍历获取,然后获取到Map.Entry<String, String>,再得到getKey()和getValue()
            Iterator<Map.Entry<String, Integer>> it=map1.entrySet().iterator();
            while(it.hasNext()){
                Map.Entry<String, Integer> entry=it.next(); 
                System.out.println("键key :"+entry.getKey()+" value :"+entry.getValue());
            }
            
        }
    复制代码
    

    HashMap

    底层是哈希表数据结构,线程是不同步的,可以存入null键,null值。要保证键的唯一性,需要覆盖hashCode方法,和equals方法。
       案例:自定义对象作为Map的键。

    复制代码
        public class Demo3 {  
            public static void main(String[] args) {  
                HashMap<Person, String> hm = new HashMap<Person, String>();  
                hm.put(new Person("jack", 20), "1001");  
                hm.put(new Person("rose", 18), "1002");  
                hm.put(new Person("lucy", 19), "1003");  
                hm.put(new Person("hmm", 17), "1004");  
                hm.put(new Person("ll", 25), "1005");  
                System.out.println(hm);  
                System.out.println(hm.put(new Person("rose", 18), "1006"));  //重写hashCode和equalse后key相同不会覆盖
          
                Set<Entry<Person, String>> entrySet = hm.entrySet();  
                Iterator<Entry<Person, String>> it = entrySet.iterator();  
                while (it.hasNext()) {  
                    Entry<Person, String> next = it.next();  
                    Person key = next.getKey();  
                    String value = next.getValue();  
                    System.out.println(key + " = " + value);  
                }  
            }  
        }  
          
        class Person {  
            private String name;  
            private int age;  
          
            Person() {  
          
            }  
          
            public Person(String name, int age) {  
                this.name = name;  
                this.age = age;  
            }  
          
            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;  
            }  
          
            @Override  
            public int hashCode() {  
          
                return this.name.hashCode() + age * 37;  
            }  
          
            @Override  
            public boolean equals(Object obj) {  
                if (obj instanceof Person) {  
                    Person p = (Person) obj;  
                    return this.name.equals(p.name) && this.age == p.age;  
                } else {  
                    return false;  
                }  
            }  
          
            @Override  
            public String toString() {  
          
                return "Person@name:" + this.name + " age:" + this.age;  
            }  
          
        }  
        }  
    复制代码
    

    TreeMap

    TreeMap的排序,TreeMap可以对集合中的键进行排序。如何实现键的排序?

    方式一:元素自身具备比较性

    和TreeSet一样原理,需要让存储在键位置的对象实现Comparable接口,重写compareTo方法,也就是让元素自身具备比较性,这种方式叫做  元素的自然排序也叫做默认排序。

    方式二:容器具备比较性

    当元素自身不具备比较性,或者自身具备的比较性不是所需要的。那么此时可以让容器自身具备。需要定义一个类实现接口Comparator,重  写compare方法,并将该接口的子类实例对象作为参数传递给TreeMap集合的构造方法。

    注意:当Comparable比较方式和Comparator比较方式同时存在时,以Comparator的比较方式为主;

    注意:在重写compareTo或者compare方法时,必须要明确比较的主要条件相等时要比较次要条件。(假设姓名和年龄一致的人为相同的人,  如果想要对人按照年龄的大小来排序,如果年龄相同的人,需要如何处理?不能直接return 0,以为可能姓名不同(年龄相同姓名不同的人  是不同的人)。此时就需要进行次要条件判断(需要判断姓名),只有姓名和年龄同时相等的才可以返回0.)

    通过return 0来判断唯一性。

    public class Demo4 { 
        public static void main(String[] args) { 
            TreeMap<String, Integer> tree = new TreeMap<String, Integer>(); 
            tree.put("张三", 19); 
            tree.put("李四", 20); 
            tree.put("王五", 21); 
            tree.put("赵六", 22); 
            tree.put("周七", 23); 
            tree.put("张三", 24); 
            System.out.println(tree); 
            System.out.println("张三".compareTo("李四"));//-2094 
        } 
    } 
    

    自定义元素排序

    复制代码
        public class Demo3 {  
            public static void main(String[] args) {  
                TreeMap<Person, String> hm = new TreeMap<Person, String>(  
                        new MyComparator());  
                hm.put(new Person("jack", 20), "1001");  
                hm.put(new Person("rose", 18), "1002");  
                hm.put(new Person("lucy", 19), "1003");  
                hm.put(new Person("hmm", 17), "1004");  
                hm.put(new Person("ll", 25), "1005");  
                System.out.println(hm);  
                System.out.println(hm.put(new Person("rose", 18), "1006"));  
          
                Set<Entry<Person, String>> entrySet = hm.entrySet();  
                Iterator<Entry<Person, String>> it = entrySet.iterator();  
                while (it.hasNext()) {  
                    Entry<Person, String> next = it.next();  
                    Person key = next.getKey();  
                    String value = next.getValue();  
                    System.out.println(key + " = " + value);  
                }  
            }  
        }  
          
        class MyComparator implements Comparator<Person> {  
          
            @Override  
            public int compare(Person p1, Person p2) {  
                if (p1.getAge() > p2.getAge()) {  
                    return -1;  
                } else if (p1.getAge() < p2.getAge()) {  
                    return 1;  
                }  
                return p1.getName().compareTo(p2.getName());  
            }  
          
        }  
          
        class Person implements Comparable<Person> {  
            private String name;  
            private int age;  
          
            Person() {  
          
            }  
          
            public Person(String name, int age) {  
          
                this.name = name;  
                this.age = age;  
            }  
          
            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;  
            }  
          
            @Override  
            public int hashCode() {  
          
                return this.name.hashCode() + age * 37;  
            }  
          
            @Override  
            public boolean equals(Object obj) {  
                if (obj instanceof Person) {  
                    Person p = (Person) obj;  
                    return this.name.equals(p.name) && this.age == p.age;  
                } else {  
                    return false;  
                }  
            }  
          
            @Override  
            public String toString() {  
          
                return "Person@name:" + this.name + " age:" + this.age;  
            }  
          
            @Override  
            public int compareTo(Person p) {  
          
                if (this.age > p.age) {  
                    return 1;  
                } else if (this.age < p.age) {  
                    return -1;  
                }  
                return this.name.compareTo(p.name);  
            }  
          
        }  
    复制代码
    

    注意:Set的元素不可重复,Map的键不可重复,如果存入重复元素如何处理

    Set元素重复元素不能存入add方法返回false

    Map的重复健将覆盖旧键,将旧值返回。

    展开全文
  • 考考基础部分,你能说出 TreeMap 原理实现及常用方法吗? 目录 TreeMap概述 红黑树回顾 TreeMap构造 put方法 get 方法 remove方法 遍历 总结 一. TreeMap概述 TreeMap存储K-V键值对,通过红黑树(R-B tree)实现; ...

    考考基础部分,你能说出 TreeMap 原理实现及常用方法吗?

    目录

    TreeMap概述

    红黑树回顾

    TreeMap构造

    put方法

    get 方法

    remove方法

    遍历

    总结

    一. TreeMap概述

    TreeMap存储K-V键值对,通过红黑树(R-B tree)实现;

    TreeMap继承了NavigableMap接口,NavigableMap接口继承了SortedMap接口,可支持一系列的导航定位以及导航操作的方法,当然只是提供了接口,需要TreeMap自己去实现;

    TreeMap实现了Cloneable接口,可被克隆,实现了Serializable接口,可序列化;

    TreeMap因为是通过红黑树实现,红黑树结构天然支持排序,默认情况下通过Key值的自然顺序进行排序;

    二. 红黑树回顾

    因为TreeMap的存储结构是红黑树,我们回顾一下红黑树的特点以及基本操作,红黑树的原理可参考关于红黑树(R-B tree)原理:

    https://www.cnblogs.com/LiaHon/p/11203229.html

    下图为典型的红黑树:
    在这里插入图片描述

    红黑树规则特点:

    节点分为红色或者黑色;

    根节点必为黑色;

    叶子节点都为黑色,且为null;

    连接红色节点的两个子节点都为黑色(红黑树不会出现相邻的红色节点);

    从任意节点出发,到其每个叶子节点的路径中包含相同数量的黑色节点;

    新加入到红黑树的节点为红色节点;

    红黑树自平衡基本操作:

    变色:在不违反上述红黑树规则特点情况下,将红黑树某个node节点颜色由红变黑,或者由黑变红;

    左旋:逆时针旋转两个节点,让一个节点被其右子节点取代,而该节点成为右子节点的左子节点

    右旋:顺时针旋转两个节点,让一个节点被其左子节点取代,而该节点成为左子节点的右子节点

    三. TreeMap构造

    我们先看一下TreeMap中主要的成员变量
    /**

    • 我们前面提到TreeMap是可以自动排序的,默认情况下comparator为null,这个时候按照key的自然顺序进行排
    • 序,然而并不是所有情况下都可以直接使用key的自然顺序,有时候我们想让Map的自动排序按照我们自己的规则,
    • 这个时候你就需要传递Comparator的实现类
      */
      private final Comparator<? super K> comparator;

    /**

    • TreeMap的存储结构既然是红黑树,那么必然会有唯一的根节点。
      */
      private transient Entry<K,V> root;

    /**

    • Map中key-val对的数量,也即是红黑树中节点Entry的数量
      */
      private transient int size = 0;

    /**

    • 红黑树结构的调整次数
      */
      private transient int modCount = 0;

    上面的主要成员变量根节点root是Entry类的实体,我们来看一下Entry类的源码
    static final class Entry<K,V> implements Map.Entry<K,V> {
    //key,val是存储的原始数据
    K key;
    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;
    }
    
    /**
     * 获取节点的key值
     */
    public K getKey() {return key;}
    
    /**
     * 获取节点的value值
     */
    public V getValue() {return value;}
    
    /**
     * 用新值替换当前值,并返回当前值
     */
    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静态内部类实现了Map的内部接口Entry,提供了红黑树存储结构的java实现,通过left属性可以建立左子树,通过right属性可以建立右子树,通过parent可以往上找到父节点。

    大体的实现结构图如下:
    在这里插入图片描述

    TreeMap构造函数:
    //默认构造函数,按照key的自然顺序排列
    public TreeMap() {comparator = null;}
    //传递Comparator具体实现,按照该实现规则进行排序
    public TreeMap(Comparator<? super K> comparator) {this.comparator = comparator;}
    //传递一个map实体构建TreeMap,按照默认规则排序
    public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    putAll(m);
    }
    //传递一个map实体构建TreeMap,按照传递的map的排序规则进行排序
    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) {
    }
    }

    四. put方法

    put方法为Map的核心方法,TreeMap的put方法大概流程如下:

    在这里插入图片描述

    我们来分析一下源码
    public V put(K key, V value) {
    Entry<K,V> t = root;
    /**
    * 如果根节点都为null,还没建立起来红黑树,我们先new Entry并赋值给root把红黑树建立起来,这个时候红
    * 黑树中已经有一个节点了,同时修改操作+1。
    /
    if (t == null) {
    compare(key, key);
    root = new Entry<>(key, value, null);
    size = 1;
    modCount++;
    return null;
    }
    /
    *
    * 如果节点不为null,定义一个cmp,这个变量用来进行二分查找时的比较;定义parent,是new Entry时必须
    * 要的参数
    /
    int cmp;
    Entry<K,V> parent;
    // cpr表示有无自己定义的排序规则,分两种情况遍历执行
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
    /
    *
    * 从root节点开始遍历,通过二分查找逐步向下找
    * 第一次循环:从根节点开始,这个时候parent就是根节点,然后通过自定义的排序算法
    * cpr.compare(key, t.key)比较传入的key和根节点的key值,如果传入的key<root.key,那么
    * 继续在root的左子树中找,从root的左孩子节点(root.left)开始:如果传入的key>root.key,
    * 那么继续在root的右子树中找,从root的右孩子节点(root.right)开始;如果恰好key==root.key,
    * 那么直接根据root节点的value值即可。
    * 后面的循环规则一样,当遍历到的当前节点作为起始节点,逐步往下找
    *
    * 需要注意的是:这里并没有对key是否为null进行判断,建议自己的实现Comparator时应该要考虑在内
    /
    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 {
    //从这里看出,当默认排序时,key值是不能为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,节点已经遍历到最后了,我们只需要new一个Entry放到
    * parent下面即可,但放到左子节点上还是右子节点上,就需要按照红黑树的规则来。
    /
    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方法源码中通过fixAfterInsertion(e)方法来进行自平衡处理,我们回顾一下插入时自平衡调整的逻辑

    在这里插入图片描述

    接下来我们看一看这个方法
    private void fixAfterInsertion(Entry<K,V> x) {
    //新插入的节点为红色节点
    x.color = RED;
    //我们知道父节点为黑色时,并不需要进行树结构调整,只有当父节点为红色时,才需要调整
    while (x != null && x != root && x.parent.color == RED) {
    //如果父节点是左节点,对应上表中情况1和情况2
    if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
    Entry<K,V> y = rightOf(parentOf(parentOf(x)));
    //如果叔父节点为红色,对应于“父节点和叔父节点都为红色”,此时通过变色即可实现平衡
    //此时父节点和叔父节点都设置为黑色,祖父节点设置为红色
    if (colorOf(y) == RED) {
    setColor(parentOf(x), BLACK);
    setColor(y, BLACK);
    setColor(parentOf(parentOf(x)), RED);
    x = parentOf(parentOf(x));
    } else {
    //如果插入节点是黑色,插入的是右子节点,通过【左右节点旋转】(这里先进行父节点左旋)
    if (x == rightOf(parentOf(x))) {
    x = parentOf(x);
    rotateLeft(x);
    }
    //设置父节点和祖父节点颜色
    setColor(parentOf(x), BLACK);
    setColor(parentOf(parentOf(x)), RED);
    //进行祖父节点右旋(这里【变色】和【旋转】并没有严格的先后顺序,达成目的就行)
    rotateRight(parentOf(parentOf(x)));
    }
    } else {
    //父节点是右节点的情况
    Entry<K,V> y = leftOf(parentOf(parentOf(x)));
    //对应于“父节点和叔父节点都为红色”,此时通过变色即可实现平衡
    if (colorOf(y) == RED) {
    setColor(parentOf(x), BLACK);
    setColor(y, BLACK);
    setColor(parentOf(parentOf(x)), RED);
    x = parentOf(parentOf(x));
    } else {
    //如果插入节点是黑色,插入的是左子节点,通过【右左节点旋转】(这里先进行父节点右旋)
    if (x == leftOf(parentOf(x))) {
    x = parentOf(x);
    rotateRight(x);
    }
    setColor(parentOf(x), BLACK);
    setColor(parentOf(parentOf(x)), RED);
    //进行祖父节点左旋(这里【变色】和【旋转】并没有严格的先后顺序,达成目的就行)
    rotateLeft(parentOf(parentOf(x)));
    }
    }
    }
    //根节点必须为黑色
    root.color = BLACK;
    }

    源码中通过 rotateLeft 进行【左旋】,通过 rotateRight 进行【右旋】。都非常类似,我们就看一下【左旋】的代码,【左旋】规则如下:“逆时针旋转两个节点,让一个节点被其右子节点取代,而该节点成为右子节点的左子节点”。
    private void rotateLeft(Entry<K,V> p) {
    if (p != null) {
    /**
    * 断开当前节点p与其右子节点的关联,重新将节点p的右子节点的地址指向节点p的右子节点的左子节点
    * 这个时候节点r没有父节点
    */
    Entry<K,V> r = p.right;
    p.right = r.left;
    //将节点p作为节点r的父节点
    if (r.left != null)
    r.left.parent = p;
    //将节点p的父节点和r的父节点指向同一处
    r.parent = p.parent;
    //p的父节点为null,则将节点r设置为root
    if (p.parent == null)
    root = r;
    //如果节点p是左子节点,则将该左子节点替换为节点r
    else if (p.parent.left == p)
    p.parent.left = r;
    //如果节点p为右子节点,则将该右子节点替换为节点r
    else
    p.parent.right = r;
    //重新建立p与r的关系
    r.left = p;
    p.parent = r;
    }
    }

    就算是看了上面的注释还是并不清晰,看下图你就懂了

    在这里插入图片描述

    五. get 方法

    get方法是通过二分查找的思想,我们看一下源码
    public V get(Object key) {
    Entry<K,V> p = getEntry(key);
    return (p==null ? null : p.value);
    }
    /**

    • 从root节点开始遍历,通过二分查找逐步向下找

    • 第一次循环:从根节点开始,这个时候parent就是根节点,然后通过k.compareTo(p.key)比较传入的key和

    • 根节点的key值;

    • 如果传入的key<root.key, 那么继续在root的左子树中找,从root的左孩子节点(root.left)开始;

    • 如果传入的key>root.key, 那么继续在root的右子树中找,从root的右孩子节点(root.right)开始;

    • 如果恰好key==root.key,那么直接根据root节点的value值即可。

    • 后面的循环规则一样,当遍历到的当前节点作为起始节点,逐步往下找
      */
      //默认排序情况下的查找
      final Entry<K,V> getEntry(Object key) {

      if (comparator != null)
      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;
      }
      /**

    • 从root节点开始遍历,通过二分查找逐步向下找

    • 第一次循环:从根节点开始,这个时候parent就是根节点,然后通过自定义的排序算法

    • cpr.compare(key, t.key)比较传入的key和根节点的key值,如果传入的key<root.key,那么

    • 继续在root的左子树中找,从root的左孩子节点(root.left)开始:如果传入的key>root.key,

    • 那么继续在root的右子树中找,从root的右孩子节点(root.right)开始;如果恰好key==root.key,

    • 那么直接根据root节点的value值即可。

    • 后面的循环规则一样,当遍历到的当前节点作为起始节点,逐步往下找
      */
      //自定义排序规则下的查找
      final Entry<K,V> getEntryUsingComparator(Object key) {
      @SuppressWarnings(“unchecked”)
      K k = (K) key;
      Comparator<? super K> cpr = comparator;
      if (cpr != null) {
      Entry<K,V> p = root;
      while (p != null) {
      int cmp = cpr.compare(k, p.key);
      if (cmp < 0)
      p = p.left;
      else if (cmp > 0)
      p = p.right;
      else
      return p;
      }
      }
      return null;
      }

    六. remove方法

    remove方法可以分为两个步骤,先是找到这个节点,直接调用了上面介绍的getEntry(Object key),这个步骤我们就不说了,直接说第二个步骤,找到后的删除操作。往期:一百期面试题汇总
    public V remove(Object key) {
    Entry<K,V> p = getEntry(key);
    if (p == null)
    return null;

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

    }

    通过deleteEntry§进行删除操作,删除操作的原理我们在前面已经讲过

    删除的是根节点,则直接将根节点置为null;

    待删除节点的左右子节点都为null,删除时将该节点置为null;

    待删除节点的左右子节点有一个有值,则用有值的节点替换该节点即可;

    待删除节点的左右子节点都不为null,则找前驱或者后继,将前驱或者后继的值复制到该节点中,然后删除前驱或者后继(前驱:左子树中值最大的节点,后继:右子树中值最小的节点);

    private void deleteEntry(Entry<K,V> p) {
    modCount++;
    size–;
    //当左右子节点都不为null时,通过successor§遍历红黑树找到前驱或者后继
    if (p.left != null && p.right != null) {
    Entry<K,V> s = successor§;
    //将前驱或者后继的key和value复制到当前节点p中,然后删除节点s(通过将节点p引用指向s)
    p.key = s.key;
    p.value = s.value;
    p = s;
    }
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);
    /**
    * 至少有一个子节点不为null,直接用这个有值的节点替换掉当前节点,给replacement的parent属性赋值,给
    * parent节点的left属性和right属性赋值,同时要记住叶子节点必须为null,然后用fixAfterDeletion方法
    * 进行自平衡处理
    /
    if (replacement != null) {
    //将待删除节点的子节点挂到待删除节点的父节点上。
    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;
    p.left = p.right = p.parent = null;
    /
    *
    * p如果是红色节点的话,那么其子节点replacement必然为红色的,并不影响红黑树的结构
    * 但如果p为黑色节点的话,那么其父节点以及子节点都可能是红色的,那么很明显可能会存在红色相连的情
    * 况,因此需要进行自平衡的调整
    /
    if (p.color == BLACK)
    fixAfterDeletion(replacement);
    } else if (p.parent == null) {//这种情况就不用多说了吧
    root = null;
    } else {
    /
    *
    * 如果p节点为黑色,那么p节点删除后,就可能违背每个节点到其叶子节点路径上黑色节点数量一致的规则,
    * 因此需要进行自平衡的调整
    */
    if (p.color == BLACK)
    fixAfterDeletion§;
    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;
    }
    }
    }

    操作的操作其实很简单,场景也不多,我们看一下删除后的自平衡操作方法fixAfterDeletion
    private void fixAfterDeletion(Entry<K,V> x) {
    /**
    * 当x不是root节点且颜色为黑色时
    /
    while (x != root && colorOf(x) == BLACK) {
    /
    *
    * 首先分为两种情况,当前节点x是左节点或者当前节点x是右节点,这两种情况下面都是四种场景,这里通过
    * 代码分析一下x为左节点的情况,右节点可参考左节点理解,因为它们非常类似
    */
    if (x == leftOf(parentOf(x))) {
    Entry<K,V> sib = rightOf(parentOf(x));

            /**
             * 场景1:当x是左黑色节点,兄弟节点sib是红色节点
             * 兄弟节点由红转黑,父节点由黑转红,按父节点左旋,
             * 左旋后树的结构变化了,这时重新赋值sib,这个时候sib指向了x的兄弟节点
             */
            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateLeft(parentOf(x));
                sib = rightOf(parentOf(x));
            }
    
            /**
             * 场景2:节点x、x的兄弟节点sib、sib的左子节点和右子节点都为黑色时,需要将该节点sib由黑变
             * 红,同时将x指向当前x的父节点
             */
            if (colorOf(leftOf(sib))  == BLACK &&
                colorOf(rightOf(sib)) == BLACK) {
                setColor(sib, RED);
                x = parentOf(x);
            } else {
                /**
                 * 场景3:节点x、x的兄弟节点sib、sib的右子节点都为黑色,sib的左子节点为红色时,
                 * 需要将sib左子节点设置为黑色,sib节点设置为红色,同时按sib右旋,再将sib指向x的
                 * 兄弟节点
                 */
                if (colorOf(rightOf(sib)) == BLACK) {
                    setColor(leftOf(sib), BLACK);
                    setColor(sib, RED);
                    rotateRight(sib);
                    sib = rightOf(parentOf(x));
                }
                /**
                 * 场景4:节点x、x的兄弟节点sib都为黑色,而sib的左右子节点都为红色或者右子节点为红色、
                 * 左子节点为黑色,此时需要将sib节点的颜色设置成和x的父节点p相同的颜色,
                 * 设置x的父节点为黑色,设置sib右子节点为黑色,左旋x的父节点p,然后将x赋值为root
                 */
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(rightOf(sib), BLACK);
                rotateLeft(parentOf(x));
                x = root;
            }
        } else {//x是右节点的情况
            Entry<K,V> sib = leftOf(parentOf(x));
    
            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateRight(parentOf(x));
                sib = leftOf(parentOf(x));
            }
    
            if (colorOf(rightOf(sib)) == BLACK &&
                colorOf(leftOf(sib)) == BLACK) {
                setColor(sib, RED);
                x = parentOf(x);
            } else {
                if (colorOf(leftOf(sib)) == BLACK) {
                    setColor(rightOf(sib), BLACK);
                    setColor(sib, RED);
                    rotateLeft(sib);
                    sib = leftOf(parentOf(x));
                }
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(leftOf(sib), BLACK);
                rotateRight(parentOf(x));
                x = root;
            }
        }
    }
    
    setColor(x, BLACK);
    

    }

    当待操作节点为左节点时,上面描述了四种场景,而且场景之间可以相互转换,如deleteEntry后进入了场景1,经过场景1的一些列操作后,红黑树的结构并没有调整完成,而是进入了场景2,场景2执行完成后跳出循环,将待操作节点设置为黑色,完成。

    我们下面用图来说明一下四种场景帮助理解,当然大家最好自己手动画一下。往期:一百期面试题汇总

    场景1:

    当x是左黑色节点,兄弟节点sib是红色节点,需要兄弟节点由红转黑,父节点由黑转红,按父节点左旋,左旋后树的结构变化了,这时重新赋值sib,这个时候sib指向了x的兄弟节点。

    在这里插入图片描述

    但经过这一系列操作后,并没有结束,而是可能到了场景2,或者场景3和4

    场景2:

    节点x、x的兄弟节点sib、sib的左子节点和右子节点都为黑色时,需要将该节点sib由黑变红,同时将x指向当前x的父节点

    在这里插入图片描述

    经过场景2的一系列操作后,循环就结束了,我们跳出循环,将节点x设置为黑色,自平衡调整完成。

    场景3:

    节点x、x的兄弟节点sib、sib的右子节点都为黑色,sib的左子节点为红色时,需要将sib左子节点设置为黑色,sib节点设置为红色,同时按sib右旋,再将sib指向x的兄弟节点

    在这里插入图片描述

    并没有完,场景3的一系列操作后,会进入到场景4

    场景4:

    节点x、x的兄弟节点sib都为黑色,而sib的左右子节点都为红色或者右子节点为红色、左子节点为黑色,此时需要将sib节点的颜色设置成和x的父节点p相同的颜色,设置x的父节点颜色为黑色,设置sib右孩子的颜色为黑色,左旋x的父节点p,然后将x赋值为root

    在这里插入图片描述

    四种场景讲完了,删除后的自平衡操作不太好理解,代码层面的已经弄明白了,但如果让我自己去实现的话,还是差了一些,还需要再研究。

    七. 遍历

    遍历比较简单,TreeMap的遍历可以使用map.values(), map.keySet(),map.entrySet(),map.forEach(),这里不再多说。

    八. 总结

    本文详细介绍了TreeMap的基本特点,并对其底层数据结构红黑树进行了回顾,同时讲述了其自动排序的原理,并从源码的角度结合红黑树图形对put方法、get方法、remove方法进行了讲解,最后简单提了一下遍历操作,若有不对之处,请批评指正,望共同进步,谢谢!

    展开全文
  • Map集合的五种遍历方式及Treemap方法 废话不多说,直接上代码 //循环遍历map的方法 public class MapF { public static void main(String[] args) { Map<String, Integer> tempMap = new HashMap<String,...

    哈喽,欢迎来到小朱课堂,下面开始你的学习吧!

    Map集合的五种遍历方式及Treemap方法
    废话不多说,直接上代码

    //循环遍历map的方法
    public class MapF {
     public static void main(String[] args) {
      Map<String, Integer> tempMap = new HashMap<String, Integer>();
      tempMap.put("a","12");
      tempMap.put("b","34");
      tempMap.put("c","56");
      // JDK1.4中
      // 遍历方法一 hashmap entrySet() 遍历
      Iterator it = tempMap.entrySet().iterator();
      while (it.hasNext()) {
       Map.Entry entry = (Map.Entry) it.next();
       Object key = entry.getKey();
       Object value = entry.getValue();
       System.out.println("key=" + key + " value=" + value);
      }
      System.out.println("");
      // JDK1.5中,应用新特性For-Each循环
      // 遍历方法二
      for (Map.Entry<String, Integer> entry : tempMap.entrySet()) {
       String key = entry.getKey().toString();
       String value = entry.getValue().toString();
       System.out.println("key=" + key + " value=" + value);
      }
      System.out.println("");
      // 遍历方法三 hashmap keySet() 遍历
      for (Iterator i = tempMap.keySet().iterator(); i.hasNext();) {
       Object obj = i.next();
       System.out.println(obj);// 循环输出key
       System.out.println("key=" + obj + " value=" + tempMap.get(obj));
      }
      for (Iterator i = tempMap.values().iterator(); i.hasNext();) {
       Object obj = i.next();
       System.out.println(obj);// 循环输出value
      }
      // 遍历方法四 treemap keySet()遍历
      for (Object o : tempMap.keySet()) {
       System.out.println("key=" + o + " value=" + tempMap.get(o));
      }
      System.out.println("11111");
      // java如何遍历Map <String, ArrayList> map = new HashMap <String,
      // ArrayList>();
      System.out.println("java  遍历Map <String, ArrayList> map = new HashMap<String, ArrayList>();");
      Map<String, ArrayList> map = new HashMap<String, ArrayList>();
      Set<String> keys = map.keySet();
      Iterator<String> iterator = keys.iterator();
      while (iterator.hasNext()) {
       String key = iterator.next();
       ArrayList arrayList = map.get(key);
       for (Object o : arrayList) {
        System.out.println(o);
       }
      }
      Map<String, List> map = new HashMap<String, List>();
      for (Map.Entry entry : map.entrySet()) {
       String key = entry.getKey().toString();
       List<String> list= (List) entry.getValue();
       for (String value : list) {
        System.out.println(key + "====" + value);
       }
      }
     }
    }
    

    Map接口简介
    今天来看一看map集合,map映射接口,用于存放键值对,<key,value>,通过key来查找value,顾名思义key不能为空,唯一且不重复,不然底层怎么查呢!

    可以从图中看出Map为单独的接口,他和Collection有什么区别呢?

    Map和Collection在集合中并列存在。 
    Map集合是双列的,键值对,而Collection是单列集合
    Map存储元素使用put方法,Collection使用Put方法。
    Map遍历没有直接取出元素的方法,而是先转成Set集合,再通过迭代获取元素。
     --Map常用方法
      
    –Map应用
    添加:使用HashMap。立了学生姓名和年龄之间的映射关系。并试图添加重复的键

       public static void main(String[] args) {
            // 定义一个Map的容器对象  
            Map<String, Integer > map1 = new HashMap<String, Integer >();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);
          // map1.put("jack", 30); 在没有hashCode和equals方式   添加重复的键值(值不同),会覆盖掉前面key值相同的值
            System.out.println(map1);  
          
            Map<String, Integer> map2 = new HashMap<String, Integer>();  
            map2.put("张三丰", 100);  
            map2.put("虚竹", 20);  
            System.out.println("map2:" + map2);  
            // 从指定映射中将所有映射关系复制到此映射中。  
            map1.putAll(map2);  
            System.out.println("map1:" + map1);  
        }  
    

    删除:

       public static void main(String[] args) {   
       // 删除:  
            // remove() 删除关联对象,指定key对象  
            // clear() 清空集合对象  
      
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            System.out.println(map1);                 
            // 指定key,返回删除的键值对映射的值。  
            map1.remove("java");
            System.out.println(map1);  
            map1.clear();  
            System.out.println("map1:" + map1);  
        }  
    

    获取:

    public static void main(String[] args) {
         // 获取:  
            // V get(Object key) 通过指定的key对象获取value对象  
            // int size() 获取容器的大小  
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            System.out.println(map1);  
            // V get(Object key) 通过指定的key对象获取value对象  
            System.out.println("value:" + map1.get("jack"));  
            // int size() 获取容器的大小
            System.out.println("map.size:" + map1.size()); 
        } 
    

    判断:

    public static void main(String[] args) {
            // 判断:  
            // boolean isEmpty() 判断集合是否为空   长度为0返回true否则false  
            // boolean containsKey(Object key) 判断集合中是否包含指定的key  
            // boolean containsValue(Object value)  
      
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            System.out.println(map1);  
            System.out.println("isEmpty:" + map1.isEmpty());  
            System.out.println("containskey:" + map1.containsKey("jack"));  
            System.out.println("containsvalues:" + map1.containsValue(100));  
        }  
    

    遍历Map的4种方式:
    第一种:

    public static void main(String[] args) {
            //遍历Map 第一种方式
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            
            //通过 map1.keySet() 获取key  通过key 找到value
            for (String key : map1.keySet()) {
                Integer value = map1.get(key);
                System.out.println("key : "+key+" value : "+value);
            }
        }
    

    第二种:

    public static void main(String[] args) {
            //遍历Map 第二种方式
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            
           //通过Map.Entry(String,Integer) 获取,然后使用entry.getKey()获取到键,通过entry.getValue()获取到值
           for(Map.Entry<String, Integer> entry : map1.entrySet()){
               System.out.println("键 key :"+entry.getKey()+" 值value :"+entry.getValue());
           }
        }
    

    第三种:

    //遍历Map 第三种方式
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            //第三种只遍历键或者值,通过加强for循环
            for(String s1:map1.keySet()){//遍历map的键
               System.out.println("键key :"+s1);
            }
            for(Integer s2:map1.values()){//遍历map的值
                System.out.println("值value :"+s2);
            }
               System.out.println("====================================");    
        }
    

    第四种:

    public static void main(String[] args) {
            //遍历Map 第一种方式
            Map<String, Integer> map1 = new HashMap<String, Integer>();  
            map1.put("jack", 20);  
            map1.put("rose", 18);  
            map1.put("lucy", 17);  
            map1.put("java", 25);  
            
            //第四种Iterator遍历获取,然后获取到Map.Entry<String, String>,再得到getKey()和getValue()
            Iterator<Map.Entry<String, Integer>> it=map1.entrySet().iterator();
            while(it.hasNext()){
                Map.Entry<String, Integer> entry=it.next(); 
                System.out.println("键key :"+entry.getKey()+" value :"+entry.getValue());
            }
            
        }
    

    HashMap

    底层是哈希表数据结构,线程是不同步的,可以存入null键,null值。要保证键的唯一性,需要覆盖hashCode方法,和equals方法。
       案例:自定义对象作为Map的键。

        public class Demo3 {  
            public static void main(String[] args) {  
                HashMap<Person, String> hm = new HashMap<Person, String>();  
                hm.put(new Person("jack", 20), "1001");  
                hm.put(new Person("rose", 18), "1002");  
                hm.put(new Person("lucy", 19), "1003");  
                hm.put(new Person("hmm", 17), "1004");  
                hm.put(new Person("ll", 25), "1005");  
                System.out.println(hm);  
                System.out.println(hm.put(new Person("rose", 18), "1006"));  //重写hashCode和equalse后key相同不会覆盖
          
                Set<Entry<Person, String>> entrySet = hm.entrySet();  
                Iterator<Entry<Person, String>> it = entrySet.iterator();  
                while (it.hasNext()) {  
                    Entry<Person, String> next = it.next();  
                    Person key = next.getKey();  
                    String value = next.getValue();  
                    System.out.println(key + " = " + value);  
                }  
            }  
        }  
          
        class Person {  
            private String name;  
            private int age;  
          
            Person() {  
          
            }  
          
            public Person(String name, int age) {  
                this.name = name;  
                this.age = age;  
            }  
          
            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;  
            }  
          
            @Override  
            public int hashCode() {  
          
                return this.name.hashCode() + age * 37;  
            }  
          
            @Override  
            public boolean equals(Object obj) {  
                if (obj instanceof Person) {  
                    Person p = (Person) obj;  
                    return this.name.equals(p.name) && this.age == p.age;  
                } else {  
                    return false;  
                }  
            }  
          
            @Override  
            public String toString() {  
          
                return "Person@name:" + this.name + " age:" + this.age;  
            }  
          
        }  
        }  
    

    TreeMap
    TreeMap的排序,TreeMap可以对集合中的键进行排序。如何实现键的排序?

    方式一:元素自身具备比较性
    和TreeSet一样原理,需要让存储在键位置的对象实现Comparable接口,重写compareTo方法,也就是让元素自身具备比较性,这种方式叫做  元素的自然排序也叫做默认排序。

    方式二:容器具备比较性
    当元素自身不具备比较性,或者自身具备的比较性不是所需要的。那么此时可以让容器自身具备。需要定义一个类实现接口Comparator,重  写compare方法,并将该接口的子类实例对象作为参数传递给TreeMap集合的构造方法。

    注意:当Comparable比较方式和Comparator比较方式同时存在时,以Comparator的比较方式为主;

    注意:在重写compareTo或者compare方法时,必须要明确比较的主要条件相等时要比较次要条件。(假设姓名和年龄一致的人为相同的人,  如果想要对人按照年龄的大小来排序,如果年龄相同的人,需要如何处理?不能直接return 0,以为可能姓名不同(年龄相同姓名不同的人  是不同的人)。此时就需要进行次要条件判断(需要判断姓名),只有姓名和年龄同时相等的才可以返回0.)

    通过return 0来判断唯一性。

    public class Demo4 { 
        public static void main(String[] args) { 
            TreeMap<String, Integer> tree = new TreeMap<String, Integer>(); 
            tree.put("张三", 19); 
            tree.put("李四", 20); 
            tree.put("王五", 21); 
            tree.put("赵六", 22); 
            tree.put("周七", 23); 
            tree.put("张三", 24); 
            System.out.println(tree); 
            System.out.println("张三".compareTo("李四"));//-2094 
        } 
    } 
    

    自定义元素排序

        public class Demo3 {  
            public static void main(String[] args) {  
                TreeMap<Person, String> hm = new TreeMap<Person, String>(  
                        new MyComparator());  
                hm.put(new Person("jack", 20), "1001");  
                hm.put(new Person("rose", 18), "1002");  
                hm.put(new Person("lucy", 19), "1003");  
                hm.put(new Person("hmm", 17), "1004");  
                hm.put(new Person("ll", 25), "1005");  
                System.out.println(hm);  
                System.out.println(hm.put(new Person("rose", 18), "1006"));  
          
                Set<Entry<Person, String>> entrySet = hm.entrySet();  
                Iterator<Entry<Person, String>> it = entrySet.iterator();  
                while (it.hasNext()) {  
                    Entry<Person, String> next = it.next();  
                    Person key = next.getKey();  
                    String value = next.getValue();  
                    System.out.println(key + " = " + value);  
                }  
            }  
        }  
          
        class MyComparator implements Comparator<Person> {  
          
            @Override  
            public int compare(Person p1, Person p2) {  
                if (p1.getAge() > p2.getAge()) {  
                    return -1;  
                } else if (p1.getAge() < p2.getAge()) {  
                    return 1;  
                }  
                return p1.getName().compareTo(p2.getName());  
            }  
          
        }  
          
        class Person implements Comparable<Person> {  
            private String name;  
            private int age;  
          
            Person() {  
          
            }  
          
            public Person(String name, int age) {  
          
                this.name = name;  
                this.age = age;  
            }  
          
            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;  
            }  
          
            @Override  
            public int hashCode() {  
          
                return this.name.hashCode() + age * 37;  
            }  
          
            @Override  
            public boolean equals(Object obj) {  
                if (obj instanceof Person) {  
                    Person p = (Person) obj;  
                    return this.name.equals(p.name) && this.age == p.age;  
                } else {  
                    return false;  
                }  
            }  
          
            @Override  
            public String toString() {  
          
                return "Person@name:" + this.name + " age:" + this.age;  
            }  
          
            @Override  
            public int compareTo(Person p) {  
          
                if (this.age > p.age) {  
                    return 1;  
                } else if (this.age < p.age) {  
                    return -1;  
                }  
                return this.name.compareTo(p.name);  
            }  
          
        }  
    

    注意:Set的元素不可重复,Map的键不可重复,如果存入重复元素如何处理

    Set元素重复元素不能存入add方法返回false

    Map的重复健将覆盖旧键,将旧值返回。
    原文链接:https://blog.csdn.net/qq_30225725/article/details/88019622
    搬砖路上,希望对你有帮助!可以关注一下哟,持续更新哟! 有问题可以私聊博主,快发表一下你的看法吧!

    展开全文
  • } } 二、TreeMap实现原理 1、TreeMap的基本概念: TreeMap集合是基于红黑树(Red-Black tree)的 NavigableMap实现。TreeMap继承AbstractMap,实现NavigableMap、Cloneable、Serializable三个接口。其中AbstractMap...
  • TreeMap工作原理

    千次阅读 2018-06-12 09:43:45
    一、红黑树简介TreeMap是通过红黑树实现的,增删改查的操作底层都是对红黑树的相关操作,因此先介绍红黑树的相关性质。红黑树顾名思义就是节点是红色或者黑色的平衡二叉树,它通过颜色的约束来维持着二叉树的平衡。...
  • 其实从宏观上来讲,就相当于树的中序遍历(LDR)。我们先看一下迭代输出的步骤 1 2 3 for (Entry, String> entry : tmap.entrySet()) { ...
  • treeMap原理及其实现

    千次阅读 2017-11-07 14:08:48
    TreeMap是一个有序的key-value集合,他是通过红黑树实现的。 TreeMap继承于AbstractMap,所以它是一个Map,即一个key-value集合。 TreeMap实现了navigableMap接口,意味着它支持一系列的导航方法,返回有序的key...
  • 前言 今天我们要来啃Map体系中的硬骨头了TreeMap。...在我们介绍TreeMap之前,我认为我们有必要彻底的了解一下什么是红黑树:其实它是一种特殊的二叉树,能够提供最坏情况下遍历树的时间——O(logn),而链...
  • TreeMap实现有序的原理

    万次阅读 2019-02-13 14:07:52
    上一篇讲了LinkedHashMap实现有序的原理,这票介绍一个另一种有序的Map,TreeMap。 同样是有序,两者大不一样,LinkedHashMap是按照插入顺序排序,而TreeMap是按照Key的自然顺序或者Comprator的顺序进行排序。在...
  • 在介绍TreeMap之前,我们来了解一种数据结构:二叉树。相信学过数据结构的同学知道,这种结构的数据存储形式在查找的时候效率非常高。 二叉树结构又可再细分为二叉查找树和二叉平衡树 二叉查找树是一种有序...
  • HashMap遍历方法和实现原理分析

    千次阅读 2015-11-15 17:10:50
    HashMap遍历方法;HashMap实现原理分析
  • Map集合的实现类—TreeMap集合 TreeMap 集合用来存储简直映射关系的,不允许出现重复的键,内部通过二叉树的原理保证键的唯一。因为 TreeMap 内部实现了 SortedMap 接口,会默认按照自然顺序对 Key 进行排序。 ...
  • TreeMap 底层原理

    2019-06-04 19:57:39
    一:TreeMap的基本概念: 二:源码解析 一:TreeMap的基本概念: TreeMap集合是基于红黑树(Red-Black tree)的 NavigableMap实现。该集合最重要的特点就是可排序,该映射根据其键的自然顺序进行排序,或者根据...
  • TreeMap实现原理

    2016-08-24 16:59:00
    为了更了解TreeMap原理,下面我们将根据TreeMap中实现的方法来对源码做一个详细的说明。 2.1、TreeMap数据结构  TreeMap的定义如下: public class TreeMap,V> extends AbstractMap,V> implements ...
  • TreepMap底层原理解析 一.TreeMap概述 TreeMap存储K-V键值对,通过红黑树(R-B tree)实现; TreeMap继承了NavigableMap接口,NavigableMap接口继承了SortedMap接口,可支持一系列的导航定位以及导航操作的方法...
  • Java集合,TreeMap底层实现和原理

    千次阅读 2018-02-28 12:51:00
    定制排序:创建TreeMap对象传入了一个Comparator对象,该对象负责对TreeMap中所有的key进行排序,采用定制排序不要求Map的key实现Comparable接口。等下面分析到比较方法的时候在分析这两种比较有何不同。 对于Map...
  • 1 前言 本人使用的是jdk1.8版本。...学过数据结构的都知道二叉查找树是一种有序树,即进行中序遍历可以得到一组有序序列,所以TreeMap也是有序的Map集合。 在红黑树的加持下,TreeMap的众多方...
  • 目录前言原结构TreeMap+Lambda表达式TreeMap+方法引用结语 前言 今天同事提了一句到list转map排序有什么技巧性的方法,他用了LinkedHashMap,而且用惯stream了,就想根据链表特性和stream的方法,先整理再顺序塞值,...
  • 转载Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例 一、TreeMap 简单介绍 什么是Map?  在数组中我们通过数组下标来对数组内容进行索引的,而在Map中我们通过对象来对 对象进行索引,用来索引的对象...
  • Java TreeMap工作原理及实现(一)

    千次阅读 2016-05-16 13:16:00
    TreeMap定义TreeMap特点HashMap与TreeMap的区别 TreeMap定义 package java.util; public class TreeMap extends AbstractMap implements NavigableMap, Cloneable, java.io.Serializable{ } pub
  • 1、TreeMap概述:原文链接 TreeMap存储K-V键值对,通过红黑树(R-B tree)实现; TreeMap继承了NavigableMap接口,NavigableMap接口继承了SortedMap接口,可支持一系列的导航定位以及导航操作的方法,当然只是提供...
  • 一、TreeMap 1.1 数据结构 源码定义如下 public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable TreeMap继承AbstractMap...
  • TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,通过这篇博文你可以获得如下知识点: 1. 红黑树的基本概念。 2. 红黑树增加节点、删除节点的实现过程。 3. 红黑树左旋转、右...
  • TreeMap运用与原理

    2019-02-20 02:37:54
    我们提到,HashMap有一个重要局限,键值对之间没有特定的顺序,我们还提到,Map接口有另一个重要的实现类TreeMap,在TreeMap中,键值对之间按键有序,TreeMap的实现基础是排序二叉树,上节我们介绍了排序二叉树的...
  • Java面试题大全(2020版)

    万次阅读 多人点赞 2019-11-26 11:59:06
    基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。 23. 说一下 HashMap 的实现原理? HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 18,572
精华内容 7,428
关键字:

treemap遍历原理