前面我们已经介绍了两个AbstractMap的实现了,分别是HashMap和LinkedHashMap.我们也看到了,LinkedHashMap是HashMap的一个优化版本,它能够根据元素的插入顺序或者元素的访问顺序来进行遍历.
那么今天要介绍的TreeMap又是什么鬼?
简介
我们这里直接贴出文档首部的说明.从说明中我们可以看到,我们关注的主要有三点:
- 这个map能够根据key进行排序
- 它是基于Red-Black tree实现的
- containsKey(), get(), put(), remove()等方法的时间复杂度为O(logn)
就是这样的一个map而已.
注意上面的第三点,我们知道,HashMap和LinkedHashMap中,这些操作的时间复杂度基本都为O(1).所以,如果不是要求按Key的顺序进行排序的话,是没有理由使用这个map的.本文后面还会给你不使用这个map的理由.
这里还有一点要说明,就是为什么采用Red-Black tree,而不是BST或者AVL.关于这一点,我们需要理解Red-Black tree相对于后两者的优势.请参考Java集合框架源码研读-HashMap中的相关说明来理解.
结构
HashMap,有一个数组用于存放键值对,如果发生碰撞,则用链表或者Red-Black tree来存放碰撞的元素,采用链地址法来解决冲突问题.
而在TreeMap中,则仅有一棵Red-Black Tree而已,我们看一下其中的每个Node的定义:
从其定义中,我们可以看到,在碰撞时,它并没有一个链表来存放碰撞的元素.那么它是如何解决碰撞的呢?下文中将会给你答案.
关键操作
我们来看三个关键操作的实现,分别是get(), put()方法和用于按key的顺序访问的实现.
get()方法的实现
get()方法实际上是调用的getEntry(Object key),所以这里我们仅仅贴出这个方法的实现.
这个应该没有什么好说的吧?仅仅是一个中序遍历而已.学过数据结构中的树的朋友应该都很容易理解.
put()方法的实现
很简单,就是中序遍历找到特定节点,然后替换其value.
这里就给出了前面如何解决冲突的答案.出现冲突时,并不会采用链地址法或者开放定址法等方法来解决冲突,而是直接替换掉对应节点的value.这就很尴尬了,万一在你的应用场景中,出现了冲突的情况,在你不知情的情况下,就会替换掉对应的值.
其实这个问题也不是什么大问题.因为比较器comparator应该是你自己定义的,也就是说,你应该意识到其实它是不能存放重复的元素的.
另一个需要注意的地方就是,不允许key为null.
有序遍历是如何实现的?
查看keySet()方法,一直向后寻找,我们能够发现其中最重要的方法是successor(Entry<K,V> t).
很容易的就能发现,这实际上就是一个中序遍历.
但是,中序遍历得到的结果不应该是有序的呀,前序遍历得到的结果不应该才是有序的吗?
为什么呢?
因为实际上第一次调用successor(Entry<K, V> t)时,实际上传入的就是这棵Red-Black Tree的最左节点.
看图中keyIterator()以及getFirstEntry()的实现.
理解了吧?
后记
其实TreeMap中还提供了很多其他的方法,比如获取跟传入的key最接近的键值对等.
核心方法上面已经介绍了,其他的方法,各位自行探索吧.