博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java集合——TreeMap源码详解
阅读量:5095 次
发布时间:2019-06-13

本文共 8176 字,大约阅读时间需要 27 分钟。

0. 前言

本文对TreeMap的源码进行了解析,本文原创,转载请注明出处:

先对TreeMap的特性进行一个概述: 

1TreeMap 是一个有序的key-value集合,它是通过实现的。因为红黑树是平衡的二叉搜索树,所以其put(包含update操作)、getremove的时间复杂度都为log(n)

(2)TreeMap 相比于HashMap多实现了了NavigableMap接口(也就是这个接口,决定了TreeMapHashMap的不同:HashMapkey是无序的,TreeMapkey是有序的)。

public class TreeMap
extends AbstractMap
implements NavigableMap
, Cloneable, java.io.Serializable

3TreeMap非同步的。

1.  红黑树

红黑树是一种
,让我们在一起回忆下二叉搜索树的一些性质:

二叉搜索树左子树的值小于根节点,右子树的值大于根节点。很明显,二叉搜索树每进行一次判断就是能将问题的规模减少一半,所以二叉搜索树查找元素的时间复杂度为log(n)。下面在看一下红黑树的样子:

叶子节点为上图中的NIL节点,国内一些教材中没有这个NIL节点,我们在画图时有时也会省略这些NIL节点,但是我们需要明确,当我们说叶子节点时,指的就是这些NIL节点。

 红黑树通过下面5条规则,保证了树是平衡的:

1)树的节点只有红与黑两种颜色。

2)根节点为黑色的。

3)叶子节点为黑色的。

4)红色节点的子节点必定是黑色的。

5)从任意节点出发,到其后继的叶子节点的路径中,黑色节点的数目相同。

满足了上面5个条件后,就能够保证根节点到叶子节点的最长路径不会大于根节点到叶子最短路径的2

简单证明如下:假设根节点到叶子节点最短的路径中,黑色节点数目为B,那么根据性质5,根节点到叶子节点的最长路径中,黑色节点数目也是B,最长的情况就是每两个黑色节点中间有个红色节点(也就是红黑相间的情况),所以红色节点最多为B1个。所以B+B-1<=2B得证。

 

红黑树操作包括插入、删除、左旋、右旋,这里有个,把这些操作都按动画做出来了,生动形象。

 

2.  NavigableMap接口

public interface NavigableMap
extends SortedMap
public interface SortedMap
extends Map

TreeMap实现了NavigableMap接口,NavigableMapJDK1.6新增的,NavigableMap继承了SortedMap(这个Map是有序的),顺序一般是指由提供的keys自然序,或者也可以在创建SortedMap实例时,指定一个来决定。插入SortedMap中的key的类都必须继承Comparable类(或指定一个comparator,这样才能通过k1.compareTo(k2)comparator.compare(k1, k2)来比较两个key

 NavigableMap接口在SortedMap的基础上增加了一些导航方法来返回与搜索目标最近的元素。例如下面这些方法:

lowerEntry//返回所有比给定Map.Entry小的元素floorEntry//返回所有比给定Map.Entry小或相等的元素ceilingEntry//返回所有比给定Map.Entry大或相等的元素higherEntry//返回所有比给定Map.Entry大的元素

3.  
TreeMap
的存储结构

Entry作为TreeMap存储的结点,包括键、值、父结点、左孩子、右孩子、颜色。源码如下所示:

static final class Entry
implements Map.Entry
{ K key;V value; //左孩子 Entry
left; //右孩子 Entry
right; //父节点 Entry
parent; //节点颜色 boolean color = BLACK; //构造函数 Entry(K key, V value, Entry
parent){ this.key = key; this.value = value; this.parent = parent; } ......}

4.  TreeMap的构造方法

TreeMap有四种构造函数,分别对应不同的参数。不同构造方法的功能已经在注释里标明了。

//1.键自然顺序public TreeMap() {        comparator = null;}//2.根据给定比较器进行排序public TreeMap(Comparator
comparator){ this.comparator = comparator;}//3.构造一个与给定映射具有相同映射关系的新的树映射,该映射根据其键的自然顺序进行排序public TreeMap(Map
m) { comparator = null; putAll(m);}// putAll()将m中的所有元素添加到TreeMap中public void putAll(Map
m) { for (Map.Entry
e : m.entrySet()) put(e.getKey(), e.getValue());}//4.构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射public TreeMap(SortedMap
m){ comparator = m.comparator(); try{ buildFromSorted(m.size(), m.entrySet().iterator(), null, null);} catch (Exception e){}}

4.  TreeMapput操作

public V put(K key, V value) {    Entry
t = root; //若根节点为空,则以(key,value)为参数新建节点 if (t == null){ compare(key, key); // type check root = new Entry<>(key, value, null);//第三个参数为父节点 size = 1; modCount++; return null;} int cmp;Entry
parent; Comparator
cpr = comparator; //自定义比较器 if (cpr != null) {//优先通过比较器比较两个结点的大小 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); //相等则覆盖旧值 } while (t != null); //直到把新节点要插入的位置置空 } //如果没有定义比较器,则采用默认的排序算法进行创建TreeMap集合 else{ if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") 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);} //将新增节点当做parent的子节点 Entry
e = new Entry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; //进行着色和旋转等操作修复红黑树 fixAfterInsertion(e); size++; modCount++; return null;}

从源码中可以总结出几个需要注意的点:

1TreeMap的查询和更新都涉及比较操作,故TreeMap键必须实现Comparable接口或者构造时传入比较器(比较器优先级更高)。

2默认的排序算法中put操作不允许null,但是值(value)允许为null;若传入自定义比较器,可以手动处理null键的情况。

3)键重复的情况下,新值会覆盖掉旧值

6.  TreeMapget操作

public V get(Object key) {//查询操作         Entry
p = getEntry(key); return (p==null ? null : p.value); } final Entry
getEntry(Object key) {//跟普通二叉排序树的查询操作一致 if (comparator != null)//存在比较器 return getEntryUsingComparator(key);//根据比较器定义的比较规则查找 if (key == null) throw new NullPointerException(); Comparable
k = (Comparable
) key; Entry
p = root; while (p != null) {//根据Comparable接口定义的比较规则查找 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
getEntryUsingComparator(Object key) {//根据比较器定义的比较规则查找 K k = (K) key; Comparator
cpr = comparator; if (cpr != null) { Entry
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; }

7.  TreeMapremove操作

public V remove(Object key) {        Entry
p = getEntry(key);//首先找到待删结点 if (p == null) return null; V oldValue = p.value; deleteEntry(p);//删除结点,并修复红黑树 return oldValue; }

8.  TreeMap的两种迭代操作

//1.根据entrySet()和Iterator迭代器遍历Integer integ = null;Iterator iter = map.entrySet().iterator();while(iter.hasNext()) {    Map.Entry entry = (Map.Entry)iter.next();    key = (String)entry.getKey();    integ = (Integer)entry.getValue();}
//2.根据keySet()和Iterator迭代器遍历String key = null;Integer integ = null;Iterator iter = map.keySet().iterator();while (iter.hasNext()) {    key = (String)iter.next();    integ = (Integer)map.get(key);}

9.  TreeMapHashMap的关系

9.1    TreeMapHashMap的相同点

1)两者均是线程不安全的。

2)两者插入节点时,key重复均覆盖旧值。

 

9.2  TreeMapHashMap的不同点

1HashMap的实现基于数组和单链表,而TreeMap的实现基于红黑树,必要时数组会扩容、保持树平衡。

2HashMapkey无序的,TreeMapkey有序的。

3TreeMap要求key必须实现Comparable接口,或者初始化时传入Comparator比较器。

4HashMap增删改查操作的时间复杂度为O(1)TreeMap增删改查操作的时间复杂度为O(log(n))

5HashMap允许null值,TreeMap默认的排序算法中put操作不允许null,但是值(value)允许为null;若传入自定义比较器,可以手动处理null键的情况。

10.  TreeMapTreeSet的关系

TreeMap Map 接口的常用实现类,而TreeSet Set 接口的常用实现类。虽然 TreeMap TreeSet 实现的接口规范不同,但 TreeSet 底层是通过 TreeMap 来实现的(如同HashSet底层是是通过HashMap来实现、LinkedHashSet底层基于LinkedHashMap实现一样),因此二者的实现方式完全一样,都是红黑树算法。

 

相同点:

1TreeMapTreeSet都是有序的集合(仅仅key对象有序)。

2TreeMapTreeSet都是非同步集合,不过都可以使用Collections.synchroinzedMap()来实现同步。

3)内部对元素的操作时间复杂度都是O(logN),而HashMap/HashSet/HashTable则为O(1)

 

不同点:

最主要的区别就是TreeSetTreeMap分别实现SetMap接口,相同的道理可以用于HashSetHashMapLinkedHashMapLinkedHashSet

TreeSet只存储一个对象(通过MapKey的部分实现,下面贴出的HashSet的构造方法源码可以证明),而TreeMap存储两个对象KeyValue,前者通过put()将元素放入Map,后者使用add()将元素放入SetHashSet内部是大量借用HashMap的实现,它本身不过是调用HashMap的一个代理。

private transient HashMap
map; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); /** * Constructs a new, empty set; the backing HashMap instance has * default initial capacity (16) and load factor (0.75). */ public HashSet() { map = new HashMap
(); }public boolean add(E e) { return map.put(e, PRESENT)==null; } public boolean remove(Object o) { return map.remove(o)==PRESENT; } public boolean contains(Object o) { return map.containsKey(o); }

转载请注明出处:

同时请多点赞多多支持~

转载于:https://www.cnblogs.com/qitian1/p/6461465.html

你可能感兴趣的文章
overflow 属性
查看>>
Java中多态的一些简单理解
查看>>
洛谷 1449——后缀表达式(线性数据结构)
查看>>
[最小割][Kruskal] Luogu P5039 最小生成树
查看>>
Data truncation: Out of range value for column 'Quality' at row 1
查看>>
Dirichlet分布深入理解
查看>>
Javascript的调试利器:Firebug使用详解
查看>>
(转)Android之发送短信的两种方式
查看>>
使用vue脚手架搭建项目
查看>>
Java基础之ArrayList与LinkedList、Vector,以及HashMap与HashTable的区别
查看>>
网络爬虫初步:从一个入口链接开始不断抓取页面中的网址并入库
查看>>
iOS archive(归档)的总结 (序列化和反序列化,持久化到文件)
查看>>
python第九天课程:遇到了金角大王
查看>>
字符串处理
查看>>
ECharts(Enterprise Charts 商业产品图表库)初识
查看>>
LeetCode Factorial Trailing Zeroes (阶乘后缀零)
查看>>
hdu 5402 Travelling Salesman Problem (技巧,未写完)
查看>>
[AIR] 获取U盘,打开U盘
查看>>
HtmlUnitDriver 网页内容动态抓取
查看>>
ad logon hour
查看>>