TreeMap 实现原理

juejin.cn · · 79 次点击 · · 开始浏览    

在用TreeMap之前我们要对TreeMap有个整体的认识。

1、TreeMap介绍

TreeMap是一个通过红黑树实现有序的key-value集合。

TreeMap继承AbstractMap,也即实现了Map,它是一个Map集合

TreeMap实现了NavigableMap接口,它支持一系列的导航方法,

TreeMap实现了Cloneable接口,它可以被克隆

TreeMap introduction:A Red-Black tree based NavigableMap implementation. The map is sorted according to the natural ordering of its keys, or by a Comparator provided at map creation time, depending on which constructor is used.
This implementation provides guaranteed log(n) time cost for the containsKey, get, put and remove operations. Algorithms are adaptations of those in Cormen, Leiserson, and Rivest’s Introduction to Algorithms.复制代码

TreeMap基于红黑树(Red-Black tree)实现。映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。TreeMap的基本操作containsKey、get、put、remove方法,它的时间复杂度是log(n)。--(翻译JDK中关于TeeMap简介)

TreeMap是非同步的。

TreeMap与Map的关系图如下:

TreeMap本质是Red-Black Tree,它包含几个重要的成员变量:root、size、comparator。其中root是红黑树的根节点。它是Entry类型,Entry是红黑树的节点,它包含了红黑树的6个基本组成:key、value、left、right、parent和color。Entry节点根据根据Key排序,包含的内容是value。Entry中key比较大小是根据比较器comparator来进行判断的。size是红黑树的节点个数。具体的红黑树算法,请自行百度。

2、TreeMap源码解析(JDK1.6版本)

为了更了解TreeMap的原理,下面我们将根据TreeMap中实现的方法来对源码做一个详细的说明。

2.1、TreeMap数据结构

    TreeMap的定义如下:

public class TreeMap
    extends AbstractMap
    implements NavigableMap, Cloneable, java.io.Serializable复制代码

 TreeMap继承AbstractMap,实现NavigableMap、Cloneable、Serializable三个接口。其中AbstractMap表明TreeMap为一个Map即支持key-value的集合, NavigableMap则意味着它支持一系列的导航方法,具备针对给定搜索目标返回最接近匹配项的导航方法 。

    TreeMap中同时包含如下几个重要的属性:

/**
     * The comparator used to maintain order in this tree map, or
     * null if it uses the natural ordering of its keys.
     * 比较器,用来给TreeMap排序
     * @serial
     */
    private final Comparator comparator;
	/**
	 * 红黑树的根节点
	 */
    private transient Entry root = null;

    /**
     * The number of entries in the tree
	 * 红黑树的节点总数
     */
    private transient int size = 0;

    /**
     * The number of structural modifications to the tree.
	 * 红黑树的修改次数
     */
    private transient int modCount = 0;

    // Red-black mechanics
    /**
     * 红黑树的颜色--红色
     */
    private static final boolean RED   = false;

    /**
     * 红黑树的颜色--黑色
     */
    private static final boolean BLACK = true;复制代码

    对于叶子节点Entry来说,Entry是TreeMap的内部类,它有几个重要属性:

static final class Entry implements Map.Entry {
	//键
	K key;
	//值
        V value;
	//左孩子
        Entry left = null;
	//右孩子
        Entry right = null;
	//父亲
        Entry parent;
	//颜色
        boolean color = BLACK;

        /**
         * Make a new cell with given key, value, and parent, and with
         * null child links, and BLACK color.
         */
        Entry(K key, V value, Entry 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;
        }
    }复制代码

2.2、TreeMap的构造器

    2.2.1、默认构造器

        使用默认构造器构造TreeMap时,使用java的默认的比较器比较Key的大小,从而对TreeMap进行排序

/**
     * Constructs a new, empty tree map, using the natural ordering of its keys. 
     * 默认构造器
     */
    public TreeMap() {
        comparator = null;
    }复制代码

    2.2.2、带比较器的构造函数

/**
     * Constructs a new, empty tree map, ordered according to the given comparator. 
     * 给定比较器的构造函数
     */
    public TreeMap(Comparator comparator) {
        this.comparator = comparator;
    }复制代码

    2.2.3、带Map的构造函数,Map会成为TreeMap的子集

/**
     * Constructs a new tree map containing the same mappings as the given
     * map
     */
    public TreeMap(Map m) {
        comparator = null;
        putAll(m);
    }复制代码

    该构造函数会调用putAll()将m中的所有元素添加到TreeMap中。putAll()源码如下 :

/**
     * Copies all of the mappings from the specified map to this map.
     * These mappings replace any mappings that this map had for any
     * of the keys currently in the specified map.
	 * 将Map中的节点全部添加到TreeMap中
     */
    public void putAll(Map 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);
    }复制代码

    2.2.4、带SortedMap的构造函数,SortedMap会成为TreeMap的子集

/**
     * Constructs a new tree map containing the same mappings and
     * using the same ordering as the specified sorted map.  This
     * method runs in linear time.
     */
    public TreeMap(SortedMap m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }复制代码

带map参数的构造器都调用了buildFromSorted()。buildFromSorted()设计代码如下:

// 根据已经一个排好序的map创建一个TreeMap
    // 将map中的元素逐个添加到TreeMap中,并返回map的中间元素作为根节点。
    private final Entry buildFromSorted(int level, int lo, int hi,
					     int redLevel,
					     Iterator it,
					     java.io.ObjectInputStream str,
					     V defaultVal)
        throws  java.io.IOException, ClassNotFoundException {
        /*
         * Strategy: The root is the middlemost element. To get to it, we
         * have to first recursively construct the entire left subtree,
         * so as to grab all of its elements. We can then proceed with right
         * subtree.
         *
         * The lo and hi arguments are the minimum and maximum
         * indices to pull out of the iterator or stream for current subtree.
         * They are not actually indexed, we just proceed sequentially,
         * ensuring that items are extracted in corresponding order.
         */

        if (hi < lo) return null;

        int mid = (lo + hi) / 2;

        Entry left  = null;
        if (lo < mid)
            left = buildFromSorted(level+1, lo, mid - 1, redLevel,
				   it, str, defaultVal);

        // extract key and/or value from iterator or stream
        K key;
        V value;
        if (it != null) {
            if (defaultVal==null) {
                Map.Entry entry = (Map.Entry)it.next();
                key = entry.getKey();
                value = entry.getValue();
            } else {
                key = (K)it.next();
                value = defaultVal;
            }
        } else { // use stream
            key = (K) str.readObject();
            value = (defaultVal != null ? defaultVal : (V) str.readObject());
        }

        Entry middle =  new Entry(key, value, null);

        // color nodes in non-full bottommost level red
        if (level == redLevel)
            middle.color = RED;

        if (left != null) {
            middle.left = left;
            left.parent = middle;
        }

        if (mid < hi) {
            Entry right = buildFromSorted(level+1, mid+1, hi, redLevel,
					       it, str, defaultVal);
            middle.right = right;
            right.parent = middle;
        }

        return middle;
    }复制代码

对buildFromSorted方法解读   

  • buildFromSorted是通过递归将SortedMap中的元素逐个关联
  • buildFromSorted返回middle节点作为root
  • buildFromSorted添加到红黑树中时,只将level == redLevel的节点设为红色。

2.3、TreeMap实现的Serializable接口

    TreeMap实现了java.io.Serializable,分别实现了串行读取、写入功能。串行写入函数是writeObject(),它的作用是将TreeMap的容量,所有的Entry都写入到输出流。而串行读取函数是readObject(),它的作用是将TreeMap的容量,所有的Entry依次读出。通过readObject和writeObject能够帮助我们实现TreeMap的串行传输。

private static final long serialVersionUID = 919286545866124006L;

    /**
     * Save the state of the TreeMap instance to a stream (i.e.,
     * serialize it).
     *
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out the Comparator and any hidden stuff
        s.defaultWriteObject();

        // Write out size (number of Mappings)
        s.writeInt(size);

        // Write out keys and values (alternating)
        for (Iterator> i = entrySet().iterator(); i.hasNext(); ) {
            Map.Entry e = i.next();
            s.writeObject(e.getKey());
            s.writeObject(e.getValue());
        }
    }

    /**
     * Reconstitute the TreeMap instance from a stream (i.e.,
     * deserialize it).
     */
    private void readObject(final java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in the Comparator and any hidden stuff
        s.defaultReadObject();

        // Read in size
        int size = s.readInt();

        buildFromSorted(size, null, s, null);
    }复制代码

2.4、TreeMap实现了Cloneable接口

    TreeMap实现了Cloneable接口,即实现了clone()方法。clone()方法的作用很简单,就是克隆一个TreeMap对象并返回。

/**
     * Returns a shallow copy of this TreeMap instance. (The keys and
     * values themselves are not cloned.)
     *
     * @return a shallow copy of this map
     */
    public Object clone() {
        TreeMap clone = null;
        try {
            clone = (TreeMap) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError();
        }

        // Put clone into "virgin" state (except for comparator)
        clone.root = null;
        clone.size = 0;
        clone.modCount = 0;
        clone.entrySet = null;
        clone.navigableKeySet = null;
        clone.descendingMap = null;

        // Initialize clone with our mappings
        try {
            clone.buildFromSorted(size, entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }

        return clone;
    }复制代码

2.5、TreeMap重要方法的实现原理

    TreeMap继承AbstractMap,也即是一个Map。这里对map的主要方法做一个解析

    2.5.1、TreeMap的put()方法实现原理

/**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     */
    public V put(K key, V value) {
	//用t表示二叉树的当前节点
        Entry t = root;
		//t为null表示一个空树,即TreeMap中没有任何元素,直接插入
        if (t == null) {
		//将新的key-value键值对创建为一个Entry节点,并将该节点赋予给root
            root = new Entry(key, value, null);
            size = 1;
			//修改次数加1
            modCount++;
            return null;
        }
		//cmp表示key排序的返回结果
        int cmp;
        Entry parent;
        // 指定的排序算法
        Comparator cpr = comparator;
		//如果cpr不为空,则采用给定的排序算法创建TreeMap集合
        if (cpr != null) {
            do {
                parent = t;//parent指向上次循环后的t
                cmp = cpr.compare(key, t.key);
				//cmp返回值小于0,表示新增节点的key小于当前key的值,则以当前节点的左孩子作为新的当前节点
				//否则,以当前节点的右孩子作为新的当前节点
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
		//如果cpr为空,则采用默认的排序算法进行创建TreeMap集合
        else {
            if (key == null)
                throw new NullPointerException();
            Comparable k = (Comparable) 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 e = new Entry(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
		//上面已经完成了排序二叉树的构建,将新增节点插入该树中的合适位置,下面fixAfterInsertion方法就是对这棵树进行调整、平衡
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }复制代码

    上述代码中do代码块实现排序二叉树的核心算法,通过该算法我们可以确认新增节点在该树的正确位置。找到该位置后将插入即可,这样做其实还没有完成,因为我们知道TreeMap的底层实现是红黑树,红黑树是一个平衡排序二叉树,普通的排序二叉树可能会出现失衡的情况,所以下一步就是要进行调整。fixAfterInsertion(e); 调整的过程务必会涉及到红黑树的左旋、右旋、着色三个基本操作。代码如下:

/** From CLR */
    private void fixAfterInsertion(Entry x) {
        x.color = RED;

        while (x != null && x != root && x.parent.color == RED) {
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry 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 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())和着色(setColor)等符合红黑树新增节点的处理过程。

    2.5.2、TreeMap的remove()方法实现原理

/**
     * Removes the mapping for this key from this TreeMap if present.
     *
     */
    public V remove(Object key) {
        Entry p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        deleteEntry(p);
        return oldValue;
    }复制代码

    通过这段代码可以看出,TreeMap的remove()方法中执行删除的真正方式是deleteEntry()方法。deleteEntry()代码如下:

    /**
     * Delete node p, and then rebalance the tree.
     */
    private void deleteEntry(Entry p) {
        modCount++;//修改次数 +1;
        size--;//元素个数 -1

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
		/*
         * 被删除节点的左子树和右子树都不为空,那么就用 p节点的中序后继节点代替 p 节点
         * successor(P)方法为寻找P的替代节点。规则是右分支最左边,或者 左分支最右边的节点
         * ---------------------(1)
         */
        if (p.left != null && p.right != null) {
            Entry 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.
		//replacement为替代节点,如果P的左子树存在那么就用左子树替代,否则用右子树替代
        Entry replacement = (p.left != null ? p.left : p.right);
		/*
         * 删除节点,分为上面提到的三种情况
         * -----------------------(2)
         */
        //如果替代节点不为空
        if (replacement != null) {
            // Link replacement to parent
			//replacement来替代P节点
            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;
            }
        }
    }复制代码

 3、TreeMap的使用场景

    我们了解了什么是TreeMap以及它的部分实现原理。那我们该如何运用treeMap呢?TreeMap是Map接口的具体实现,它的应用场景与Map应用场景大体相同。Map用于保存具有"映射关系"的数据,因此Map集合里保存着两组值,一组值用于保存Map里的key,另外一组值用于保存Map里的value。key和value都可以是任何引用类型的数据。Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较结果总是返回false。 关于Map,我们要从代码复用的角度去理解,java是先实现了Map,然后通过包装了一个所有value都为null的Map就实现了Set集合 Map的这些实现类和子接口中key集的存储形式和Set集合完全相同(即key不能重复) Map的这些实现类和子接口中value集的存储形式和List非常类似(即value可以重复、根据索引来查找) 。TreeMap通常比HashMap、Hashtable要慢(尤其是在插入、删除key-value对时更慢),因为TreeMap底层采用红黑树来管理键值对。但是TreeMap有一个好处就是:TreeMap中的key-value对总是处于有序状态,无须专门进行排序操作。并且虽然TreeMap在插入和删除方面性能比较差,但是在分类处理的时候作用很大,遍历的速度很快。TreeMap的使用示例

/**
	 * 该方法用于获得初始化成员变量sysbasecodeCache,调用remote接口,
	 * 获得所有的cbossBaseCode,并按code_type分
	 * 类,每一类编码存放在一个TreeMap中,并把所有生成的TreeMap作 为m_SysBaseTable的成员保存。
	 * 
	 * @return void
	 * @throws
	 */
	public static void initSysBaseCode() throws CBossDataAccessException {
		long start = System.currentTimeMillis();
		CBossLogUtil.getLogger(BaseCodeHelper.class).info(">>开始缓存["+CbossBaseCodeDataModule.TABLE_NAME+"]数据...");
		// 初始化前先清理缓存
		cleancache();
		int iCodeType = -999;
		Map treeMap = null;
		Connection conn = null;
		try {
			conn = DataSourceProxy.getConnectionBySchema(CbossBaseCodeDataModule.SCHEMA_NAME);
			CbossBaseCodeDAO basedao = new CbossBaseCodeDAO(conn);
			List baseCodes = basedao.selectAll();
			for (CbossBaseCodeDataModule cbossBaseCode : baseCodes) {
				iCodeType = cbossBaseCode.getCodeType();
				treeMap = (TreeMap) sysbasecodeCache.get(new Integer(iCodeType));
				if (treeMap == null) {
					treeMap = new TreeMap();
					sysbasecodeCache.put(new Integer(iCodeType), treeMap);
				}
				treeMap.put(cbossBaseCode.getCodeId(), cbossBaseCode);
			}
		} catch (Exception e) {
			throw new CBossDataAccessException(e.getMessage() + "基础代码初始化时出现异常");
		} finally {
			DataSourceProxy.closeConnection(conn);
			CBossLogUtil.getLogger(BaseCodeHelper.class).info(">>["+CbossBaseCodeDataModule.TABLE_NAME+"]数据缓存完成, cost("+(System.currentTimeMillis() - start)+")ms");
		}
	}复制代码

    在遍历TreeMap的时候,能够直接确定分类,然后在分类中遍历,最终能够以最快的速度获取想要的结果。

本文来自:juejin.cn

感谢作者:juejin.cn

查看原文:TreeMap 实现原理

79 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传