加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
16925007307182.html 21.47 KB
一键复制 编辑 原始数据 按行查看 历史
leocook 提交于 2023-08-21 17:28 . x
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>
2.6. Map - TreeSet & TreeMap 源码解析 -
</title>
<meta name="description" content="">
<link href="atom.xml" rel="alternate" title="" type="application/atom+xml">
<link rel="stylesheet" href="asset/css/foundation.min.css" />
<link rel="stylesheet" href="asset/css/docs.css" />
<script src="asset/js/vendor/modernizr.js"></script>
<script src="asset/js/vendor/jquery.js"></script>
<script src="asset/highlightjs/highlight.pack.js"></script>
<link href="asset/highlightjs/styles/github.css" media="screen, projection" rel="stylesheet" type="text/css">
<script>hljs.initHighlightingOnLoad();</script>
</head>
<body class="antialiased hide-extras">
<div class="marketing off-canvas-wrap" data-offcanvas>
<div class="inner-wrap">
<nav class="top-bar docs-bar hide-for-small" data-topbar>
<div id="header">
<h1><a href="index.html"></a></h1>
</div>
</nav>
<nav class="tab-bar show-for-small">
<a href="javascript:void(0)" class="left-off-canvas-toggle menu-icon">
<span> &nbsp; </span>
</a>
</nav>
<aside class="left-off-canvas-menu">
<ul class="off-canvas-list">
<li><a href="index.html">Home</a></li>
<li class="divider"></li>
<li><label>1. Java 基础</label></li>
<li><a title="1. Java 基础" href="16923474457318.html">1. Java 基础</a></li>
<li class="divider"></li>
<li><label>2. JCF(Java 集合)</label></li>
<li><a title="2. JCF(Java 集合)" href="16923736135980.html">2. JCF(Java 集合)</a></li>
<li><a title="2.4. Map - HashSet & HashMap 源码解析" href="16924335832240.html">2.4. Map - HashSet & HashMap 源码解析</a></li>
<li><a title="2.5. Map - LinkedHashSet & LinkedHashMap源码解析" href="16924372183713.html">2.5. Map - LinkedHashSet & LinkedHashMap源码解析</a></li>
<li><a title="2.6. Map - TreeSet & TreeMap 源码解析" href="16925007307182.html">2.6. Map - TreeSet & TreeMap 源码解析</a></li>
<li class="divider"></li>
<li><label>mysql</label></li>
<li><a title="事务隔离" href="16925938083498.html">事务隔离</a></li>
<li class="divider"></li>
<li><label>常见文档</label></li>
<li><a title="Paxos选举算法" href="16926055818559.html">Paxos选举算法</a></li>
</ul>
</aside>
<a class="exit-off-canvas" href="#"></a>
<section id="main-content" role="main" class="scroll-container">
<div class="row">
<div class="large-3 medium-3 columns">
<div class="hide-for-small">
<div class="sidebar">
<nav>
<ul id="side-nav" class="side-nav">
<li class="side-title"><span>1. Java 基础</span></li>
<li><a title="1. Java 基础" href="16923474457318.html">1. Java 基础</a></li>
<li class="side-title"><span>2. JCF(Java 集合)</span></li>
<li><a title="2. JCF(Java 集合)" href="16923736135980.html">2. JCF(Java 集合)</a></li>
<li><a title="2.4. Map - HashSet & HashMap 源码解析" href="16924335832240.html">2.4. Map - HashSet & HashMap 源码解析</a></li>
<li><a title="2.5. Map - LinkedHashSet & LinkedHashMap源码解析" href="16924372183713.html">2.5. Map - LinkedHashSet & LinkedHashMap源码解析</a></li>
<li><a title="2.6. Map - TreeSet & TreeMap 源码解析" href="16925007307182.html">2.6. Map - TreeSet & TreeMap 源码解析</a></li>
<li class="side-title"><span>mysql</span></li>
<li><a title="事务隔离" href="16925938083498.html">事务隔离</a></li>
<li class="side-title"><span>常见文档</span></li>
<li><a title="Paxos选举算法" href="16926055818559.html">Paxos选举算法</a></li>
</ul>
</nav>
</div>
</div>
</div>
<div class="large-9 medium-9 columns">
<div class="markdown-body">
<h1>2.6. Map - TreeSet & TreeMap 源码解析</h1>
<ul>
<li>
<a href="#toc_0">2.6.1. TreeMap中左旋代码</a>
</li>
<li>
<a href="#toc_1">2.6.2. TreeMap中右旋代码</a>
</li>
<li>
<a href="#toc_2">2.6.3. 寻找节点后继</a>
</li>
<li>
<a href="#toc_3">2.6.4. get()</a>
</li>
<li>
<a href="#toc_4">2.6.5. put()</a>
</li>
<li>
<a href="#toc_5">2.6.6. remove()</a>
</li>
<li>
<a href="#toc_6">2.6.7. TreeSet</a>
</li>
</ul>
<p>之所以把 TreeSet 和 TreeMap 放在一起讲解,是因为二者在Java里有着相同的实现,前者仅仅是对后者做了一层包装,也就是说TreeSet里面有一个TreeMap(适配器模式)。</p>
<p>因此本文将重点分析TreeMap。Java TreeMap实现了SortedMap接口,也就是说会按照key的大小顺序对Map中的元素进行排序,key大小的评判可以通过其本身的自然顺序(natural ordering),也可以通过构造时传入的比较器(Comparator)。</p>
<p>TreeMap底层通过红黑树(Red-Black tree)实现,也就意味着containsKey(), get(), put(), remove()都有着log(n)的时间复杂度。<br/>
<img src="media/16925007307182/16925015375091.jpg" alt=""/></p>
<p>出于性能原因,TreeMap是非同步的(not synchronized),如果需要在多线程环境使用,需要程序员手动同步;或者通过 Collections.synchronizedXXX 包装成(wrapped)同步的:</p>
<pre><code>SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
</code></pre>
<p>红黑树是实际应用中最常用的平衡二叉查找树,它<B>不严格的具有平衡属性</B>,但平均的使用性能非常良好。相比与拥有更高平衡性的AVL树,红黑树失衡恢复较快,用此在删除和插入时性能更高,而AVL树属于易查找但不易删除、插入得平衡二叉树。综合来看,红黑树有着较为均衡的性能表现。</p>
<p>很多人到这里就直接晕了,那么我们先聊一下二叉树的相关知识,再聊一下红黑树的左旋、右旋、以及插入和删除后的调整策略。</p>
<h3 id="toc_0">2.6.1. TreeMap中左旋代码</h3>
<pre><code>//Rotate Left
private void rotateLeft(Entry&lt;K,V&gt; p) {
if (p != null) {
Entry&lt;K,V&gt; r = p.right;
p.right = r.left;
if (r.left != null)
r.left.parent = p;
r.parent = p.parent;
if (p.parent == null)
root = r;
else if (p.parent.left == p)
p.parent.left = r;
else
p.parent.right = r;
r.left = p;
p.parent = r;
}
}
</code></pre>
<h3 id="toc_1">2.6.2. TreeMap中右旋代码</h3>
<pre><code>//Rotate Right
private void rotateRight(Entry&lt;K,V&gt; p) {
if (p != null) {
Entry&lt;K,V&gt; l = p.left;
p.left = l.right;
if (l.right != null) l.right.parent = p;
l.parent = p.parent;
if (p.parent == null)
root = l;
else if (p.parent.right == p)
p.parent.right = l;
else p.parent.left = l;
l.right = p;
p.parent = l;
}
}
</code></pre>
<h3 id="toc_2">2.6.3. 寻找节点后继</h3>
<p>TreeMap中寻找节点后继的代码如下:<br/>
```<br/>
// 寻找节点后继函数successor()<br/>
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {<br/>
if (t == null)<br/>
return null;<br/>
else if (t.right != null) { //1. t的右子树不空,则t的后继是其右子树中最小的那个元素<br/>
Entry<K,V> p = t.right;<br/>
while (p.left != null) //1.1. 一直找到该节点右子节点下的最左节点<br/>
p = p.left;<br/>
return p;<br/>
} else {// 2. t的右孩子为空,则t的后继是其第一个向右走的祖先<br/>
Entry<K,V> p = t.parent;<br/>
Entry<K,V> ch = t;</p>
<pre><code> /*
* 1.父节点为空时,说明该节点是跟节点,直接返回;
* 2.向上寻找时,当前节点不是父节点的右子节点(父节点第一次右拐)时,
* 返回右拐后的第一个节点。
*/
while (p != null &amp;&amp; ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
</code></pre>
<p>}<br/>
```</p>
<h3 id="toc_3">2.6.4. get()</h3>
<p>get(Object key)方法根据指定的key值返回对应的value,该方法调用了getEntry(Object key)得到相应的entry,然后返回entry.value。因此getEntry()是算法的核心。算法思想是根据key的自然顺序(或者比较器顺序)对二叉查找树进行查找,直到找到满足k.compareTo(p.key) == 0的entry。</p>
<p><img src="media/16925007307182/16925213108570.jpg" alt=""/></p>
<p>具体代码如下:</p>
<pre><code>//getEntry()方法
final Entry&lt;K,V&gt; getEntry(Object key) {
......
if (key == null)//不允许key值为null
throw new NullPointerException();
Comparable&lt;? super K&gt; k = (Comparable&lt;? super K&gt;) key;//使用元素的自然顺序
Entry&lt;K,V&gt; p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp &lt; 0) //key小于当前比较节点的值,向左子树查找
p = p.left;
else if (cmp &gt; 0) //key大于当前比较节点的值,向右子树查找
p = p.right;
else //key等于当前比较节点的值,说明找到了,直接返回
return p;
}
return null;
}
</code></pre>
<h3 id="toc_4">2.6.5. put()</h3>
<p>put(K key, V value)方法是将指定的key, value对添加到map里。该方法首先会对map做一次查找,看是否包含该元组,如果已经包含则直接返回,查找过程类似于getEntry()方法;如果没有找到则会在红黑树中插入新的entry,如果插入之后破坏了红黑树的约束条件,还需要进行调整(旋转,改变某些节点的颜色)。</p>
<pre><code>public V put(K key, V value) {
......
int cmp;
Entry&lt;K,V&gt; parent;
if (key == null)
throw new NullPointerException();
Comparable&lt;? super K&gt; k = (Comparable&lt;? super K&gt;) key;//使用元素的自然顺序
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp &lt; 0) t = t.left; //key小于当前比较节点的值,向左子树查找
else if (cmp &gt; 0) t = t.right; //key大于当前比较节点的值,向右子树查找
else return t.setValue(value); //key值已存在,直接覆盖,并直接返回
} while (t != null); //找到要插入的叶子节点时跳出循环,插在 parent 下
Entry&lt;K,V&gt; e = new Entry&lt;&gt;(key, value, parent);//创建并插入新的entry
if (cmp &lt; 0) parent.left = e;//key小于parent节点的值,应该插在左边
else parent.right = e; //key大于parent节点的值,应该插在左边
fixAfterInsertion(e);//修复红黑树,保持平衡性
size++;//插入成功,size+1
return null;
}
</code></pre>
<p>上述代码就在红黑树上找到合适的位置,然后创建新的entry并插入,如key节点已存在则直接覆盖。</p>
<p>插入值后需要检查和修复树的平衡度,则调用 fixAfterInsertion() 实现:1.改变某些节点的颜色;2.对某些节点进行旋转。<br/>
<img src="media/16925007307182/16925225781198.jpg" alt=""/></p>
<pre><code>/**
* 采用先序遍历的方式来对红黑树进行调整。&lt;br/&gt;
* 找到合适的地方进行左旋与右旋
*/
private void fixAfterInsertion(Entry&lt;K,V&gt; x) {
x.color = RED; //当前写入的节点颜色变为RED
while (x != null &amp;&amp; x != root &amp;&amp; x.parent.color == RED) {
//假设x节点的父节点为A,A若是A父节点的左节点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry&lt;K,V&gt; y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x); //左旋
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x))); //右旋
}
} else {
Entry&lt;K,V&gt; y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x); //右旋
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x))); //左旋
}
}
}
root.color = BLACK;
}
</code></pre>
<h3 id="toc_5">2.6.6. remove()</h3>
<p>remove(Object key) 的作用是删除 key 值对应的 entry,该方法首先通过上文中提到的 getEntry(Object key) 方法找到 key值 对应的 entry,然后调用 deleteEntry(Entry<K,V> entry) 删除对应的 entry。由于删除操作会改变红黑树的结构,有可能破坏红黑树的约束条件,因此有可能要进行调整。<br/>
deleteEntry() 函数删除指定的entry并在红黑树的约束被破坏时进行调用 fixAfterDeletion(Entry<K,V> x) 进行调整。<br/>
由于红黑树是一棵增强版的二叉查找树,红黑树的删除操作跟普通二叉查找树的删除操作也就非常相似,区别是红黑树在节点删除之后可能需要进行调整。<br/>
一棵普通二叉查找树的删除过程,可以简单分为两种情况:</p>
<ul>
<li><p>情况一:删除节点p的左右子树都为空,或者只有一棵子树非空。<br/>
当左右子树都为空时,直接将节点p删除。当只有一棵子树时,直接用子树替代节点p。</p></li>
<li><p>情况二:删除点p的左右子树都非空。<br/>
可以用p的后继节点s(树中大于x的最小的那个元素)代替p。红黑树的节点删除函数deleteEntry()代码如下:</p></li>
</ul>
<pre><code>// 红黑树entry删除函数deleteEntry()
private void deleteEntry(Entry&lt;K,V&gt; p) {
modCount++;
size--;
if (p.left != null &amp;&amp; p.right != null) { //待删除节点p的左右子树都非空。
Entry&lt;K,V&gt; s = successor(p); //找到节点p的后继节点
p.key = s.key;
p.value = s.value;
p = s;
}
Entry&lt;K,V&gt; replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {// 1. 删除点p只有一棵子树非空。
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;
p.left = p.right = p.parent = null;
if (p.color == BLACK)
fixAfterDeletion(replacement);// 调整
} else if (p.parent == null) {
root = null;
} else { // 1. 删除点p的左右子树都为空
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;
}
}
}
</code></pre>
<p>只有删除BLACK节点的时候,才会触发调整函数,因为删除RED节点不会破坏红黑树的任何约束,而删除BLACK节点会破坏红黑树的规则。</p>
<p>与 fixAfterInsertion() 函数一样,这里也分成多种情况。但具体的调整操作只有两种:</p>
<ul>
<li>1.改变某些节点的颜色;</li>
<li>2.对某些节点进行旋转。</li>
</ul>
<p><img src="media/16925007307182/16925467665936.jpg" alt=""/></p>
<p>代码如下:</p>
<pre><code>private void fixAfterDeletion(Entry&lt;K,V&gt; x) {
while (x != root &amp;&amp; colorOf(x) == BLACK) {
if (x == leftOf(parentOf(x))) {
Entry&lt;K,V&gt; sib = rightOf(parentOf(x));
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateLeft(parentOf(x)); //左旋
sib = rightOf(parentOf(x));
}
if (colorOf(leftOf(sib)) == BLACK &amp;&amp;
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(rightOf(sib)) == BLACK) {
setColor(leftOf(sib), BLACK);
setColor(sib, RED);
rotateRight(sib); //右旋
sib = rightOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(rightOf(sib), BLACK);
rotateLeft(parentOf(x)); //左旋
x = root;
}
} else { // 跟前四种情况对称
Entry&lt;K,V&gt; sib = leftOf(parentOf(x));
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateRight(parentOf(x)); //右旋
sib = leftOf(parentOf(x));
}
if (colorOf(rightOf(sib)) == BLACK &amp;&amp;
colorOf(leftOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(leftOf(sib)) == BLACK) {
setColor(rightOf(sib), BLACK);
setColor(sib, RED);
rotateLeft(sib); //左旋
sib = leftOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(leftOf(sib), BLACK);
rotateRight(parentOf(x)); //右旋
x = root;
}
}
}
setColor(x, BLACK);
}
</code></pre>
<h3 id="toc_6">2.6.7. TreeSet</h3>
<p>前面已经说过TreeSet是对TreeMap的简单包装,对TreeSet的函数调用都会转换成合适的TreeMap方法,因此TreeSet的实现非常简单。这里不再赘述。</p>
<pre><code>// TreeSet是对TreeMap的简单包装
public class TreeSet&lt;E&gt; extends AbstractSet&lt;E&gt;
implements NavigableSet&lt;E&gt;, Cloneable, java.io.Serializable
{
......
private transient NavigableMap&lt;E,Object&gt; m;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public TreeSet() {
this.m = new TreeMap&lt;E,Object&gt;();// TreeSet里面有一个TreeMap
}
......
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
......
}
</code></pre>
</div>
<br /><br />
<hr />
<div class="row clearfix">
<div class="large-6 columns">
<div class="text-left" style="padding:15px 0px;">
<a href="16924372183713.html" title="Previous Post: 2.5. Map - LinkedHashSet & LinkedHashMap源码解析">&laquo; 2.5. Map - LinkedHashSet & LinkedHashMap源码解析</a>
</div>
</div>
<div class="large-6 columns">
<div class="text-right" style="padding:15px 0px;">
<a href="16926055818559.html"
title="Next Post: Paxos选举算法">Paxos选举算法 &raquo;</a>
</div>
</div>
</div>
<div class="row">
<div style="padding:0px 0.93em;" class="share-comments">
</div>
</div>
<script type="text/javascript">
$(function(){
var currentURL = '16925007307182.html';
$('#side-nav a').each(function(){
if($(this).attr('href') == currentURL){
$(this).parent().addClass('active');
}
});
});
</script>
</div></div>
<div class="page-bottom">
<div class="row">
<hr />
<div class="small-9 columns">
<p class="copyright">Copyright &copy; 2015
Powered by <a target="_blank" href="http://www.mweb.im">MWeb</a>,&nbsp;
Theme used <a target="_blank" href="http://github.com">GitHub CSS</a>.</p>
</div>
<div class="small-3 columns">
<p class="copyright text-right"><a href="#header">TOP</a></p>
</div>
</div>
</div>
</section>
</div>
</div>
<script src="asset/js/foundation.min.js"></script>
<script src="asset/js/foundation/foundation.offcanvas.js"></script>
<script>
$(document).foundation();
</script>
</body>
</html>
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化