源码分析——TreeMap

TreeMap同样属于Map集合中的一员,但是它没有用到散列表和链表这些数据结构,它只使用了红黑树这一数据结构,可以直接在红黑树中进行增删改查操作。我们首先回顾一下Map集合的家族成员:

可以看出TreeMap是和HashMap并列的,而HashMap中的键值对是无序的,TreeMap中的键值对是按照一定的顺序进行排序的。

接下来继续看其继承结构:

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

我们在继承体系中看到了一个陌生的接口“NavigableMap”,这个接口有什么用呢?

public interface NavigableMap<K,V> extends SortedMap<K,V>{

    Map.Entry<K,V> lowerEntry(K key);
    //获得小于key值的最大Entry
    K lowerKey(K key);
    //获得小于key值的最大Key
    Map.Entry<K,V> floorEntry(K key);
    //获得不大于key值的最大Entry
    K floorKey(K key);
    //获得不大于key值的最大Key
    Map.Entry<K,V> ceilingEntry(K key);
    //获得不小于key值的最小Entry
    K ceilingKey(K key);
    //获得不小于key值的最小Key
    Map.Entry<K,V> higherEntry(K key);
    //获得大于key值的最小Entry
    K higherKey(K key);
    //获得大于key值的最小key
    Map.Entry<K,V> firstEntry();
    //获得第一个Entry
    Map.Entry<K,V> lastEntry();
    //获得最后一个Entry
    Map.Entry<K,V> pollFirstEntry();
    //获得并删除第一个Entry
    Map.Entry<K,V> pollLastEntry();
    //获得并删除最后一个Entry
    NavigableMap<K,V> descendingMap();
    //获得逆序的Map
    NavigableSet<K> navigableKeySet();
    //获得顺序的key值集合
    NavigableSet<K> descendingKeySet();
    //获得逆序的key值集合
    NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
                             K toKey,   boolean toInclusive);
    //获得子Map
    NavigableMap<K,V> headMap(K toKey, boolean inclusive);
    //获得首部子Map
    NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);
    //获得尾部子Map
    SortedMap<K,V> subMap(K fromKey, K toKey);
    //获得SortedMap类型的子Map
    SortedMap<K,V> headMap(K toKey);
    //获得SotredMap类型的首部子Map
    SortedMap<K,V> tailMap(K fromKey);
    //获得SortedMap类型的尾部子Map
} 

public interface SortedMap<K,V> extends Map<K,V> {
    Comparator<? super K> comparator();
    //比较器
    SortedMap<K,V> subMap(K fromKey, K toKey);
    //获得子Map
    SortedMap<K,V> headMap(K toKey);
    //获得首部子Map
    SortedMap<K,V> tailMap(K fromKey);
    //获得尾部子Map
    K firstKey();
    //获取第一个Key值
    K lastKey();
    //获取最后一个key值
    Set<K> keySet();
    //获得所有的Key组成的集合
    Collection<V> values();
    //获得所有的value组成的集合
    Set<Map.Entry<K, V>> entrySet();
    //获得所有的Entry组成的集合
}

由上面源码可知,SortedMap继承自Map接口,并且新增了比较器方法、获取首尾key方法、获得子Map方法与获得key、value、Entry集合的方法,而NavigableMap接口除了继承SortedMap类中的方法之外,还新增了一系列获取边界的key和Entry结点的方法。总之,就是除了有Map接口的方法之外,还新增了一个排序的功能。

接下来看看它的成员变量:

private final Comparator<? super K> comparator;
//自定义比较器
		
private transient Entry<K,V> root;
//根节点,不可序列化
		
private transient int size = 0;
//结点个数,不可序列化
		
private transient int modCount = 0;
//修改次数,不可序列化

static final class Entry<K,V> implements Map.Entry<K,V>{
	//红黑树结点类
	
	//成员变量
	K key;//key值
	V value;//value值
	Entry<K,V> left;//左孩子
	Entry<K,V> right;//右孩子
	Entry<K,V> parent;//父结点
	boolean color = BLACK;//默认黑色结点

}

由上可知,TreeMap的底层数据结构就是红黑树,并可以通过自己设置比较器来构建相应的红黑树。

接下来研究一下其构造器:

public TreeMap(){
	//构造器1,不定义比较器
	comparator = null;
}

public TreeMap(Comparator<? super K> comparator){
	//构造器2,传入构造器,根据构造器建树
	this.comparator = comparator;
}

public TreeMap(Map<? extends K, ? extends V> m){
	//构造器3,传入普通Map类型的容器,将容器中的键值对加入到树中
	comparator = null;
	//将比较器设置为Null
	putAll(m);
}

public TreeMap(SortedMap<K, ? extends V> m){
	//构造器4,传入的是一个排序的Map类型容器,将容器中的键值对按照顺序
	//加入到树中
	comparator = m.comparator();
	//将m的比较器设置成本类的比较器
	try{
		//使用迭代的方法将m的键值对复制到新的TreeMap的容器中
		buildFromSorted(m.size(), m.entrySet().iterator(),null,null)
	}catch(java.io.IOException cannotHappen){
		
	}catch(ClassNotFoundException cannotHappen){
		
	}
}

由上可知,TreeMap有四个构造器,当参数为空时,则直接将比较器设置为空,加入键值对构造红黑树时就利用键自己的比较器进行比较;当参数为比较器时,则将比较器设置为传入的比较器,加入键值对构造红黑树时就采用传入的比较器;当参数为Map集合时,就直接将比较器置空,再将map中的键值对依次插入到红黑树中;当参数为SortedMap集合时,则获取其比较器作为自己的比较器,再将其键值对依次插入到红黑树中。

下面再来看看常用方法:

1.put

public V put(K key, V value){
	//将一个键值对放入红黑树中
	Entry<K,V> t = root;
	//获取红黑树的根
	if(t == null){
		//如果根为空,则说明红黑树中没有结点
		compare(key,key);//类型验证
		root = new Entry<>(key, value, null);
		//新建结点作为根
		size = 1;
		//结点数量设置为1
		modCount++;
		//修改次数
		return null;
		//插入成功则返回null
	}
	int cmp;
	//标记,记录新节点应该插在哪里或者寻找插入点时下一次向什么方向寻找
	Entry<K,V> parent;
	Comparator<? super K> cpr = comparator;
	//获得比较器
	if(cpr != null){
		//如果比较器不为空,则用TreeMap中的比较器进行比较
		do{
			parent = t;
			//将当前结点作为父结点
			cmp = cpr.compare(key, t.key);
			//获得比较结果
			if(cmp < 0)
				//如果传入的key值比当前结点的key值小
				t = t.left;
				//则向其左子树下找
			else if(cmp > 0)
				//如果传入key值比当前结点的key值大
				t = t.right;
				//则向其右子树下找
			else
				return t.setValue(value);
			//如果相等,则将该结点的value值设置为传进来的value值
			//从此处可以看出其和HashMap的不同,key值不允许有相同的
		}while(t != null);
	}
	else{
		//否则表示比较器为空,用key类自己的比较方式进行比较
		if(key == null)
			throw new NullPointerException();
		@SuppressWarnings("unchecked")
			Comparable<? super K> k = (Comparable<? super K>)key;
			//对传进来的key进行强制类型转换
		do{
			//进行比较,寻找插入点
			parent = t;
			cmp = k.compareTo(t.key);
			if(cmp < 0)
				t = t.left;
			if(cmp > 0)
				t = t.right;
			else
				return t.setValue(value);
		}while(t != null);	
	}
	//上面为寻找插入点,下面开始正式插入
	Entry<K,V> e = new Entry<>(key,value,parent);
	//新建结点
	if(cmp < 0)
		//如果cmp小于零,则表示将e插入其父结点的左孩子处
		parent.left = e;
	else
		parent.right = e;
		//否则将e插入其父节点的右孩子处
	fixAfterInsertion(e);
	//插入后的调整,类似HashMap中的balanceInsertion方法
	size++;
	//结点数目加一
	modCount++;
	//修改次数加一
	return null;
	//插入成功,返回Null
}

插入操作即直接在红黑树中操作,先找到需要插入的位置,如果该位置中已经有键值对了,则将该键值对更新成传进去的value值并返回新的value值,否则直接将键值对插入并且开始调整红黑树。

2.get

public V get(Object key){
	//根据key寻找相应键值对,如果没找到返回null,
	//找到了则返回key对应的value值
	Entry<K,V> p = getEntry(key);
	return (p == null ? null : p.value);
}

final Entry<K,V> getEntry(Object key){
//根据key值获得键值对
	if(comparator != null)
	//如果比较器不为空,则调用使用比较器的方法
		return getEntryUsingComparator(key);
	if(key == null)
	//如果key值为空,则抛出空指针异常
		throw new NullPointerException();
	@SuppressWarnings("unchecked")
		Comparable<? super K> k = (Comparable<? super K>)key;
		//如果没有自定义比较器,则按照key值的比较方法进行比较
	Entry<K,V> p = root;
	//首先获取红黑树的根
	while(p != null){
		//向下寻找,深度遍历
		int cmp = k.compareTo(p.key);
		if(cmp < 0)
			p = p.left;
		else if(cmp > 0)
			p = p.right;
		else
			return p;
	}
	return null;
}

get方法即首先判断有没有比较器,如果没有比较器则按照key值自己的比较器进行比较,寻找对应的红黑树结点并返回其value值,如果value值为空或者没有找到对应的键值对时则返回null。

3.remove

public V remove(Object key){
	//根据传入的key值删除对应的键值对
	Entry<K,V> p = getEntry(key);
	//先找到该键值对
	if(p == null)
		return null;
	//如果找不到,则返回Null
	
	V oldValue = p.value;
	//如果找到该结点,则先得到其value值
	deleteEnrty(p);
	//删除该结点
	return oldValue;
	//返回被删除结点的value值
}

remove方法首先通过key获得相应的结点,如果找不到结点则返回null,否则先将结点的value值取出,再将结点删除,最后返回被删除结点的value值。

4.获得边界结点

public Map.Entry<K,V> firstEntry(){
	//获得第一个键值对
	return exportEntry(getFirstEntry());
	//返回调用exportEntry方法
}

public Map.Entry<K,V> lastEntry(){
	//获得最后一个键值对
	return exportEntry(getLastEntry());
}

public Map.Entry<K,V> pollFirstEntry(){
	//删除并返回第一个结点
	Entry<K,V> p = getFirstEntry();
	//获得第一个结点
	Map.Entry<K,V> result = exportEntry(p);
	//返回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)
		deleteEnrty(p);
	return result;
}

5.获取相应集合

public Set<K> keySet(){
	//获取遍历TreeMap中key的Set容器
	return navigableKeySet();
}

public NavigableSet<K> navigableKeySet(){
	//获取顺序的Set容器的key集合
	KeySet<K> nks = navigableKeySet;
	return (nks != null) ? nks : (navigableKeySet = new KeySet<>(this));
}

public NavigableSet<K> descendingKeySet(){
	//返回逆序的Set容器的key集合
	return descendingMap().navigableKeySet();
}

public Collection<V> values(){
	//返回包含TreeMap中的所有value的集合
	Collection<V> vs = values;
	if(vs == null){
		vs = new Values();
		values = vs;
	}
	return vs;
}

public Set<Map.Entry<K,V>> entrySet(){
	//获取包含TreeMap中的所有键值对集合的Set容器
	EntrySet es = entrySet;
	return (es != null) ? es : (entrySet = new EntrySet());
}

6.其他方法

public void clear(){
	//清空红黑树
	modCount++;
	//修改次数加一
	size = 0;
	//将结点数目重置为0
	root = null;
	//将根节点置为空就够了
}


public int size(){
	//获得树中的结点数量
	return size;
}

public boolean containsKey(Object key){
	//判断树中是否存在含有key值的结点
	return getEntry(key) != null;
}

public boolean containsValue(Object value){
	//判断树中是否存在含有value值的结点
	for(Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
		//遍历树中的结点,一个个进行比较,存在则返回true,不存在则返回false
		if(valEquals(value,e.value))
			return true;
		return false;
}

总结:TreeMap的底层数据结构是红黑树,可以通过自定义比较器的方式让TreeMap中的键值对按照相应的方式进行排序,并可以通过顺序或逆序对TreeMap中的键值对进行遍历。除此之外还可以获得TreeMap中的子Map,即红黑树中的子树,并对其进行操作。

总源码

public class TreeMap<K,V> extends AbstractMap<K,V> 
						implements NavigableMap<K,V>, Cloneable, java.io.Serializable
	{
		private final Comparator<? super K> comparator;
		//自定义比较器
		
		private transient Entry<K,V> root;
		//根节点,不可序列化
		
		private transient int size = 0;
		//结点个数,不可序列化
		
		private transient int modCount = 0;
		//修改次数,不可序列化
		
		public TreeMap(){
			//构造器1,不定义比较器
			comparator = null;
		}

		public TreeMap(Comparator<? super K> comparator){
			//构造器2,传入构造器,根据构造器建树
			this.comparator = comparator;
		}

		public TreeMap(Map<? extends K, ? extends V> m){
			//构造器3,传入普通Map类型的容器,将容器中的键值对加入到树中
			comparator = null;
			//将比较器设置为Null
			putAll(m);
		}

		public TreeMap(SortedMap<K, ? extends V> m){
			//构造器4,传入的是一个排序的Map类型容器,将容器中的键值对按照顺序
			//加入到树中
			comparator = m.comparator();
			//将m的比较器设置成本类的比较器
			try{
				//使用迭代的方法将m的键值对复制到新的TreeMap的容器中
				buildFromSorted(m.size(), m.entrySet().iterator(),null,null)
			}catch(java.io.IOException cannotHappen){
				
			}catch(ClassNotFoundException cannotHappen){
				
			}
		}

		public int size(){
			//获得树中的结点数量
			return size;
		}

		public boolean containsKey(Object key){
			//判断树中是否存在含有key值的结点
			return getEntry(key) != null;
		}

		public boolean containsValue(Object value){
			//判断树中是否存在含有value值的结点
			for(Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
				//遍历树中的结点,一个个进行比较,存在则返回true,不存在则返回false
				if(valEquals(value,e.value))
					return true;
				return false;
		}
		
		public V get(Object key){
			//根据key寻找相应键值对,如果没找到返回null,
			//找到了则返回key对应的value值
			Entry<K,V> p = getEntry(key);
			return (p == null ? null : p.value);
		}
		
		public Comparator<? super K> comparator(){
			//获得树中的比较器
			return comparator;
		}
		
		public K firstKey(){
			//获得第一个结点的key值
			return key(getFirstEntry());
		}
		
		public K lastKey(){
			//获得最后一个结点的Key值
			return key(getLastEntry());
		}
		
		public void putAll(Map<? extends K, ? extends V> map){
			//将Map类型容器中的键值对放入本容器中
			int mapSize = map.size();
			//获得m中的结点数量
			if(size == 0 && mapSize != 0 && map instanceof SortedMap){
				//如果当前容器中没有结点且传进来的容器有结点并是SortedMap类型
				//的容器
				Comparator<?> c = ((SortedMap<?,?>)map).comparator();
				//获得map中的比较器
				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);
			//如果不满足,则调用父类的方法进行加入
		}
		
		final Entry<K,V> getEntry(Object key){
		//根据key值获得键值对
			if(comparator != null)
			//如果比较器不为空,则调用使用比较器的方法
				return getEntryUsingComparator(key);
			if(key == null)
			//如果key值为空,则抛出空指针异常
				throw new NullPointerException();
			@SuppressWarnings("unchecked")
				Comparable<? super K> k = (Comparable<? super K>)key;
				//如果没有自定义比较器,则按照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;
		}
		
		final Entry<K,V> getEntryUsingComparator(Object key){
		//按照比较器进行查询,根据key值获得相应的键值对
			@Suppressings("unchecked")
				K k = (K) key;
				//获得传入的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;
			//如果找不到,则返回null
		}
		
		final Entry<K,V> getCeilingEntry(K key){
		//寻找不小于key的最小结点,也就是说要么是它自己
		//要么是红黑树中序遍历中最接近自己的且比自己大的结点
			Entry<K,V> p = root;
			//首先获得根节点
			while(p != null){
				//开始深度遍历,使用比较器
				int cmp = compare(key, p.key);
				if(cmp < 0){
					//如果传进来的key比当前结点的key小,向左孩子深入
					if(p.left != null)
						p = p.left;
					else
						//如果左孩子为空,说明此结点已经是这棵树中大于key
						//中结点的最小结点了
						return p;
				}else if(cmp > 0){
					//如果传进来的key比当前结点的key值大,则向右孩子深入
					if(p.right != null){
						p = p.right;
					}else{
						//如果右孩子为空,则要找到中序遍历的后一个结点,也就是说
						//如果在遍历的路径上有一条路是通过深入父结点的左孩子下来的
						//则就找那最近的父结点,如果没有,说明是一直是通过深入父结点的
						//右孩子下来的,则一直追溯到根节点,返回null
						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
					//如果找到相等的结点,则返回该结点
					return p;
			}
			return null;
			//否则返回null
		}
		
		final Entry<K,V> getFloorEntry(K key){
		//和上一个方法相反,找到不大于key的最大结点,如果没有,则返回Null
			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.parnet;
						}
						return parent;
					}
				}else
					return p;
			}
			return null;
		}
		
		final Entry<K,V> getHigherEntry(K key){
			//获得大于key值的最小结点,自己不算,和getCeilingEntry方法类似
			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;
						
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值