java集合框架10——TreeMap和源码分析(一)

前面讨论完了HashMap和HashTable的源码,这一节我们来讨论一下TreeMap。先从整体上把握TreeMap,然后分析其源码,深入剖析TreeMap的实现。

1. TreeMap简介

TreeMap是一个有序的key-value集合,它内部是通过红-黑树实现的,如果对红-黑树不太了解,请先参考下这篇博文:红-黑树。下面我们先来看看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继承与AbstractMap,实现了NavigableMap接口,意味着它支持一系列的导航方法,比如返回有序的key集合。它还实现了Cloneable接口,意味着它能被克隆。另外也实现了Serializable接口,表示它支持序列化。

TreeMap是基于红-黑树实现的,该映射根据其key的自然顺序进行排序,或者根据用户创建映射时提供的Comarator进行排序,另外,TreeMap是非同步的。

我们先总览一下TreeMap都有哪些API:

Entry<K, V>                ceilingEntry(K key)
K                          ceilingKey(K key)
void                       clear()
Object                     clone()
Comparator<? super K>      comparator()
boolean                    containsKey(Object key)
NavigableSet<K>            descendingKeySet()
NavigableMap<K, V>         descendingMap()
Set<Entry<K, V>>           entrySet()
Entry<K, V>                firstEntry()
K                          firstKey()
Entry<K, V>                floorEntry(K key)
K                          floorKey(K key)
V                          get(Object key)
NavigableMap<K, V>         headMap(K to, boolean inclusive)
SortedMap<K, V>            headMap(K toExclusive)
Entry<K, V>                higherEntry(K key)
K                          higherKey(K key)
boolean                    isEmpty()
Set<K>                     keySet()
Entry<K, V>                lastEntry()
K                          lastKey()
Entry<K, V>                lowerEntry(K key)
K                          lowerKey(K key)
NavigableSet<K>            navigableKeySet()
Entry<K, V>                pollFirstEntry()
Entry<K, V>                pollLastEntry()
V                          put(K key, V value)
V                          remove(Object key)
int                        size()
SortedMap<K, V>            subMap(K fromInclusive, K toExclusive)
NavigableMap<K, V>         subMap(K from, boolean fromInclusive, K to, boolean toInclusive)
NavigableMap<K, V>         tailMap(K from, boolean inclusive)
SortedMap<K, V>            tailMap(K fromInclusive)

从这些API中可以看出,总共可分为这几大类:跟Entry相关的,跟key相关的以及跟Map相关的,另外还有keySet和entrySet等方法,下文详细讨论这些API。

2. TreeMap的源码分析(基于JDK1.7)

2.1 存储结构

由于TreeMap是基于红黑树实现的,所以其内部维护了一个红-黑树的数据结构,每个key-value对也存储在一个Entry里,只不过这个Entry和前面HashMap或者HashTable中的Entry不同,TreeMap的Entry其实是红-黑树的一个节点。我们来看一下TreeMap中的Entry定义:

private transient Entry<K,V> root = null;

然后我们看看Entry实体类的实现:

2.2 Entry实体类和相关方法

先看一下Entry实体类:

private static final boolean RED   = false;
private static final boolean BLACK = true;
//就是一个红黑树的节点
static final class Entry<K,V> implements Map.Entry<K,V> {
    K key;
    V value;
    Entry<K,V> left = null; //左子节点
    Entry<K,V> right = null; //右子节点
    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;
    }

    public K getKey() { //获得key
        return key;
    }

    public V getValue() { //获得value
        return value;
    }

    public V setValue(V value) { //设置value
        V oldValue = this.value;
        this.value = value;
        return oldValue;
    }

    public boolean equals(Object o) { //key和value均相等才返回true
        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() { //计算hashCode
        int keyHash = (key==null ? 0 : key.hashCode());
        int valueHash = (value==null ? 0 : value.hashCode());
        return keyHash ^ valueHash;
    }

    public String toString() { //重写toString方法
        return key + "=" + value;
    }
}

从Entry实体类中可以看出,TreeMap中的Entry就是一个红黑树的节点。跟这个Entry相关的方法都有firstEntry()、lastEntry()、lowerEntry()、higherEntry()、floorEntry()、ceilingEntry()。它们的原理都是类似的,我们只分析firstEntry(),其他的放到源码里分析:

public Map.Entry<K,V> firstEntry() {
    return exportEntry(getFirstEntry());
}

//获得TreeMap里第一个节点(即根据key排序最小的节点),如果TreeMap为空,返回null
final Entry<K,V> getFirstEntry() {
    Entry<K,V> p = root;
    if (p != null)
        while (p.left != null)
            p = p.left;
    return p;
}

代码很简单,一直往下走,直到找到那个节点,然后返回即可。这里为什么不直接调用getFirtstEntry(),而是对外提供firstEntry()供外界调用呢?这就说到了exportEntry()方法的作用了,因为如果直接调用getFirstEntry()方法的话,返回的Entry是可以被修改的,但是经过exportEntry()方法包装过之后就不能修改了,所以这么做事防止用于修改返回的Entry。我们来看看exportEntry()方法是如何对Entry对象进行包装的:

static <K,V> Map.Entry<K,V> exportEntry(TreeMap.Entry<K,V> e) {
	return (e == null) ? null :
		new AbstractMap.SimpleImmutableEntry<>(e);
}

我们可以看出,它是通过新new一个AbstractMap类中的一个静态类SimpleImmutableEntry实现的,那么SimpleImmutableEntry类中是如何实现的呢,为了方便,我们也把该类拿过来(当然也可以看这篇博文Map架构与源码分析,里面有讲到AbstractMap抽象类的源码),下面看看这个SimpleImmutableEntry静态类是如何实现的:

public static class SimpleImmutableEntry<K,V>
	implements Entry<K,V>, java.io.Serializable
{
	private static final long serialVersionUID = 7138329143949025153L;

	private final K key;
	private final V value;

	public SimpleImmutableEntry(K key, V value) { //通过key和value初始化对象
		this.key   = key;
		this.value = value;
	}

	public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) { //通过传进来一个Entry初始化对象
		this.key   = entry.getKey();
		this.value = entry.getValue();
	}

	public K getKey() {
		return key;
	}

	public V getValue() {
		return value;
	}

	public V setValue(V value) { //!!!关键地方在这里,不能setValue,否则会抛出UnsupportedOperationException异常
		throw new UnsupportedOperationException();
	}

	public boolean equals(Object o) {
		if (!(o instanceof Map.Entry))
			return false;
		Map.Entry e = (Map.Entry)o;
		return eq(key, e.getKey()) && eq(value, e.getValue());
	}

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

	//重写了toString方法,返回key=value形式
	public String toString() {
		return key + "=" + value;
	}
}

从上面代码中可以看出,被这个类包装过的Entry是不允许被修改内容的,这也就是为什么TreeMap类不直接把getFirstEntry()方法暴露出去,而是提供了firstEntry()供外界调用的原因。关于Entry的其他类似的方法我就不一一赘述了,我放到源代码里分析,都不难理解。如下:

/*********************** 与Entry相关的方法 ******************************/	

 //获取TreeMap中键为key对应的节点
 final Entry<K,V> getEntry(Object key) {
	// 若比较器不为null,则通过getEntryUsingComparator来获得
	if (comparator != null)
		return getEntryUsingComparator(key);
	if (key == null)
		throw new NullPointerException();
	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;
}
//使用比较器获得与key对应的Entry
final Entry<K,V> getEntryUsingComparator(Object key) {
	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;
}
/*--------------------------------------------------------*/
public Map.Entry<K,V> firstEntry() {
	return exportEntry(getFirstEntry());
}
//获得TreeMap里第一个节点(即根据key排序最小的节点),如果TreeMap为空,返回null
final Entry<K,V> getFirstEntry() {
	Entry<K,V> p = root;
	if (p != null)
		while (p.left != null)
			p = p.left;
	return p;
}
/*-----------------------------------------------*/
public Map.Entry<K,V> lastEntry() {
	return exportEntry(getLastEntry());
}
//获得TreeMap里最后一个节点(根据key排序最大的节点),如果TreeMap为空,返回null
final Entry<K,V> getLastEntry() {
	Entry<K,V> p = root;
	if (p != null)
		while (p.right != null)
			p = p.right;
	return p;
}
/*------------------------------------------------*/
//弹出第一个节点,即删除
public Map.Entry<K,V> pollFirstEntry() {
	Entry<K,V> p = getFirstEntry();
	Map.Entry<K,V> result = exportEntry(p);
	if (p != null)
		deleteEntry(p);
	return result;
}
//弹出最后一个节点,即删除
public Map.Entry<K,V> pollLastEntry() {
	Entry<K,V> p = getLastEntry();
	Map.Entry<K,V> result = exportEntry(p);
	if (p != null)
		deleteEntry(p);
	return result;
}
/*-------------------------------------------------*/
public Map.Entry<K,V> floorEntry(K key) {
	return exportEntry(getFloorEntry(key));
}
public Map.Entry<K,V> ceilingEntry(K key) {
	return exportEntry(getCeilingEntry(key));
}

//获取TreeMap中不小于key的最小的节点;
//若不存在(即TreeMap中所有节点的键都比key大),就返回null
final Entry<K,V> getCeilingEntry(K key) {
	Entry<K,V> p = root;
	while (p != null) {
		int cmp = compare(key, p.key);
		if (cmp < 0) { //情况1. 若p.key > key
			if (p.left != null) //若p有左子节点
				p = p.left; //往左下走
			else
				return p; //否则返回p
		} else if (cmp > 0) { //情况2:p.key < key
			if (p.right != null) {
				p = p.right;
			} else {
				// 若 p 不存在右孩子,则找出 p 的后继节点,并返回
				// 注意:这里返回的 “p的后继节点”有2种可能性:第一,null;第二,TreeMap中大于key的最小的节点。
				// 理解这一点的核心是,getCeilingEntry是从root开始遍历的。
				// 若getCeilingEntry能走到这一步,那么,它之前“已经遍历过的节点的key”都 > key。
				// 能理解上面所说的,那么就很容易明白,为什么“p的后继节点”又2种可能性了。
				Entry<K,V> parent = p.parent;
				Entry<K,V> ch = p;
				while (parent != null && ch == parent.right) {
					ch = parent;
					parent = parent.parent;
				}
				return parent;
			}
		} else //情况3:p.key = key
			return p;
	}
	return null;
}
// 获取TreeMap中不大于key的最大的节点;
// 若不存在(即TreeMap中所有节点的键都比key小),就返回null
// getFloorEntry的原理和getCeilingEntry类似,这里不再多说。
final Entry<K,V> getFloorEntry(K key) {
	Entry<K,V> p = root;
	while (p != null) {
		int cmp = compare(key, p.key);
		if (cmp > 0) {
			if (p.right != null)
				p = p.right;
			else
				return p;
		} else if (cmp < 0) {
			if (p.left != null) {
				p = p.left;
			} else {
				Entry<K,V> parent = p.parent;
				Entry<K,V> ch = p;
				while (parent != null && ch == parent.left) {
					ch = parent;
					parent = parent.parent;
				}
				return parent;
			}
		} else
			return p;

	}
	return null;
}
/*--------------------------------------------------*/
public Map.Entry<K,V> lowerEntry(K key) {
	return exportEntry(getLowerEntry(key));
}
public Map.Entry<K,V> higherEntry(K key) {
	return exportEntry(getHigherEntry(key));
} 

// 获取TreeMap中大于key的最小的节点。
// 若不存在,就返回null。
// 请参照getCeilingEntry来对getHigherEntry进行理解。
final Entry<K,V> getLowerEntry(K key) {
	Entry<K,V> p = root;
	while (p != null) {
		int cmp = compare(key, p.key);
		if (cmp > 0) {
			if (p.right != null)
				p = p.right;
			else
				return p;
		} else {
			if (p.left != null) {
				p = p.left;
			} else {
				Entry<K,V> parent = p.parent;
				Entry<K,V> ch = p;
				while (parent != null && ch == parent.left) {
					ch = parent;
					parent = parent.parent;
				}
				return parent;
			}
		}
	}
	return null;
}
// 获取TreeMap中小于key的最大的节点。
// 若不存在,就返回null。
// 请参照getCeilingEntry来对getLowerEntry进行理解。
final Entry<K,V> getHigherEntry(K key) {
	Entry<K,V> p = root;
	while (p != null) {
		int cmp = compare(key, p.key);
		if (cmp < 0) {
			if (p.left != null)
				p = p.left;
			else
				return p;
		} else {
			if (p.right != null) {
				p = p.right;
			} else {
				Entry<K,V> parent = p.parent;
				Entry<K,V> ch = p;
				while (parent != null && ch == parent.right) {
					ch = parent;
					parent = parent.parent;
				}
				return parent;
			}
		}
	}
	return null;
}
/*---------------------------------------------------*/

2.3 成员属性的构造方法

分析完了Entry实体相关的源码后,我们来看看TreeMap里的成员属性。

/********************* 成员属性 ****************************/
private final Comparator<? super K> comparator; //比较器
private transient Entry<K,V> root = null; //实体对象
private transient int size = 0; //红黑树节点个数,即Entry数
private transient int modCount = 0; //修改次数

/*********************** 构造方法 **************************/
public TreeMap() {  //默认构造方法
	comparator = null;
}
public TreeMap(Comparator<? super K> comparator) { //带比较器的构造方法
	this.comparator = comparator;
}
public TreeMap(Map<? extends K, ? extends V> m) { //带Map的构造方法,Map会为TreeMap的子类
	comparator = null;
	putAll(m);
}
////带sortedMap的构造方法,sortedMap会为TreeMap的子类
public TreeMap(SortedMap<K, ? extends V> m) {
	comparator = m.comparator();
	try {
		buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
	} catch (java.io.IOException cannotHappen) {
	} catch (ClassNotFoundException cannotHappen) {
	}
}

我们可以看出,TreeMap有四个构造函数,这里分析一下第三个构造函数,内部调用了putAll方法,我们看一下putAll方法:

public void putAll(Map<? extends K, ? extends V> map) {
	int mapSize = map.size();//获取map的大小
	//如果TreeMap大小是0,且map的大小不为0,且map是已排序的key-value对,才执行下面的程序
	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;
		}
	}
	//否则调用AbstractMap中的putAll()
	//AbstractMap中的putAll()又会调用TreeMap的put()方法
	super.putAll(map);
}

在putAll方法内部,会先进行判断,如果TreeMap是空的,且传进来的map符合条件,则执行if内的语句,然后调用buildFromSorted方法(后面放到源码中分析)来put进去,否则调用父类的putAll方法,父类的putAll方法则直接调用TreeMap的put方法。

由于TreeMap的源码比较多,这里先写到这,后面的源码分析放到下一篇博客中写:java集合框架11——TreeMap和源码分析(二)。

如有错误之处,欢迎留言指正~

_____________________________________________________________________________________________________________________________________________________

-----乐于分享,共同进步!

-----更多文章请看:http://blog.csdn.net/eson_15

时间: 2024-08-25 22:21:14

java集合框架10——TreeMap和源码分析(一)的相关文章

java集合框架11——TreeMap和源码分析(二)

我们继续分析TreeMap的源码 1.TreeMap源码分析(续) 1. 存取方法 TreeMap中的存取方法本质上就是对红黑树的插入和删除操作,从源码里体现的更为明显,其实就是对红黑树的插入和删除(可以参考:红黑树),下面简单看下源码: /*************************** put和remove **********************************/ //将key-value对添加到TreeMap中,理解TreeMap的前提是理解红黑树 //因为和红黑树中的

java集合框架04——LinkedList和源码分析

上一章学习了ArrayList,并分析了其源码,这一章我们将对LinkedList的具体实现进行详细的学习.依然遵循上一章的步骤,先对LinkedList有个整体的认识,然后学习它的源码,深入剖析LinkedList. LinkedList简介 首先看看LinkedList与Collection的关系: LinkedList的继承关系如下: java.lang.Object ? java.util.AbstractCollection<E> ? java.util.AbstractList&l

java集合框架08——HashMap和源码分析

本文为博主原创文章,转载请注明出处:http://blog.csdn.net/eson_15/article/details/51154989 上一章总体分析了Map架构,并简单分析了一下AbstractMap源码,这一章开始我们将对Map的具体实现类进行详细的学习.本章先研究HashMap.依然遵循以下步骤:先对HashMap有个整体的认识,然后学习它的源码,深入剖析HashMap. 1.HashMap简介 首先看一下HashMap的继承关系 java.lang.Object ? java.u

java集合框架09——HashTable和源码分析

上一章我们学习了HashMap的源码,这一节我们来讨论一下HashTable,HashTable和HashMap在某种程度上是类似的.我们依然遵循以下步骤:先对HashTable有个整体的认识,然后学习它的源码,深入剖析HashTable. 1.HashTable简介 首先看一下HashTable的继承关系 java.lang.Object ? java.util.Dictionary<K, V> ? java.util.Hashtable<K, V> public class H

Java集合系列之TreeMap源码分析

一.概述 TreeMap是基于红黑树实现的.由于TreeMap实现了java.util.sortMap接口,集合中的映射关系是具有一定顺序的,该映射根据其键的自然顺序进行排序或者根据创建映射时提供的Comparator进行排序,具体取决于使用的构造方法.另外TreeMap中不允许键对象是null. 1.什么是红黑树? 红黑树是一种特殊的二叉排序树,主要有以下几条基本性质: 每个节点都只能是红色或者黑色 根节点是黑色 每个叶子节点是黑色的 如果一个节点是红色的,则它的两个子节点都是黑色的 从任意一

Java集合系列:-----------03ArrayList源码分析

上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayList.先对ArrayList有个整体认识,再学习它的源码,最后再通过例子来学习如何使用它.内容包括: ArrayList简介 ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAccess

Java集合系列之ArrayList源码分析

一.ArrayList简介 ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类. 该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加.如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能. ArrayList的用法和Vector向类似,但是Vect

Java集合系列之HashMap源码分析

一.HashMap简介 HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射.此类不保证映射的顺序,假定哈希函数将元素适当的分布在各桶之间,可为基本操作(get和put)提供稳定的性能. ps:本文中的源码来自jdk1.8.0_45/src. 1.重要参数 HashMap的实例有两个参数影响其性能. 初始容量:哈希表中桶的数量 加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度 当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的

Java集合:HashSet的源码分析

Java集合---HashSet的源码分析 一.  HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特别是它不保证该顺序恒久不变.此类允许使用null元素. 二.  HashSet的实现: 对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成, Has