-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
294 lines (161 loc) · 309 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Redmaple1的博客</title>
<subtitle>我的个人技术博客</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://redmapleren.com/"/>
<updated>2020-12-11T12:18:21.223Z</updated>
<id>http://redmapleren.com/</id>
<author>
<name>Xiaoya Ren</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>不一样的tree-并查集</title>
<link href="http://redmapleren.com/2020/12/07/%E4%B8%8D%E4%B8%80%E6%A0%B7%E7%9A%84tree-%E5%B9%B6%E6%9F%A5%E9%9B%86/"/>
<id>http://redmapleren.com/2020/12/07/不一样的tree-并查集/</id>
<published>2020-12-07T07:55:00.000Z</published>
<updated>2020-12-11T12:18:21.223Z</updated>
<content type="html"><![CDATA[<p>    很久没有更新文章了,周末又巩固了一下数据结构基础,这次我们来看一下有些不一样的树结构-并查集。</p><h1 id="一、什么是并查集"><a href="#一、什么是并查集" class="headerlink" title="一、什么是并查集"></a>一、什么是并查集</h1><p>    下图所示的一些节点,节点之间可以相连,如果想要知道某两个节点是否可以连通,这个问题如何解呢?<br>    这是典型的连接问题,像下图这种节点很少的情况,肉眼一看就会得到答案,但当有成千上万的节点时,又该如何解决问题呢?我们要说的并查集就是解决连接问题的一种解决方案。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/%E8%BF%9E%E6%8E%A5%E9%97%AE%E9%A2%98.png" alt="image"> </p><p>    并查集可以非常快速地判断网络中节点的连接状态。这里的网络是一个抽象的概念,比如可以是社交网络,那么人就是一个一个的节点,人与人之间的关系就是节点与节点之间的边,若要知道两个人之间是否有直接或间接的好友关系,就可以使用并查集来实现。 </p><p>    对于一组数据,并查集主要支持两个动作: </p><ul><li><code>union(p,q)</code><br>在并查集内部,将p、q两个元素以及他们所在的集合合并起来。</li><li><code>isConnected(p,q)</code><br>查询对于给定的两个数据p、q,他们是否属于同一个集合。 </li></ul><p>    对于并查集的这两个动作,我们可以抽象出一个接口来表示。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * UnionFind并查集接口</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> renxiaoya</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2020-12-06</span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UF</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取并查集中有多少元素</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 并查集中有多少元素</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">getSize</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 两个元素是否相连</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> p 元素编号p</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> q 元素编号q</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 是否相连</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">boolean</span> <span class="title">isConnected</span><span class="params">(<span class="keyword">int</span> p, <span class="keyword">int</span> q)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 将两个元素并在一起</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> p 元素编号p</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> q 元素编号q</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">unionElement</span><span class="params">(<span class="keyword">int</span> p, <span class="keyword">int</span> q)</span></span>;</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这里我们的p和q指的是一个抽象的编号,可以理解为索引编号,比如<code>isConnected(p,q)</code>代表了编号为p的元素和编号为q的元素是否相连,至于p、q元素到底指的是什么,我们并不关心。接下来,我们来看看如何实现这个接口,实现并查集的功能。</p><h1 id="二、并查集的实现"><a href="#二、并查集的实现" class="headerlink" title="二、并查集的实现"></a>二、并查集的实现</h1><p>    假设有10个元素,编号从0到9,当然这些节点都是业务的抽象,可以是10个人、10辆车、10本书等等,具体的含义与不同的业务逻辑有关。在并查集的内部,我们只存这10个编号,表示具体的10个元素,每一个节点所存储的是它所属的这个集合的id。下图中,元素0所属的集合id为0,元素1所属的集合id为1,元素2所属的集合id为0,以此类推。不同的id值可以理解为不同的集合所对应的编号。下图的例子可以看成是把10个元素分成了两个集合,其中0,2,3,6,7,9这几个元素在一个集合中1,4,5,8这几个元素在另外的一个集合中。其实可以很容易的想到,我们可以使用一个数组来存储这样的结构,数组的索引就是对应的元素编号,索引所对应数组中的值就是集合的id。图中的元素0和元素2所对应的id值都为0,可以说元素0和元素2可以相连接。 </p><h2 id="Quick-Find"><a href="#Quick-Find" class="headerlink" title="Quick Find"></a>Quick Find</h2><p>    上面说到并查集主要的两个操作<code>union(p,q)</code>和<code>isConnected(p,q)</code>,对于数组存储的并查集来说,可以很容易的回答<code>isConnected(p,q)</code>,只需要看,数组索引p、q对应的值是否相同即可。这里我们把找到数组索引p或者q对应值的过程,也抽象为一个方法<code>find(p)</code>。那么判断<code>isConnected(p,q)</code>就转化为了判断<code>find(p)</code>与<code>find(q)</code>是否相等。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/isConnected%E8%BD%AC%E5%8C%96.png" width="50%" height="50%"><br>    很显然,<strong>以上操作的时间复杂度是<code>O(1)</code>,对于这种存储的并查集,find这个操作是非常快的。</strong> </p><p>    上面介绍了find的思路,接下来我们来看一下并查集另一个重要的动作<code>union(p,q)</code>,我们可以称为Quick Union。<br>    还是以上面的并查集为例,如果想要实现<code>union(1,2)</code>,就需要把元素1所在集合与元素2所在集合合并起来,也就是说,这两个集合合并之后,集合中每一个元素对应的集合id值都是相同的。我们可以把元素1所在集合合并到元素2所在集合,也可以把元素2所在集合合并到元素1所在集合,要么全是0,要么全是1。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/quickUnion%E5%9B%BE%E7%A4%BA.png" width="60%" height="60%"></p><p>    实现思路也很简单,先找到元素p、q对应的id,如果id已经是相等的,说明元素p、q已经在一个集合中,直接return即可;否则的话,遍历一遍当前数组,每次遍历,如果当前索引下标对应的值与元素p对应的id相等,此时将当前索引下标对应的值设置为元素q对应的id。<br>    <strong>因为遍历了一遍数组元素,所以<code>union(p,q)</code>的时间复杂度是<code>O(n)</code></strong>。</p><p>    因为find操作时间复杂度是<code>O(1)</code>,而union的时间复杂度是<code>O(n)</code>,所以这样实现的并查集我们也称为是<strong>Quick Find</strong><br>    思路清楚了,下面我们看看代码如何实现<strong>Quick Find</strong>的并查集。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 并查集实现 - v1</span></span><br><span class="line"><span class="comment"> * <p></span></span><br><span class="line"><span class="comment"> * 0 1 2 3 4 5 6 7 8 9</span></span><br><span class="line"><span class="comment"> * -------------------</span></span><br><span class="line"><span class="comment"> * 0 1 0 1 0 1 0 1 0 1</span></span><br><span class="line"><span class="comment"> * <p></span></span><br><span class="line"><span class="comment"> * OP | 时间复杂度</span></span><br><span class="line"><span class="comment"> * unionElement(p,q) --> O(n)</span></span><br><span class="line"><span class="comment"> * isConnected(p,q) --> O(1)</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> renxiaoya</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2020-12-06</span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UnionFind1</span> <span class="keyword">implements</span> <span class="title">UF</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span>[] id;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">UnionFind1</span><span class="params">(<span class="keyword">int</span> size)</span> </span>{</span><br><span class="line"> id = <span class="keyword">new</span> <span class="keyword">int</span>[size];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < id.length; i++) {</span><br><span class="line"> id[i] = i;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getSize</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> id.length;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isConnected</span><span class="params">(<span class="keyword">int</span> p, <span class="keyword">int</span> q)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> find(p) == find(q);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">unionElement</span><span class="params">(<span class="keyword">int</span> p, <span class="keyword">int</span> q)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> pId = find(p);</span><br><span class="line"> <span class="keyword">int</span> qId = find(q);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (pId == qId) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < id.length; i++) {</span><br><span class="line"> <span class="keyword">if</span> (id[i] == pId) {</span><br><span class="line"> id[i] = qId;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查找元素p对应的集合编号</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> p 元素p</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 集合编号</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">find</span><span class="params">(<span class="keyword">int</span> p)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (p < <span class="number">0</span> || p >= id.length) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"p is out of bound."</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> id[p];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>    以上,我们实现了<strong>Quick Find</strong>的并查集。既然有<strong>Quick Find</strong>,是否有<strong>Quick Union</strong>呢?答案是肯定的。接下来我们来看一看如改进上面的实现,来提高union操作的效率。</p><h2 id="Quick-Union"><a href="#Quick-Union" class="headerlink" title="Quick Union"></a>Quick Union</h2><p>    上面实现的<strong>Quick Find</strong>好像和树没有任何关系,不要着急,我们来看看<strong>Quick Union</strong>的实现思路。<br>    我们把每一个元素都看作是一个节点,节点之间相连接,形成了一个树的结构。不过这里的树和之前实现的二叉树、trie等是不同的,在<strong>并查集中实现的树结构是孩子指向父亲的</strong>。 </p><p>    看个简单的示例。节点3与节点2相连接,其实就是节点3指向了节点2,而这里节点2是树的根节点,对于节点2,它指向自己。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/3%E6%8C%87%E5%90%912.png" width="40%" height="40%"><br>    如果节点1想要和节点3合并,就把节点1指向节点3所在树的根节点,也就是节点2。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/%E8%8A%82%E7%82%B91%E5%90%88%E5%B9%B6%E5%88%B0%E8%8A%82%E7%82%B93.png" width="60%" height="60%"><br>    如果并查集中有一棵树是左边子树的样子,5和6节点指向7节点,此时想要将节点7与节点2进行合并,如何操作呢?实际上是将节点7所在树的根节点指向节点2即可。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/%E5%B7%A6%E5%AD%90%E6%A0%91%E5%90%88%E5%B9%B6%E5%88%B02.png" width="80%" height="80%"><br>    如果我们想将节点7与节点3合并,得到的结果还是上图的样子,其实合并操作是将节点7所在树的根节点指向节点3所在树的根节点,也就是节点2。 </p><p>    以上就是真正并查集实现的思路,和上面<strong>Quick Find</strong>实现的存储发生了变化,实际上也是很简单的。通过观察,我们发现针对每一个节点,只有一个指针,只会指向另一个元素,对于这个指针的存储,我们依然可以使用数组的方式来存储。这个数组,我们可以叫作<code>parent</code>,<code>parent[i]</code>表示的就是第i个元素所在的节点指向了哪个元素。<br>    在初始化的时候,每一个节点都没有和任何一个节点合并,都指向了自己,也就是<code>parent[i]</code>都等于<code>i</code>。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/parent%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81.png" width="80%" height="80%"> </p><p>    当要执行<code>union(4,3)</code>时,将节点4指向节点3,对应parent数组,就是将<code>parent[4]</code>设置为<code>parent[3]</code>的值。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/parent-union4-3.png" width="80%" height="80%"> </p><p>    要执行<code>union(3,8)</code>,就是将节点3所在的树指向节点8,相应的<code>parent[3]</code>设置为8;执行<code>union(6,5)</code>,就是将节点6指向节点5,<code>parent[6]</code>设置为5。</p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/union3-8-union6-5.png" width="80%" height="80%"> </p><p>    执行<code>union(9,4)</code>,就是将节点9指向节点4所在这棵树的根节点,这里涉及到了一个查询操作,查询节点4所在的根节点,我们先看节点4的父节点是3,不是4本身,继续查找节点3的父节点为节点8,也不是节点3本身,说明还不是这棵树的根节点,我们继续往下找,看节点8的父节点是8,与节点8本身相等,说明这是个根节点,此时我们便找到了节点4所在这棵树的根节点,也就是节点8。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/parent-union9-4.png" width="80%" height="80%"><br>    上图中可以看到,节点9指向了节点8,我们为什么不直接指向节点4呢?很显然,指向节点4的话就形成了一个链表,之后如果再进行查找节点9所在树的根节点时,就等同于遍历了这个链表,树的优势就体现不出来了;而指向节点8时,当查找节点9所在树的根节点时,我们向上查1次就能得到结果。所以在数组中,<code>parent[9]</code>设置成8。 </p><p>    按照上面的规律,我们执行<code>union(2,1)</code>和<code>union(5,0)</code>,可以得到下图的结果。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/union2-1-union-5-0.png" width="80%" height="80%"> </p><p>    执行<code>union(7,2)</code>和<code>union(6,2)</code>,前者很简单,无需多说,后者稍微复杂一些,我们要找到节点6所在树的根节点,让它指向节点2所在树的根节点,也就是节点0指向节点1。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/union-7-2-union-6-2.png" width="80%" height="80%"> </p><p>    以上的操作,是并查集通常的实现。通过分析,我们知道<strong>这种<code>union</code>操作的时间复杂度是O(h)</strong>,h表示的是union的两个元素所在树深度的大小,通常这个深度的大小比元素的个数要小的多,所以这个<code>union</code>的过程相对来说会快一些;不过相应的代价就是查询操作,如果我们想知道节点5和节点4是否相连,我们要找到节点5和节点4对应的根节点,比较两个根节点是否相等,这里节点5的根节点是1,节点4的根节点是8,1和8不相等,所以节点5和节点4不是相连的,<strong>这一查找的过程时间复杂度也是O(h)</strong>。我们通过牺牲了查询操作的性能提升了合并操作的性能,不过通常情况下,树的深度是远远小于元素的总数的,所以我们让查询和合并都是树的高度这个时间复杂度,在大多数情况下,性能是可以接受的。 </p><p>    知道了查找和合并的思路,下面我们来看看代码实现。 </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 并查集实现 - v2</span></span><br><span class="line"><span class="comment"> * parent 0 1 2 3 4 5 6 7 8 9</span></span><br><span class="line"><span class="comment"> * ---------------------</span></span><br><span class="line"><span class="comment"> * 1 1 0 3 4 5 6 6 5 9</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Operate | 时间复杂度</span></span><br><span class="line"><span class="comment"> * unionElement(p,q) --> O(h) h: height of tree</span></span><br><span class="line"><span class="comment"> * isConnected(p,q) --> O(h) h: height of tree</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> renxiaoya</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2020-12-06</span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UnionFind2</span> <span class="keyword">implements</span> <span class="title">UF</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span>[] parent;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">UnionFind2</span><span class="params">(<span class="keyword">int</span> size)</span> </span>{</span><br><span class="line"> parent = <span class="keyword">new</span> <span class="keyword">int</span>[size];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < size; i++) {</span><br><span class="line"> parent[i] = i;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getSize</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> parent.length;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isConnected</span><span class="params">(<span class="keyword">int</span> p, <span class="keyword">int</span> q)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> find(p) == find(q);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">unionElement</span><span class="params">(<span class="keyword">int</span> p, <span class="keyword">int</span> q)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> pRoot = find(p);</span><br><span class="line"> <span class="keyword">int</span> qRoot = find(q);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (pRoot == qRoot) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> parent[pRoot] = qRoot;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查找元素p对应的集合编号</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> p 元素p</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> p对应的元素编号</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">find</span><span class="params">(<span class="keyword">int</span> p)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (p < <span class="number">0</span> || p >= parent.length) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"p is out of bound."</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//不断地查找根节点</span></span><br><span class="line"> <span class="keyword">while</span> (p != parent[p]) {</span><br><span class="line"> p = parent[p];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> p;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="三、可以再优化吗"><a href="#三、可以再优化吗" class="headerlink" title="三、可以再优化吗"></a>三、可以再优化吗</h1><p>    上面我们实现了两种并查集,这两种实现的性能如何呢?我们来测试一下。 </p><p>    对于size=10000,进行10000次合并和10000次查询操作。具体测试代码这里就不贴出来了,来看结果吧。对比之下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">UnionFind1 : 0.05409653s</span><br><span class="line">UnionFind2 : 0.035575485s</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure></p><p>    上面结果差异并不明显,我们增加size,对于size=100000,进行10000次合并和10000次查询,结果变成了如下的情况: </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">UnionFind1 : 0.269145621s</span><br><span class="line">UnionFind2 : 0.003194141s</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure><p>    如果我们把操作次数也设为100000,又会变成什么样呢?<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">UnionFind1 : 5.205210688s</span><br><span class="line">UnionFind2 : 10.711065036s</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure></p><p>    对于v1版本的并查集,我们就是使用数组的顺序遍历,是对一片连续的空间进行了一次循环的操作,这样的操作,jvm有很好的优化,所以运行速度会非常快。<br>    而对于v2版本的并查集来说,这个查询的过程其实是一个不断索引的过程,它不是一个顺次访问一片连续空间的过程,要在不同的地址空间进行跳转,因此速度会相对慢一些;再者,v2的并查集,find和union的复杂度都是O(h),我们增大了操作数,意味着在union的过程中,将更多的元素都组织在了一个集合中,这棵树就变得非常庞大,深度也有可能会非常高,导致我们进行这100000次操作的时候,<code>isConnected</code>消耗的时间也会比较多。所以这次的测试结果,v2版本的效率显得更慢一些。</p><h2 id="针对size的优化"><a href="#针对size的优化" class="headerlink" title="针对size的优化"></a>针对size的优化</h2><p>    其实对于v2版本的并查集,我们有很大的优化空间,我们回忆一下,在进行union操作的时候,是没有考虑树的特点就进行合并指向的,我们来看下面的过程。<br>    依次进行<code>union(0,1)</code>,<code>union(0,2)</code>,<code>union(0,3)</code>操作,我们的并查集变成了这个样子。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/union%E9%93%BE%E8%A1%A8.png" width="80%" height="80%"> </p><p>    很明显,这样已经形成了一个链表,当我们继续进行<code>union(0,n)</code>,最终我们的并查集实际上就是一条长长的链表了。这种情况我们如何进行优化呢? </p><p>    一个简单的思路,我们考虑当前节点所在的这棵树的节点多少,将节点少的这棵树合并到节点多的树上,就可以有效的降低树的高度。比如上述例子,我们进行<code>union(0,3)</code>时,比较一下节点0所在树的size(3)和节点3所在树的size(1),将size小的向size大的树合并,即将节点3指向节点2。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/union0-3size%E4%BC%98%E5%8C%96.png" width="80%" height="80%"><br>    这样我们可以有效地降低树的高度。代码改造如下:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 并查集实现 - v3</span></span><br><span class="line"><span class="comment"> * 与v2相比,优化了size,unionElement时,将节点数少的树指向节点数多的树</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> renxiaoya</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2020-12-06</span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UnionFind3</span> <span class="keyword">implements</span> <span class="title">UF</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span>[] parent;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * sz[i]表示以i为根的集合中元素的个数</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span>[] sz;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">UnionFind3</span><span class="params">(<span class="keyword">int</span> size)</span> </span>{</span><br><span class="line"> parent = <span class="keyword">new</span> <span class="keyword">int</span>[size];</span><br><span class="line"> sz = <span class="keyword">new</span> <span class="keyword">int</span>[size];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < size; i++) {</span><br><span class="line"> parent[i] = i;</span><br><span class="line"> sz[i] = <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getSize</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> parent.length;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isConnected</span><span class="params">(<span class="keyword">int</span> p, <span class="keyword">int</span> q)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> find(p) == find(q);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">unionElement</span><span class="params">(<span class="keyword">int</span> p, <span class="keyword">int</span> q)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> pRoot = find(p);</span><br><span class="line"> <span class="keyword">int</span> qRoot = find(q);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (pRoot == qRoot) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 若pRoot所在元素比qRoot所在元素少,将pRoot指向qRoot,并维护sz</span></span><br><span class="line"> <span class="keyword">if</span> (sz[pRoot] < sz[qRoot]) {</span><br><span class="line"> parent[pRoot] = qRoot;</span><br><span class="line"> sz[qRoot] += sz[pRoot];</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 否则将qRoot指向pRoot</span></span><br><span class="line"> parent[qRoot] = pRoot;</span><br><span class="line"> sz[pRoot] += sz[qRoot];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查找元素p对应的集合编号</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> p 元素p</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> p对应的元素编号</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">find</span><span class="params">(<span class="keyword">int</span> p)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (p < <span class="number">0</span> || p >= parent.length) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"p is out of bound."</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span> (p != parent[p]) {</span><br><span class="line"> p = parent[p];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> p;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>    现在我们再来看看在size=100000,操作数也为100000的测试用例中,并查集的表现。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">UnionFind1 : 5.266835514s</span><br><span class="line">UnionFind2 : 11.177287476s</span><br><span class="line">UnionFind3 : 0.021229747s</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure></p><p>    优化的效果是非常明显的,v3版本的并查集仅用了0.02秒就完成了操作。 </p><h2 id="基于rank的优化"><a href="#基于rank的优化" class="headerlink" title="基于rank的优化"></a>基于rank的优化</h2><p>    上面基于size的优化,我们考虑了树所在的节点个数多少来降低树的高度,那何不更直接的来比较树的高度呢?<br>假设有如下的并查集,需要进行<code>union(4,2)</code>操作。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/rank%E5%88%9D%E5%A7%8B.png" width="80%" height="80%"><br>    若按照size优化的思路,我们会将节点8指向节点7,因为节点4所在树的size比节点2所在树的size小,如下图所示: </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/rank-size.png" width="80%" height="80%"><br>    这样树的高度是4。如果我们直接来比较树的高度来进行合并呢?将高度低的向高度高的树合并,如下图所示: </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/rank-union.png" width="80%" height="80%"><br>    这样树的高度是3,比基于size的优化得到的树的高度更低。<br>    我们来看看代码如何实现,其实与size优化的代码很像,只是用一个int数组代表了以i为根的树的高度,合并的时候判断高度,将高度低的向高度高的树合并,并在合适的时候维护rank的值即可。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 并查集实现 - v4</span></span><br><span class="line"><span class="comment"> * 针对rank优化,unionElement时,深度低的树指向深度高的树</span></span><br><span class="line"><span class="comment"> * 这里是rank,排名、序的意思,不是绝对的高度</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> renxiaoya</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2020-12-06</span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UnionFind4</span> <span class="keyword">implements</span> <span class="title">UF</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span>[] parent;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * rank[i]表示以i为根的集合中元素的高度rank</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span>[] rank;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">UnionFind4</span><span class="params">(<span class="keyword">int</span> size)</span> </span>{</span><br><span class="line"> parent = <span class="keyword">new</span> <span class="keyword">int</span>[size];</span><br><span class="line"> rank = <span class="keyword">new</span> <span class="keyword">int</span>[size];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < size; i++) {</span><br><span class="line"> parent[i] = i;</span><br><span class="line"> rank[i] = <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getSize</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> parent.length;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isConnected</span><span class="params">(<span class="keyword">int</span> p, <span class="keyword">int</span> q)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> find(p) == find(q);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">unionElement</span><span class="params">(<span class="keyword">int</span> p, <span class="keyword">int</span> q)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> pRoot = find(p);</span><br><span class="line"> <span class="keyword">int</span> qRoot = find(q);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (pRoot == qRoot) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 根据两个元素所在的树的rank不同判断合并的方向</span></span><br><span class="line"> <span class="comment">// 将rank低的集合指向rank高的集合</span></span><br><span class="line"> <span class="keyword">if</span> (rank[pRoot] < rank[qRoot]) {</span><br><span class="line"> parent[pRoot] = qRoot;</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (rank[qRoot] < rank[pRoot]) {</span><br><span class="line"> parent[qRoot] = pRoot;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// rank[qRoot] == rank[pRoot]</span></span><br><span class="line"> parent[qRoot] = pRoot;</span><br><span class="line"> rank[pRoot] += <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查找元素p对应的集合编号</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> p 元素p</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> p对应的元素编号</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">find</span><span class="params">(<span class="keyword">int</span> p)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (p < <span class="number">0</span> || p >= parent.length) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"p is out of bound."</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span> (p != parent[p]) {</span><br><span class="line"> p = parent[p];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> p;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>    我们来测试一下基于rank的优化结果吧。由于前两种并查集实现的效率很低,这里就不进行测试了,我们来比较v3和v4的效率。<br>    我们增大数据量,拿size=10000000,操作数也为10000000来进行测试,结果如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">UnionFind3 : 4.168454361s</span><br><span class="line">UnionFind4 : 4.285047797s</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure></p><p>    可以看到实际上基于size和基于rank的优化的结果是差不多的,只不过基于rank的优化在思路上更加顺一些,所以通常实现的并查集也使用基于rank的优化方案。</p><h2 id="路径压缩"><a href="#路径压缩" class="headerlink" title="路径压缩"></a>路径压缩</h2><p>    有如下图的情况,这三种都可以表示并查集中相连的一些节点。我们执行<code>isConnected</code>来查询任意两个节点,都可以得到相同的结果,但是很显然,这些树的深度不同,对应的效率也是区别的。比如最左侧的树,高度达到了5,那么在进行<code>find(4)</code>的时候需要花费的时间就会比较长;而下面的这棵树,高度只有2,那么进行<code>find(4)</code>的操作速度就会很快。<br>    在我们上面实现的并查集中,进行<code>union</code>的过程,免不了会使树的高度越来越高,路径压缩就是可以将一棵树可以压缩成比较矮的树 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/%E8%B7%AF%E5%BE%84%E5%8E%8B%E7%BC%A9.png" width="80%" height="80%"> </p><p>    我们在进行<code>find</code>操作的时候,会依次向上查询根节点,如果在向上查询的过程中,我们同时进行路径压缩,是否可以降低树的高度呢?<br>    下图中的并查集,我们进行<code>find(4)</code>操作。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/%E5%8E%8B%E7%BC%A9%E5%88%9D%E5%A7%8B.png" width="80%" height="80%"><br>    如果当前节点不是根节点,那么我们执行<code>parent[i] = parent[parent[i]]</code>操作,即将当前节点的父节点,指向父节点的父节点,文字描述比较绕,看图就比较清晰了。<br>    节点4不是根节点,将节点4的父节点指向其父节点也就是节点3的父节点–节点2。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/%E5%8E%8B%E7%BC%A9-1.png" width="80%" height="80%"><br>    节点2不是根节点,将节点2的父节点指向其父节点也就是节点1的父节点–节点0。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/unionFind/%E5%8E%8B%E7%BC%A9-2.png" width="80%" height="80%"><br>    经过上述的操作,就将高度为5的树压缩成了高度为3,完成了一次路径压缩。<br>    代码也是很简单的,在基于rank优化的代码中,我们改造<code>find</code>方法,判断节点p是否是根节点,不是的话就执行<code>parent[i] = parent[parent[i]]</code>。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">find</span><span class="params">(<span class="keyword">int</span> p)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (p < <span class="number">0</span> || p >= parent.length) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"p is out of bound."</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span> (p != parent[p]) {</span><br><span class="line"> parent[p] = parent[parent[p]];</span><br><span class="line"> p = parent[p];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> p;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>    我们用同样的测试用例再来看看我们路径压缩的优化效果。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">UnionFind3 : 4.289546148s</span><br><span class="line">UnionFind4 : 4.266191791s</span><br><span class="line">UnionFind5 : 3.837546687s</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure></p><p>    我们进行路径压缩的过程中,其实我们树的高度是降低的,但是并没有去维护rank数组的值,其实这是合理的,这也是这个数组取名rank,而不是height的原因,它表示的其实是一个排名,当我们进行路径压缩的时候,这个rank就不是严格意义上的树的高度了,它仅代表了树的一个排名。同一棵树不同节点对应的rank值可能是不同的,但是整体上的排名是一定的,这个排名依然可以用作union操作判断的依据。这里不做rank的维护,也是出于对性能的考虑,不维护的情况下已经可以胜任判断排名的依据,就无需费时去管理每一个节点的rank值使同一棵树的节点rank保持一致。 </p><p>    以上,我们一步步优化,实现了5个版本的并查集,相信还有不同思路的优化实现,比如路径压缩有很多种方法,我们只实现了一种,还可以使用递归来实现等等。<br>    到此为止,并查集的内容就介绍完了,记录下学习的历程,相信对自己也是很大的提高。</p>]]></content>
<summary type="html">
<p>&ensp;&ensp;&ensp;&ensp;很久没有更新文章了,周末又巩固了一下数据结构基础,这次我们来看一下有些不一样的树结构-并查集。</p>
<h1 id="一、什么是并查集"><a href="#一、什么是并查集" class="headerlink" titl
</summary>
<category term="数据结构" scheme="http://redmapleren.com/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
<category term="数据结构" scheme="http://redmapleren.com/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
<category term="树" scheme="http://redmapleren.com/tags/%E6%A0%91/"/>
</entry>
<entry>
<title>简单聊聊Puppet Manager的实现</title>
<link href="http://redmapleren.com/2020/06/16/%E7%AE%80%E5%8D%95%E8%81%8A%E8%81%8APuppet%20Manager%E7%9A%84%E5%AE%9E%E7%8E%B0/"/>
<id>http://redmapleren.com/2020/06/16/简单聊聊Puppet Manager的实现/</id>
<published>2020-06-16T02:55:00.000Z</published>
<updated>2020-06-16T03:43:32.484Z</updated>
<content type="html"><![CDATA[<p><img src="https://wechaty.github.io/java-wechaty/images/java-wechaty.png" alt="Java Wechaty"> </p><p>    今天我们来聊一聊最近参与的一个开源项目。<br><a id="more"></a> </p><p>    这里借用java-wechaty的maintainer犀利豆的博客介绍一下wechaty是什么,以及java版本的前世今生。</p><blockquote><p>犀利豆<br><a href="https://xilidou.com/2020/06/03/java-wechaty/" target="_blank" rel="noopener">终于有一个Java可以用的微信机器人了</a></p></blockquote><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>    参与开发java-wechaty有一个多月的时间,在开发的过程中,不免要进行自测。在前期没有token调试不便的情况下,就想借助单测对所写代码进行验证,但是我发现想要进行单测也不是一件容易的事情。与以往 java web 开发不同,没有Spring封装好的带有上下文的test。于是,我借鉴ts版本wechaty的mock模块,实现了java-wechaty的mock puppet,专门用于测试wechaty上层代码逻辑。开心地实现完成mock puppet之后,又发现了新的问题。那就是现有java版本的wechaty在初始化puppet的时候,在代码中写死了hostie puppet,也就是图中的<code>GrpcPuppet</code>。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/wechaty/%E8%AE%BE%E8%AE%A1%E8%83%8C%E6%99%AF.png" alt="image"> </p><p>    这样的话,我就无法初始化mock puppet了,所以我就思考能不能有一个manager来管理puppet的具体实现。有了这个想法,那么如何进行优雅的实现呢?接下来我们简单来聊聊。</p><h2 id="实现思路"><a href="#实现思路" class="headerlink" title="实现思路"></a>实现思路</h2><ul><li><code>PuppetManager</code>来管理具体的puppet实现类</li><li>在wechaty中通过调用<code>PuppetManager</code>的实例化方法,初始化出需要的puppet实现</li><li>可使用反射机制处理puppet和具体实现类</li></ul><p>    使用上述思路的改造,wechaty初始化的流程就变成了下图的样子。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/wechaty/manager%E7%AE%A1%E7%90%86%E5%90%8E.png" alt="image"> </p><p>    可以看到,在wechaty的<code>initPuppet()</code>方法中,不再是直接初始化<code>GrpcPuppet</code>,而是使用了<code>PuppetManager</code>的<code>resolveInstance()</code>方法拿到了初始化过的puppet实现。<br>    那么<code>PuppetManager</code>具体是怎么实现的呢?其实很简单,直接看代码。 </p><pre><code class="java"><span class="keyword">const</span> val REFLECTION_BASE_PACKAGE = <span class="string">"io.github.wechaty"</span><span class="class"><span class="keyword">class</span> <span class="title">PuppetManager</span> </span>{ companion object { <span class="keyword">private</span> val log = LoggerFactory.getLogger(PuppetManager::<span class="class"><span class="keyword">class</span>.<span class="title">java</span>)</span><span class="class"></span><span class="class"> @<span class="title">JvmStatic</span></span> fun resolveInstance(wechatyOptions: WechatyOptions): Future<Puppet> { log.info(<span class="string">"PuppetManager resolveInstance(${JsonUtils.write(wechatyOptions)})"</span>) val reflections = Reflections(ConfigurationBuilder().setUrls(ClasspathHelper.forPackage(REFLECTION_BASE_PACKAGE, Thread.currentThread().contextClassLoader))) val subTypes: Set<*> = reflections.getSubTypesOf(Puppet::<span class="class"><span class="keyword">class</span>.<span class="title">java</span>)</span><span class="class"> <span class="title">if</span> (<span class="title">subTypes</span>.<span class="title">isEmpty</span>()) </span>{ <span class="keyword">throw</span> java.lang.RuntimeException(<span class="string">"expect one puppet,but can not found any one."</span>) } <span class="keyword">if</span> (subTypes.size > <span class="number">1</span>) { <span class="keyword">throw</span> RuntimeException(<span class="string">"expect one puppet,but found ${subTypes.size}"</span>) } val clazz = subTypes.first() as Class<*> val declaredConstructor = clazz.getDeclaredConstructor(PuppetOptions::<span class="class"><span class="keyword">class</span>.<span class="title">java</span>)</span><span class="class"> <span class="title">return</span> <span class="title">CompletableFuture</span>.<span class="title">completedFuture</span>(<span class="title">declaredConstructor</span>.<span class="title">newInstance</span>(<span class="title">wechatyOptions</span>.<span class="title">puppetOptions</span>!!) <span class="title">as</span> <span class="title">Puppet</span>)</span><span class="class"> }</span><span class="class"> }</span><span class="class"></span><span class="class"></span><span class="class">}</span></code></pre><p>    首先定义了一个基础的包路径,在该路径下使用<code>reflections</code>库,扫描所有实现了Puppet的类。因为我们必须需要一个puppet具体实现类,所以当我们扫描不到任何puppet的实现时,会抛出异常,告知开发者期望有一个puppet实现类,但是在classpath下并没有找到。 </p><p>    其次,当前我们只支持单次实例化一种puppet实现,所以当在路径下扫描到多于1个puppet实现类时,同样会抛出异常,提示开发者classpath中存在一个以上的puppet实现。 </p><p>    上面两种情况均未抛出异常的,说明manager已经找到了需要实例化的puppet实现,接下来就使用java反射,直接实例化即可。 </p><p>    以上就是manager实例化puppet的简单实现。 </p><h2 id="展望"><a href="#展望" class="headerlink" title="展望"></a>展望</h2><p>    首次引入manager的版本中,我们把之前引入到wechaty sdk中的hostie puppet实现拿了出来,在sdk中仅引入puppet定义层。这样,使用者需要哪种puppet实现,自己引入即可,作为sdk就不再关心了。<br>    但是这样又增添了使用者的使用门坎,不如之前仅引入sdk包就能使用方便,所以在接下来的版本中,我们还是打算在sdk中使用hostie puppet作为puppet的默认实现,如果使用者有使用其他puppet实现类的诉求,需要手动在pom中exclude掉hostie puppet的默认实现,然后引入需要的puppet实现即可。 </p><p>    后续可能会支持多个puppet,那么manager就需要适当的改造去适配多个puppet实现。这里有一个简单的设计思路,在puppet定义层,我们可以定义一个mapping()方法,该方法的意思是作为一个puppet,我需要如何的映射才能初始化。那么具体怎么映射就交给子类,也就是具体的puppet实现类自己去实现。在manager中,我们只需要使用puppet定义层的mapping()方法就可以实现对子类的映射处理,作为manager,并不用关心当前到底是谁在初始化。下图是大体的结构。 </p><p><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/wechaty/manager%E5%B1%95%E6%9C%9B.png" alt="image"> </p><p>    有了<code>PuppetManager</code>,我们的java-wechaty实现得更加优雅了一些,而且实现了mock puppet,后续就可以通过单测提高代码的质量,提升稳定性,相信我们的java-wechaty会越来越完善。<br>    如果你对wechaty感兴趣,恰巧又是java developer,对java-wechaty有自己的想法或对目前的代码实现有任何好的建议,期待你的加入,为java-wechaty贡献代码~</p>]]></content>
<summary type="html">
<p><img src="https://wechaty.github.io/java-wechaty/images/java-wechaty.png" alt="Java Wechaty"> </p>
<p>&ensp;&ensp;&ensp;&ensp;今天我们来聊一聊最近参与的一个开源项目。<br>
</summary>
<category term="Wechaty" scheme="http://redmapleren.com/categories/Wechaty/"/>
<category term="Wechaty" scheme="http://redmapleren.com/tags/Wechaty/"/>
<category term="分享" scheme="http://redmapleren.com/tags/%E5%88%86%E4%BA%AB/"/>
</entry>
<entry>
<title>内功修炼-线段树(二)</title>
<link href="http://redmapleren.com/2020/04/02/%E5%86%85%E5%8A%9F%E4%BF%AE%E7%82%BC-%E7%BA%BF%E6%AE%B5%E6%A0%91%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
<id>http://redmapleren.com/2020/04/02/内功修炼-线段树(二)/</id>
<published>2020-04-02T03:55:00.000Z</published>
<updated>2020-04-02T07:44:27.898Z</updated>
<content type="html"><![CDATA[<p>    上篇我们认识了线段树,并创建了一棵线段树,这篇我们继续来看如何在线段树中查询和更新。 </p><h1 id="一、区间查询"><a href="#一、区间查询" class="headerlink" title="一、区间查询"></a>一、区间查询</h1><p>    我们创建线段树的时候,利用了树的天然递归特性,进行查询我们同样可以使用递归的思想。<br>    定义一个 query 方法,queryL 参数表示查询区间的左边界,queryR 参数表示查询区间的右边界。首先考虑超出边界的异常情况,抛出相应异常。接下来就是我们核心的递归方法。<br>    我们可以思考一下,定义这个递归函数,需要哪些必要的条件呢? </p><ul><li><p>当前递归中正在处理的线段树 </p><p> 如何表示正在处理的线段树?指出当前树的根以及其左右边界即可。</p><ul><li>正在处理的线段树的根节点索引(treeIndex)</li><li>正在处理的线段树的左边界(l)</li><li>正在处理的线段树的右边界(r)</li></ul></li><li>我们最终需要查询的区间<ul><li>区间查询左边界(queryL)</li><li>区间查询右边界(queryR)</li></ul></li></ul><p>    所以我们的递归方法可以定义以上这 5 个参数。<br>    定义好了方法,如何进行具体的查询操作呢?<br>    首先考虑递归终止的条件,即我们正在处理的线段树的左边界等于需要查询的左边界,并且正在处理的线段树的右边界等于需要查询的右边界,此时,线段树中的节点 tree[treeIndex] 即为方法的结果。<br>    接下来考虑递归的逻辑。和创建线段树一样,先找到当前处理树的中间节点索引和左右子树的索引。之后有以下几种情况: </p><ul><li>查询左边界在中间节点索引 + 1的位置或还靠右,即 queryL >= mid + 1<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/segmentTree/queryL%E5%A4%A7%E4%BA%8E%E4%B8%AD%E9%97%B4.png" alt><br>此时去右子树,从中间位置 mid + 1 到 r 的区间去查询 queryL 到 queryR</li><li>查询右边界在中间节点索引的位置或还靠左,即 queryR <= mid<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/segmentTree/queryR%E5%B0%8F%E4%BA%8E%E4%B8%AD%E9%97%B4.png" alt> </li></ul><p>此时去左子树,从 l 到 中间位置 mid 的区间去查询 queryL 到 queryR</p><ul><li>查询的左右边界涉及左右子树的结果<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/segmentTree/query%E5%B7%A6%E5%8F%B3%E5%9D%87.png" alt><br>此时左右子树均涉及,需要在左子树查询 queryL 到 mid 区间,在右子树查询 mid + 1 到 queryR 区间,之后将左右结果聚合即可。 </li></ul><p>    具体代码如下: </p><pre><code class="java"><span class="comment">/**</span><span class="comment"> * 返回区间[queryL...queryR]的值</span><span class="comment"> *</span><span class="comment"> * <span class="doctag">@param</span> queryL 左边界</span><span class="comment"> * <span class="doctag">@param</span> queryR 右边界</span><span class="comment"> * <span class="doctag">@return</span> 返回值</span><span class="comment"> */</span> <span class="function"><span class="keyword">public</span> E <span class="title">query</span><span class="params">(<span class="keyword">int</span> queryL, <span class="keyword">int</span> queryR)</span> </span>{ <span class="keyword">if</span> (queryL < <span class="number">0</span> || queryL >= data.length || queryR < <span class="number">0</span> || queryR >= data.length || queryL > queryR) { <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"Index is illegal."</span>); } <span class="keyword">return</span> query(<span class="number">0</span>, <span class="number">0</span>, data.length - <span class="number">1</span>, queryL, queryR); } <span class="comment">/**</span><span class="comment"> * 在以 treeIndex 为根的线段树[l...r]中的范围里,搜索区间[queryL...queryR]的值</span><span class="comment"> *</span><span class="comment"> * <span class="doctag">@param</span> treeIndex 正在处理的树的根节点索引</span><span class="comment"> * <span class="doctag">@param</span> l 正在处理的线段树的左边界</span><span class="comment"> * <span class="doctag">@param</span> r 正在处理的线段树的右边界</span><span class="comment"> * <span class="doctag">@param</span> queryL 查询左边界</span><span class="comment"> * <span class="doctag">@param</span> queryR 查询右边界</span><span class="comment"> * <span class="doctag">@return</span> 搜索到的值</span><span class="comment"> */</span> <span class="function"><span class="keyword">private</span> E <span class="title">query</span><span class="params">(<span class="keyword">int</span> treeIndex, <span class="keyword">int</span> l, <span class="keyword">int</span> r, <span class="keyword">int</span> queryL, <span class="keyword">int</span> queryR)</span> </span>{ <span class="keyword">if</span> (l == queryL && r == queryR) { <span class="keyword">return</span> tree[treeIndex]; } <span class="keyword">int</span> mid = l + (r - l) / <span class="number">2</span>; <span class="keyword">int</span> leftTreeIndex = leftChild(treeIndex); <span class="keyword">int</span> rightTreeIndex = rightChild(treeIndex); <span class="keyword">if</span> (queryL >= mid + <span class="number">1</span>) { <span class="keyword">return</span> query(rightTreeIndex, mid + <span class="number">1</span>, r, queryL, queryR); } <span class="keyword">else</span> <span class="keyword">if</span> (queryR <= mid) { <span class="keyword">return</span> query(leftTreeIndex, l, mid, queryL, queryR); } E leftResult = query(leftTreeIndex, l, mid, queryL, mid); E rightResult = query(rightTreeIndex, mid + <span class="number">1</span>, r, mid + <span class="number">1</span>, queryR); <span class="keyword">return</span> merger.merge(leftResult, rightResult); }</code></pre><h1 id="二、区间更新"><a href="#二、区间更新" class="headerlink" title="二、区间更新"></a>二、区间更新</h1><p>    区间查询实现了,我们再来看看另一个重要的操作 - 区间更新。 </p><p>    区间更新,顾名思义,将某个索引位置的值更新之后,与之关联的区间都要有所更新,所以叫区间更新。<br>    我们定义一个set方法,将索引位置 index 的值更新为 e。更新的思路其实很简单: </p><ol><li>更新 data 数组相应索引的值</li><li>更新线段树 tree 数组相关联的节点值</li></ol><p>    更新 data 数组很简单,直接将 data[index] 赋值成 e 即可。<br>    我们主要看一下如何更新 tree 数组的节点值。<br>    由于更新某个位置的节点的同时,其父辈节点也要做出相应的改变,所以我们依然可以利用树的递归特性来更新。<br>    定义一个递归方法,有了之前的经验,应该很好理解,直接看代码: </p><pre><code class="java"><span class="comment">/**</span><span class="comment"> * 将 index 位置的节点更新成 e</span><span class="comment"> *</span><span class="comment"> * <span class="doctag">@param</span> index 待更新节点的索引值</span><span class="comment"> * <span class="doctag">@param</span> e 要更新成的值</span><span class="comment"> */</span><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(<span class="keyword">int</span> index, E e)</span> </span>{ <span class="keyword">if</span> (index < <span class="number">0</span> || index >= data.length) { <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"Index is illegal."</span>); } <span class="comment">//首先更新 data 对应索引的值</span> data[index] = e; <span class="comment">//更新 tree 数组(线段树)中所有关联的值</span> set(<span class="number">0</span>, <span class="number">0</span>, data.length - <span class="number">1</span>, index, e);}<span class="comment">/**</span><span class="comment"> * 在以 treeIndex 为根的线段树中更新 index 的值为 e</span><span class="comment"> *</span><span class="comment"> * <span class="doctag">@param</span> treeIndex 正在处理的线段树的根节点索引</span><span class="comment"> * <span class="doctag">@param</span> l 左边界</span><span class="comment"> * <span class="doctag">@param</span> r 右边界</span><span class="comment"> * <span class="doctag">@param</span> index 待更新的索引值</span><span class="comment"> * <span class="doctag">@param</span> e 要更新成的值</span><span class="comment"> */</span><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(<span class="keyword">int</span> treeIndex, <span class="keyword">int</span> l, <span class="keyword">int</span> r, <span class="keyword">int</span> index, E e)</span> </span>{ <span class="keyword">if</span> (l == r) { tree[treeIndex] = e; <span class="keyword">return</span>; } <span class="keyword">int</span> mid = l + (r - l) / <span class="number">2</span>; <span class="keyword">int</span> leftTreeIndex = leftChild(treeIndex); <span class="keyword">int</span> rightTreeIndex = rightChild(treeIndex); <span class="keyword">if</span> (index >= mid + <span class="number">1</span>) { <span class="comment">//index 比中间节点位置还靠右,去右子树操作</span> set(rightTreeIndex, mid + <span class="number">1</span>, r, index, e); } <span class="keyword">else</span> { <span class="comment">//否则去左子树操作</span> set(leftTreeIndex, l, mid, index, e); } <span class="comment">//set过相应节点的值后不要忘记合并左右子树的结果,否则被操作的节点的父辈节点值将不会更新</span> tree[treeIndex] = <span class="keyword">this</span>.merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);}</code></pre><p>    这里需要特殊注意一点,在调用完左或右子树的 set 方法之后,一定不要忘记将调整后的左右子树的结果进行合并,并赋值给 tree[treeIndex],否则被更新的节点的父辈节点将不会进行更新。 </p><p>    到此,线段树的区间查询和区间更新我们都实现了。 </p><h1 id="三、相关问题"><a href="#三、相关问题" class="headerlink" title="三、相关问题"></a>三、相关问题</h1><p>    我们建立好了线段树的数据结构,就可以利用线段树来解决相应的问题。<br>    比如上一篇文章中提到的,查询2019年注册用户里截至目前消费额最高的用户,这里我们可以将用户消费额信息包装成对象,当做线段树中的节点,data 的长度就是所有用户的数量,创建线段树,merger 实现这里是选出消费额最大值。伴随用户消费额的改变,使用区间更新的操作更新线段树相应节点的值(时间复杂度 O(logn)),比从头到尾遍历的更新(时间复杂度 O(n))效率更高。<br>    当然这里只是一个思路,具体实现会有更加细节的考虑。</p><h3 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h3><ul><li><p>如何对一个区间进行更新<br> 比如将区间 [2,5] 中所有元素 +5。<br> <img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/segmentTree/%E5%8C%BA%E9%97%B4%E6%89%B9%E9%87%8F%E6%9B%B4%E6%96%B0.png" alt><br>     首先可以通过 O(logn) 的时间复杂度找到 A[2…3] 和 A[4…5] 这两个节点,以求和业务为例,这两个节点都包含2个元素,每个元素都需要 +5,所以这两个节点每个都要加 5*2=10 ,在回溯回去的时候,相应的这两个节点的祖辈节点也需要进行更新。需要注意,我们不能只更新中间节点(即涉及到的非叶子节点),图中红色标识的节点其实是要做 +5 操作的,如果我们对这些叶子节点同时也进行更新的话,其实我们进行了一次 O(n) 复杂度的操作,这个操作相对来说是比较慢的。<br>     此时我们其实可以先不更新叶子节点的值,先使用一个数组 lazy 来记录未更新的内容,有了这个记录我们就不需要实际地去更新这些节点,当我们再有一次更新或查询操作再次碰到这些节点的时候,根据 lazy 记录判断,是否有需要更新的内容未更新,先更新之后再进行之后的操作,这种操作是一种懒惰更新的思想。这样以来,我们对于更新一个区间的操作,时间复杂度又变成了 O(logn)</p></li><li><p>数据量如此大的情况,我们是否还需要按照上面的方式创建 4n 大小的空间<br>     这样浪费的空间就会很多,出于这个考虑,我们能否将上面的结构改用链式存储?<br>     比如给定一个区间 [0,100000000],而我们需要关注 [15,20] 区间的数据,此时如果建立 4n 的空间,将是浪费了很多很多空间,这时候我们可以根据需要动态地进行构建线段树。大体思路如下图: </p><p> <img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/segmentTree/%E5%8A%A8%E6%80%81%E7%BA%BF%E6%AE%B5%E6%A0%91.png" alt></p><p>根据关注区间,分段创建线段树,这样节省了很多空间而且也减少了一些无用的递归操作。 </p></li></ul><p>    关于线段树我们先讨论这么多,主要知道线段树能够解决哪些问题和背后的一些思想。当然线段树也是一种比较高级的数据结构,能够探索的地方还有很多很多,我们只是讨论了一些基础的定义和思想。能够把这些看似基础抽象的数据结构与实际应用关联,其实也是很有意思的事情。</p>]]></content>
<summary type="html">
<p>&ensp;&ensp;&ensp;&ensp;上篇我们认识了线段树,并创建了一棵线段树,这篇我们继续来看如何在线段树中查询和更新。 </p>
<h1 id="一、区间查询"><a href="#一、区间查询" class="headerlink" title="一、区间
</summary>
<category term="数据结构" scheme="http://redmapleren.com/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
<category term="数据结构" scheme="http://redmapleren.com/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
<category term="树" scheme="http://redmapleren.com/tags/%E6%A0%91/"/>
</entry>
<entry>
<title>内功修炼-线段树(一)</title>
<link href="http://redmapleren.com/2020/03/30/%E5%86%85%E5%8A%9F%E4%BF%AE%E7%82%BC-%E7%BA%BF%E6%AE%B5%E6%A0%91%EF%BC%88%E4%B8%80%EF%BC%89/"/>
<id>http://redmapleren.com/2020/03/30/内功修炼-线段树(一)/</id>
<published>2020-03-30T11:55:00.000Z</published>
<updated>2020-03-30T12:22:41.965Z</updated>
<content type="html"><![CDATA[<p>    前一阵子朋友换工作,去了新公司的一个基础服务的部门。对数据结构和算法的要求着实不低,不是平常的CRUD,而是通过各种巧妙的数据结构去完成对应的业务需求。我发现是时候夯实一下基础了,这次来看一下树结构中的线段树。</p><h1 id="一、什么是线段树"><a href="#一、什么是线段树" class="headerlink" title="一、什么是线段树"></a>一、什么是线段树</h1><p>    有一种很经典的线段树问题:区间染色。<br>    讲的是有一面长度为 n 的墙,每次选择一段儿墙进行染色。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/segmentTree/%E5%BC%95%E5%85%A5%E5%8C%BA%E9%97%B4%E6%9F%93%E8%89%B2.png" alt="image"><br>    在染色的过程中,会有一部分被重复染色,那么 m 次操作后,我们可以看见多少种颜色呢?在 [i,j] 区间内可以看见多少种颜色呢?<br>    我们可以知道上述的问题是对区间进行以下的操作:</p><ul><li>更新区间(染色操作)</li><li>查询区间(查询操作)<br>    通过上面的图片描述,我们很容易可以想到利用数组来实现,对于染色操作和查询操作时间复杂度均是是 O(n)。<br>    其实更普遍一点,这一类问题的实质,是基于区间的统计查询。比如一个电商网站,需要看2019年的注册用户消费额最高的用户。这里需要注意,我们关注的是动态的情况,是看在2019年注册的用户在到现在为止消费额的统计量,并不是说在单单在2019年消费最高的。如果是单单2019年消费最高的统计,直接拿出2019年的数据进行统计即可,因为2019年的数据已经是定值了。我们考虑的是动态的情况,是伴随更新的同时进行查询,即既有更新又有查询的操作,此时,使用线段树是一种好的选择。当然依然可以使用普通数组进行实现,只不过使用线段树的时间复杂度会低一些。 </li></ul><table><thead><tr><th></th><th>使用数组实现</th><th>使用线段树</th></tr></thead><tbody><tr><td>更新</td><td>O(n)</td><td>O(logn)</td></tr><tr><td>查询</td><td>O(n)</td><td>O(logn) </td></tr></tbody></table><p>    我们总结以上讨论的问题场景: </p><p>    对于给定的区间,支持以下两种操作。 </p><ul><li><p>更新: 更新区间中一个元素或者一个区间的值 </p></li><li><p>查询一个区间 [i,j] 的最大值、最小值,或者区间数字和等。 </p></li></ul><p>    那么我们怎么抽象出线段树的数据结构呢?<br>    通过上面的说明,我们是不会像线段树中进行添加和删除元素的,即区间的长度是固定不变的,只是区间中的元素可能会发生变化。所以我们可以使用一个静态数组来表示,线段树具体就是下面的样子。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/segmentTree/%E7%BA%BF%E6%AE%B5%E6%A0%91%E8%A1%A8%E7%A4%BA.png" alt><br>和普通二叉树的区别是,每一个节点所表示的是一个区间内的信息。以求合为例,每一个节点存储的是每个区间相应的数字和,根节点是整个区间的数字和,之后往下分成两个子区间,以此类推,直到最后叶子节点是单个的数字。 </p><p>    当我们需要求[4…7]区间的和,可以很方便的从树中找到,并不需要对数组进行挨个遍历。 </p><h1 id="二、线段树的表示"><a href="#二、线段树的表示" class="headerlink" title="二、线段树的表示"></a>二、线段树的表示</h1><p>    在上面线段树有8个叶子节点,比较特殊,正好是一个满的二叉树,如果有10个元素,线段树是下面的情况:<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/segmentTree/%E7%BA%BF%E6%AE%B5%E6%A0%91-10%E8%8A%82%E7%82%B9.png" alt><br>    当一个节点不能平均分的时候,这里把这个节点分成一个节点少一点,一个节点多一点。<br>    所以,线段树不是完全二叉树,但是是平衡二叉树。<br>    为了简便,其实我们可以把线段树看作是一棵满的二叉树,只不过最后一层的叶子点有些是空的。满的二叉树我们可以很方便的用数组来表示,那么问题来了,我们需要多少节点来表示这课树呢?<br>    根据满二叉树的节点规律,我们可以看到每一层的节点和层数是有如下的关系的: </p><ul><li>0层:1</li><li>1层:2</li><li>3层:4<br>…</li><li>h-1层:2^(h-1)<br>    根据等比数列求合公式,可得出,对满二叉树,h 层,一共有 2^h - 1 个节点,大约是 2^h,这里富裕一个节点,肯定可以放下 2^h - 1 个节点。<br>    最后一层(h-1层),有 2^(h-1) 个节点。<br>    可以看到,最后一层的节点数大致等于前面所有层节点之和。<br>    有了上面的结论,如果区间有 n 个元素,用数组描述线段树需要有多少节点呢?<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/segmentTree/%E6%95%B0%E7%BB%84%E6%89%80%E9%9C%80%E8%8A%82%E7%82%B9%E4%B8%89%E8%A7%92.png" alt> </li></ul><p>    通过上面的推导,如果区间有 n 个元素,需要 4n 的空间来存储整个线段树。由于我们不考虑添加元素,即区间是固定的,所以使用 4n 的静态空间即可。 </p><p>    当然 4n 是一个估计值,这 4n 的空间并不是都被占满的,我们留有空余。比如下图的情况,最后一层大部分的节点是 null。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/segmentTree/%E6%B5%AA%E8%B4%B9%E8%8A%82%E7%82%B9%E6%BC%94%E7%A4%BA.png" alt> </p><p>    我们暂不考虑这种浪费的问题。对于现代计算机来说,多出来的这些节点基本是不影响空间的,这里就是算法的空间换时间的体现。 </p><h1 id="三、创建线段树"><a href="#三、创建线段树" class="headerlink" title="三、创建线段树"></a>三、创建线段树</h1><p>    有了上面的分析,具体的代码实现其实很简单。data 是存储元素的数组,tree 是树结构使用的数组。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SegmentTree</span><<span class="title">E</span>> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Merger<E> merger;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 树结构 - 这里使用数组</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> E[] tree;</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 真正的数据</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> E[] data;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">SegmentTree</span><span class="params">(E[] arr, Merger<E> merger)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.data = (E[]) <span class="keyword">new</span> Object[arr.length];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < arr.length; i++) {</span><br><span class="line"> data[i] = arr[i];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 通过归纳,4倍的data长度用来创建线段树比较合适,可能会有空间的浪费,但是可以接受</span></span><br><span class="line"> tree = (E[]) <span class="keyword">new</span> Object[<span class="number">4</span> * arr.length];</span><br><span class="line"> <span class="keyword">this</span>.merger = merger;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建线段树</span></span><br><span class="line"> buildSegmentTree(<span class="number">0</span>, <span class="number">0</span>, arr.length - <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">buildSegmentTree</span><span class="params">(<span class="keyword">int</span> treeIndex, <span class="keyword">int</span> l, <span class="keyword">int</span> r)</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取数据大小</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 数据大小</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getSize</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> data.length;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 根据索引获得数据</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> index 索引值</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 数据</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> E <span class="title">get</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (index < <span class="number">0</span> || index >= data.length) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"Index is illegal."</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> data[index];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 返回一个索引表示的元素的左孩子的索引</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> index 索引值</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 左孩子的索引</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">leftChild</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="number">2</span> * index + <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 返回一个索引表示的元素的右孩子的索引</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> index 索引值</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 右孩子的索引</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">rightChild</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="number">2</span> * index + <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>{</span><br><span class="line"> StringBuilder res = <span class="keyword">new</span> StringBuilder();</span><br><span class="line"> res.append(<span class="string">"["</span>);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < tree.length; i++) {</span><br><span class="line"> <span class="keyword">if</span> (tree[i] != <span class="keyword">null</span>) {</span><br><span class="line"> res.append(tree[i]);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> res.append(<span class="string">"null"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (i != tree.length - <span class="number">1</span>) {</span><br><span class="line"> res.append(<span class="string">", "</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> res.append(<span class="string">"]"</span>);</span><br><span class="line"> <span class="keyword">return</span> res.toString();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>    下面具体分析一下如何 build 线段树的节点。<br>    我们利用树的递归结构来创建,treeIndex 表示正在创建的线段树的根节点的索引,l 表示区间的左边界,r 表示区间的右边界。递归终止的条件就是当左边界等于右边界(l == r)时,这时要构建的树 tree[treeIndex] 就是 元素数组对应的索引的值(data[l] 或 data[r])。之后找到当前线段树的左右子树的根节点索引和索引中间值 mid,接下来就是递归调用该方法,先创建左子树,后创建右子树,之后合并两棵子树得到正在创建的树的根节点数据。代码如下: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 在 treeIndex 的位置创建表示区间[l...r]的线段树</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> treeIndex 正在创建的线段树的根节点</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> l 表示区间的左边界</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> r 表示区间的右边界</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">buildSegmentTree</span><span class="params">(<span class="keyword">int</span> treeIndex, <span class="keyword">int</span> l, <span class="keyword">int</span> r)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (l == r) {</span><br><span class="line"> tree[treeIndex] = data[l];</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> leftTreeIndex = leftChild(treeIndex);</span><br><span class="line"> <span class="keyword">int</span> rightTreeIndex = rightChild(treeIndex);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> mid = l + (r - l) / <span class="number">2</span>;</span><br><span class="line"> buildSegmentTree(leftTreeIndex, l, mid);</span><br><span class="line"> buildSegmentTree(rightTreeIndex, mid + <span class="number">1</span>, r);</span><br><span class="line"></span><br><span class="line"> tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>    代码中的 merger 是对于合并这个操作的抽象,因为我们的线段树结构是一个通用的结构,不可能仅支持求合或求积的操作,这里就利用 java 的多态性,使用接口来抽象这个合并操作。具体的业务可以通过实现这个接口来实现自己的逻辑。 </p><pre><code class="java"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Merger</span><<span class="title">E</span>> </span>{ <span class="function">E <span class="title">merge</span><span class="params">(E a, E b)</span></span>;}</code></pre><p>    以上,我们就完成了线段树的创建。<br>    在下篇文章中,我们将看如何在线段树中进行查询和更新操作。</p>]]></content>
<summary type="html">
<p>&ensp;&ensp;&ensp;&ensp;前一阵子朋友换工作,去了新公司的一个基础服务的部门。对数据结构和算法的要求着实不低,不是平常的CRUD,而是通过各种巧妙的数据结构去完成对应的业务需求。我发现是时候夯实一下基础了,这次来看一下树结构中的线段树。</p>
<h1
</summary>
<category term="数据结构" scheme="http://redmapleren.com/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
<category term="数据结构" scheme="http://redmapleren.com/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
<category term="树" scheme="http://redmapleren.com/tags/%E6%A0%91/"/>
</entry>
<entry>
<title>谈谈SpringBoot的监听器</title>
<link href="http://redmapleren.com/2020/01/17/%E8%B0%88%E8%B0%88SpringBoot%E7%9A%84%E7%9B%91%E5%90%AC%E5%99%A8/"/>
<id>http://redmapleren.com/2020/01/17/谈谈SpringBoot的监听器/</id>
<published>2020-01-17T06:55:00.000Z</published>
<updated>2020-01-19T11:46:51.939Z</updated>
<content type="html"><![CDATA[<p>    最近在看SpringBoot的源码,在SpringBoot项目启动的过程中,监听器在不同阶段都会监听相应的事件,今天我们就来谈谈SpringBoot启动过程中的监听器。</p><h2 id="一、监听器扫盲"><a href="#一、监听器扫盲" class="headerlink" title="一、监听器扫盲"></a>一、监听器扫盲</h2><p>    监听器模式,顾名思义就是某个对象监听某个或某些事件的触发,然后做出相应的操作。这句话可以看出,监听器模式包含几个特定的元素: </p><ul><li>事件,即监听什么(Event)</li><li>监听者,即谁来监听(Listener)</li><li>广播器,即谁来发布事件(Multicaster)</li><li>事件触发机制,即事件什么时候发布 </li></ul><p>整体的原理如下所示。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/%E7%9B%91%E5%90%AC%E5%99%A8%E6%A8%A1%E5%BC%8F.png" width="100%" height="100%"><br><br>    当系统运行在某些关键节点的时候,会通过广播器去发布一些事件,而系统中存在着一些监听器,对某些事件感兴趣,去订阅这些事件。当这些事件被发布出去之后,监听器监听到这些事件,会触发一些行为。<br>    这就是监听器的简单解释,那么在SpringBoot中监听器是如何实现的呢?接下来我们就来看看吧!</p><h2 id="二、揭开面纱-深入肌理"><a href="#二、揭开面纱-深入肌理" class="headerlink" title="二、揭开面纱 深入肌理"></a>二、揭开面纱 深入肌理</h2><p>    在SpringBoot中,系统<font color="#008000">监听器</font>是 ApplicationListener,可以看到源码的注释,通过实现这个接口来实现监听器。<br><figure class="highlight java"><figcaption><span>ApplicationListener.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> org.springframework.context;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.EventListener;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Interface to be implemented by application event listeners.</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * 通过实现这个接口来实现监听器</span></span><br><span class="line"><span class="comment"> * 这个接口是按照监听器模式的标准来设计的</span></span><br><span class="line"><span class="comment"> * <p>Based on the standard {<span class="doctag">@code</span> java.util.EventListener} interface</span></span><br><span class="line"><span class="comment"> * for the Observer design pattern.</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * 在Spring 3.0之后,一个应用监听器通常可以定义自己感兴趣的事件。当注册到Spring容器之后,当程序运行到一些关键节点时,</span></span><br><span class="line"><span class="comment"> * 会发出这些事件,并根据对应事件筛选出感兴趣的监听器进行触发。</span></span><br><span class="line"><span class="comment"> * <p>As of Spring 3.0, an {<span class="doctag">@code</span> ApplicationListener} can generically declare</span></span><br><span class="line"><span class="comment"> * the event type that it is interested in. When registered with a Spring</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@code</span> ApplicationContext}, events will be filtered accordingly, with the</span></span><br><span class="line"><span class="comment"> * listener getting invoked for matching event objects only.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Rod Johnson</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Juergen Hoeller</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> <E> the specific {<span class="doctag">@code</span> ApplicationEvent} subclass to listen to</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see</span> org.springframework.context.ApplicationEvent</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see</span> org.springframework.context.event.ApplicationEventMulticaster</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see</span> org.springframework.context.event.EventListener</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@FunctionalInterface</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ApplicationListener</span><<span class="title">E</span> <span class="keyword">extends</span> <span class="title">ApplicationEvent</span>> <span class="keyword">extends</span> <span class="title">EventListener</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Handle an application event.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> event the event to respond to</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">onApplicationEvent</span><span class="params">(E event)</span></span>;</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>这个接口继承自 EventListener 接口,看源码可知 EventListener 接口就是一个接口定义,声明这是一个事件监听的接口<br><figure class="highlight java"><figcaption><span>EventListener.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> java.util;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * A tagging interface that all event listener interfaces must extend.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 1.1</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">EventListener</span> </span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>    上面的 ApplicationListener 接口还有一个泛型,继承自 ApplicationEvent,这就是说在实现这个接口的时候,可以声明自己感兴趣的事件。系统在触发这个系统监听器的时候会根据其感兴趣的事件做一个过滤。这个接口定义了一个 onApplicationEvent 方法,是当它监听到事件发生的时候,会去做什么事情。<br>    接下来我们看一下监听器模式的<font color="#008000">【广播器】</font>在SpringBoot中的实现。<br>    系统广播器是 ApplicationEventMulticaster ,实现这个接口来管理一些应用监听器,并且广播事件。其中定义了添加、删除监听器的方法以及广播事件的方法。<br><figure class="highlight java"><figcaption><span>ApplicationEventMulticaster.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 实现这个接口来管理一些应用监听器,并且广播事件</span></span><br><span class="line"><span class="comment"> * Interface to be implemented by objects that can manage a number of</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@link</span> ApplicationListener} objects and publish events to them.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <p>An {<span class="doctag">@link</span> org.springframework.context.ApplicationEventPublisher}, typically</span></span><br><span class="line"><span class="comment"> * a Spring {<span class="doctag">@link</span> org.springframework.context.ApplicationContext}, can use an</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@code</span> ApplicationEventMulticaster} as a delegate for actually publishing events.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Rod Johnson</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Juergen Hoeller</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Stephane Nicoll</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see</span> ApplicationListener</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ApplicationEventMulticaster</span> </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Add a listener to be notified of all events.添加监听器</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> listener the listener to add</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">addApplicationListener</span><span class="params">(ApplicationListener<?> listener)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Add a listener bean to be notified of all events.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> listenerBeanName the name of the listener bean to add</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">addApplicationListenerBean</span><span class="params">(String listenerBeanName)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Remove a listener from the notification list.删除监听器</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> listener the listener to remove</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">removeApplicationListener</span><span class="params">(ApplicationListener<?> listener)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Remove a listener bean from the notification list.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> listenerBeanName the name of the listener bean to remove</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">removeApplicationListenerBean</span><span class="params">(String listenerBeanName)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Remove all listeners registered with this multicaster.移除所有监听器</span></span><br><span class="line"><span class="comment"> * <p>After a remove call, the multicaster will perform no action</span></span><br><span class="line"><span class="comment"> * on event notification until new listeners are registered.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">removeAllListeners</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Multicast the given application event to appropriate listeners.广播事件</span></span><br><span class="line"><span class="comment"> * <p>Consider using {<span class="doctag">@link</span> #multicastEvent(ApplicationEvent, ResolvableType)}</span></span><br><span class="line"><span class="comment"> * if possible as it provides better support for generics-based events.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> event the event to multicast</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">multicastEvent</span><span class="params">(ApplicationEvent event)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Multicast the given application event to appropriate listeners.</span></span><br><span class="line"><span class="comment"> * <p>If the {<span class="doctag">@code</span> eventType} is {<span class="doctag">@code</span> null}, a default type is built</span></span><br><span class="line"><span class="comment"> * based on the {<span class="doctag">@code</span> event} instance.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> event the event to multicast</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> eventType the type of event (can be {<span class="doctag">@code</span> null})</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 4.2</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">multicastEvent</span><span class="params">(ApplicationEvent event, @Nullable ResolvableType eventType)</span></span>;</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>    系统<font color="#008000">事件</font>在SpringBoot中的类图如下图所示。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/SpringApplicationEvent%E7%B1%BB%E5%9B%BE.png" width="100%" height="100%"><br><br>    最顶层是 EventObject,它代表的是一个事件对象,接着 ApplicationEvent 继承它,代表这是一个应用事件,之后SpringApplicationEvent代表了这是 Spring 中的系统事件,ApplicationStartedEvent、ApplicationFailedEvent等都是 SpringApplicationEvent 的子类。<br>    上图提到了这么多的事件,那在 SpringBoot 中这些事件的发送顺序是怎样的呢?<br>    下面是SpringBoot启动过程中涉及的事件触发流程图:<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/%E4%BA%8B%E4%BB%B6%E8%A7%A6%E5%8F%91%E9%A1%BA%E5%BA%8F.png" width="100%" height="100%"><br><br>    根据上述介绍的 SpringBoot 的事件相关的接口,我们可以自己定义一些监听器,然后注册到 SpringBoot 容器中。SpringBoot本身也有一些监听器的实现,上面我们已经提到,那么这些监听器是如何注册到 SpringBoot 容器中的呢?<br>    监听器注册的简明释义如下图所示,也是很容易理解的。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/%E7%9B%91%E5%90%AC%E5%99%A8%E6%B3%A8%E5%86%8C%E6%B5%81%E7%A8%8B.png" width="100%" height="100%"><br><br>    具体代码是如何实现的呢?<br><figure class="highlight java"><figcaption><span>SpringApplication.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Create a new {<span class="doctag">@link</span> SpringApplication} instance. The application context will load</span></span><br><span class="line"><span class="comment"> * beans from the specified primary sources (see {<span class="doctag">@link</span> SpringApplication class-level}</span></span><br><span class="line"><span class="comment"> * documentation for details. The instance can be customized before calling</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@link</span> #run(String...)}.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> resourceLoader the resource loader to use</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> primarySources the primary bean sources</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see</span> #run(Class, String[])</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see</span> #setSources(Set)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@SuppressWarnings</span>({ <span class="string">"unchecked"</span>, <span class="string">"rawtypes"</span> })</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">SpringApplication</span><span class="params">(ResourceLoader resourceLoader, Class<?>... primarySources)</span> </span>{</span><br><span class="line"><span class="keyword">this</span>.resourceLoader = resourceLoader;</span><br><span class="line">Assert.notNull(primarySources, <span class="string">"PrimarySources must not be null"</span>);</span><br><span class="line"><span class="keyword">this</span>.primarySources = <span class="keyword">new</span> LinkedHashSet<>(Arrays.asList(primarySources));</span><br><span class="line"><span class="comment">// 根据 classpath 判断 web 应用类型</span></span><br><span class="line"><span class="keyword">this</span>.webApplicationType = WebApplicationType.deduceFromClasspath();</span><br><span class="line"><span class="comment">// 初始化 initializers 属性</span></span><br><span class="line">setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));</span><br><span class="line"><span class="comment">// 初始化 listeners 属性</span></span><br><span class="line">setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));</span><br><span class="line"><span class="comment">// 获得是调用了哪个 main 方法</span></span><br><span class="line"><span class="keyword">this</span>.mainApplicationClass = deduceMainApplicationClass();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>    可以看到,在 SpringApplication 的构造方法中,调用 getSpringFactoriesInstances 方法获取 ApplicationListener 的实现,然后使用 setListener 方法设置监听器到 SpringBoot 容器中。<br><figure class="highlight java"><figcaption><span>Game.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获得指定类对应的对象们</span></span><br><span class="line"><span class="keyword">private</span> <T> <span class="function">Collection<T> <span class="title">getSpringFactoriesInstances</span><span class="params">(Class<T> type)</span> </span>{</span><br><span class="line"><span class="keyword">return</span> getSpringFactoriesInstances(type, <span class="keyword">new</span> Class<?>[] {});</span><br><span class="line">}</span><br><span class="line"><span class="keyword">private</span> <T> <span class="function">Collection<T> <span class="title">getSpringFactoriesInstances</span><span class="params">(Class<T> type, Class<?>[] parameterTypes,Object... args)</span> </span>{</span><br><span class="line">ClassLoader classLoader = getClassLoader();</span><br><span class="line"><span class="comment">// Use names and ensure unique to protect against duplicates</span></span><br><span class="line"><span class="comment">// 加载指定类型对应的,在 `META-INFO/spring.factories` 里的类名的数组</span></span><br><span class="line">Set<String> names = <span class="keyword">new</span> LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));</span><br><span class="line"><span class="comment">// 创建对象们</span></span><br><span class="line">List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);</span><br><span class="line"><span class="comment">// 排序对象们</span></span><br><span class="line">AnnotationAwareOrderComparator.sort(instances);</span><br><span class="line"><span class="keyword">return</span> instances;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>    先通过 spring.factory 获得实现的类名,然后依次实例化,之后进行排序,返回结果。我们来看一下 spring.factory 中监听器的描述。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/springfactories-listener.png" width="100%" height="100%"><br><br>    在 getSpringFactoriesInstances 方法中打断点,可以清楚地看到,通过 spring-factories 加载这些监听器的实现的类名<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/springfactories%E7%9B%91%E5%90%AC%E5%99%A8debug.png" width="100%" height="100%"><br><br>    监听器模式的4个要素,上面我们已经看了3个,还差一个<font color="#008000">事件触发机制</font>,我们来看一下源码吧。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/run-listener%E6%97%B6%E6%9C%BA.png" width="100%" height="100%"><br><br>    上图圈中的部分,通过 SpringApplicationRunListener 数组 listeners 直接或进入方法触发事件。下面我们来具体看一下第一个 starting 事件。<br>    进入 starting 方法内部,可以看到它是遍历调用 SpringApplicationRunListener 的 starting方法。<br><figure class="highlight java"><figcaption><span>SpringApplicationRunListener.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">starting</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">for</span> (SpringApplicationRunListener listener : <span class="keyword">this</span>.listeners) {</span><br><span class="line">listener.starting();</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>这个 SpringApplicationRunListener 中定义了各个阶段的事件,比如 starting、environmentPrepared、contextPrepared等等。<br><figure class="highlight java"><figcaption><span>SpringApplicationRunListener.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Listener for the {<span class="doctag">@link</span> SpringApplication} {<span class="doctag">@code</span> run} method.</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@link</span> SpringApplicationRunListener}s are loaded via the {<span class="doctag">@link</span> SpringFactoriesLoader}</span></span><br><span class="line"><span class="comment"> * and should declare a public constructor that accepts a {<span class="doctag">@link</span> SpringApplication}</span></span><br><span class="line"><span class="comment"> * instance and a {<span class="doctag">@code</span> String[]} of arguments. A new</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@link</span> SpringApplicationRunListener} instance will be created for each run.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Phillip Webb</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Dave Syer</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Andy Wilkinson</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 1.0.0</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">SpringApplicationRunListener</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Called immediately when the run method has first started. Can be used for very</span></span><br><span class="line"><span class="comment"> * early initialization.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">default</span> <span class="keyword">void</span> <span class="title">starting</span><span class="params">()</span> </span>{</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Called once the environment has been prepared, but before the</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@link</span> ApplicationContext} has been created.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> environment the environment</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">default</span> <span class="keyword">void</span> <span class="title">environmentPrepared</span><span class="params">(ConfigurableEnvironment environment)</span> </span>{</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Called once the {<span class="doctag">@link</span> ApplicationContext} has been created and prepared, but</span></span><br><span class="line"><span class="comment"> * before sources have been loaded.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> context the application context</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">default</span> <span class="keyword">void</span> <span class="title">contextPrepared</span><span class="params">(ConfigurableApplicationContext context)</span> </span>{</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Called once the application context has been loaded but before it has been</span></span><br><span class="line"><span class="comment"> * refreshed.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> context the application context</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">default</span> <span class="keyword">void</span> <span class="title">contextLoaded</span><span class="params">(ConfigurableApplicationContext context)</span> </span>{</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The context has been refreshed and the application has started but</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@link</span> CommandLineRunner CommandLineRunners} and {<span class="doctag">@link</span> ApplicationRunner</span></span><br><span class="line"><span class="comment"> * ApplicationRunners} have not been called.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> context the application context.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 2.0.0</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">default</span> <span class="keyword">void</span> <span class="title">started</span><span class="params">(ConfigurableApplicationContext context)</span> </span>{</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Called immediately before the run method finishes, when the application context has</span></span><br><span class="line"><span class="comment"> * been refreshed and all {<span class="doctag">@link</span> CommandLineRunner CommandLineRunners} and</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@link</span> ApplicationRunner ApplicationRunners} have been called.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> context the application context.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 2.0.0</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">default</span> <span class="keyword">void</span> <span class="title">running</span><span class="params">(ConfigurableApplicationContext context)</span> </span>{</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Called when a failure occurs when running the application.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> context the application context or {<span class="doctag">@code</span> null} if a failure occurred before</span></span><br><span class="line"><span class="comment"> * the context was created</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> exception the failure</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 2.0.0</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">default</span> <span class="keyword">void</span> <span class="title">failed</span><span class="params">(ConfigurableApplicationContext context, Throwable exception)</span> </span>{</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>    因为这个类定义了 SpringBoot 启动过程中各个阶段的事件,所以只用调用这个类的不同方法就可以在相应的节点触发对应的事件。<br>在 starting 方法内部,其实也很简单,就是调用了广播器的 multicastEvent 方法发送一个相应的 ApplicationStartingEvent 事件。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/starting%E8%B0%83%E7%94%A8%E5%B9%BF%E6%92%AD%E5%99%A8.png" width="100%" height="100%"><br><br>    SpringBoot容器通过这种机制,使监听器的内部实现与外部调用隔离开来。SpringBoot 容器在运行阶段,只需要调用这个类的各个关键方法就可以了,不需要 SpringBoot 容器自己去构造相应的事件来发送。<br>    我们进入广播器的 multicastEvent 方法内部。<br><figure class="highlight java"><figcaption><span>SimpleApplicationEventMulticaster.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">multicastEvent</span><span class="params">(ApplicationEvent event)</span> </span>{</span><br><span class="line">multicastEvent(event, resolveDefaultEventType(event));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">multicastEvent</span><span class="params">(<span class="keyword">final</span> ApplicationEvent event, @Nullable ResolvableType eventType)</span> </span>{</span><br><span class="line"><span class="comment">//对 eventType 做了一层包装</span></span><br><span class="line"> ResolvableType type = (eventType != <span class="keyword">null</span> ? eventType : resolveDefaultEventType(event));</span><br><span class="line"><span class="comment">//获取线程池</span></span><br><span class="line"> Executor executor = getTaskExecutor();</span><br><span class="line"> <span class="comment">//获取对当前事件感兴趣的监听器列表,然后遍历</span></span><br><span class="line"><span class="keyword">for</span> (ApplicationListener<?> listener : getApplicationListeners(event, type)) {</span><br><span class="line"><span class="keyword">if</span> (executor != <span class="keyword">null</span>) {</span><br><span class="line">executor.execute(() -> invokeListener(listener, event));</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span> {</span><br><span class="line">invokeListener(listener, event);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> ResolvableType <span class="title">resolveDefaultEventType</span><span class="params">(ApplicationEvent event)</span> </span>{</span><br><span class="line"><span class="keyword">return</span> ResolvableType.forInstance(event);</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><br>    上述方法中 getApplicationListeners 方法是获取对当前事件感兴趣的监听器列表。我们看一下源码,是如何实现的。<br><figure class="highlight java"><figcaption><span>AbstractApplicationEventMulticaster.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Return a Collection of ApplicationListeners matching the given</span></span><br><span class="line"><span class="comment"> * event type. Non-matching listeners get excluded early.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> event the event to be propagated. Allows for excluding</span></span><br><span class="line"><span class="comment"> * non-matching listeners early, based on cached matching information.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> eventType the event type</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> a Collection of ApplicationListeners</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see</span> org.springframework.context.ApplicationListener</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">protected</span> Collection<ApplicationListener<?>> getApplicationListeners(</span><br><span class="line">ApplicationEvent event, ResolvableType eventType) {</span><br><span class="line"></span><br><span class="line"> <span class="comment">//首先先获取事件的源 source,也就是 SpringApplication</span></span><br><span class="line">Object source = event.getSource();</span><br><span class="line"> <span class="comment">//获得source的class type</span></span><br><span class="line">Class<?> sourceType = (source != <span class="keyword">null</span> ? source.getClass() : <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">//通过sourceType和eventType构造一个缓存key。</span></span><br><span class="line"> <span class="comment">//目的是若当前已经获得过对当前事件感兴趣的监听器列表,则从缓存中读取,不必再重新进行计算哪些监听器对该事件感兴趣,提升了效率</span></span><br><span class="line">ListenerCacheKey cacheKey = <span class="keyword">new</span> ListenerCacheKey(eventType, sourceType);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Quick check for existing entry on ConcurrentHashMap...</span></span><br><span class="line">ListenerRetriever retriever = <span class="keyword">this</span>.retrieverCache.get(cacheKey);</span><br><span class="line"> <span class="comment">//第一次调用的话retriever为null</span></span><br><span class="line"><span class="keyword">if</span> (retriever != <span class="keyword">null</span>) {</span><br><span class="line"><span class="keyword">return</span> retriever.getApplicationListeners();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"> <span class="comment">//beanClassLoader为null,进入if条件内</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">this</span>.beanClassLoader == <span class="keyword">null</span> ||</span><br><span class="line">(ClassUtils.isCacheSafe(event.getClass(), <span class="keyword">this</span>.beanClassLoader) &&</span><br><span class="line">(sourceType == <span class="keyword">null</span> || ClassUtils.isCacheSafe(sourceType, <span class="keyword">this</span>.beanClassLoader)))) {</span><br><span class="line"><span class="comment">// Fully synchronized building and caching of a ListenerRetriever</span></span><br><span class="line"> <span class="comment">//同步块</span></span><br><span class="line"><span class="keyword">synchronized</span> (<span class="keyword">this</span>.retrievalMutex) {</span><br><span class="line"> <span class="comment">//进入同步块,先从缓存中获取retriever</span></span><br><span class="line">retriever = <span class="keyword">this</span>.retrieverCache.get(cacheKey);</span><br><span class="line"> <span class="comment">//缓存中retriever不为null,直接返回retriever的获取监听器方法</span></span><br><span class="line"><span class="keyword">if</span> (retriever != <span class="keyword">null</span>) {</span><br><span class="line"><span class="keyword">return</span> retriever.getApplicationListeners();</span><br><span class="line">}</span><br><span class="line"> <span class="comment">//缓存retriever为null,创建ListenerRetriever实例</span></span><br><span class="line">retriever = <span class="keyword">new</span> ListenerRetriever(<span class="keyword">true</span>);</span><br><span class="line"> <span class="comment">//调用retrieveApplicationListeners方法,检索监听器</span></span><br><span class="line">Collection<ApplicationListener<?>> listeners =</span><br><span class="line">retrieveApplicationListeners(eventType, sourceType, retriever);</span><br><span class="line"> <span class="comment">//将检索到的retriever放进缓存中 </span></span><br><span class="line"><span class="keyword">this</span>.retrieverCache.put(cacheKey, retriever);</span><br><span class="line"> <span class="comment">//返回监听器列表</span></span><br><span class="line"><span class="keyword">return</span> listeners;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span> {</span><br><span class="line"><span class="comment">// No ListenerRetriever caching -> no synchronization necessary</span></span><br><span class="line"><span class="keyword">return</span> retrieveApplicationListeners(eventType, sourceType, <span class="keyword">null</span>);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><br>    retrieveApplicationListeners 方法是如何检索监听器呢?我们继续来看。<br><figure class="highlight java"><figcaption><span>AbstractApplicationEventMulticaster.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Actually retrieve the application listeners for the given event and source type.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> eventType the event type</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> sourceType the event source type</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> retriever the ListenerRetriever, if supposed to populate one (for caching purposes)</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the pre-filtered list of application listeners for the given event and source type</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> Collection<ApplicationListener<?>> retrieveApplicationListeners(</span><br><span class="line">ResolvableType eventType, <span class="meta">@Nullable</span> Class<?> sourceType, <span class="meta">@Nullable</span> ListenerRetriever retriever) {</span><br><span class="line"></span><br><span class="line">List<ApplicationListener<?>> allListeners = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line">Set<ApplicationListener<?>> listeners;</span><br><span class="line">Set<String> listenerBeans;</span><br><span class="line"><span class="keyword">synchronized</span> (<span class="keyword">this</span>.retrievalMutex) {</span><br><span class="line"> <span class="comment">//获得默认的applicationListeners和applicationLIstenerBeans</span></span><br><span class="line"> <span class="comment">//applicationListeners就是上文提到spring.factories加载进来的listener的实现</span></span><br><span class="line">listeners = <span class="keyword">new</span> LinkedHashSet<>(<span class="keyword">this</span>.defaultRetriever.applicationListeners);</span><br><span class="line">listenerBeans = <span class="keyword">new</span> LinkedHashSet<>(<span class="keyword">this</span>.defaultRetriever.applicationListenerBeans);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Add programmatically registered listeners, including ones coming</span></span><br><span class="line"><span class="comment">// from ApplicationListenerDetector (singleton beans and inner beans).</span></span><br><span class="line"> <span class="comment">//遍历监听器</span></span><br><span class="line"><span class="keyword">for</span> (ApplicationListener<?> listener : listeners) {</span><br><span class="line"> <span class="comment">//依次判断,当前监听器是否对该事件感兴趣</span></span><br><span class="line"><span class="keyword">if</span> (supportsEvent(listener, eventType, sourceType)) {</span><br><span class="line"> <span class="comment">//若感兴趣,会加入到集合中</span></span><br><span class="line"><span class="keyword">if</span> (retriever != <span class="keyword">null</span>) {</span><br><span class="line">retriever.applicationListeners.add(listener);</span><br><span class="line">}</span><br><span class="line">allListeners.add(listener);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Add listeners by bean name, potentially overlapping with programmatically</span></span><br><span class="line"><span class="comment">// registered listeners above - but here potentially with additional metadata.</span></span><br><span class="line"><span class="keyword">if</span> (!listenerBeans.isEmpty()) {</span><br><span class="line">ConfigurableBeanFactory beanFactory = getBeanFactory();</span><br><span class="line"><span class="keyword">for</span> (String listenerBeanName : listenerBeans) {</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"><span class="keyword">if</span> (supportsEvent(beanFactory, listenerBeanName, eventType)) {</span><br><span class="line">ApplicationListener<?> listener =</span><br><span class="line">beanFactory.getBean(listenerBeanName, ApplicationListener.class);</span><br><span class="line"><span class="keyword">if</span> (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {</span><br><span class="line"><span class="keyword">if</span> (retriever != <span class="keyword">null</span>) {</span><br><span class="line"><span class="keyword">if</span> (beanFactory.isSingleton(listenerBeanName)) {</span><br><span class="line">retriever.applicationListeners.add(listener);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span> {</span><br><span class="line">retriever.applicationListenerBeans.add(listenerBeanName);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">allListeners.add(listener);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span> {</span><br><span class="line"><span class="comment">// Remove non-matching listeners that originally came from</span></span><br><span class="line"><span class="comment">// ApplicationListenerDetector, possibly ruled out by additional</span></span><br><span class="line"><span class="comment">// BeanDefinition metadata (e.g. factory method generics) above.</span></span><br><span class="line">Object listener = beanFactory.getSingleton(listenerBeanName);</span><br><span class="line"><span class="keyword">if</span> (retriever != <span class="keyword">null</span>) {</span><br><span class="line">retriever.applicationListeners.remove(listener);</span><br><span class="line">}</span><br><span class="line">allListeners.remove(listener);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">catch</span> (NoSuchBeanDefinitionException ex) {</span><br><span class="line"><span class="comment">// Singleton listener instance (without backing bean definition) disappeared -</span></span><br><span class="line"><span class="comment">// probably in the middle of the destruction phase</span></span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"> <span class="comment">//对监听器通过order值进行排序</span></span><br><span class="line">AnnotationAwareOrderComparator.sort(allListeners);</span><br><span class="line"><span class="keyword">if</span> (retriever != <span class="keyword">null</span> && retriever.applicationListenerBeans.isEmpty()) {</span><br><span class="line">retriever.applicationListeners.clear();</span><br><span class="line">retriever.applicationListeners.addAll(allListeners);</span><br><span class="line">}</span><br><span class="line"> <span class="comment">//将集合返回</span></span><br><span class="line"><span class="keyword">return</span> allListeners;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><br>    上面方法中 supportsEvent 方法是如何判断是否感兴趣呢?我们来打断点看一下。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/supportEvent-cloudFoundry.png" width="100%" height="100%"><br><br>    首先进入的是 CloudFoundryVcapEnvironmentPostProcessor,该类不是 GenericApplicationListener 的子类,所以会为该类创建一个GenericApplicationListenerAdapter,作为smartListener。断点进入GenericApplicationListenerAdapter中,可以看到,delegate就是CloudFoundryVcapEnvironmentPostProcessor,接着会调用resolveDeclaredEventType方法,来计算当前代理类对哪个事件感兴趣。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/listenerAdapter-cloudFoundry.png" width="100%" height="100%"><br><br>    这个resolveDeclaredEventType方法,是spring内部实现的一个泛型解析器,会根据类定义获得该类声明的事件类型,由于CloudFoundryVcapEnvironmentPostProcessor类实现ApplicationListener,泛型是ApplicationPreparedEvent。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/cloudFoundry%E6%B3%9B%E5%9E%8B.png" width="100%" height="100%"><br><br>    所以该方法就会获得 ApplicationPreparedEvent。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/resolveType-cloudFoundry.png" width="100%" height="100%"><br><br>    接着会调用 smartListener 的 supportsEventType 方法,判断是否支持该事件。进入方法,首先判断该类是否是 SmartApplicationListener 的子类,通过上面 CloudFoundryVcapEnvironmentPostProcessor 的定义,它不是 SmartApplicationListener 的子类,所以会进入 else 中。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/supportEventType-cloudFoundry.png" width="100%" height="100%"><br><br>    上面得到 declaredEventType 是 ApplicationPreparedEvent ,所以不为null。当前 eventType 是 ApplicationStartingEvent,显然“||”后半部分结果也是false,所以 smartListener.supportsEventType 是false,因为supportsEvent方法后面条件是“&&”,所以后面的内容不必在意,可以直接确定该监听器不支持该事件。<br>    我们接着调试,接下来进入的是 ConfigFileApplicationListener,按照上述同样的方法进行计算,由于它是 SmartApplicationListener 的子类。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/configFile%E5%AE%9A%E4%B9%89.png" width="100%" height="100%"><br><br>    所以会进入 if 条件内<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/configFile%E8%BF%9B%E5%85%A5if.png" width="100%" height="100%"><br><br>    这里会调用实现类的 supportEvent 方法,即 ConfigFileApplicationListener 类的 supportsEventType 方法。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/configFile%E5%86%85%E9%83%A8supportEventType.png" width="100%" height="100%"><br><br>    判断当前这个event是否是 ApplicationEnvironmentPreparedEvent,或者是否是 ApplicationPreparedEvent,都不是的话,会返回false,也就是说对该事件不感兴趣,所以不会将 ConfigFileApplicationListener 添加到上述的感兴趣监听器列表中。<br>    通过以上的方法,就检索出了哪些监听器对当前事件感兴趣。<br>    之后在遍历这些监听器的过程中,会调用 invokeListener 方法<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/multicastEvent-else.png" width="100%" height="100%"><br><br>    invokeListener方法中会调用doInvokeListener方法,最终我们可以看到listener.onApplicationEvent,这里会进入具体事件的触发<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/doInvokeListener%E6%96%AD%E7%82%B9.png" width="100%" height="100%"><br><br>    以 ConfigFileApplicationListener 为例,会根据不同的event触发不同的事件。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/configFile-onApplicationEvent.png" width="100%" height="100%"><br><br>    通过上面的分析,总结一下获取监听器列表逻辑,如下图所示。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/%E8%8E%B7%E5%8F%96%E7%9B%91%E5%90%AC%E5%99%A8%E5%88%97%E8%A1%A8%E6%B5%81%E7%A8%8B.png" width="40%" height="60%"><br><br>    其中supportsEvent流程如下图所示。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/spring-boot/listener/%E8%A7%A6%E5%8F%91%E6%9D%A1%E4%BB%B6%E6%B5%81%E7%A8%8B.png" width="100%" height="100%"><br> </p><h2 id="三、总结"><a href="#三、总结" class="headerlink" title="三、总结"></a>三、总结</h2><p>    以上关于SpringBoot中监听器的主要核心方法的实现我们已经一点点看过了,看上去很复杂,但是实际上还是按照标准的监听器模式实现的。其中将一系列事件的触发方法封装在一个runListener中,降低了系统的耦合度,使得调用的时候也变得很轻松,这一点很值得我们学习。</p>]]></content>
<summary type="html">
<p>&ensp;&ensp;&ensp;&ensp;最近在看SpringBoot的源码,在SpringBoot项目启动的过程中,监听器在不同阶段都会监听相应的事件,今天我们就来谈谈SpringBoot启动过程中的监听器。</p>
<h2 id="一、监听器扫盲"><a href
</summary>
<category term="SpringBoot" scheme="http://redmapleren.com/categories/SpringBoot/"/>
<category term="SpringBoot" scheme="http://redmapleren.com/tags/SpringBoot/"/>
<category term="源码" scheme="http://redmapleren.com/tags/%E6%BA%90%E7%A0%81/"/>
</entry>
<entry>
<title>基础加固-建造者模式</title>
<link href="http://redmapleren.com/2019/07/09/%E5%9F%BA%E7%A1%80%E5%8A%A0%E5%9B%BA-%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F/"/>
<id>http://redmapleren.com/2019/07/09/基础加固-建造者模式/</id>
<published>2019-07-09T03:30:00.000Z</published>
<updated>2019-07-10T07:33:01.383Z</updated>
<content type="html"><![CDATA[<p>   上篇我们复习了工厂模式,这节我们来看一下创建型设计模式的另一种模式–建造者模式。 </p><h2 id="一-定义"><a href="#一-定义" class="headerlink" title="一.定义"></a>一.定义</h2><p>   建造者模式是指将一个复杂对象的构建和它的表示分离,使同样的构建过程可以构建不同的表示。用户只需要指定需要建造的类型就可以得到它们,建造过程以及细节并不需要知道。适用于那些创建流程固定,但顺序不一定固定的对象。如果一个对象有非常复杂的内部结构,即有很多属性,我们想把这种复杂对象的创建和使用进行分离,我们可以使用建造者模式。这样看定义难免抽象,接下来我们一起编写代码来加深对工厂模式的理解。 </p><h2 id="二、标准版"><a href="#二、标准版" class="headerlink" title="二、标准版"></a>二、标准版</h2><h3 id="1-代码实现"><a href="#1-代码实现" class="headerlink" title="1.代码实现"></a>1.代码实现</h3><p>   首先我们有一个类Game,有名称、简介、制作团队、宣传视频、社区几个属性。代码如下:<br><figure class="highlight java"><figcaption><span>Game.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Game</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"> <span class="keyword">private</span> String introduce;</span><br><span class="line"> <span class="keyword">private</span> String team;</span><br><span class="line"> <span class="keyword">private</span> String video;</span><br><span class="line"> <span class="keyword">private</span> String community;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getName</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setName</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getIntroduce</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> introduce;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setIntroduce</span><span class="params">(String introduce)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.introduce = introduce;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getTeam</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> team;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setTeam</span><span class="params">(String team)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.team = team;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getVideo</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> video;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setVideo</span><span class="params">(String video)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.video = video;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getCommunity</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> community;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setCommunity</span><span class="params">(String community)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.community = community;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Game{"</span> +</span><br><span class="line"> <span class="string">"name='"</span> + name + <span class="string">'\''</span> +</span><br><span class="line"> <span class="string">", introduce='"</span> + introduce + <span class="string">'\''</span> +</span><br><span class="line"> <span class="string">", team='"</span> + team + <span class="string">'\''</span> +</span><br><span class="line"> <span class="string">", video='"</span> + video + <span class="string">'\''</span> +</span><br><span class="line"> <span class="string">", community='"</span> + community + <span class="string">'\''</span> +</span><br><span class="line"> <span class="string">'}'</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> </p><p>我们创建一个抽象类GameBuilder,其中定义了创建各个属性的方法和生成Game对象的方法,代码如下:<br><figure class="highlight java"><figcaption><span>GameBuilder.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">GameBuilder</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">buildName</span><span class="params">(String name)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">buildIntroduce</span><span class="params">(String introduce)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">buildTeam</span><span class="params">(String team)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">buildVideo</span><span class="params">(String video)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">buildCommunity</span><span class="params">(String community)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> Game <span class="title">makeGame</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure> </p><p>接下来我们要创建具体的builder实现,比如我们要创建一个动作游戏的建造者ActionGameBuilder,我们只需要继承上面的抽象builder,重写里面的方法即可,代码如下:<br><figure class="highlight java"><figcaption><span>ActionGameBuilder.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ActionGameBuilder</span> <span class="keyword">extends</span> <span class="title">GameBuilder</span> </span>{</span><br><span class="line"> Game game = <span class="keyword">new</span> Game();</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">buildName</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> game.setName(name);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">buildIntroduce</span><span class="params">(String introduce)</span> </span>{</span><br><span class="line"> game.setIntroduce(introduce);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">buildTeam</span><span class="params">(String team)</span> </span>{</span><br><span class="line"> game.setTeam(team);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">buildVideo</span><span class="params">(String video)</span> </span>{</span><br><span class="line"> game.setVideo(video);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">buildCommunity</span><span class="params">(String community)</span> </span>{</span><br><span class="line"> game.setCommunity(community);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Game <span class="title">makeGame</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> game;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> </p><p>其实现在我们已经可以使用我们创建的builder进行创建游戏了,不过这里可以定义一个管理角色的对象,对builder进行管理,用它来进行管理对象的创建,这里我们定义一个游戏经理类GameManager。<br><figure class="highlight java"><figcaption><span>GameManager.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GameManager</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> GameBuilder gameBuilder;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setGameBuilder</span><span class="params">(GameBuilder gameBuilder)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.gameBuilder = gameBuilder;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Game <span class="title">makeGame</span><span class="params">(String name, String introduce, String team, String video, String community)</span> </span>{</span><br><span class="line"> gameBuilder.buildName(name);</span><br><span class="line"> gameBuilder.buildIntroduce(introduce);</span><br><span class="line"> gameBuilder.buildTeam(team);</span><br><span class="line"> gameBuilder.buildVideo(video);</span><br><span class="line"> gameBuilder.buildCommunity(community);</span><br><span class="line"> <span class="keyword">return</span> gameBuilder.makeGame();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>写到这里我们已经完成了一个标准的建造者模式代码,写一个测试类来测试一下。<br><figure class="highlight java"><figcaption><span>Test.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> GameBuilder gameBuilder = <span class="keyword">new</span> ActionGameBuilder();</span><br><span class="line"></span><br><span class="line"> GameManager manager = <span class="keyword">new</span> GameManager();</span><br><span class="line"> manager.setGameBuilder(gameBuilder);</span><br><span class="line"></span><br><span class="line"> Game game = manager.makeGame(<span class="string">"游戏名称"</span>,</span><br><span class="line"> <span class="string">"这是一个很好玩的动作游戏"</span>,</span><br><span class="line"> <span class="string">"制作团队"</span>,</span><br><span class="line"> <span class="string">"游戏宣传视频"</span>,</span><br><span class="line"> <span class="string">"游戏社区"</span>);</span><br><span class="line"> System.out.println(game);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>控制台可以看到创建的游戏信息。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Game{name='游戏名称', introduce='这是一个很好玩的动作游戏', team='制作团队', video='游戏宣传视频', community='游戏社区'}</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure> </p><h3 id="2-UML"><a href="#2-UML" class="headerlink" title="2.UML"></a>2.UML</h3><p>   我们可以看到标准的建造者模式的UML类图。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/design_pattern/BuilderStandardUML.png" width="50%" height="50%"><br>我们测试时通过游戏经理和动作游戏建造者创建了含有较多属性的游戏类,并不关心创建的过程和顺序,有很好的封装性,使创建和使用分离,并且有很好的扩展性,建造类之间独立,在一定程度上解耦。</p><h2 id="三、演进版"><a href="#三、演进版" class="headerlink" title="三、演进版"></a>三、演进版</h2><h3 id="1-代码实现-1"><a href="#1-代码实现-1" class="headerlink" title="1.代码实现"></a>1.代码实现</h3><p>   更多时候我们需要一种可以链式调用的形式进行建造我们的对象,实现其实也很简单,我们可以创建一个静态内部类作为对象的builder,具体代码如下:<br><figure class="highlight java"><figcaption><span>Game.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Game</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"> <span class="keyword">private</span> String introduce;</span><br><span class="line"> <span class="keyword">private</span> String team;</span><br><span class="line"> <span class="keyword">private</span> String video;</span><br><span class="line"> <span class="keyword">private</span> String community;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Game</span><span class="params">(GameBuilder gameBuilder)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = gameBuilder.name;</span><br><span class="line"> <span class="keyword">this</span>.introduce = gameBuilder.introduce;</span><br><span class="line"> <span class="keyword">this</span>.team = gameBuilder.team;</span><br><span class="line"> <span class="keyword">this</span>.video = gameBuilder.video;</span><br><span class="line"> <span class="keyword">this</span>.community = gameBuilder.community;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Game{"</span> +</span><br><span class="line"> <span class="string">"name='"</span> + name + <span class="string">'\''</span> +</span><br><span class="line"> <span class="string">", introduce='"</span> + introduce + <span class="string">'\''</span> +</span><br><span class="line"> <span class="string">", team='"</span> + team + <span class="string">'\''</span> +</span><br><span class="line"> <span class="string">", video='"</span> + video + <span class="string">'\''</span> +</span><br><span class="line"> <span class="string">", community='"</span> + community + <span class="string">'\''</span> +</span><br><span class="line"> <span class="string">'}'</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">GameBuilder</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"> <span class="keyword">private</span> String introduce;</span><br><span class="line"> <span class="keyword">private</span> String team;</span><br><span class="line"> <span class="keyword">private</span> String video;</span><br><span class="line"> <span class="keyword">private</span> String community;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> GameBuilder <span class="title">buildName</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> GameBuilder <span class="title">buildIntroduce</span><span class="params">(String introduce)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.introduce = introduce;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> GameBuilder <span class="title">buildTeam</span><span class="params">(String team)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.team = team;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> GameBuilder <span class="title">buildVideo</span><span class="params">(String video)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.video = video;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> GameBuilder <span class="title">buildCommunity</span><span class="params">(String community)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.community = community;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Game <span class="title">build</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Game(<span class="keyword">this</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>   跟标准版本不同的是,在builder中的buildXxx方法返回的是builder本身,这样我们就可以使用链式调用的方式进行创建,最后调用build方法返回我们的实体类。写一个测试类测试一下。<br><figure class="highlight java"><figcaption><span>Test.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> Game game = <span class="keyword">new</span> Game.GameBuilder().buildName(<span class="string">"游戏名称"</span>)</span><br><span class="line"> .buildIntroduce(<span class="string">"这是一个很好玩的游戏"</span>)</span><br><span class="line"> .buildTeam(<span class="string">"制作团队"</span>)</span><br><span class="line"> .buildVideo(<span class="string">"介绍视频"</span>)</span><br><span class="line"> .buildCommunity(<span class="string">"游戏社区"</span>)</span><br><span class="line"> .build();</span><br><span class="line"> System.out.println(game);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>执行程序,可以看到我们创建的game的信息。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Game{name='游戏名称', introduce='这是一个很好玩的游戏', team='制作团队', video='介绍视频', community='游戏社区'}</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure> </p><h3 id="2-UML-1"><a href="#2-UML-1" class="headerlink" title="2.UML"></a>2.UML</h3><p>   演进版本的UML类图更加简单,可以很清楚地看到,对于应用方,只需要使用对象的builder进行链式调用,最后调用build方法返回所需的对象实例即可。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/design_pattern/BuilderV2UML.png" width="50%" height="50%"> </p><h2 id="四、总结"><a href="#四、总结" class="headerlink" title="四、总结"></a>四、总结</h2><p>   以上介绍了两个版本的建造者模式的实现,总体来说都是比较简单,容易理解的。在日常应用中,第二种是比较常用的,各种开源框架也都广泛使用,比如Guava中CacheBuilder,都是通过第二种链式调用的方式创建对象,使用方便。对于以后的扩展维护也比较方便。</p>]]></content>
<summary type="html">
<p>&ensp;&ensp;&ensp;上篇我们复习了工厂模式,这节我们来看一下创建型设计模式的另一种模式–建造者模式。 </p>
<h2 id="一-定义"><a href="#一-定义" class="headerlink" title="一.定义"></a>一.定义</
</summary>
<category term="设计模式" scheme="http://redmapleren.com/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="基础" scheme="http://redmapleren.com/tags/%E5%9F%BA%E7%A1%80/"/>
<category term="设计模式" scheme="http://redmapleren.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>基础加固-工厂模式</title>
<link href="http://redmapleren.com/2019/06/27/%E5%9F%BA%E7%A1%80%E5%8A%A0%E5%9B%BA-%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/"/>
<id>http://redmapleren.com/2019/06/27/基础加固-工厂模式/</id>
<published>2019-06-27T06:00:00.000Z</published>
<updated>2019-07-02T06:31:50.671Z</updated>
<content type="html"><![CDATA[<p>   随着工作年限的增加,对工作中的代码结构和质量的追求也在增加。之前看书学习过的设计模式,当时也就是看看,看过或许有的忘记,或许不会使用。入职新公司以来,看团队高级工程师的代码,为了实现一个需求,可以使用合适的设计模式去规范代码,使代码的可读性和可扩展性都大大提升,我意识到是时候巩固一下基础,系统学习一下设计模式了。从这边文章开始,我将从创建型模式中的工厂模式开始复习,实现简单的demo,对比各个模式的UML类图,希望提升自己的编码能力,同时也便于阅读各种开源框架的源码。闲话少许,我们开始吧~ </p><h2 id="一、简单工厂模式"><a href="#一、简单工厂模式" class="headerlink" title="一、简单工厂模式"></a>一、简单工厂模式</h2><h3 id="1-问题引入"><a href="#1-问题引入" class="headerlink" title="1.问题引入"></a>1.问题引入</h3><p>   首先来看一个场景,我有一个抽象类Game,标识这个类为一个游戏,游戏可以有很多种,比如“马里奥”、“塞尔达”等等。创建一个具体的游戏,一种最简单的方式就是创建一个具体游戏类继承Game抽象类,然后重写里面的建造方法。代码如下:<br><figure class="highlight java"><figcaption><span>Game.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Game</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">produce</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure> </p><p>Mario游戏<br><figure class="highlight java"><figcaption><span>MarioGame.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MarioGame</span> <span class="keyword">extends</span> <span class="title">Game</span></span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">produce</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"Mario game produced!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> </p><p>Zelda游戏<br><figure class="highlight java"><figcaption><span>ZeldaGame.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ZeldaGame</span> <span class="keyword">extends</span> <span class="title">Game</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">produce</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"Zelda game produced!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>这样我们在需要创建某种游戏的时候,直接new对应的具体游戏类,调用其中的建造方法即可。<br><figure class="highlight java"><figcaption><span>ZeldaGame.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> Game game = <span class="keyword">new</span> MarioGame();</span><br><span class="line"> game.produce();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>可以看到控制台中打印的结果,创建了Mario游戏<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Mario game produced!</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure><br>   看到这里,大家肯定已经发现了这种写法的弊端。每次需要直接new出来想要的具体游戏类,我们如果能使用一个工具去创建我们想要的具体游戏类,不需要关心创建的过程,那岂不是很爽吗?这时候简单工厂模式就登场了。 </p><h3 id="2-代码实现"><a href="#2-代码实现" class="headerlink" title="2.代码实现"></a>2.代码实现</h3><p>   和上面一样,还是有我们的抽象游戏Game类,不同的游戏去继承Game类,实现自己的produce方法。不同的是,这次我们创建一个简单工厂类GameFactory,根据参数通过这个工厂去创建我们需要的具体游戏类。具体代码如下:<br><figure class="highlight java"><figcaption><span>GameFactory.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GameFactory</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> Game <span class="title">getGame</span><span class="params">(String type)</span></span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="string">"mario"</span>.equalsIgnoreCase(type)){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> MarioGame();</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">"zelda"</span>.equalsIgnoreCase(type)){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ZeldaGame();</span><br><span class="line"> }<span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>这样,我们在使用工厂类创建具体游戏类的时候就不用直接new出来具体的游戏,直接使用工厂类就可以了。<br><figure class="highlight java"><figcaption><span>GameFactory.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> GameFactory gameFactory = <span class="keyword">new</span> GameFactory();</span><br><span class="line"> Game game = gameFactory.getGame(<span class="string">"zelda"</span>);</span><br><span class="line"> <span class="keyword">if</span> (game == <span class="keyword">null</span>){</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> game.produce();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>结果如下,我们成功创建了zelda游戏<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Zelda game produced!</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure> </p><h3 id="3-UML"><a href="#3-UML" class="headerlink" title="3.UML"></a>3.UML</h3><p>我们先看一下未引入简单工厂时候的UML类图。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/design_pattern/GameOriginUML.png" width="50%" height="50%"><br>   可以看到我们创建Mario和Zelda两个游戏,都分别通过具体的类进行创建,以后如果要创建更多的游戏,那时的UML大家应该可以想象,从应用方向不同的具体游戏类都会有create联系,整个关系就非常杂乱。我们使用了简单工厂模式之后的UML是什么样的呢?<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/design_pattern/GameSimpleFactoryUML.png" width="50%" height="50%"> </p><p>   这样应用方使用简单工厂创建不同的游戏时,只需要告诉工厂我需要什么游戏即可,就不必关心我应该具体new什么游戏了。<br>   当然这里实现的简单工厂是最简单的形式,其实完全可以在工厂类中使用java反射创建不同的游戏,在应用方使用的时候,传入对应的Class即可 </p><h2 id="二、工厂方法模式"><a href="#二、工厂方法模式" class="headerlink" title="二、工厂方法模式"></a>二、工厂方法模式</h2><h3 id="1-问题引入-1"><a href="#1-问题引入-1" class="headerlink" title="1.问题引入"></a>1.问题引入</h3><p>   通过上面简单工厂模式,我们已经可以通过工厂来创建具体的对象。但是这时候我需要增加一个新的“精灵宝可梦”的游戏,我们怎么操作呢?也很简单,新建一个Pokemon游戏的类继承Game,实现自己的produce方法,在工厂类中修改逻辑,使工厂可以创建新的游戏。你可能会说,这样也很好啊,创建新游戏的时候我只需要去改工厂里的逻辑就好了,但是当类越来越多,逻辑越来越复杂,你的工厂类就会变得特别庞大,每次创建新的游戏都要修改这个工厂类,是不符合软件设计中的开闭原则的。这时候,如果按不同游戏的类型把工厂分为不同的工厂,创建的时候只需要使用对应的工厂生产我们需要的游戏就好了。 </p><h3 id="2-代码实现-1"><a href="#2-代码实现-1" class="headerlink" title="2.代码实现"></a>2.代码实现</h3><p>   首先我们新创建“精灵宝可梦”游戏类,同样让它继承Game。<br><figure class="highlight java"><figcaption><span>PokemonGame.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PokemonGame</span> <span class="keyword">extends</span> <span class="title">Game</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">produce</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"Pokemon game produced!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>刚刚说到,要按不同游戏的类型创建不同的工厂,这里我们首先把工厂抽象出来,建一个抽象工厂类<br><figure class="highlight java"><figcaption><span>GameFactory.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">GameFactory</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> Game <span class="title">getGame</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>之后,通过继承这个抽象工厂类,创建“马里奥”、“塞尔达”、“精灵宝可梦”的工厂<br><figure class="highlight java"><figcaption><span>MarioGameFactory.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MarioGameFactory</span> <span class="keyword">extends</span> <span class="title">GameFactory</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Game <span class="title">getGame</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> MarioGame();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br><figure class="highlight java"><figcaption><span>ZeldaGameFactory.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ZeldaGameFactory</span> <span class="keyword">extends</span> <span class="title">GameFactory</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Game <span class="title">getGame</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ZeldaGame();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br><figure class="highlight java"><figcaption><span>PokemonGameFactory.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PokemonGameFactory</span> <span class="keyword">extends</span> <span class="title">GameFactory</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Game <span class="title">getGame</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> PokemonGame();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>这些不同的工厂创建后,我们就可以使用各自的工厂来生产各自的游戏了,这里以生产Pokemon游戏为例<br><figure class="highlight java"><figcaption><span>Test.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> GameFactory gameFactory = <span class="keyword">new</span> PokemonGameFactory();</span><br><span class="line"> Game game = gameFactory.getGame();</span><br><span class="line"> game.produce();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>执行代码,控制台输出如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Pokemon game produced!</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure><br>我们已经成功将简单工厂升级成了工厂方法模式,提高了代码的可复用性。</p><h3 id="3-UML-1"><a href="#3-UML-1" class="headerlink" title="3.UML"></a>3.UML</h3><p>这时候的UML类图如下:<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/design_pattern/GameFactoryMethodUML.png" width="50%" height="50%"> </p><h2 id="三、抽象工厂模式"><a href="#三、抽象工厂模式" class="headerlink" title="三、抽象工厂模式"></a>三、抽象工厂模式</h2><h3 id="1-问题引入-2"><a href="#1-问题引入-2" class="headerlink" title="1.问题引入"></a>1.问题引入</h3><p>   假如每个游戏都有一个游戏社区,比如Mario有游戏还有游戏社区,Pokemon也有游戏和游戏社区,这里Mario的游戏和社区属于同一个产品族,Pokemon的游戏和社区也属于同一个产品族;而Mario的游戏社区和Pokemon的游戏社区属于同一产品等级,Mario游戏和Pokemon游戏属于同一产品等级。有类似于这种关系的场景,我们使用一种怎样的模式呢?</p><h3 id="2-代码实现-2"><a href="#2-代码实现-2" class="headerlink" title="2.代码实现"></a>2.代码实现</h3><p>   首先我们创建一个接口,抽象出来一个工厂GameFactory,这个抽象工厂定义了可以获取游戏和游戏社区的方法。<br><figure class="highlight java"><figcaption><span>GameFactory.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">GameFactory</span> </span>{</span><br><span class="line"> <span class="function">Game <span class="title">getGame</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function">Community <span class="title">getCommunity</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>其中Game和Community是抽象类,具体的游戏和社区需要继承这两个抽象类自己去实现。<br><figure class="highlight java"><figcaption><span>Game.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Game</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">produce</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br><figure class="highlight java"><figcaption><span>Community.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Community</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">produce</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br><figure class="highlight java"><figcaption><span>MarioGame.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MarioGame</span> <span class="keyword">extends</span> <span class="title">Game</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">produce</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"Mario game produced!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br><figure class="highlight java"><figcaption><span>MarioCommunity.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MarioCommunity</span> <span class="keyword">extends</span> <span class="title">Community</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">produce</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"Mario community produced."</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>之后定义不同的具体工厂,实现GameFactory接口,实现接口中的获取游戏和游戏社区的方法。这里的游戏和社区属于同一产品族,使用具体的工厂可以获得同一产品族的对象。<br>Mario产品族的工厂<br><figure class="highlight java"><figcaption><span>MarioFactory.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MarioFactory</span> <span class="keyword">implements</span> <span class="title">GameFactory</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Game <span class="title">getGame</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> MarioGame();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Community <span class="title">getCommunity</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> MarioCommunity();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>Pokemon产品族的工厂<br><figure class="highlight java"><figcaption><span>PokemonFactory.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PokemonFactory</span> <span class="keyword">implements</span> <span class="title">GameFactory</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Game <span class="title">getGame</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> PokemonGame();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Community <span class="title">getCommunity</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> PokemonCommunity();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>我们写一个测试类测试一下。<br><figure class="highlight java"><figcaption><span>Test.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> <span class="comment">//创建抽象工厂,指定是Mario工厂</span></span><br><span class="line"> GameFactory gameFactory = <span class="keyword">new</span> MarioFactory();</span><br><span class="line"> <span class="comment">//调用抽象工厂的方法获取游戏和游戏社区</span></span><br><span class="line"> Game game = gameFactory.getGame();</span><br><span class="line"> Community community = gameFactory.getCommunity();</span><br><span class="line"> <span class="comment">//会根据具体工厂的不同获得不同的游戏和游戏社区,这里是Mario产品族</span></span><br><span class="line"> game.produce();</span><br><span class="line"> community.produce();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> </p><h3 id="3-UML-2"><a href="#3-UML-2" class="headerlink" title="3.UML"></a>3.UML</h3><p>我们来看一下现在的UML类图。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/design_pattern/AbstractFactoryUML.png" width="50%" height="50%"><br>   关系很清晰,大家都可以理解。现在想一个问题,如果我们的业务场景需要经常增加产品族内的产品,那么我们总是需要增加抽象工厂里的方法,进而修改抽象工厂的实现,这么一来就不符合软件设计的开闭原则了;如果我们的业务场景需要经常增加产品等级,这时候我们仅需要添加相应的产品工厂和产品类即可。 </p><p>   以上介绍了三种工厂相关的模式,不能说哪种模式更优于哪种,只能根据具体的业务场景,选择合适的模式。没有最好的设计模式,只有最适合的设计模式。 </p>]]></content>
<summary type="html">
<p>&ensp;&ensp;&ensp;随着工作年限的增加,对工作中的代码结构和质量的追求也在增加。之前看书学习过的设计模式,当时也就是看看,看过或许有的忘记,或许不会使用。入职新公司以来,看团队高级工程师的代码,为了实现一个需求,可以使用合适的设计模式去规范代码,使代码的可读
</summary>
<category term="设计模式" scheme="http://redmapleren.com/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="基础" scheme="http://redmapleren.com/tags/%E5%9F%BA%E7%A1%80/"/>
<category term="设计模式" scheme="http://redmapleren.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>记一次大批量物理删除数据</title>
<link href="http://redmapleren.com/2019/04/29/%E8%AE%B0%E4%B8%80%E6%AC%A1%E5%A4%A7%E6%89%B9%E9%87%8F%E7%89%A9%E7%90%86%E5%88%A0%E9%99%A4%E6%95%B0%E6%8D%AE/"/>
<id>http://redmapleren.com/2019/04/29/记一次大批量物理删除数据/</id>
<published>2019-04-29T03:00:00.000Z</published>
<updated>2019-04-30T10:02:09.381Z</updated>
<content type="html"><![CDATA[<p>   接上次闹钟项目更改字符集之后,这几天又需要对线上数据做处理。背景是,同步闹钟的时候会把用户之前删除过的闹钟都同步下来,而删除的闹钟在客户端没有任何显示,也没有任何恢复的操作,对于用户来说其实是完全没有用的数据。当用户的无用历史闹钟增多到一定数量,同步的时候,客户端上报的数据body就特别大,已经超过了Nginx配置的request最大限制,这样就导致了部分老用户无法同步的情况。解决思路其实很简单,将客户端的上报策略修改成分批上传,服务端分批的返回,最后的结果客户端在本地做聚合,显示给用户。但是这需要客户端和服务端共同修改,客户端还要发版审核,现在需要一种比较快速的方式,让用户在尽可能短的时间内可以进行同步。最后决定将数据库中2018年以前用户无用的闹钟进行删除,找到dba同学商量要删除数据,但是很不幸,dba同学告知我们目前他们没有成熟的工具操作,让我们自己写程序删除,他们可以负责备份数据。看来只能靠自己了,接下来就看一下从分析到实现整个删除任务的具体过程。 </p><h2 id="一、思路分析"><a href="#一、思路分析" class="headerlink" title="一、思路分析"></a>一、思路分析</h2><p>   需要进行删除的这张表是一个很宽的数据量很大的表,当前共有七千多万条数据,经过筛选查询,发现2018年之前且状态为无效的闹钟数量达到了五千多万,也就是说现在需求是要物理删除这五千多万条数据。需求明确了,下面就要考虑几个问题。</p><ol><li>要删除的五千多万条数据如何定位?</li><li>怎样高效地删除这么大量的数据同时保证负载正常?</li><li>怎样保证集群环境下,删除任务只执行一次? </li></ol><p>我们分别看一下解决这些问题的思路。 </p><h3 id="1-定位目标数据"><a href="#1-定位目标数据" class="headerlink" title="1.定位目标数据"></a>1.定位目标数据</h3><p>   表中主要字段包括user_id,status,init_time,分别表示闹钟所属的用户id、闹钟状态、闹钟初始化时间。删除的大体思路是通过in user_id字段来delete,那么如何找到要in哪些user_id呢?从上面的分析可以知道,这张表拥有很大的数据量,想要一次delete是不可能的事情,需要进行分批删除,每次in一部分user_id。那么每次的user_id如何获取呢?可以通过分页排序的group by语句得到分批的user_id。 </p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> user_id <span class="keyword">from</span> clocks <span class="keyword">order</span> <span class="keyword">by</span> user_id <span class="keyword">group</span> <span class="keyword">by</span> user_id <span class="keyword">limit</span> <span class="number">0</span>,<span class="number">500</span>;</span><br></pre></td></tr></table></figure><p>   上面的分页查询看上去没什么问题,但是随着翻页次数增大,效率也越来越慢,假设我们翻到了2000页,这个语句查询的2000之前的数据都是无用的,效率特别低下。由数据量分析可知,这张表里通过user_id分组,可以得到200W+数据,如果我们每次分页查询500条,计算可得 最后我们需要将 200W / 500 作为limit的起点,这样的查询是灾难性的。但是通过下面的sql修改,可以大大提高分页的查询效率。 </p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> user_id <span class="keyword">from</span> clocks <span class="keyword">where</span> user_id > <span class="number">0</span> <span class="keyword">order</span> <span class="keyword">by</span> user_id <span class="keyword">group</span> <span class="keyword">by</span> user_id <span class="keyword">limit</span> <span class="number">500</span>;</span><br><span class="line"><span class="keyword">select</span> user_id <span class="keyword">from</span> clocks <span class="keyword">where</span> user_id > <span class="number">500</span> <span class="keyword">order</span> <span class="keyword">by</span> user_id <span class="keyword">group</span> <span class="keyword">by</span> user_id <span class="keyword">limit</span> <span class="number">500</span>;</span><br><span class="line"><span class="keyword">select</span> user_id <span class="keyword">from</span> clocks <span class="keyword">where</span> user_id > <span class="number">1000</span> <span class="keyword">order</span> <span class="keyword">by</span> user_id <span class="keyword">group</span> <span class="keyword">by</span> user_id <span class="keyword">limit</span> <span class="number">500</span>;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>   通过where过滤当前页之前的数据,可以大大提高查询效率。只需要每次记下当次分页结果中最大的user_id,下次分页将此user_id作为分页起始条件进行过滤即可。因为我们使用order by进行排序,查询结果都是有序的,可以将每次的user_id结果放进一个LinkedList中,每次使用的时候peekLast()就能得到当前分组的最大user_id。定位目标数据的思路大体就是这样,思路清晰后代码实现也是很容易的。 </p><figure class="highlight java"><figcaption><span>ClockDeleteUser.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ClockDeleteUser</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 下一次分页的起始user_id</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">long</span> nextFirstUserId;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> LinkedList<Long> userIds;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> perLimit;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">ClockDeleteUser</span><span class="params">(<span class="keyword">long</span> nextFirstUserId,<span class="keyword">int</span> perLimit)</span></span>{</span><br><span class="line"> <span class="keyword">this</span>.nextFirstUserId = nextFirstUserId;</span><br><span class="line"> <span class="keyword">this</span>.perLimit = perLimit;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure> <figure class="highlight java"><figcaption><span>ClockDeleteService.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ClockDeleteService</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> <span class="keyword">int</span> DELETE_USER_PER_LIMIT = <span class="number">500</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> SyncDao syncDao;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取删除语句中in的userId的信息集合</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> List<List<Long>> getDeleteUser(){</span><br><span class="line"> List<List<Long>> result = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line"> <span class="keyword">long</span> nextFirstUserId = <span class="number">0</span>;</span><br><span class="line"> ClockDeleteUser clockDeleteUser = <span class="keyword">new</span> ClockDeleteUser(nextFirstUserId,DELETE_USER_PER_LIMIT);</span><br><span class="line"> LinkedList<Long> userIds = syncDao.getClockDeleteUserIds(clockDeleteUser);</span><br><span class="line"> <span class="keyword">while</span> (CollectionUtils.isNotEmpty(userIds)){</span><br><span class="line"> result.add(userIds);</span><br><span class="line"> clockDeleteUser.setNextFirstUserId(userIds.peekLast());</span><br><span class="line"> userIds = syncDao.getClockDeleteUserIds(clockDeleteUser);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 按照userId集合删除无用的闹钟</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> userIds</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">deleteUnusedClock</span><span class="params">(List<Long> userIds)</span></span>{</span><br><span class="line"> <span class="comment">// 分批删除无用闹钟</span></span><br><span class="line"> <span class="keyword">return</span> syncDao.deleteUnusedClocksByUserInitTime(userIds);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure> <h3 id="2-多线程删除"><a href="#2-多线程删除" class="headerlink" title="2.多线程删除"></a>2.多线程删除</h3><p>   找到了每次分批的user_id条件,接下来就可以进行删除操作了。这么庞大的数据量,每次串行执行delete where,明显效率很低,估计删除完这些数据也要进行几个小时吧。这时我们很容易地想到了使用多个线程同时进行delete操作。因为user_id字段是这张表的索引,所以delete的时候走索引,并不会锁住整个表,所以我们可以使用多个线程同时进行删除。但是由于数据量大,分组要达4000+,我们要使用多少个线程同时工作呢?<br>这里我们通过 Runtime.getRuntime().availableProcessors() 获取当前可用处理器数量,用来创建线程池。<br>   我们使用 Executors.newFixedThreadPool() 创建固定线程数的线程池,传入的参数就是上面获取的处理器的数量。当工作线程到达了处理器数量,新进来的任务便会进入阻塞队列等待,待工作线程中有任务完成,阻塞队列中的任务再执行。线程池的工作原理,大家应该都已经很熟悉了,在此就不多说了。<br>多线程执行当然能提高效率,但是我们能将这4000+的任务一下子提交给线程池来执行吗?这样的话cpu会有突然增长,这里我们可以使用限流策略,控制任务进入线程池的速度。Google Guava中提供了一个很好用的限流工具,它就是 RateLimiter,一个基于令牌桶算法实现的限流器,想必大家也都知道。使用RateLimiter可以很方便地实现限流。<br>   通过以上的思考,多线程删除也可以很简单地实现,在文章的后面我会给出实现代码。 </p><h3 id="3-集群中单点执行任务"><a href="#3-集群中单点执行任务" class="headerlink" title="3.集群中单点执行任务"></a>3.集群中单点执行任务</h3><p>   应用部署在集群中,但是我们需求的任务只需要一台机器执行即可。我们如何来保证集群中只有一台机器执行这个删除任务呢?<br>   我们可以使用Redis来实现。大体思路如下:<br>   判断标识删除任务执行的Key是否存在,存在的话直接返回,不存在则使用 SETNX 尝试设置Key的value为当前自己的Pid,再次获取key对应的value值,若value和自己当前的pid不同,说明不是当前节点获取的锁,不能执行任务,只有value和当前自己的pid相同时才执行删除任务。这样就可以保证集群中只有一个节点执行了删除任务,在任务执行结束之后要删除key。下面给出流程图,思路一目了然。 </p><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/database/delete_clock/%E5%88%A0%E9%99%A4%E4%BB%BB%E5%8A%A1redis.png" width="50%" height="50%"><br></div><h3 id="4-在哪里触发任务"><a href="#4-在哪里触发任务" class="headerlink" title="4.在哪里触发任务"></a>4.在哪里触发任务</h3><p>   分析了如何定位以及删除数据,那我们如何触发任务的执行呢?这里我在配置文件中设置了一个开关,用来标识本次启动是否需要执行删除任务。这个开关和上面提到的redis key共同决定是否在当前节点执行任务。<br>   什么时机进行删除呢?因为删除任务中使用了spring bean service,所以应该在spring容器初始化bean完成后执行删除任务。<br>   可以通过实现 ApplicationRunner 接口,实现接口的run方法来执行我们的任务。查阅springboot官方文档<br><blockquote><footer><strong>@SpringbootDoc</strong><cite><a href="https://docs.spring.io/spring-boot/docs/2.1.4.RELEASE/reference/htmlsingle/#using-boot" target="_blank" rel="noopener">docs.spring.io/spring-boot/docs/2.1.4.RELEASE/reference/htmlsingle/#using-boot</a></cite></footer></blockquote></p><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/database/delete_clock/ApplicationRunner%E6%96%87%E6%A1%A3.png" width="100%" height="100%"><br></div> <h2 id="二、代码实现"><a href="#二、代码实现" class="headerlink" title="二、代码实现"></a>二、代码实现</h2><p>   通过以上分析,实现思路已经非常清晰,下面给出实现代码,仅供参考。<br><figure class="highlight java"><figcaption><span>DeleteClockTask.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeleteClockTask</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> List<Long> userIds;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> ClockDeleteService clockDeleteService;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">DeleteClockTask</span><span class="params">(String name, List<Long> userIds)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> <span class="keyword">this</span>.userIds = userIds;</span><br><span class="line"> <span class="keyword">this</span>.clockDeleteService = (ClockDeleteService) SpringContextUtils.getBeanByClass(ClockDeleteService.class);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> log.info(<span class="string">"delete clock task {} start..."</span>, name);</span><br><span class="line"> clockDeleteService.deleteUnusedClock(userIds);</span><br><span class="line"> log.info(<span class="string">"delete unused clock task {} end."</span>, name);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> </p><figure class="highlight java"><figcaption><span>InitialBeanHandler.java</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">InitialBeanHandler</span> <span class="keyword">implements</span> <span class="title">ApplicationRunner</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> ClockDeleteService clockDeleteService;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Value</span>(<span class="string">"${task.delete.status}"</span>)</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> deleteSwitch;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> RedissonHandler redissonHandler;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> <span class="keyword">long</span> TASK_EXPIRE_MILLS_TIME = <span class="number">60</span> * <span class="number">60</span> * <span class="number">1000</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String DELETE_CLOCK_TASK_KEY = <span class="string">"delete_used_clock_running"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">(ApplicationArguments args)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 先判断删除开关是否开启</span></span><br><span class="line"> log.info(<span class="string">"删除无用闹钟开关 deleteSwitch : "</span> + deleteSwitch);</span><br><span class="line"> <span class="comment">// 若开关开启,并且当前没有节点在执行删除任务,则执行删除任务</span></span><br><span class="line"> <span class="comment">//通过redis查询是否有节点已经运行了删除任务</span></span><br><span class="line"> <span class="keyword">boolean</span> taskRunning = redissonHandler.exists(DELETE_CLOCK_TASK_KEY);</span><br><span class="line"></span><br><span class="line"> List<DeleteClockTask> tasks = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (deleteSwitch == <span class="number">1</span> && !taskRunning) {</span><br><span class="line"> <span class="comment">//当前节点执行删除任务,设置redis中的任务状态</span></span><br><span class="line"> String nowPid = ManagementFactory.getRuntimeMXBean().getName();</span><br><span class="line"> redissonHandler.setNX(DELETE_CLOCK_TASK_KEY, nowPid, TASK_EXPIRE_MILLS_TIME);</span><br><span class="line"> String taskRunningPid = redissonHandler.get(DELETE_CLOCK_TASK_KEY, String.class);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!StringUtils.equals(taskRunningPid, nowPid)) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//获取分批删除的userId的list</span></span><br><span class="line"> List<List<Long>> deleteUsersList = clockDeleteService.getDeleteUser();</span><br><span class="line"> <span class="keyword">if</span> (CollectionUtils.isNotEmpty(deleteUsersList)) {</span><br><span class="line"> <span class="keyword">int</span> size = deleteUsersList.size();</span><br><span class="line"> log.info(<span class="string">"There are {} delete clock tasks totally."</span>, size);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < size; i++) {</span><br><span class="line"> List<Long> userIds = deleteUsersList.get(i);</span><br><span class="line"> DeleteClockTask task = <span class="keyword">new</span> DeleteClockTask(<span class="string">"deleteTask"</span> + i, userIds);</span><br><span class="line"> tasks.add(task);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//限流</span></span><br><span class="line"> RateLimiter rateLimiter = RateLimiter.create(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (DeleteClockTask task : tasks) {</span><br><span class="line"> log.info(<span class="string">"delete clock task {} wait time {}"</span>, task.getName(), rateLimiter.acquire());</span><br><span class="line"> pool.execute(task);</span><br><span class="line"> log.info(<span class="string">"delete clock task {} finished."</span>, task.getName());</span><br><span class="line"> }</span><br><span class="line"> log.info(<span class="string">"delete clock tasks all finished"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//执行完成,将redis中标志任务执行状态的key删除</span></span><br><span class="line"> redissonHandler.del(DELETE_CLOCK_TASK_KEY);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <h2 id="三、线上执行"><a href="#三、线上执行" class="headerlink" title="三、线上执行"></a>三、线上执行</h2><p>   经过测试环境反复测试,最终挑了个风和日丽的日子,准备在生产环境执行。<br>   合并master,开始部署,盯着日志,静静等待…<br>   线上删除任务共分为了4014个组,按每秒钟2组的速度进入线程池,开始执行删除任务,观察cpu使用率,基本稳定,没有出现激增。半个多小时后,所有任务执行完成。一共删除了58115102条数据,至此这次删除历史数据的任务完成。<br>   第一次在线上物理删除这么大量的数据,仅此记录一下本次处理的思路和实现方法。</p>]]></content>
<summary type="html">
<p>&ensp;&ensp;&ensp;接上次闹钟项目更改字符集之后,这几天又需要对线上数据做处理。背景是,同步闹钟的时候会把用户之前删除过的闹钟都同步下来,而删除的闹钟在客户端没有任何显示,也没有任何恢复的操作,对于用户来说其实是完全没有用的数据。当用户的无用历史闹钟增多到一
</summary>
<category term="数据库" scheme="http://redmapleren.com/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
<category term="数据库" scheme="http://redmapleren.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
<category term="线上" scheme="http://redmapleren.com/tags/%E7%BA%BF%E4%B8%8A/"/>
<category term="多线程" scheme="http://redmapleren.com/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
</entry>
<entry>
<title>记一次数据库更改字符集踩的坑</title>
<link href="http://redmapleren.com/2019/04/19/%E8%AE%B0%E4%B8%80%E6%AC%A1%E6%95%B0%E6%8D%AE%E5%BA%93%E6%9B%B4%E6%94%B9%E5%AD%97%E7%AC%A6%E9%9B%86%E8%B8%A9%E7%9A%84%E5%9D%91/"/>
<id>http://redmapleren.com/2019/04/19/记一次数据库更改字符集踩的坑/</id>
<published>2019-04-19T06:30:00.000Z</published>
<updated>2019-04-19T09:19:21.571Z</updated>
<content type="html"><![CDATA[<p>最近接手了一个闹钟App的后端项目,其中有个功能就是用户可以将本地闹钟同步到服务器,以便更换设备后能从服务器同步数据到新设备。但是这几天观察后台日志发现,同步的时候总是ERROR,定位错误发现跟数据库的字符集有关,在此记录一下本次线上错误的排查过程。</p><h2 id="一、定位ERROR"><a href="#一、定位ERROR" class="headerlink" title="一、定位ERROR"></a>一、定位ERROR</h2><p>通过Kibana查看最近的ERROR日志,可发现如下的错误描述。<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/database/index_accident/incorrect.png" width="100%" height="100%"><br><br>   很明显可以发现,是因为insert的时候插入的数据不符合该列的要求。进而查看插入的数据,定位到插入内容含有特殊字符,比如emoji表情和其他一些字符。查询table的字符集发现这张表是utf8字符集,并不是utf8mb4的字符集,所以报这个错也是很正常了。看到这里你可能会问,这个问题应该在很久之前就应该发现了,为什么这几天才把它揪出来呢?这个项目很早了,包括后端和客户端,经过客户端同学的代码筛查,发现由于历史的原因,客户端对同步的结果的处理存在bug,导致后台同步失败某冲情况下也会提示同步成功,所以前台并不会暴露这个问题。<br>   所以当时建表使用的utf8字符集已经不能满足用户的自定义名称的需求,需要变更这张表的字符集为utf8mb4。但是一查数据,这张表就有七千多万的数据,更改字符集的话对之前的内容是否有影响,要执行多久?问题找到了,就开始找dba同学商量,最后决定先创建一张utf8mb4的新表,将原来的数据导入到新表,完成后,再将原表rename成新表,完成切换。说干就干,晚上提了工单,dba开始执行。</p><h2 id="二、黎明前的黑暗"><a href="#二、黎明前的黑暗" class="headerlink" title="二、黎明前的黑暗"></a>二、黎明前的黑暗</h2><p>   第二天早上起床,发现接到了n多条服务报警,正是昨天更改字符集的服务。到了公司立马查看服务日志,发现服务挂了,重新部署启动,没有两分钟又挂了。这是什么原因呢?没有更改任何代码,只更改了一个表的字符集,整个服务就崩了吗?这是什么道理,一时间也是很懵。这时候dba那边发来了一长串slow sql,是一个两个表的连表查询,一开始我并未用explain分析sql语句,因为sql较简单,发现语句的书写还算规范,该走的索引也会走,一时间也无法定位问题。<br>   难道是昨天修改字符集之后,insert无异常,表的增量变大,查询变得更加慢了?根据这个猜测,为了临时解决这个问题,准备将这一小段代码修改成分表查询,在内存里聚合处理。正在准备拉分支修改代码的时候,dba同学那边发消息说是索引失效了。我赶紧explain了一下刚刚的sql语句,果然,之前应该走的索引在改了字符集之后就失效了。<br>修改字符集之后的explain结果:<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/database/index_accident/%E7%B4%A2%E5%BC%95%E5%A4%B1%E6%95%88explain.png" width="100%" height="100%"><br><br>修改字符集之前的explain结果:<br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/database/index_accident/%E7%B4%A2%E5%BC%95%E5%A4%B1%E6%95%88explain.png" width="100%" height="100%"><br><br>原来连表查询的时候,两个表的字符集不同会导致索引失效,所以在这个如此多数据的表中进行连表查询是一件灾难性的事。<br>找到原因后,将连表查询的另一个表的字符集同样做出修改,之后索引正常,服务也正常了。 </p><h2 id="三、总结"><a href="#三、总结" class="headerlink" title="三、总结"></a>三、总结</h2><p>通过这次处理线上问题,深深体会到在表数据很多的时候进行连表查询是一件多么可怕的事情,一不小心就把数据库或者服务搞崩溃了。还有一点很关键,不要像我一样看到sql简单就理所当然地以为会按照理论的情况走索引,拿到有问题的sql,一定要仔细分析,使用工具,比如explain来辅助分析sql,这样才不会遗漏问题。后续我们会对这个项目进行重构,将所有连表查询改成单表查询,然后在内存中对数据做聚合。另外这个表的数据已经到达七千多万了,修改了正确的字符集之后表的增量会更大,分表的工作也迫在眉睫。<br>本文仅此记录下问题排查经过,如果大家也遇到了类似的问题,希望可以进行参考。</p>]]></content>
<summary type="html">
<p>最近接手了一个闹钟App的后端项目,其中有个功能就是用户可以将本地闹钟同步到服务器,以便更换设备后能从服务器同步数据到新设备。但是这几天观察后台日志发现,同步的时候总是ERROR,定位错误发现跟数据库的字符集有关,在此记录一下本次线上错误的排查过程。</p>
<h2 id=
</summary>
<category term="数据库" scheme="http://redmapleren.com/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
<category term="数据库" scheme="http://redmapleren.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
<category term="线上" scheme="http://redmapleren.com/tags/%E7%BA%BF%E4%B8%8A/"/>
</entry>
<entry>
<title>关于红黑树的学习笔记</title>
<link href="http://redmapleren.com/2018/09/23/%E5%85%B3%E4%BA%8E%E7%BA%A2%E9%BB%91%E6%A0%91%E7%9A%84%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<id>http://redmapleren.com/2018/09/23/关于红黑树的学习笔记/</id>
<published>2018-09-23T07:37:05.000Z</published>
<updated>2019-04-19T06:30:23.959Z</updated>
<content type="html"><![CDATA[<p>前一段时间组内技术分享,正好趁这个机会好好研究了一下红黑树。在这里写下学习红黑树的一些成果和体会。</p><h2 id="一、什么是红黑树"><a href="#一、什么是红黑树" class="headerlink" title="一、什么是红黑树"></a>一、什么是红黑树</h2><p>先看一下《算法导论》中对红黑树的定义。</p><ol><li>每个节点或者是红色,或者是黑色</li><li>根节点是黑色</li><li>每一个叶子节点(最后的空节点)是黑色的</li><li>如果一个节点是红色的,那么它的孩子节点都是黑色的</li><li>从任意一个节点到叶子节点,经过的黑色节点是一样的 <div style="display:none"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E5%B9%B3%E9%93%BA%E5%B0%81%E9%9D%A2%20%281%29.png" alt="post-cover"><br></div></li></ol><p>这5条红黑树的定义看过之后感觉自己还是不太懂什么是红黑树,个人觉得有这种感觉的原因是定义比较抽象,不容易让人理解。那么,我们就从另一个角度入手来理解红黑树。</p><h3 id="1-2-3树"><a href="#1-2-3树" class="headerlink" title="1. 2-3树"></a>1. 2-3树</h3><p>这里先介绍一下2-3树。因为2-3树和红黑树有一定的联系,对于理解红黑树会有很大的帮助,所以我们先来看一下2-3树相关的一些性质。<br>首先,2-3树满足二分搜索树的性质。不同的是在2-3树中,存在两种节点。一种是有两个叶子节点的,我们称作“2节点”;另一种是有三个叶子节点的,我们称作“3节点”。 </p><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/2-3%E6%A0%91%E8%8A%82%E7%82%B9%E6%A6%82%E8%A7%88.png" width="50%" height="50%"><br></div><br>如下是一整颗2-3树的示例。需要强调的是2-3树是完全平衡的树,即从根节点到任意一个叶子节点的高度都是相同的。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/2-3%E6%A0%91%E6%95%B4%E4%BD%93%E7%A4%BA%E4%BE%8B.png" width="60%" height="60%"><br></div> <h3 id="2-2-3树怎样保持完全平衡性"><a href="#2-2-3树怎样保持完全平衡性" class="headerlink" title="2. 2-3树怎样保持完全平衡性"></a>2. 2-3树怎样保持完全平衡性</h3><p>向2-3树中添加一个节点,遵循向二分搜索树中添加节点的基本思路,插入节点比当前节点小,则向当前节点的左子树添加,否则向右子树添加。不过由于2-3树特殊的性质,当要向“2节点”添加节点时,将待插入的节点与该“2节点”进行融合,组成一个新的“3节点”,如下图所示。 </p><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/2-3%E6%A0%91%E6%8F%92%E5%85%A52%E8%8A%82%E7%82%B9.png" width="50%" height="50%"><br></div><br>如果要向“3节点”添加节点,同向“2节点”添加节点一样,先组成一个临时的4节点,之后再拆分成3个“2节点”,如图所示。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/2-3%E6%A0%91%E6%8F%92%E5%85%A53%E8%8A%82%E7%82%B9.png" width="60%" height="60%"><br></div><br>如果要插入的“3节点”的父节点是一个“2节点”,通过上述步骤得到的拆分过成为父节点的“2节点”,需要向原“3节点”的父节点进行融合,组成新的“3节点”。过程如下图所示。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/2-3%E6%A0%91%E6%8F%92%E5%85%A53%E8%8A%82%E7%82%B9-%E7%88%B6%E8%8A%82%E7%82%B9.png" width="80%" height="80%"><br></div><br>如果要插入的“3节点”的父节点是一个“3节点”,大体思路相同,向父节点进行融合,只不过此时融合后成为一个临时的“4节点”,之后要再次进行拆分。过程如图所示。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/2-3%E6%A0%91%E6%8F%92%E5%85%A53%E8%8A%82%E7%82%B9-%E7%88%B6%E8%8A%82%E7%82%B93.png" width="80%" height="80%"><br></div> <p>如上所述,2-3树保持了完全的平衡性。说了这么长时间的2-3树,那么2-3树和红黑树之间到底有怎样的关系,下面我们具体来看一下。 </p><h3 id="3-2-3树与红黑树"><a href="#3-2-3树与红黑树" class="headerlink" title="3. 2-3树与红黑树"></a>3. 2-3树与红黑树</h3><p>对于2-3树中的“2节点”,对应于红黑树中的“黑节点”,即相当于普通二分搜索树中的一个节点。<br>对于2-3树中的“3节点”,相当于普通二分搜索树中的两个节点融合在一起,我们如何来描述这种融合在一起的两个节点之间的关系呢?其实很简单,如果我们将连接这两个节点的边涂成红色,就可以表示这两个节点是融合的关系,即2-3树中的一个“3节点”。那么问题又来了,对于树这种数据结构,我们在定义的时候通常都是针对节点进行定义,并没有对节点之间的边进行定义,我们如何来表示这条被涂成红色的边呢?大家都知道,对于树中的任意一个节点,都是只有一个父亲节点,所以与其父节点相连接的边可以用该节点进行表示。那么我们就可以将这两个节点中较小的节点(作为左子树的节点)涂成红色,就可以很好地表示这两个节点融合的关系了。 </p><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/2-3%E6%A0%91%E4%B8%8E%E7%BA%A2%E9%BB%91%E6%A0%91-3%E8%8A%82%E7%82%B9%E5%AF%B9%E5%BA%94.png" width="60%" height="60%"><br></div><br>综合以上描述,2-3树与红黑树之间的关系,我们可以用下图很好地进行表示。我们这里说的红色节点都是向左倾斜的。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/2-3%E6%A0%91%E4%B8%8E%E7%BA%A2%E9%BB%91%E6%A0%91-%E8%8A%82%E7%82%B9%E7%BB%BC%E5%90%88.png" width="80%" height="80%"><br></div><br>看过2-3树中的两种节点和红黑树中节点的对应关系后,我们就来看一下一棵2-3树与红黑树之间的对比,如图所示。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/2-3%E6%A0%91%E4%B8%8E%E7%BA%A2%E9%BB%91%E6%A0%91-%E6%95%B4%E4%BD%93%E5%AF%B9%E6%AF%94.png" width="100%" height="100%"><br></div> <h3 id="4-红黑树的性质"><a href="#4-红黑树的性质" class="headerlink" title="4. 红黑树的性质"></a>4. 红黑树的性质</h3><p>讨论了2-3树与红黑树之间的关系,我们再回过头来看一下红黑树的5条定义和性质,会发现很好理解了。 </p><ol><li>每个节点或者是红色,或者是黑色<br>这条定义很好理解,在此不做解释。</li><li>根节点是黑色<br>根据之前说过的,红色的节点对应于2-3树中“3节点”中较小的那个节点,拆成两个“2节点”的话则是一个左子树的节点,即红色的节点总是可以和其父节点进行融合,所以红色节点一定有父节点,显然根节点不能是红色,所以根节点是黑色。</li><li>每一个叶子节点(最后的空节点)是黑色的<br>这条性质和第2条是对应的。对于叶子节点(最后的空节点),一颗空树的根节点也为黑色,所以与其说第三条是一条性质,不如说也是一个定义。</li><li>如果一个节点是红色的,那么它的孩子节点都是黑色的<br>根据上面2-3树与红黑树两种节点的对比图,我们很容易看到,红色节点的两个子树,对应2-3树中的话,要么是一个“2节点”,要么是一个“3节点”,而不管是“2节点”还是“3节点”,相连的第一个节点都是黑色的,所以说红色节点的孩子节点都是黑色的。</li><li>从任意一个节点到叶子节点,经过的黑色节点是一样的<br>根据2-3树与红黑树的关系对比图,可以发现,红黑树中一个黑色节点对应2-3树中一整个节点(“2节点”或“3节点”),而2-3树是完全平衡的树,从根节点到任意路径的叶子节点,经过的节点个数都是相同的,对应红黑树中,即从任意节点到叶子节点,经过的黑色节点是一样的。 </li></ol><h2 id="二、-红黑树添加元素"><a href="#二、-红黑树添加元素" class="headerlink" title="二、 红黑树添加元素"></a>二、 红黑树添加元素</h2><p>回忆刚刚提到的向2-3树中添加元素的过程,或者添加进一个“2节点”,形成一个“3节点”,或者添加进一个“3节点”,形成一个临时的“4节点”。理解了2-3树如何添加节点,对应红黑树就很好理解了。很容易知道,我们总是会将待插入的节点向父节点进行融合,所以我们将待插入的节点看成红色,即永远添加红色节点。<br>向一棵空树添加节点42。插入后,该节点是根节点,根据红黑树的性质,根节点必须是黑色,所以讲该节点染成黑色。 </p><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E6%B7%BB%E5%8A%A0-%E6%A0%B9%E8%8A%82%E7%82%B9.png" width="50%" height="50%"><br></div><br>若向如图的红黑树中添加节点37。因为37比42小,所以添加在42的左子树,对应2-3树中,形成一个“3节点”。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E6%B7%BB%E5%8A%A0-%E5%B7%A6%E5%AD%90%E6%A0%91.png" width="80%" height="80%"><br></div><br>若向如图的红黑树中添加节点42。因为42比37大,所以添加在37的右子树。这样的话红色节点就出现在了一个节点的右子树中,所以此时需要进行左旋转,让树满足红黑树的性质。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E6%B7%BB%E5%8A%A0-%E5%B7%A6%E6%97%8B%E8%BD%AC.png" width="80%" height="80%"><br></div> <h3 id="1-左旋转"><a href="#1-左旋转" class="headerlink" title="1. 左旋转"></a>1. 左旋转</h3><p>对于一般的情况,如何进行左旋转呢?我们要对下图的红黑树进行左旋转。</p><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E5%B7%A6%E6%97%8B%E8%BD%AC-%E5%88%9D%E5%A7%8B.png" width="40%" height="40%"><br></div><br>首先将node节点与x节点断开,其次将x的左子树作为node的右子树。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E5%B7%A6%E6%97%8B%E8%BD%AC-%E8%BF%87%E7%A8%8B1.png " width="70%" height="70%"><br></div><br>然后再将node作为x新的左子树,之后要把x的颜色染成node的颜色,最后将node的颜色变为红色,这样就完成了左旋转的操作。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E5%B7%A6%E6%97%8B%E8%BD%AC-%E8%BF%87%E7%A8%8B2.png" width="70%" height="70%"><br></div> <h3 id="2-颜色翻转(flipColors)"><a href="#2-颜色翻转(flipColors)" class="headerlink" title="2. 颜色翻转(flipColors)"></a>2. 颜色翻转(flipColors)</h3><p>向红黑树中插入节点66,很容易知道插入到42右子树的位置,对应于2-3树的插入如图所示。 </p><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E9%A2%9C%E8%89%B2%E7%BF%BB%E8%BD%AC-1.png" width="70%" height="70%"><br></div><br>然而上面我们说到,我们总是要将新拆分出来的树的父亲节点向上进行融合,即这个父亲节点在红黑树中总是红色的,根据红黑树的性质,该父亲节点的两个孩子节点一定是黑色的。这样就需要将上一步形成的树进行颜色的翻转,变成如下图的形态。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E9%A2%9C%E8%89%B2%E7%BF%BB%E8%BD%AC-2.png" width="50%" height="50%"><br></div> <h3 id="3-右旋转"><a href="#3-右旋转" class="headerlink" title="3. 右旋转"></a>3. 右旋转</h3><p>向如图的红黑树中插入节点12,根据二分搜索树插入的操作,此时会形成一条链状的结构,对于2-3树中则是变形成为图中的样子,才能保证平衡性。所以在红黑树中,也要通过变形,变成与2-3树对应的形态。这种情况的变形操作,称为“右旋转”。 </p><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E5%8F%B3%E6%97%8B%E8%BD%AC-1.png" width="70%" height="70%"><br></div><br>一般的情况,右旋转操作同上面的左旋转操作很类似,下面我们一起来看一下过程。我们要对下图的红黑树进行右旋转的操作。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E5%8F%B3%E6%97%8B%E8%BD%AC%E4%B8%80%E8%88%AC-%E5%88%9D%E5%A7%8B.png" width="50%" height="50%"><br></div><br>首先将node和x节点断开,将x的右子树T1作为node的左子树。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E5%8F%B3%E6%97%8B%E8%BD%AC%E4%B8%80%E8%88%AC-%E8%BF%87%E7%A8%8B1.png" width="50%" height="50%"><br></div><br>其次将node作为x的右子树。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E5%8F%B3%E6%97%8B%E8%BD%AC%E4%B8%80%E8%88%AC-%E8%BF%87%E7%A8%8B2.png" width="50%" height="50%"><br></div><br>接着要把x的颜色染成原来node的颜色,把node染成红色。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E5%8F%B3%E6%97%8B%E8%BD%AC%E4%B8%80%E8%88%AC-%E8%BF%87%E7%A8%8B3.png" width="50%" height="50%"><br></div><br>然后很显然,需要再进行一次颜色翻转操作,才能满足红黑树的性质。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E5%8F%B3%E6%97%8B%E8%BD%AC%E5%90%8E%E9%A2%9C%E8%89%B2%E7%BF%BB%E8%BD%AC.png" width="50%" height="50%"><br></div><br><br><br><br><br>有一种比较复杂的情况,向下图的红黑树中插入节点40,要满足的红黑树的性质我们需要怎么操作呢?<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E6%8F%92%E5%85%A5%E5%A4%8D%E6%9D%82-1.png" width="70%" height="70%"><br></div><br>对应2-3树中最终的形态,第一步我们可以通过一次左旋转,变成下图的样子。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E6%8F%92%E5%85%A5%E5%A4%8D%E6%9D%82%E5%B7%A6%E6%97%8B%E8%BD%AC.png" width="60%" height="60%"><br></div><br>会发现,这样就变成了上面说到的需要右旋转的形态,所以再进行一次右旋转和颜色翻转,就可以满足红黑树的性质了。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E6%8F%92%E5%85%A5%E5%A4%8D%E6%9D%82%E5%8F%B3%E6%97%8B%E8%BD%AC%E9%A2%9C%E8%89%B2%E7%BF%BB%E8%BD%AC.png" width="90%" height="90%"><br></div> <h3 id="4-红黑树插入总结"><a href="#4-红黑树插入总结" class="headerlink" title="4.红黑树插入总结"></a>4.红黑树插入总结</h3><p>上面分情况讨论了向红黑树中添加节点的各种情况,这里总结一下。其实根据上面的讨论,我们可以发现,最后一种复杂的情况可以涵盖其余简单的情况,复杂的操作包含了左旋转、右旋转、颜色翻转,这三种操作,完全可以保持红黑树的性质。下面的一张图,很好的总结了向红黑树中添加节点不同情况下的过程。 </p><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E6%8F%92%E5%85%A5%E6%80%BB%E7%BB%93.png" width="90%" height="90%"><br></div> <h2 id="三、红黑树删除元素"><a href="#三、红黑树删除元素" class="headerlink" title="三、红黑树删除元素"></a>三、红黑树删除元素</h2><p>关于红黑树的删除操作,比插入操作要复杂一些,需要分情况进行讨论。下面我们具体来看一下。<br>红黑树的删除操作大体分为2步: </p><ol><li>二分搜索树删除节点 </li><li>删除修复操作 </li></ol><p>红黑树的删除首先满足二分搜索树的删除,然后对删除节点后的树进行修复操作,让其重新满足红黑树的5条性质。<br>对于二分搜索树的删除,这里就不再赘述,我们主要讨论红黑树的删除修复操作。以下所说的当前节点意思是通过二分搜索树的方式删除要删除的节点后,代替原来节点的节点。<br>当删除节点是红色节点时,那么原来红黑树的性质依旧保持,此时不用做修复操作。<br>当删除节点是黑色节点时,情况很多,我们分情况讨论。 </p><h3 id="1-简单情况"><a href="#1-简单情况" class="headerlink" title="1.简单情况"></a>1.简单情况</h3><ol><li>当前节点是红色节点<br>直接把当前节点染成黑色,结束,红黑树的性质全部恢复。 </li><li>当前节点是黑色节点,并且是根节点<br>什么都不做,直接结束。 </li></ol><h3 id="2-复杂情况"><a href="#2-复杂情况" class="headerlink" title="2.复杂情况"></a>2.复杂情况</h3><ol><li><p>N、S、SL、SR、P都为黑色<br>其中N是上述的当前节点,S是N的兄弟节点,P是N的父节点,SL和SR是N兄弟节点的左右孩子节点。</p><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B51-1.png" width="40%" height="40%"><br></div><br>此时将S染成红色,这样经过N路径的黑色节点就和N的兄弟子树中的黑色节点相同了,但是经过P节点的黑色节点少了一个,此时需要将P当做新的N再进行操作,具体怎么操作可以见以下一些情况。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B51-2.png" width="40%" height="40%"><br></div> </li><li><p>N、S、SL、SR为黑色,P为红色</p><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B52-1.png" width="40%" height="40%"><br></div><br>此时将P和S的颜色进行交换,P成为了黑色,它为经过节点N的路径添加了一个黑色节点,从而补偿了被删除的黑色节点。S的颜色只是上移到父节点P上,因而经过S节点路径的黑色节点的数目也没有发生改变。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B52-2.png" width="40%" height="40%"><br></div> </li><li><p>N、S为黑色,SR为红色<br>图中蓝色节点表示该节点可以为黑色也可以为红色,即对该节点的颜色没有要求。<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B53-1.png" width="40%" height="40%"><br></div><br>此时将以P为根的子树进行左旋转<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B53-2.png" width="40%" height="40%"><br></div><br>然后交换P和S的颜色<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B53-3.png" width="40%" height="40%"><br></div><br>将SR染成黑色<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B53-4.png" width="40%" height="40%"><br></div><br>调整后经由N的路径的黑色节点数比调整前增加了一个,恰好补偿了被删除的黑色节点。对于不经过N但经过其他节点的任意一个路径来说,它们贡献的黑色节点数目不变。 </p></li><li><p>N、S为黑色,SL为红色,SR为黑色<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B54-1.png" width="40%" height="40%"><br></div><br>此时,将以S为根的子树进行右旋转<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B54-2.png" width="40%" height="40%"><br></div><br>接着交换S和SL的颜色<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B54-3.png" width="40%" height="40%"><br></div><br>节点SL的左孩子在旋转前后不变,而SL原来为红色,所以SL的左孩子必定为黑色。所以旋转后对于N节点来说,相当于情况3。之后再通过情况3中的描述进行操作。整体上情况4需要进行一次右旋转和一次左旋转。 </p></li><li><p>N为黑色,S为红色<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B55-1.png" width="40%" height="40%"><br></div><br>此时,将以P为根的子树进行左旋转<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B55-2.png" width="40%" height="40%"><br></div><br>将P和S颜色交换<br><div align="center"><br><img src="https://hexo-rxy.oss-cn-beijing.aliyuncs.com/data_structure/RBTree/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%88%A0%E9%99%A4%E6%83%85%E5%86%B55-3.png" width="40%" height="40%"><br></div><br>经过这样的变换后,把该情形转化成了N为黑色,其兄弟为黑色的情形,再通过以上描述的几种情况进行变换,最终保持红黑树的性质。<br><br><br><br><br>红黑树删除的各种复杂的情况,以上都进行了讨论,虽然比较繁琐,但是认真研究后还是可以理解的,并没有之前想象地那么困难。 </p></li></ol><h2 id="四、红黑树的性能"><a href="#四、红黑树的性能" class="headerlink" title="四、红黑树的性能"></a>四、红黑树的性能</h2><p>红黑树的增删改查的复杂度显然是O(logn)级别的,通常说红黑树是统计性能更优的树结构。<br>为什么说统计性能更优呢?因为若是单纯的读操作,AVL树的性能比红黑树强一些,红黑树不是严格的平衡树,它是保持“黑平衡”的树。对于红黑树,最坏的情况,是树中最左侧的节点的左子树都是红色的节点,即对应2-3树中的“3节点”,所以这时红黑树的高度就是2logn(除了logn个黑色节点外,还有logn个红色节点),红黑树要比AVL树要高一些。所以从单纯的查询性能来说,红黑树的性能并没有AVL树强。<br>对于插入删除操作来说,红黑树相比于AVL树减少了左旋转或右旋转的次数,所以红黑树的插入删除的性能比AVL树强一些。<br>综合增删改查各方面的性能,红黑树的综合性能比较高。 </p><h2 id="五、红黑树的应用"><a href="#五、红黑树的应用" class="headerlink" title="五、红黑树的应用"></a>五、红黑树的应用</h2><ol><li>Java中的TreeMap,Java8中HashMap的TreeNode节点采用了红黑树实现 </li><li>C++中,STL的map和set也应用了红黑树</li><li>Linux中完全公平调度算法CFS(Completely Fair Schedule)</li><li>用红黑树管理进程控制块epoll在内核中的实现,用红黑树管理事件块</li><li>Nginx中,用红黑树管理timer等 </li></ol><hr><p>这次的分享,主要对红黑树的性质以及向红黑树中插入、删除元素进行分析,对于红黑树的应用并没有很深入的进行研究,如上所述的几种红黑树的应用,也只是了解,还需要在以后的工作学习中进行完善。以上是本人对红黑树学习的一些成果和心得,记下来让自己所学的知识体系化,也方便日后的复习回顾。</p>]]></content>
<summary type="html">
<p>前一段时间组内技术分享,正好趁这个机会好好研究了一下红黑树。在这里写下学习红黑树的一些成果和体会。</p>
<h2 id="一、什么是红黑树"><a href="#一、什么是红黑树" class="headerlink" title="一、什么是红黑树"></a>一、什么是
</summary>
<category term="数据结构" scheme="http://redmapleren.com/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
<category term="红黑树" scheme="http://redmapleren.com/tags/%E7%BA%A2%E9%BB%91%E6%A0%91/"/>
</entry>
</feed>