博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【java集合框架源码剖析系列】java源码剖析之TreeMap
阅读量:6224 次
发布时间:2019-06-21

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

注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本。本博客将从源码角度带领大家学习关于TreeMap的知识。

一TreeMap的定义:

public class TreeMap
extends AbstractMap
implements NavigableMap
, Cloneable, java.io.Serializable
可以看到TreeMap是继承自AbstractMap同时实现了NavigableMap,Cloneable,Serializable三个接口,其中Cloneable,Serializable这两个接口基本上是java集合框架中所有的集合类都要实现的接口。

二TreeMap类中的一些重要属性:

 private final Comparator
comparator; private transient Entry
root; private transient int size = 0; private transient int modCount = 0;
第一个属性是Comparator<? super K> comparator比较器,从这里就可以知道TreeMap会运用比较器接口来对插入的元素进行排序。而第二个成员属性为Entry<K,V>即为红黑树,红黑树是一种数据结构,它和AVL树一样是一种自平衡二叉查找树,该数据结构具备非常高的插入,删除,查找的效率。Entry被定义为TreeMap的一个内部类,代码如下:

static final class Entry
implements Map.Entry
{ K key; V value; Entry
left; Entry
right; Entry
parent; boolean color = BLACK; /** * Make a new cell with given key, value, and parent, and with * {@code 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; } }
可以看到Entry红黑树的代码一点也不复杂,和普通的二叉树差不多,仅仅多了一个判断颜色的属性boolean color,该属性默认值为黑色,即BLACK,关于红黑树的具体知识,在此不做过多介绍,博主打算在数据结构与算法那块进行详细介绍。可以先点此 查看百度百科做初步了解。

三TreeMap内部的实现原理:我们首先看一下其构造器

public TreeMap() {// 构造方法一,默认的构造方法,comparator为空,即采用自然顺序维持TreeMap中节点的顺序        comparator = null;    } public TreeMap(Comparator
comparator) {// 构造方法二,提供指定的比较器 this.comparator = comparator; }public TreeMap(Map
m) {// 构造方法三,采用自然序维持TreeMap中节点的顺序,同时将传入的Map中的内容添加到TreeMap中 comparator = null; putAll(m); }/** *构造方法四,接收SortedMap参数,根据SortedMap的比较器维持TreeMap中的节点顺序, 同时通过buildFromSorted(int size, Iterator it, java.io.ObjectInputStream str, V defaultVal)方法将SortedMap中的内容添加到TreeMap中*/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参数采用自然序维持节点的顺序,因为很多情况下,不同对象的比较大小的方法是不一样的,所以很多时候我们需要自己指定比较器。另外可以看到在构造器三种调用了putAll方法,我们来看一下其源码:

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); } public void putAll(Map
m) { for (Map.Entry
e : m.entrySet()) put(e.getKey(), e.getValue()); }
我们可以看到在putAll方法中调用了buildFromSorted(int size, Iterator<?> it, java.io.ObjectInputStream str,V defaultVal),该方法的作用即是在线性时间内对数据进行排序(Linear time tree building algorithm from sorted data),看到这里我们就明白TreeMap排序的原理了,即当使用一个Map集合作为参数构造一个TreeMap的时候,TreeMap会将Map中的元素先排序,然后排序后的元素put到TreeMap中。其中在TreeMap的putAll方法的最后会调用其父类AbstractMap的putAll方法,在其父类的putAll方法中才会调用put方法。

四TreeMap中的重要函数:

1put方法

public V put(K key, V value) {        Entry
t = root; if (t == null) { compare(key, key); // type (and possibly null) check root = new Entry<>(key, value, null); size = 1; modCount++; return null; } int cmp; Entry
parent; // split comparator and comparable paths Comparator
cpr = comparator; if (cpr != null) {//如果比较器 cpr 不为 null,即表明采用自定义的排序 do {// do while作用是在以root为根节点的红黑树中根据传入的key值寻找待插入的位置 parent = t; cmp = cpr.compare(key, t.key);//将待插入节点的值与当前节点比较 if (cmp < 0)//如果待插入的节点的值小于当前节点,则将当前结点的左孩子作为新的当前结点 t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value);// 如果两个 key 相等,新的 value 覆盖原有的 value, 然后返回原 value } while (t != null); } 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); } Entry
e = new Entry<>(key, value, parent);//当没找到key值相同的节点,则创建新的节点存储该key值 if (cmp < 0) parent.left = e;// 如果新插入 key 小于 parent 的 key,则 e 作为 parent 的左子节点 else parent.right = e;// 如果新插入 key 小于 parent 的 key,则 e 作为 parent 的右子节点 fixAfterInsertion(e);// 修复红黑树,当往TreeMap中插入新的节点之后可能破坏了红黑树的性质,所以得调用该函数将其调整为红黑树 size++; modCount++; return null; }

从上面的代码可以看到put方法的本质就是构造排序二叉树的过程,即当往TreeMap中添加一个节点元素时,首先会寻找待插入的位置,如果在寻找的过程中在TreeMap中找到了与待插入节点的key值相同的节点,则替换然后返回该原来的vlaue,如果没找到,则创建一个新的节点,在恰当的位置处插入该结点,插入完之后会调用fixAfterInsertion(e);来重新修复TreeMap,使其始终满足红黑树的性质。因此可以看到对于相同的key只存在唯一的value值与之对应,因为原来的会被新的替换。

2get方法

public V get(Object key) {        Entry
p = getEntry(key); return (p==null ? null : p.value); } final Entry
getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null)// 如果比较器不为空,返回getEntryUsingComparator(Object key)的结果 return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable
k = (Comparable
) key; Entry
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方法中会调用getEntry()方法,getEntry()方法会根据传入的key值寻找相应的value然后返回,get的过程也包含两种情况即依据比较器是否为空分别进行get操作,get寻找的过程事实上与构造二叉排序树的过程非常相似,代码也很简单,在此不做赘述。

3remove方法

public V remove(Object key) {        Entry
p = getEntry(key); if (p == null) return null; V oldValue = p.value; deleteEntry(p); return oldValue; } private void deleteEntry(Entry
p) { modCount++; size--; // If strictly internal, copy successor's element to p and then make p // point to successor. 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. Entry
replacement = (p.left != null ? p.left : p.right); if (replacement != null) { // Link replacement to parent 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; } } }
可以看到在remove方法中调用了deleteEntry方法,即用来从红黑树中删除某一个节点,在这个过程中同样会调用fixAfterDeletion(p);方法,即涉及到树的调整过程。

4clear()方法

public void clear() {        modCount++;        size = 0;        root = null;    }
代码非常简洁,主要就是将size置为0,同时将根节点root置为null,这样就不能通过root访问其它的节点,这样GC就会回收该TreeMap的内存空间。

5containsKey(Object key)/containsValue(Object value)

public boolean containsKey(Object key) {        return getEntry(key) != null;    }public boolean containsValue(Object value) {        for (Entry
e = getFirstEntry(); e != null; e = successor(e)) if (valEquals(value, e.value)) return true; return false; }
其中containsKey非常简单,不做赘述,在containsValue(Object value)中可以看到调用了getFirstEntry()方法和successor(e)方法,我们来看一下其源码:

final Entry
getFirstEntry() { Entry
p = root; if (p != null) while (p.left != null) p = p.left; return p; } static
TreeMap.Entry
successor(Entry
t) { if (t == null) return null; else if (t.right != null) { Entry
p = t.right; while (p.left != null) p = p.left; return p; } else { Entry
p = t.parent; Entry
ch = t; while (p != null && ch == p.right) { ch = p; p = p.parent; } return p; } }
其中getFirstEntry()方法是用来取整个红黑树中的第一个节点,实际是获取的整棵树中“最左”的节点,因为红黑树是排序的树,所以“最左”的节点也是值最小的节点。而successor(e)方法是返回节点e的继承者,如果e的左孩子非空则返回其左孩子,因此在for循环中配合使用getFirstEntry()方法和successor(Entry<K,V> e)及e!=null是遍历树的一种方法。

五总结:

1TreeMap中的元素是排序的,其内部是通过Comparator接口来实现的,可以通过Comparator接口自定义排序规则。

2TreeMap内部是采用红黑树Entry来实现的,当使用一个Map集合作为参数构造一个TreeMap的时候,TreeMap会将Map中的元素先排序,然后排序后的元素put到TreeMap中,put的过程本质上是构造二叉排序树的过程,插入完之后会调用fixAfterInsertion(e);来重新修复TreeMap,使其始终满足红黑树的性质。

3TreeMap中的元素的key值是唯一的且对于相同的key只存在唯一的value值与之对应,因为在put的过程中原来的会被新的替换。

4TreeMap不是线程同步的,因为TreeMap中的方法都未使用synchronized关键字修饰,即TreeMap是非同步的。

转载于:https://www.cnblogs.com/hainange/p/6334037.html

你可能感兴趣的文章
WPS Office Linux版本一年未更新:已中止开发
查看>>
云计算性能常见问题:云计算何处何从?
查看>>
优秀OA系统的五大特性
查看>>
线路愈加明晰?万达牵手IBM进军公有云业务
查看>>
【转】Zookeeper-Watcher机制与异步调用原理
查看>>
纽约州推出“被遗忘权”提案 用户或能要求将个人隐私信息从搜索结果中移
查看>>
降低测试难度及成本 加速物联网普及
查看>>
融入欧洲产业链 华为在数学上投注希望
查看>>
中国实现城域量子隐形传态为全球量子网络打基础
查看>>
超算入云
查看>>
沃达丰完成5G毫微波测试 室外单用户速率达到20Gbps
查看>>
Facebook宣布支持在Android上使用Tor访问
查看>>
即便背靠微信,微信企业号累积 2000 万用户也用了近两年时间
查看>>
MuleSoft发布新的Anypoint Platform,用户可操控API
查看>>
牙疼怎么快速止痛,三招解决牙痛立竿见影
查看>>
大数据云计算悄然改变服务器市场格局 英特尔霸主地位受IBM、ARM威胁
查看>>
英利宣布退出欧盟限价限协议
查看>>
深圳运用大数据推动"智慧司法"
查看>>
Windows 10免费升级服务终成历史 说说我们和它的恩怨
查看>>
苹果为何在中国一南一北设两个研发中心?五重考量
查看>>