-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
605 lines (292 loc) · 369 KB
/
Copy pathsearch.xml
File metadata and controls
605 lines (292 loc) · 369 KB
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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Go init 函数顺序(转)</title>
<link href="/2024/09/02/Go-init%E5%87%BD%E6%95%B0%E9%A1%BA%E5%BA%8F/"/>
<url>/2024/09/02/Go-init%E5%87%BD%E6%95%B0%E9%A1%BA%E5%BA%8F/</url>
<content type="html"><![CDATA[<p>转自<a href="https://cloud.tencent.com/developer/article/2138066">一文读懂 Golang init 函数执行顺序</a></p><p>Go 语言中,<code>init</code> 函数用于在程序启动时执行初始化操作。每个包都可以包含一个 <code>init</code> 函数,Go 会按顺序执行所有包的 <code>init</code> 函数。</p><p>以下为 <code>init</code> 函数的执行顺序一图流:</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/init-sequence.png" alt="init 函数执行顺序"></p><p>程序中如果在不同包的不同源文件有多个 init 函数时,其执行顺序可概述为:</p><ul><li>同一个源文件的 init 函数执行顺序与其定义顺序一致,从上到下;</li><li>同一个包中不同文件的 init 函数的执行顺序按照文件名的字典序;</li><li>对于不同的包,如果不相互依赖的话,按照 main 包中 import 的顺序调用其包中的 init 函数;</li><li>如果包存在依赖,不同包的 init 函数按照包导入的依赖关系决定执行顺序。 调用顺序为最后被依赖的最先被初始化,如导入顺序 main > a > b > c,则初始化顺序为 c > b > a > main,依次执行对应的 init 方法;</li><li>如果包存在包级变量,则先于包的 init 函数完成初始化。</li></ul>]]></content>
<categories>
<category> Go </category>
</categories>
<tags>
<tag> Go </tag>
</tags>
</entry>
<entry>
<title>Go 具名返回值</title>
<link href="/2024/09/02/Go%E5%85%B7%E5%90%8D%E8%BF%94%E5%9B%9E%E5%80%BC/"/>
<url>/2024/09/02/Go%E5%85%B7%E5%90%8D%E8%BF%94%E5%9B%9E%E5%80%BC/</url>
<content type="html"><![CDATA[<p>Go 语言可以对函数的返回值命名,返回值会被自动被初始化为零值而不需要你的处理,同时也可以使用“裸”返回语句。以下是 <a href="https://go.dev/tour/basics/7">A Tour of Go</a> 中的例子:</p><figure class="highlight go"><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">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">split</span><span class="params">(sum <span class="type">int</span>)</span></span> (x, y <span class="type">int</span>) {</span><br><span class="line">x = sum * <span class="number">3</span> / <span class="number">9</span></span><br><span class="line">y = sum - x</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="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line">fmt.Println(split(<span class="number">10</span>))</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="什么时候使用"><a href="#什么时候使用" class="headerlink" title="什么时候使用"></a>什么时候使用</h2><p><a href="https://go.dev/doc/effective_go#named-results">Effective Go</a> 中主要说明了<strong>具名返回值</strong>可以提高函数的可读性。特别是在返回值类型相同的情况下,比如一个返回经纬度的函数:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">location</span><span class="params">(name City)</span></span> (lat, long <span class="type">float64</span>)</span><br></pre></td></tr></table></figure><p>我们可以从返回值列表中知道经度和纬度分别对应第几个参数。</p><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ol><li><p><a href="https://go.dev/tour/basics/7">A Tour of Go</a> 中也提到,应该在<strong>短方法</strong>中使用“裸返回”,否则会降低代码的可读性。</p></li><li><p>具名返回值也会因为我们处理不当而造成错误。比如:</p></li></ol><figure class="highlight go"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">todoWithTimeout</span><span class="params">(c *context.Context)</span></span> (err <span class="type">error</span>) {</span><br><span class="line"> <span class="comment">// do something</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> c.Err() != <span class="literal">nil</span> {</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">// do something and return</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在检查了上下文是否超时后,直接返回了,导致<code>err</code>还是<code>nil</code>,造成了错误。</p><p>正确做法应该是:</p><figure class="highlight go"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">todoWithTimeout</span><span class="params">(c *context.Context)</span></span> (err <span class="type">error</span>) {</span><br><span class="line"> <span class="comment">// do something</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> err = c.Err(); err != <span class="literal">nil</span> {</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">// do something and return</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> Go </category>
</categories>
<tags>
<tag> Go </tag>
</tags>
</entry>
<entry>
<title>Go 指针接收者</title>
<link href="/2024/09/02/Go%E6%8C%87%E9%92%88%E6%8E%A5%E6%94%B6%E8%80%85/"/>
<url>/2024/09/02/Go%E6%8C%87%E9%92%88%E6%8E%A5%E6%94%B6%E8%80%85/</url>
<content type="html"><![CDATA[<p>Go 语言中可以对方法声明<strong>接收者</strong>,接收者类型可以是结构体也可以是接口。这意味着被声明的方法只能被该结构体或者是接口的实例调用。</p><p>语法如下:</p><figure class="highlight go"><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="function"><span class="keyword">func</span> <span class="params">(t Type)</span></span> methodMame(params) returns {</span><br><span class="line"> <span class="comment">// method body</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>特别地,在此处可以声明指针接收者,语法如下:</p><figure class="highlight go"><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="function"><span class="keyword">func</span> <span class="params">(t *Type)</span></span> methodMame(params) returns {</span><br><span class="line"> <span class="comment">// method body</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 <a href="https://go.dev/tour/methods/4">A tour of Go</a> 中介绍到</p><blockquote><p>You can declare methods with pointer receivers.<br>// 你可以为方法绑定一个指针接收者</p><p>This means the receiver type has the literal syntax *T for some type T. (Also, T cannot itself be a pointer such as *int.)<br>// 这意味着接收者类型是 T 的指针,其中 T 不能是像 *int 这样的指针类型。</p></blockquote><p>他的作用也很简单,就是可以修改接收者指向的值。如果没有使用指针接收者,那么修改的只是接收者的一个副本,并不影响原来的值。</p><p>从字面语义上也很直观,我们通常理解的指针就是指向变量的地址,对指针的操作会影响原来的变量值。</p><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p>举例来说,声明一个结构体<code>Vertex</code>,为<code>Vertex</code>绑定方法<code>Abs</code>、<code>Scale</code>和<code>Shrink</code>,其中<code>Abs</code>、<code>Shrink</code>方法使用值接收者,<code>Scale</code>方法使用指针接收者。</p><figure class="highlight go"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"math"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Vertex <span class="keyword">struct</span> {</span><br><span class="line"> X, Y <span class="type">float64</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(v Vertex)</span></span> Abs() <span class="type">float64</span> {</span><br><span class="line"> <span class="keyword">return</span> math.Sqrt(v.X*v.X + v.Y*v.Y)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(v *Vertex)</span></span> Scale(f <span class="type">float64</span>) {</span><br><span class="line"> v.X = v.X * f</span><br><span class="line"> v.Y = v.Y * f</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(v Vertex)</span></span> Shrink(f <span class="type">float64</span>) {</span><br><span class="line"> <span class="keyword">if</span> f == <span class="number">0</span> {</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"> v.X = v.X / <span class="number">10</span></span><br><span class="line"> v.Y = v.Y / <span class="number">10</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> v := Vertex{<span class="number">3</span>, <span class="number">4</span>}</span><br><span class="line"> fmt.Println(v.Abs())</span><br><span class="line"> v.Scale(<span class="number">10</span>)</span><br><span class="line"> fmt.Println(v.Abs())</span><br><span class="line"> v.Shrink(<span class="number">10</span>)</span><br><span class="line"> fmt.Println(v.Abs())</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>输出的结果为:</p><figure class="highlight plaintext"><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">5</span><br><span class="line">50</span><br><span class="line">50</span><br></pre></td></tr></table></figure><p>可以看到,<code>Abs</code>和<code>Shrink</code>方法使用值接收者,所以<code>Abs</code>和<code>Shrink</code>方法内部对<code>v</code>的修改不会影响原来的<code>v</code>。</p><p>而<code>Scale</code>方法使用指针接收者,所以<code>Scale</code>方法内部对<code>v</code>的修改会影响原来的<code>v</code>。</p><p>如果将<code>Shrink</code>方法的接收者类型改为指针接收者:</p><figure class="highlight go"><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="function"><span class="keyword">func</span> <span class="params">(v *Vertex)</span></span> Shrink(f <span class="type">float64</span>) {</span><br><span class="line"> <span class="keyword">if</span> f == <span class="number">0</span> {</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"> v.X = v.X / <span class="number">10</span></span><br><span class="line"> v.Y = v.Y / <span class="number">10</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>结果为:</p><figure class="highlight plaintext"><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">5</span><br><span class="line">50</span><br><span class="line">5</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> Go </category>
</categories>
<tags>
<tag> Go </tag>
</tags>
</entry>
<entry>
<title>Go 中的初始化</title>
<link href="/2024/08/07/Go%E4%B8%AD%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96/"/>
<url>/2024/08/07/Go%E4%B8%AD%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96/</url>
<content type="html"><![CDATA[<p>Go 语言会在声明变量类型的时候进行初始化,如果没有指定值,那将会使用零值。各种类型的零值:</p><ul><li>int: 0</li><li>float: 0.0</li><li>bool: false</li><li>string: “”</li><li>切片: []</li><li>指针、函数、接口: nil</li><li>结构体: 由属性的零值组成</li></ul><p>需要注意的是,Go 语言中声明(declaration)和定义(definition)都会为变量分配空间。意思是不在需要像 Java 那样必须调用 new 来构造对象。</p><p>举例如下:</p><figure class="highlight go"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> StructA <span class="keyword">struct</span> {</span><br><span class="line"> A <span class="type">int</span></span><br><span class="line"> B *<span class="type">int</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Inter <span class="keyword">interface</span> {</span><br><span class="line"> Hello(<span class="type">string</span>) <span class="type">error</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> StructB <span class="keyword">struct</span> {</span><br><span class="line"> Int <span class="type">int</span></span><br><span class="line"> String <span class="type">string</span></span><br><span class="line"> Struct StructA</span><br><span class="line"> Point *StructA</span><br><span class="line"> Array []<span class="type">int</span></span><br><span class="line"> Fun <span class="function"><span class="keyword">func</span><span class="params">(<span class="type">int</span>, <span class="type">int</span>)</span></span> <span class="type">int</span></span><br><span class="line"> Interface Inter</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> declaration StructB</span><br><span class="line"> fmt.Printf(<span class="string">"Declaration StructB: \n%+v\n"</span>, declaration)</span><br><span class="line"> <span class="keyword">var</span> definition = StructB{Int: <span class="number">3</span>, String: <span class="string">"foo"</span>}</span><br><span class="line"> fmt.Printf(<span class="string">"Definition StructB: \n%+v\n"</span>, definition)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>输出为:</p><figure class="highlight plaintext"><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">Declaration StructB:</span><br><span class="line">{Int:0 String: Struct:{A:0 B:<nil>} Point:<nil> Array:[] Fun:<nil> Interface:<nil>}</span><br><span class="line">Definition StructB:</span><br><span class="line">{Int:3 String:foo Struct:{A:0 B:<nil>} Point:<nil> Array:[] Fun:<nil> Interface:<nil>}</span><br></pre></td></tr></table></figure><p>可以发现声明的变量也可以直接使用,并且属性都被赋予了零值。</p><h1 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h1><p><a href="https://go.dev/doc/effective_go#data">Effective Go</a></p>]]></content>
<categories>
<category> Go </category>
</categories>
<tags>
<tag> Go </tag>
</tags>
</entry>
<entry>
<title>杂谈——毕业一年的无病呻吟</title>
<link href="/2024/08/05/%E6%9D%82%E8%B0%88%E2%80%94%E2%80%94%E6%AF%95%E4%B8%9A%E4%B8%80%E5%B9%B4/"/>
<url>/2024/08/05/%E6%9D%82%E8%B0%88%E2%80%94%E2%80%94%E6%AF%95%E4%B8%9A%E4%B8%80%E5%B9%B4/</url>
<content type="html"><![CDATA[<p>从 CQUPT 毕业一年时间,身份从学生转换到打工人也已经一年时间。一年以来关于生活和工作可能有许多想法,但都只是在脑海中绕了一圈,有的消失了,有的还留着。所以想用文字记录下当下的想法。</p><h1 id="离职"><a href="#离职" class="headerlink" title="离职"></a>离职</h1><p>第一份正式工作在 A 公司,岗位是后台开发。我是从 22 年 10 月的时候开始实习,毕业转正,直到 24 年 6 月正式离职,算下来待了 3 + 11 + 5 = 19 个月时间。</p><p>在 A 公司学到了很多,虽然进 A 公司之前只会 Spring Boot 那一套,但是第一项目需要开发一个监控的 agent,学习了 Go 语言,对操作系统网络、进程、Shell 脚本也有了进一步了解。</p><p>第二个项目参与了多端,管理端用回了 Spring Boot,为了方便开发还熟悉了 Docker 运行各种中间件,ci / cd 流程。C 端为了开发客户端后台程序学习了 C++、Cmake,因为要跨平台运行,也进一步了解了 linux 中程序的运行。</p><h2 id="为什么?"><a href="#为什么?" class="headerlink" title="为什么?"></a>为什么?</h2><p>第一,在 A 公司做的是 toB 产品,无所谓代码质量,只要能跑,能满足客户需求,能交付,那就足够了。除非是请求一个接口十几秒导致客户端卡住了,或者下载失败了,没人会关心代码写的怎么样,也没人会关心资源占用是不是太多,当然也可能是项目体量本身就小,还没有到需要优化的地步。总之,好像在业务方面我学不到更多东西了。</p><p>第二,我好像并不清楚应该怎么评价我自己。我的意思是,我的岗位是后台开发工程师,我参与了 xx 项目的开发,应该怎么评价我的工作呢?项目有明确的工期安排,根据合同或者项目经理和客户讨论出来的工期,应该从我有没有按时完成任务来评价吗?如果 xx 时间前完成了 xx 任务,我就加一分?或者是不是应该从我有没有造成线上事故,解决事故的速度来判断?我没有找到答案,我只知道尽量在工期前完成任务,完不成就提一下延后,或者加一会儿班继续完成就行。没有人会对我进行评价,每个月的工资都是一样的,从另一个角度来说好像也挺好的。</p><p>第三,也是被下一家公司老板评价说自我矛盾的点,我好像又想走技术上升通道,精进自己的专业,又遇难则退,抱怨外部条件。在 A 公司的项目需要了解 Chromium 源码。我的说辞是它太难了,A 公司又没有人教,我水平又有限,做的都是随便谁来都可以做的工作,简单描述就是:需要改一下某个按钮的行为,那就在可能触发这个按钮的地方附近打个断点,然后 debug 找到具体位置,修改一下逻辑就好。要说什么都没学到也不对,我确实了解了浏览器的一点点知识,但我又不能沉下心来钻研它,应该被称之为浮躁,或者“挫”。</p><p>在 A 公司最后的阶段,我心理状态十分不好,变得易怒,焦虑,当然是在工作外的时间,也非常感谢女朋友的包容谅解。那时我觉得上班就是坐牢,我也不想写代码。我觉得自己没有价值,是个 loser,遇见困难就逃避,只愿意呆在自己的舒适圈,却还妄想自己如果做了 xx 事,可能就会怎么样。当时个人和家庭经济情况并不好,是负债的水平。我感觉好像是受够了?又或者是我不想再续那半年期的房租(😂)?最终我还是裸辞了,提前一个月打好招呼,和前辈老师谈了谈,六月初离开了 A 公司。</p><h1 id="过渡(摆烂)"><a href="#过渡(摆烂)" class="headerlink" title="过渡(摆烂)"></a>过渡(摆烂)</h1><p>六月离职后在出租屋待过了最后一个月,然后投奔了发小。在这两个月的时间里,我摆烂,准备面试,面试。我自认是惰性非常大,又十分拖延的人。在这段时间里,我第二天太阳出来前睡觉,睡到自然醒,吃一顿饭,打游戏,腰疼了躺床上玩手机。表面上看起来是十足的废宅,但是我的心理却不是那么放松,诚如之前所说,一是经济情况并不好,躺不了两个月;二是心里还是会想我有什么价值,我想要做什么。</p><p>就在毕业前,还是学生的时候,不管是口头上说的,还是心里想的,都是很清晰的一条路,可能也有好多其他人也这样想:赚钱,提前退休,在一个小房间里一直打游戏坐吃山空。它是如此简单,在工作前我都相信这好像就是 TM 的理想吧?现实却很骨感,上班没多久就电子 ed 了,除了发工资,消费带来的正反馈,其他的事情却没有一间能让人高兴的。当然自己还是有想过找一条出路的,或者叫做想想生活、工作的意义。</p><p>之前关注的 up 主 <a href="https://www.bilibili.com/video/BV1xW4y1T7ez">polebug23</a> 的一期视频说了对工作生活的看法:工作应该是有热情,有价值,有成就感的事。工作应该叫做事业,你愿意投入时间,你有激情,你可以从中获得回报。这是在毕业前已经在实习的我看到并且还转发到朋友圈的一条视频。工作时这个想法在脑海深处,离职前后它浮了上来。我发现自己好像没有真正意义上想要为之投入长时间的事,每每想起都觉得自己一无是处,想一会儿就去刷视频,打游戏,用短平快的刺激来麻痹自己的想法。</p><p>但是终究还是不能一直躺下去。我除了会写一点代码,没有其他特长,总是要吃饭的,所以还是继续找程序员的工作。说实话,我对工作没有抱任何期待,只是需要赚钱维持生计。大家总说环境不好,但其实互联网企业可以看见的招聘都挺多的,就自己的看法,事实上只是菜鸟(我)的环境不好。我想如果换位思考,我想要招一个什么样的人,来和我一起工作的话,我可能不会选我自己。于是开始复习八股文,看点算法题(我一直都不擅长也没有投入精力),投简历。</p><p>我发出的和有回应的比例大概是 5:1 左右吧。只拿两个面试经历来说:第一个是 OD,因为有亲戚在武汉,所以投了很多武汉的公司。OD 的 HR 是真的很多,一天得有四、五条都是 OD 的招聘。我参加的应该算是部门直招,部门的 leader 把笔试题喂到了嘴里,却不曾想我最害怕的就是算法,知道笔试截止前一天,我才开始面对,刷了几道练了手感就直接参加了笔试。一共三道题,两道 100 分的过了 100% 和 60%,200 分的实在做不出来,就直接输出了答案,结果 100% 通过了 😂。就这样应该算是通过了笔试,然后收到测评。细致的 leader 还将测评的注意事项给说了一遍。</p><p>到这有了转折,另一家 B 公司要求线下面试,这家是做 C 端 APP 的公司,我之前从没有接触过 C 端产品,也没有抱很大期望,但是结果却让我意想不到。一共三面:</p><p>一面 hr 先和我聊了聊上一份工作,然后扔了两道 leetcode 简单题:有效的括号,压缩字符串。</p><p>二面是技术面,也是之后的 mentor,暂且称呼为 Q 吧。先来看了两道算法题,压缩字符串我题意都理解错了。然后聊了上一家公司的离职原因和工作内容。然后根据我的简历开始提问,我写了自己熟悉 Docker,Git,MySQL 等等。</p><ul><li>Q: 你说的熟练使用是指怎么个熟练,让我说一些使用的场景。</li><li>Q: 有了解过 Docker 容器的跨平台吗(Q 说这比较冷门,但是遇到过问题)?A: 我只说了 Go 可以生成不同架构的产物</li><li>Q: Docker 容器怎么改时区?A: 回答的修改 /etc/localtime</li><li>Q: 有用过 Docker Compose 吗?A: 怎么配置 Docker Compose 的日志?</li><li>Q: 怎么过滤查询 Docker 的日志,两种方式?A: 重定向文件再 grep</li><li>Q: 有了解过 Git stash 吗?A: 暂存没有提交的修改</li><li>Q: 怎么修改 commit ?A: git commit –amend</li><li>Q: Git reset 三种方式(浅聊了一点)</li><li>Q: MySQL 查询有索引的字段的规则?A: 最左匹配</li><li>Q: 如果有一个字段很长,无法建索引(longtext),有没有什么办法解决?A: 在 Q 的引导下,用散列(哈希)算法压缩后建索引。</li><li>Q: 怎么用 Redis 做排行榜?A: zset</li><li>Q: 如果有同分的情况,要根据提交时间判断排名怎么做?A: 同样在 Q 的引导下,分数 + 时间戳再取倒数。</li><li>Q: 并发问题,怎么保证不超量超卖?A: 回答了一些方法。Q 说想要的答案是用 list 解决</li><li>Q: 如果非常多的请求比如 1 亿个来抢购,想想让客户端怎么处理,最简单粗暴?Q 说直接概率处理让部分用户点击按钮后直接返回,都不用请求 😂</li><li>Q: RESTful 的方法类型。</li><li>Q: 幂等性有了解吗?</li><li>Q: OPTIONS 用在什么地方?A: 网络检测</li><li>Q: HTTP 怎么解决粘包的问题呢?A: content-length</li><li>Q: Linux 怎么看磁盘占用?A: df -h。</li><li>Q: du 命令呢?怎么找大文件?</li><li>Q: 怎么看日志?A: 用 vim。Q: 很大怎么办?grep</li><li>Q: 进程和线程的关系?</li><li>Q: k8s 怎么暴露网络?用 NodePort。</li><li>Q: etcd 租约续约有了解过吗?</li></ul><p>我在二面中在 Q 的引导下基本所有问题都有回答到,总体给我的面试体验非常好,每个问题都有自己的理解,也会告诉我可能的答案,没有咄咄逼人的氛围,整体就像是在朋友聊天一样。</p><p>然后是老板 H 三面,H 指出了我很多问题,我自我矛盾,眼高手低。但是也理解了我的状态,说了说自己的经历,我也能感受到 H 的真诚。我很兴奋,有人能理解我的状态,并且指出来;我也很难过,因为我觉得自己在 H 当时的眼里应该不算是一个优秀的人,虽然事实却是是这样的,我不是一个身上带着闪光点的人。</p><p>面试结束后在写字楼下台阶坐了半个小时,正值中午,热出了汗,流进了眼睛,十分酸涩。</p><h1 id="新工作"><a href="#新工作" class="headerlink" title="新工作"></a>新工作</h1><p>当天下午还没来得及睡一觉,接到电话通知 B 公司发了 offer,我没有犹豫地接了,拒绝了 OD 后续的流程。一是面试体验非常好,不管是 Q 还是 H 给我的感觉都非常棒,至少相对前公司来说是完全不一样的感觉;二是我在一个月左右的准备面试和参加面试过程中感受到我没有什么竞争力,能有人要都不错了;三是 H 在三面说的:<code>外包项目不会给人带来成长</code>,因为没有那么高的要求,你做出提升也不会影响你的评价和薪资。<code>当下的机会成本更高</code>,也许去其他地方短时间的钱可能更多,但是也可能你的成长性远远比不过其他在成长性高的地方工作学习的同学。</p><p>我想要做什么、我能够做什么、我能创造什么价值、怎么评判我的价值。我希望能在之后的生活中找到它们的答案。</p>]]></content>
<categories>
<category> 杂谈 </category>
</categories>
<tags>
<tag> 生活 </tag>
</tags>
</entry>
<entry>
<title>HashMap 源码阅读</title>
<link href="/2024/07/10/HashMap%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/"/>
<url>/2024/07/10/HashMap%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/</url>
<content type="html"><![CDATA[<p>本文为阅读 HashMap 源码的笔记,主要了解 HashMap 增删改查的过程。</p><p>文中代码和描述基于 openjdk 17,HashMap 底层数据结构采用 <code>Node</code> 数组 + 链表 + 红黑树。</p><figure class="highlight plaintext"><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">openjdk 17.0.11 2024-04-16 LTS</span><br><span class="line">OpenJDK Runtime Environment Corretto-17.0.11.9.1 (build 17.0.11+9-LTS)</span><br><span class="line">OpenJDK 64-Bit Server VM Corretto-17.0.11.9.1 (build 17.0.11+9-LTS, mixed mode, sharing)</span><br></pre></td></tr></table></figure><h1 id="插入元素"><a href="#插入元素" class="headerlink" title="插入元素"></a>插入元素</h1><p>调用 <code>put</code> 方法插入一个键值对,流程为:</p><ol><li>调用 <code>putVal</code> 方法</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> V <span class="title function_">put</span><span class="params">(K key, V value)</span> {</span><br><span class="line"> <span class="keyword">return</span> putVal(hash(key), key, value, <span class="literal">false</span>, <span class="literal">true</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="2"><li>如果为空表,则进行初始化</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Implements Map.put and related methods. * * <span class="doctag">@param</span> hash hash for key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key the key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> value the value to put</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> onlyIfAbsent if true, don't change existing value</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> evict if false, the table is in creation mode.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> previous value, or null if none</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">final</span> V <span class="title function_">putVal</span><span class="params">(<span class="type">int</span> hash, K key, V value, <span class="type">boolean</span> onlyIfAbsent,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> evict)</span> {</span><br><span class="line"> Node<K,V>[] tab; Node<K,V> p; <span class="type">int</span> n, i;</span><br><span class="line"> <span class="keyword">if</span> ((tab = table) == <span class="literal">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line"> <span class="comment">// 如果当前表为空表,初始化表。</span></span><br><span class="line"> n = (tab = resize()).length;</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="3"><li>如果当前的哈希桶(桶数组)为空,直接插入</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> V <span class="title function_">putVal</span><span class="params">(<span class="type">int</span> hash, K key, V value, <span class="type">boolean</span> onlyIfAbsent,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> evict)</span> {</span><br><span class="line"> ...</span><br><span class="line"> <span class="comment">// 当前桶数组为空。</span></span><br><span class="line"> <span class="keyword">if</span> ((p = tab[i = (n - <span class="number">1</span>) & hash]) == <span class="literal">null</span>)</span><br><span class="line"> tab[i] = newNode(hash, key, value, <span class="literal">null</span>);</span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="4"><li>如果当前的哈希桶有值,即产生哈希冲突,首先判断链表头,即哈希桶第一个 <code>Node</code> 键值对和将要插入的键值对是否相同,相同则覆盖。</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> V <span class="title function_">putVal</span><span class="params">(<span class="type">int</span> hash, K key, V value, <span class="type">boolean</span> onlyIfAbsent,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> evict)</span> {</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">if</span> ((p = tab[i = (n - <span class="number">1</span>) & hash]) == <span class="literal">null</span>)</span><br><span class="line"> tab[i] = newNode(hash, key, value, <span class="literal">null</span>);</span><br><span class="line"> <span class="comment">// 当前桶数组不为空。</span></span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> Node<K,V> e; K k;</span><br><span class="line"> <span class="comment">// 重复的键值对</span></span><br><span class="line"> <span class="keyword">if</span> (p.hash == hash &&</span><br><span class="line"> ((k = p.key) == key || (key != <span class="literal">null</span> && key.equals(k))))</span><br><span class="line"> e = p;</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="keyword">if</span> (e != <span class="literal">null</span>) { <span class="comment">// existing mapping for key</span></span><br><span class="line"> <span class="type">V</span> <span class="variable">oldValue</span> <span class="operator">=</span> e.value;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent || oldValue == <span class="literal">null</span>)</span><br><span class="line"> e.value = value;</span><br><span class="line"> afterNodeAccess(e);</span><br><span class="line"> <span class="keyword">return</span> oldValue;</span><br><span class="line"> }</span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="5"><li>如果哈希桶第一个键值对不重复,判断是否为树形节点,如果为树形节点,调用 <code>putTreeval</code></li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> V <span class="title function_">putVal</span><span class="params">(<span class="type">int</span> hash, K key, V value, <span class="type">boolean</span> onlyIfAbsent,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> evict)</span> {</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">if</span> ((p = tab[i = (n - <span class="number">1</span>) & hash]) == <span class="literal">null</span>)</span><br><span class="line"> tab[i] = newNode(hash, key, value, <span class="literal">null</span>);</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> Node<K,V> e; K k;</span><br><span class="line"> <span class="keyword">if</span> (p.hash == hash &&</span><br><span class="line"> ((k = p.key) == key || (key != <span class="literal">null</span> && key.equals(k))))</span><br><span class="line"> e = p;</span><br><span class="line"> <span class="comment">// 如果是树形节点则调用树节点插入方法</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (p <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line"> e = ((TreeNode<K,V>)p).putTreeVal(<span class="built_in">this</span>, tab, hash, key, value);</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">if</span> (e != <span class="literal">null</span>) { <span class="comment">// existing mapping for key</span></span><br><span class="line"> <span class="type">V</span> <span class="variable">oldValue</span> <span class="operator">=</span> e.value;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent || oldValue == <span class="literal">null</span>)</span><br><span class="line"> e.value = value;</span><br><span class="line"> afterNodeAccess(e);</span><br><span class="line"> <span class="keyword">return</span> oldValue;</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><ol start="6"><li>如果不是树形节点,则遍历哈希桶,即遍历链表,在链表尾部插入,如果链表长度大于树化的阈值,则将当前哈希桶的节点树化。</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> V <span class="title function_">putVal</span><span class="params">(<span class="type">int</span> hash, K key, V value, <span class="type">boolean</span> onlyIfAbsent,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> evict)</span> {</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">if</span> ((p = tab[i = (n - <span class="number">1</span>) & hash]) == <span class="literal">null</span>)</span><br><span class="line"> tab[i] = newNode(hash, key, value, <span class="literal">null</span>);</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 遍历链表节点</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">binCount</span> <span class="operator">=</span> <span class="number">0</span>; ; ++binCount) {</span><br><span class="line"> <span class="comment">// 在尾部插入新键值对</span></span><br><span class="line"> <span class="keyword">if</span> ((e = p.next) == <span class="literal">null</span>) {</span><br><span class="line"> p.next = newNode(hash, key, value, <span class="literal">null</span>);</span><br><span class="line"> <span class="comment">// 链表节点超过阈值,进行树化</span></span><br><span class="line"> <span class="keyword">if</span> (binCount >= TREEIFY_THRESHOLD - <span class="number">1</span>) <span class="comment">// -1 for 1st</span></span><br><span class="line"> treeifyBin(tab, hash);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 存在重复节点</span></span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash &&</span><br><span class="line"> ((k = e.key) == key || (key != <span class="literal">null</span> && key.equals(k))))</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> p = e;</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><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="7"><li>如果表中元素数量大于阈值,则进行扩容。</li></ol><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="keyword">final</span> V <span class="title function_">putVal</span><span class="params">(<span class="type">int</span> hash, K key, V value, <span class="type">boolean</span> onlyIfAbsent,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> evict)</span> {</span><br><span class="line">...</span><br><span class="line"> ++modCount;</span><br><span class="line"> <span class="comment">// 元素数量大于阈值进行扩容</span></span><br><span class="line"> <span class="keyword">if</span> (++size > threshold)</span><br><span class="line"> resize();</span><br><span class="line"> afterNodeInsertion(evict);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> HashMap </tag>
<tag> 源码 </tag>
<tag> Java </tag>
</tags>
</entry>
<entry>
<title>Chromium 定制开发</title>
<link href="/2024/04/01/Chromium-%E5%AE%9A%E5%88%B6%E5%BC%80%E5%8F%91/"/>
<url>/2024/04/01/Chromium-%E5%AE%9A%E5%88%B6%E5%BC%80%E5%8F%91/</url>
<content type="html"><![CDATA[<p>本文用于记录在工作中对 Chromium 的定制化开发。基于 84 版本 Chromium 浏览器。</p><h1 id="功能"><a href="#功能" class="headerlink" title="功能"></a>功能</h1><h2 id="图标和浏览器安装名称"><a href="#图标和浏览器安装名称" class="headerlink" title="图标和浏览器安装名称"></a>图标和浏览器安装名称</h2><p>浏览器图标:</p><ul><li><code>chrome/app/theme/chromium/product_logo_*.png</code></li><li><code>chrome/app/theme/chromium/win/chormium.ico</code>, <code>chrome/app/theme/chromium/win/tiles/Logo.png</code>, <code>chrome/app/theme/chromium/win/tiles/SmallLogo.png</code></li><li><code>chrome/app/theme/default_100_percent/chromium/product_logo*.png</code>, <code>chrome/app/theme/default_200_percent/chromium/product_logo*.png</code></li></ul><p>浏览器安装名称:</p><ul><li><code>chrome/app/chromium_strings.grd</code></li></ul><h2 id="安装位置"><a href="#安装位置" class="headerlink" title="安装位置"></a>安装位置</h2><p>修改浏览器安装位置:</p><ul><li><code>chrome/installer/setup/setup_main.cc --> wWinMain --> installer_state.Initialize() --> chrome/installer/setup/installer_state.cc --> InstallerState::Initialize</code> 修改 <code>target_path_</code> 路径</li></ul><p>比如:</p><figure class="highlight cpp"><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><br><span class="line"><span class="comment">// target_path_ = GetChromeInstallPath(system_install());</span></span><br><span class="line">target_path_ = base::<span class="built_in">FilePath</span>(<span class="string">L"C:\\"</span>);</span><br><span class="line">target_path_ = taget_path_.<span class="built_in">Append</span>(install_static::<span class="built_in">GetChromeInstallSubDirectory</span>()); <span class="comment">// Append "kCompanyPathName\\"</span></span><br><span class="line">target_path_ = target_path_.<span class="built_in">Append</span>(kInstallBinaryDir); <span class="comment">// Append "Appliation\\"</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>上述代码将安装位置设置为:<code>C:\\${ComppanyPathName}\\Application\\</code></p><h1 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h1><p>参考:</p><ul><li><a href="https://stackoverflow.com/questions/64166411/how-to-change-chromium-browser-logo-and-name">图标和浏览器名称</a></li></ul>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> Chromium </tag>
</tags>
</entry>
<entry>
<title>分布式</title>
<link href="/2022/12/04/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
<url>/2022/12/04/%E5%88%86%E5%B8%83%E5%BC%8F/</url>
<content type="html"><![CDATA[<h1 id="分布式"><a href="#分布式" class="headerlink" title="分布式"></a>分布式</h1><h2 id="CAP"><a href="#CAP" class="headerlink" title="CAP"></a>CAP</h2><p>一致性(Consistency)</p><p>可用性(Availablility)</p><p>分区容错性(Partition tolerance)</p><ul><li>CP:</li></ul><p>追求一致性和分区容错性:Redis、HBase、Zookeeper</p><ul><li>AP:</li></ul><p>追求可用性和分区容错性:Eureka</p><hr><h2 id="BASE理论"><a href="#BASE理论" class="headerlink" title="BASE理论"></a>BASE理论</h2><p>BASE:基本可用(Basically Available)、软状态(Soft State)、最终一致性(Eventually Consistency)</p><p><strong>基本可用:</strong></p><p>分布式系统出现故障时,允许损失部分可用性,即保证核心可用</p><p><strong>软状态:</strong></p><p>允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。</p><p><strong>最终一致性:</strong></p><p>系统中所有数据副本经过一段时间后,最终能够达到一致的状态。</p><hr><h2 id="分布式事务"><a href="#分布式事务" class="headerlink" title="分布式事务"></a>分布式事务</h2><p><strong>2PC(Two-phaseCommit):</strong>二阶段提交协议</p><p>2PC 算法思路:<strong>参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。</strong></p><ul><li><p><strong>准备阶段(投票阶段)</strong></p></li><li><p><strong>提交阶段(执行阶段)</strong></p></li></ul><p>缺点:</p><ul><li><p><strong>同步阻塞</strong></p></li><li><p><strong>单点故障</strong></p></li><li><p><strong>数据不一致</strong></p></li><li><p><strong>脑裂:</strong>Elasticsearch、Zookeeper 集群环境中,当两个机房之间的网络通信出现故障时,选举机制可能再两个计方选举出两个 Leader。</p></li></ul><p><strong>3PC(Three-phaseCommit):</strong>三阶段提交协议</p><p>3PC 协议在 2PC 协议基础上加入了超时机制和 preCommit 阶段</p><ul><li><p><strong>CanCommit 阶段</strong></p><p>和 2PC 的准备阶段相同,协调者像参与者发送 commit 请求,参与者收到请求后返回响应</p></li><li><p><strong>PreCommit 阶段</strong></p><p>如果所有的参与者都返回 Yes 响应,则执行事务的预执行</p><ol><li><strong>发送预提交请求</strong>:协调者向参与者发送 PreCommit 请求,并进入 Prepare 阶段</li><li><strong>事务预提交</strong>:参与者收到 PreCommit 请求后,执行事务操作,并记录 undo log、redo log</li><li><strong>响应反馈</strong></li></ol><p>如果有任何一个参与者返回了 No 响应,或者等待超时后都没有接到参与者的响应,则执行事务中断</p><ol><li><strong>发送中断(abort)请求</strong></li><li><strong>终端事务</strong></li></ol></li><li><p><strong>doCommit 阶段</strong></p><p>在 doCommit 阶段如果参与者因为网络超时没有收到 doCommit 请求或者 abort 请求,则会继续进行事务的提交</p></li></ul><hr><h2 id="分布式锁"><a href="#分布式锁" class="headerlink" title="分布式锁"></a>分布式锁</h2>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> 概述 </tag>
<tag> 分布式 </tag>
</tags>
</entry>
<entry>
<title>Java IO</title>
<link href="/2022/03/29/JavaIO/"/>
<url>/2022/03/29/JavaIO/</url>
<content type="html"><![CDATA[<h1 id="JavaNIO"><a href="#JavaNIO" class="headerlink" title="JavaNIO"></a>JavaNIO</h1><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><h3 id="三种IO模型"><a href="#三种IO模型" class="headerlink" title="三种IO模型"></a>三种IO模型</h3><p>Java支持三种<code>I/O</code>模式</p><ul><li><code>BIO</code></li><li><code>NIO</code></li><li><code>AIO</code></li></ul><p><strong>BIO (Blocking):</strong></p><blockquote><p> JDK1.4以前的同步阻塞IO,当客户端有连接请求时,服务器就需要启动一个线程进行处理。</p><p> <img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153351.png" alt="img"></p></blockquote><hr><p><strong>NIO (Non-blocking)</strong></p><blockquote><p> JDK1.4以后,同步非阻塞IO,服务端使用一个线程处理多个请求,客户端发送的连接请求会注册到多路复用器上,多路复用器轮询到连接有<code>I/O</code>请求就处理</p><p> <img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153354.png" alt="img"></p></blockquote><hr><p><strong>AIO (Asynchronous)</strong></p><blockquote><p> JDK1.7以后,异步非阻塞IO。它的特点是先由操作系统完成后才通知服务端程序启动线程去处理。</p></blockquote><hr><p><strong>三种IO模型的适用场景</strong></p><ul><li><code>BIO</code> 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,程序简单易理解。</li><li><code>NIO</code> 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间通讯等,编程比较复杂。</li><li><code>AIO</code> 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 <code>OS</code> 参与并发操作,编程比较复杂。</li></ul><hr><p><strong><code>NIO</code>和<code>BIO</code>的比较</strong></p><ol><li><code>BIO</code> 以流的方式处理数据,而 <code>NIO</code> 以块的方式处理数据,块 <code>I/O</code> 的效率比流 <code>I/O</code> 高很多。</li><li><code>BIO</code> 是阻塞的,<code>NIO</code> 则是非阻塞的。</li><li><code>BIO</code> 基于字节流和字符流进行操作,而 <code>NIO</code> 基于 <code>Channel</code>(通道)和 <code>Buffer</code>(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。<code>Selector</code>(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。</li></ol><hr><h2 id="NIO原理"><a href="#NIO原理" class="headerlink" title="NIO原理"></a>NIO原理</h2><h3 id="NIO三大核心"><a href="#NIO三大核心" class="headerlink" title="NIO三大核心"></a>NIO三大核心</h3><p><code>NIO</code> 有三大核心部分: <strong><code>Channel</code>(通道)、<code>Buffer</code>(缓冲区)、<code>Selector</code>(选择器)</strong> 。</p><p><strong>NIO三大核心关系图</strong></p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153401.png" alt="img" style="zoom: 67%;" /><ol><li>每个 <code>Channel</code> 都会对应一个 <code>Buffer</code>。</li><li><code>Selector</code> 对应一个线程,一个线程对应多个 <code>Channel</code>(连接)。</li><li>该图反应了有三个 <code>Channel</code> 注册到该 <code>Selector</code> //程序</li><li>程序切换到哪个 <code>Channel</code> 是由事件决定的,<code>Event</code> 就是一个重要的概念。</li><li><code>Selector</code> 会根据不同的事件,在各个通道上切换。</li><li><code>Buffer</code> 就是一个内存块,底层是有一个数组。</li><li><code>Buffer</code>可以通过<code>flip</code>方法切换读写模式。</li></ol><hr><h3 id="缓冲区(Buffer)"><a href="#缓冲区(Buffer)" class="headerlink" title="缓冲区(Buffer)"></a>缓冲区(Buffer)</h3><p>缓冲区(<code>Buffer</code>):缓冲区本质上是一个<strong>可以读写数据的内存块</strong>,可以理解成是一个<strong>容器对象(含数组)</strong>,该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。<code>Channel</code> 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 <code>Buffer</code></p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153409.png" alt="img"></p><p><strong>Buffer类及其子类</strong></p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153414.png" alt="image-20210726001826565"></p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153417.png" alt="image-20210726001849519" style="zoom: 67%;" /><hr><h3 id="通道(Channel)"><a href="#通道(Channel)" class="headerlink" title="通道(Channel)"></a>通道(Channel)</h3><ol><li><code>NIO</code>的通道类似于流,但有些区别如下:<ul><li>通道可以同时进行读写,而流只能读或者只能写</li><li>通道可以实现异步读写数据</li><li>通道可以从缓冲读数据,也可以写数据到缓冲:</li></ul></li><li><code>BIO</code> 中的 <code>Stream</code> 是单向的,例如 <code>FileInputStream</code> 对象只能进行读取数据的操作,而 <code>NIO</code> 中的通道(<code>Channel</code>)是双向的,可以读操作,也可以写操作。</li><li><code>Channel</code> 在 <code>NIO</code> 中是一个接口 <code>public interface Channel extends Closeable{}</code></li><li>常用的 <code>Channel</code> 类有:<code>FileChannel</code>、<code>DatagramChannel</code>、<code>ServerSocketChannel</code> 和 <code>SocketChannel</code>。<code>ServerSocketChanne</code> 类似 <code>ServerSocket</code>、<code>SocketChannel</code> 类似 <code>Socket</code></li><li><code>FileChannel</code> 用于文件的数据读写,<code>DatagramChannel</code> 用于 <code>UDP</code> 的数据读写,<code>ServerSocketChannel</code> 和 <code>SocketChannel</code> 用于 <code>TCP</code> 的数据读写。h</li></ol>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> IO </tag>
</tags>
</entry>
<entry>
<title>Java 线程池</title>
<link href="/2022/03/29/Java%E7%BA%BF%E7%A8%8B%E6%B1%A0/"/>
<url>/2022/03/29/Java%E7%BA%BF%E7%A8%8B%E6%B1%A0/</url>
<content type="html"><![CDATA[<h1 id="为什么要使用线程池"><a href="#为什么要使用线程池" class="headerlink" title="为什么要使用线程池"></a>为什么要使用线程池</h1><p>使用线程池主要有以下三个原因:</p><ol><li>创建/销毁线程需要消耗系统资源,线程池可以<strong>复用已创建的线程</strong>。</li><li><strong>控制并发的数量</strong>。并发数量过多,可能会导致资源消耗过多,从而造成服务器崩溃。(主要原因)。</li><li>可以对线程做统一管理。</li></ol><p>Java 中线程池的顶层接口是<code>Executor</code>接口,而<code>ThreadPoolExecutor</code>是这个接口的实现类。</p><h1 id="线程池核心参数"><a href="#线程池核心参数" class="headerlink" title="线程池核心参数"></a>线程池核心参数</h1><ol><li><code>int corePoolSize</code>:该线程池中的核心线程数,线程池中有两类线程:核心线程和非核心线程。</li><li><code>int maximumPoolSize</code>:该线程池允许的最大线程数 = 非核心线程数 + 核心线程数。</li><li><code>long keepAliveTime</code>:非核心线程闲置的超时时长,非核心线程如果处于闲置状态超过该值,就会被销毁。</li><li><code>TimeUnit</code>:keepAliveTime 的时间单位。</li><li><code>workQueue</code>:阻塞队列,维护等待执行的线程。</li><li><code>threadFactory</code>:创建线程的工厂。</li><li><code>RejectedExecutionHandler</code>:拒绝策略处理。</li></ol><h1 id="ThreadPoolExecutor-的策略"><a href="#ThreadPoolExecutor-的策略" class="headerlink" title="ThreadPoolExecutor 的策略"></a>ThreadPoolExecutor 的策略</h1><p>线程池本身有一个调度线程,用来管理线程池的各种事务。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// runState is stored in the high-order bits</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">RUNNING</span> <span class="operator">=</span> -<span class="number">1</span> << COUNT_BITS;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">SHUTDOWN</span> <span class="operator">=</span> <span class="number">0</span> << COUNT_BITS;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">STOP</span> <span class="operator">=</span> <span class="number">1</span> << COUNT_BITS;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">TIDYING</span> <span class="operator">=</span> <span class="number">2</span> << COUNT_BITS;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">TERMINATED</span> <span class="operator">=</span> <span class="number">3</span> << COUNT_BITS;</span><br></pre></td></tr></table></figure><h1 id="线程池处理流程"><a href="#线程池处理流程" class="headerlink" title="线程池处理流程"></a>线程池处理流程</h1><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153148.png" alt="img"></p><h1 id="常见线程池"><a href="#常见线程池" class="headerlink" title="常见线程池"></a>常见线程池</h1><p><code>java.util.concurrent.Executors</code> 类提供了几种静态方法创建线程池</p><ol><li><code>newCachedThreadPool</code></li><li><code>newFixedThreadPool</code></li><li><code>newSingleThreadExecutor</code></li><li><code>newScheduledThreadPool</code></li></ol><h1 id="阻塞队列"><a href="#阻塞队列" class="headerlink" title="阻塞队列"></a>阻塞队列</h1><p>在<strong>生产者-消费者</strong>模型中,生产者负责生产资源,消费者负责消费资源,然而有时会出现生产者和消费者生产消费的速率不匹配的情况,这种情况下会导致资源池为空或为满。当资源池为空时,需要阻塞消费者,唤醒生产者;当资源池为满时,需要阻塞生产者,唤醒消费者。</p><p>JDK 中的阻塞队列(BlockingQueue)可以帮助完成这个任务,使用者只需要向队列中添加或取出资源,而无需关心共享资源的并发访问问题和阻塞唤醒流程。</p><p><strong>BlockingQueue</strong> 是 <code>java.util.concurrent</code> 包下的接口,有多种实现类提供了不同的存取方法,和普通的队列不同的是都支持多线程环境。</p><h2 id="操作方法"><a href="#操作方法" class="headerlink" title="操作方法"></a>操作方法</h2><p>BlockingQueue 提供了四组不同的方法操作队列中的元素。</p><ol><li>抛出异常:<code>add(e)</code>、<code>remove()</code>、<code>element()</code></li><li>返回特定值(<code>null</code> 或者 <code>false</code>):<code>offer(e)</code>、<code>poll()</code>、<code>peek()</code></li><li>阻塞当前线程:<code>put(e)</code>、<code>take()</code></li><li>指定唤醒时间:<code>offer(e, time, unit)</code>、<code>pool(time, unit)</code></li></ol><h2 id="实现类"><a href="#实现类" class="headerlink" title="实现类"></a>实现类</h2><h3 id="ArrayBlockingQueue"><a href="#ArrayBlockingQueue" class="headerlink" title="ArrayBlockingQueue"></a>ArrayBlockingQueue</h3><p>由<strong>数组</strong>组成的有界阻塞队列。</p><h3 id="LinkedBlockingQueue"><a href="#LinkedBlockingQueue" class="headerlink" title="LinkedBlockingQueue"></a>LinkedBlockingQueue</h3><p>由<strong>链表</strong>组成的阻塞队列。默认大小为 <code>Integer.MAX_VALUE</code>。</p><h3 id="DelayQueue"><a href="#DelayQueue" class="headerlink" title="DelayQueue"></a>DelayQueue</h3><p>由实现了 <code>Delay</code> 接口的对象组成的无界阻塞队列,传入的对象只有当延迟时间到期后才可以被取出。</p><h3 id="PriorityBlockingQueue"><a href="#PriorityBlockingQueue" class="headerlink" title="PriorityBlockingQueue"></a>PriorityBlockingQueue</h3><p>优先阻塞队列,同样也是无界的,传入的对象通过实现 <code>Comparator</code> 接口排序。因为是无界队列所以有引发 OOM 的风险。</p><h3 id="SynchronousQueue"><a href="#SynchronousQueue" class="headerlink" title="SynchronousQueue"></a>SynchronousQueue</h3><p>这个队列没有任何内部容量,每个 put 必须等待一个 take 操作,反之亦然。</p>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 多线程 </tag>
<tag> 并发 </tag>
</tags>
</entry>
<entry>
<title>操作系统笔记</title>
<link href="/2022/03/29/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
<url>/2022/03/29/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/</url>
<content type="html"><![CDATA[<h1 id="操作系统"><a href="#操作系统" class="headerlink" title="操作系统"></a>操作系统</h1><h2 id="第一章"><a href="#第一章" class="headerlink" title="第一章"></a>第一章</h2><h3 id="1-1"><a href="#1-1" class="headerlink" title="1.1"></a>1.1</h3><blockquote><ul><li><strong>资源管理者</strong>:有效地管理计算机软硬件资源</li><li><strong>扩展机器</strong>:为用户提供比实际机器更便于运用的抽象,包括进程、地址空间、文件等,提供接口。</li></ul></blockquote><hr><h3 id="1-2-操作系统发展史"><a href="#1-2-操作系统发展史" class="headerlink" title="1.2 操作系统发展史"></a>1.2 操作系统发展史</h3><blockquote><ul><li>真空管和穿孔卡片</li><li>晶体管和批处理系统</li><li>集成电路和多道系统</li><li>个人计算机,移动计算机</li></ul></blockquote><hr><h3 id="1-3-操作系统类型"><a href="#1-3-操作系统类型" class="headerlink" title="1.3 操作系统类型"></a>1.3 操作系统类型</h3><blockquote><ul><li><p>批处理系统</p><ul><li><p>单道</p></li><li><p>多道</p><blockquote><ul><li>优点:资源利用率高、系统吞吐量大、系统切换开销小</li><li>缺点:<strong>无交互能力</strong>、<strong>作业平均轮转时间长</strong></li><li>多道程序设计是现代操作系统诞生的标志</li></ul></blockquote></li></ul></li><li><p>分时系统</p><ul><li>N个用户 时间片q</li><li>轮换法</li><li>多路性、交互性、独占性、及时性</li><li>分时系统更强调<strong>人机交互</strong>功能</li></ul></li><li><p>网络操作系统</p></li><li><p>分布式操作系统</p></li></ul></blockquote><hr><h3 id="1-4-操作系统的功能"><a href="#1-4-操作系统的功能" class="headerlink" title="1.4 操作系统的功能"></a>1.4 操作系统的功能</h3><blockquote><ul><li>资源管理者<ul><li>处理器管理<ul><li><strong>进程控制</strong></li><li><strong>进程同步</strong></li><li><strong>进程通信</strong></li><li><strong>调度</strong></li></ul></li><li>内存管理<ul><li><strong>内存分配</strong></li><li><strong>内存保护</strong></li><li><strong>地址映射</strong></li><li><strong>内存扩充</strong></li></ul></li><li>设备管理<ul><li><strong>缓冲管理</strong></li><li><strong>设备分配</strong></li><li><strong>设备处理</strong></li></ul></li><li>文件管理<ul><li><strong>文件存储空间的管理</strong></li><li><strong>目录管理</strong></li><li><strong>文件的读/写管理</strong></li><li><strong>文件保护</strong></li></ul></li></ul></li><li>扩展机器<ul><li>用户接口<ul><li>图形用户接口:GUI</li><li>命令接口</li><li>程序接口</li></ul></li></ul></li><li>操作系统的特征<ul><li><strong>并发性</strong></li><li><strong>共享性</strong></li><li>虚拟性</li><li>异步性</li></ul></li></ul></blockquote><hr><h3 id="1-5-与操作系统相关的硬件基础"><a href="#1-5-与操作系统相关的硬件基础" class="headerlink" title="1.5 与操作系统相关的硬件基础"></a>1.5 与操作系统相关的硬件基础</h3><blockquote><ul><li>处理器(CPU)<ul><li>控制部件<ul><li><strong>控制器</strong>:指令控制、时序控制、总线控制、中断控制</li><li><strong>程序计数器PC</strong></li><li><strong>地址寄存器AR</strong>:保存当前CPU所访问的内存单元的地址</li><li><strong>指令寄存器IR</strong></li><li><strong>指令译码器ID</strong></li></ul></li><li>运算部件<ul><li><strong>算术逻辑单元ALU</strong></li><li><strong>累加寄存器AC</strong></li><li><strong>数据缓冲寄存器DR</strong></li><li><strong>程序状态字寄存器PSW</strong></li></ul></li><li>工作模式<ul><li>有PSW中的一个位控制</li><li><strong>用户态(目态)</strong></li><li><strong>内核态(系统态、核心态、管态)</strong></li><li>内核态CPU可以执行其指令集中的每条指令,可使用硬件的各种功能。但是用户态CPU只能执行部分指令,执行时仅使用部分功能。</li></ul></li></ul></li><li>存储系统</li><li>I/O子系统</li><li>总线</li></ul></blockquote><hr><h3 id="1-6-基本概念"><a href="#1-6-基本概念" class="headerlink" title="1.6 基本概念"></a>1.6 基本概念</h3><blockquote><ul><li>进程(process):可并发执行的程序在一个数据集合上的运行过程<ul><li><strong>程序段(正文)</strong></li><li><strong>数据段</strong></li><li><strong>堆栈段</strong></li><li><strong>进程表表项(进程控制块PCB)</strong></li><li><strong>处于执行态的进程还有CPU现场</strong></li></ul></li><li>用户接口–系统调用的步骤<ul><li>将参数压入用户栈,转标准库</li><li>将系统调用号压入寄存器</li><li>从用户态切换到内核态</li><li>内核根据系统调用号找到系统调用处理程序进行处理</li><li>从内核态返回用户态</li></ul></li></ul></blockquote><hr><h2 id="第二章-进程与线程"><a href="#第二章-进程与线程" class="headerlink" title="第二章 进程与线程"></a>第二章 进程与线程</h2><ul><li>程序顺序执行<ul><li>顺序,封闭,可再现</li></ul></li><li>程序并发执行<ul><li>间断、失去封闭、未必可再现</li></ul></li><li>原语<ul><li><strong>机器指令级</strong>:不允许中断</li><li><strong>功能级</strong>:不允许并发</li></ul></li></ul><h3 id="2-1-进程"><a href="#2-1-进程" class="headerlink" title="2.1 进程"></a>2.1 进程</h3><blockquote><ul><li>定义:<ul><li><strong>可以与其他程序并发执行的程序的一次执行</strong></li><li><strong>可并发执行的程序在一个数据集合上的运行过程</strong></li><li>是系统进行资源分配和调度的一个独立单位</li></ul></li><li>一个程序可以生成不同的进程</li></ul></blockquote><h4 id="进程的基本状态"><a href="#进程的基本状态" class="headerlink" title="进程的基本状态"></a>进程的基本状态</h4><blockquote> <figure class="highlight plaintext"><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><br><span class="line">执行 -> 就绪: 中断(时间片完)</span><br><span class="line">执行 -> 阻塞: 等待某事件</span><br><span class="line">阻塞 -> 就绪: 事件发生</span><br></pre></td></tr></table></figure><p> <img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20220706234334.png" alt="20210708014430"></p></blockquote><h4 id="进程表表项(PCB)"><a href="#进程表表项(PCB)" class="headerlink" title="进程表表项(PCB)"></a>进程表表项(PCB)</h4><blockquote><ul><li>PCB的组织方式<ul><li>链接组织方式<ul><li>把具有同一状态的PCB链接成一个队列</li><li>就绪队列、若干个阻塞队列、空队列</li></ul></li><li>索引组织方式<ul><li>建立相应的索引表</li><li>就绪索引表、阻塞索引表等</li></ul></li></ul></li><li>进程的控制<ul><li>进程创建<ul><li>创建原因<ol><li>系统初始化</li><li>正在执行的进程调用创建进程的系统调用</li><li>用户请求创建一个进程</li><li>批处理作业的初始化</li></ol></li></ul></li><li>进程阻塞<ul><li>阻塞原因<ol><li>请求系统服务</li><li>启动某种操作</li><li>数据尚未到达</li><li>无新工作课做</li></ol></li></ul></li><li>进程唤醒</li></ul></li></ul></blockquote><hr><h3 id="2-2-线程"><a href="#2-2-线程" class="headerlink" title="2.2 线程"></a>2.2 线程</h3><blockquote><p> 线程是轻量级的进程,是一个进程内的基本调度单位,有自己的<strong>程序计数器</strong>,<strong>寄存器及堆栈</strong>。共享进程的资源</p><ul><li>进程是资源管理的基本单位</li><li>线程是调度的基本单位</li></ul></blockquote><h4 id="线程实现"><a href="#线程实现" class="headerlink" title="线程实现"></a>线程实现</h4><blockquote><p> 实现方式</p><ul><li>在用户空间中实现线程<ul><li>优点<ul><li>可以在不支持线程的操作系统中实现多线程编程</li><li>…</li></ul></li><li>缺点<ul><li>实现<strong>阻塞系统调用</strong>困难,如何避免被阻塞线程影响其他线程</li><li>…</li></ul></li></ul></li><li>在内核中实现线程<ul><li>优点:<ul><li>线程阻塞时,可以运行同一进程中的另一线程或其他进程的线程</li><li>在阻塞系统调用和处理缺页问题更有优势</li></ul></li><li>缺点:<ul><li>创建和终止线程代价较大</li></ul></li></ul></li><li>混合实现</li></ul></blockquote><hr><h3 id="2-3-进程间通信"><a href="#2-3-进程间通信" class="headerlink" title="2.3 进程间通信"></a>2.3 进程间通信</h3><blockquote><p> 进程间通信需要解决的三个问题:</p><ol><li>如何确保多个进程在关键活动中不会冲突</li><li>如何确保多个合作进程的有序进行</li><li>进程间如何传递信息</li></ol></blockquote><h4 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h4><blockquote><ul><li>临界资源(竞争条件):一次只允许一个进程使用的软硬件资源</li><li>临界区:在每个进程中,访问临界资源的部分</li><li>两种形式制约关系<ul><li>间接相互制约:源于进程对资源的竞争共享 互斥</li><li>直接相互制约:源于进程间的合作 同步</li></ul></li><li><strong>进程互斥</strong>:一个进程正在访问临界资源,另一个要访问该资源的进程必须等待。</li><li><strong>进程同步</strong>:合作完成同一个任务的多个进程,再执行速度或某些时序点上必须相互协调的合作关系</li></ul></blockquote><h4 id="解决进程互斥"><a href="#解决进程互斥" class="headerlink" title="解决进程互斥"></a>解决进程互斥</h4><blockquote><p> 并发进程互斥执行准则:</p><ul><li>不假设各并发进程的相对执行速度</li><li>处于临界区外的进程不能阻止其他进程进入临界区</li><li>任何时刻只允许一个进程处于临界区中</li><li>不能使进程在临界区外永远等待</li></ul><p> <strong>屏蔽中断</strong>:每个进程进入临界区后先关中断,离开前开中断</p><blockquote><p> 缺点:</p><ul><li>简单粗暴,效率低</li><li>系统可能终止</li><li>多CPU时无用</li></ul></blockquote><p> <strong>加锁法</strong>:用锁变量来表示临界区是否可用</p><blockquote><p> 缺点:</p><ul><li>不断循环测试,CPU费时</li><li><strong>不能实现绝对互斥(lock不是原子操作)</strong></li></ul></blockquote><p> <strong>严格轮换法</strong></p><p> <strong>Peterson解决方案</strong></p><p> <strong>TSL指令</strong>:需要硬件支持</p></blockquote><h4 id="信号量(灯)机制"><a href="#信号量(灯)机制" class="headerlink" title="信号量(灯)机制"></a>信号量(灯)机制</h4><blockquote><ul><li><p><strong>信号量(semaphore)</strong>:OS中用于表示<strong>资源</strong>的实体,其值仅有<strong>down,up</strong>原语改变</p></li><li><p><strong>down</strong> = <strong>P</strong></p></li><li><p><strong>up</strong> = <strong>V</strong></p></li><li><p>设s为semaphore信号量,则:</p><ul><li>当<strong>s >= 0</strong>时,表示可供并发进程使用的资源数</li><li>当<strong>s < 0</strong>时,**|s|**表示等待使用s资源的进程数</li></ul></li></ul></blockquote><h4 id="信号量解决进程互斥"><a href="#信号量解决进程互斥" class="headerlink" title="信号量解决进程互斥"></a>信号量解决进程互斥</h4><blockquote> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">semaphore mutex;</span><br><span class="line">mutex = 1;</span><br></pre></td></tr></table></figure><p> 对mutex执行<strong>P、V</strong>操作</p></blockquote><h4 id="信号量解决进程同步"><a href="#信号量解决进程同步" class="headerlink" title="信号量解决进程同步"></a>信号量解决进程同步</h4><blockquote><ul><li>有哪些临界资源?</li><li>有哪些并发执行的进程?</li><li>……</li></ul></blockquote><h4 id="信号量机制总结"><a href="#信号量机制总结" class="headerlink" title="信号量机制总结"></a>信号量机制总结</h4><blockquote><ul><li>统一信号量S的<strong>P、V</strong>操作必须成对出现,有一个**P(S)<strong>操作就一定有一个对应的</strong>V(S)**操作<ul><li>进程互斥时,它们处在<strong>同一进程</strong></li><li>进程同步时,它们不在<strong>同一进程</strong></li></ul></li><li>一个<strong>同步P</strong>操作与一个<strong>互斥P</strong>操作在一起时,<strong>同步P</strong>操作在<strong>互斥P</strong>操作前</li></ul></blockquote><h3 id="2-5处理机调度"><a href="#2-5处理机调度" class="headerlink" title="2.5处理机调度"></a>2.5处理机调度</h3><h4 id="调度概述"><a href="#调度概述" class="headerlink" title="调度概述"></a>调度概述</h4><blockquote><ul><li><p>进程调度</p></li><li><p>功能:</p><ul><li>记录系统中所有进程的执行情况</li><li>选择占有处理机的进程</li><li>进程上下文切换</li></ul></li><li><p>时机:</p><ul><li>正常执行结束</li><li>进程阻塞等待</li><li>执行了某些原语(P,V)</li><li>从系统态返回用户态</li><li>在抢占式调度中,高优先级进程进入就绪队列</li><li>分时系统中,分配的时间片用完</li></ul></li><li><p>进程上下文切换步骤</p></li><li><p>调度方式:</p><ul><li>非抢占式:进程一直执行下去,直到完成本次CPU周期</li><li>抢占式:强行剥夺正在执行进程的CPU,并将CPU分配给另一进程</li></ul></li><li><p>调度的基本准则</p></li><li><p>作业或进程调度的主要性能评价指标</p><ul><li><p>面向<strong>系统</strong>的指标:</p><ul><li>系统吞吐量——指在单位时间内完成的作业数</li><li>处理机利用率</li><li>各类资源的平衡利用率</li></ul></li><li><p>面向<strong>用户</strong>的指标:</p><ul><li><p>平均周转时间<strong>T</strong>:</p></li><li><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221204234355.png" alt="image-20210707223837426"><br>$$<br>T = \frac{作业完成时间}{作业数}<br>$$</p></li><li><p>平均带权周转时间**T’**:<br>$$<br>T_i’ = \frac{T_i}{t_i(实际运行时间)}<br>$$</p><p>$$<br>T’ = \sum_{i=1}^n T’_i<br>$$</p></li><li><p>平均等待时间<strong>W</strong>(主要针对进程而言):<br>$$<br>{W}_i = {T}_ip-{T}_ir<br>$$</p><p>$$<br>W = \sum_i^{n}Wi<br>$$</p></li></ul></li></ul></li></ul></blockquote><h4 id="非实时调度算法"><a href="#非实时调度算法" class="headerlink" title="非实时调度算法"></a>非实时调度算法</h4><blockquote><ul><li><p><strong>先来先服务算法(FCFS)</strong>:按到来的先后次序进行调度</p><ul><li>特点:执行时间短的进程或作业等待时间将长</li></ul></li><li><p><strong>最短作业/进程有限算法(SJF/SPF)</strong>:选择估计需要执行时间最短的投入运行</p><ul><li>特点:吞吐量大</li><li>对不断有作业进入的系统,长作业将得不到执行</li></ul></li><li><p><strong>响应比高有限调度算法(HRN)</strong>:选择响应比最大的运行<br>$$<br>R=\frac{等待时间+要求服务时间}{要求服务时间}=\frac{响应时间}{要求服务时间}<br>$$</p><ul><li>特点:介于FCFS与SJF之间,吞吐量减少,增加了系统开销</li></ul></li><li><p><strong>轮转调度算法(RR)</strong>(适用于进程调度):将CPU时间划分成时间片,每个进程轮流使用时间片</p><ul><li>特别适合于分时系统的可抢占方式的调度算法</li><li>时间片q从<strong>几ms~几百ms</strong></li><li>决定因素<ul><li>系统对相应时间的要求:<strong>T = Nq</strong></li><li>就绪队列中进程的数目</li><li>系统的处理能力</li></ul></li></ul></li><li><p><strong>优先级调度</strong>:选择优先级高的进程执行</p></li><li><p>…</p></li></ul></blockquote><h4 id="实时调度算法"><a href="#实时调度算法" class="headerlink" title="实时调度算法"></a>实时调度算法</h4><blockquote><ul><li><p>硬实时任务:系统必须完全满足任务的时限要求</p></li><li><p>软实时任务:允许系统对任务的时限要求有一定的延迟</p></li><li><p><strong>时限调度算法</strong>:选择<strong>时限</strong>要求最近的任务优先占有处理机</p></li><li><p><strong>频率单调调度算法</strong>:周期长(频率低)的任务优先级越低(适用于周期性实时任务)</p><ul><li><p>实时系统可调度条件:对n个周期不同的任务,周期为Ti,执行时间为ti</p></li><li><p><img src="https://gitee.com/xunle1/drawing-bed/raw/master/typora%5C/20210707223816.png" alt="image-20210707223814477"><br>$$<br>\frac{t_1}{T_1}+\frac{t_2}{T_2}+…+\frac{t_n}{T_n} <= n(2^\frac{1}{n}-1)<br>$$</p></li></ul></li></ul></blockquote><h4 id="线程调度"><a href="#线程调度" class="headerlink" title="线程调度"></a>线程调度</h4><hr><h2 id="第三章-死锁"><a href="#第三章-死锁" class="headerlink" title="第三章 死锁"></a>第三章 死锁</h2><h3 id="3-1-资源"><a href="#3-1-资源" class="headerlink" title="3.1 资源"></a>3.1 资源</h3><blockquote><ul><li>可抢占资源</li><li>不可抢占资源</li></ul></blockquote><blockquote><ul><li>使用一个资源的顺序<ol><li>请求资源</li><li>使用资源</li><li>释放资源</li><li>若请求失败,有的系统进程将自动被阻塞,资源可用时唤醒;有的系统会返回一个错误代码,请求进程等待一段时间后重试</li></ol></li></ul></blockquote><h3 id="3-2-死锁概述"><a href="#3-2-死锁概述" class="headerlink" title="3.2 死锁概述"></a>3.2 死锁概述</h3><blockquote><ul><li>死锁定义:如果一个进程集合中的每个进程都在<strong>等待</strong>只能由该进程集合中的<strong>其他进程</strong>才能引发的<strong>事件</strong>,那么,该进程集合是死锁的。</li><li><strong>事件</strong>是指释放该进程集合中其他进程所占有的资源——资源死锁</li><li>该集合中没有任何进程会:<strong>运行、释放资源、被唤醒</strong></li></ul></blockquote><blockquote><ul><li>四个必要条件<ul><li><strong>互斥条件</strong></li><li><strong>占有和等待</strong></li><li><strong>不可抢占条件</strong></li><li><strong>环路等待条件</strong></li></ul></li></ul></blockquote><blockquote><ul><li>死锁建模</li><li><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20220623235543.png" alt="image-20210706230209431"></li><li><strong>圆圈</strong>表示进程</li><li><strong>方框</strong>表示资源</li></ul></blockquote><blockquote><ul><li>四种处理死锁的策略<ul><li>忽略该问题(鸵鸟算法)</li><li>检测死锁并恢复(解除死锁)</li><li>仔细对资源进行分配,动态地避免死锁</li><li>通过破坏引起死锁的四个条件之一,防止死锁的发生</li></ul></li></ul></blockquote><blockquote><ul><li>假设有m个进程,每个进程需要n个同类资源,最容易发生死锁时是(n-1,n-1,n-1…,n-1)时,每个进程请求最后一个。</li><li>即,最少需要<strong>m * (n-1) + 1</strong>个资源</li></ul></blockquote><h3 id="3-3-鸵鸟算法"><a href="#3-3-鸵鸟算法" class="headerlink" title="3.3 鸵鸟算法"></a>3.3 鸵鸟算法</h3><blockquote><ul><li>假装没有问题发生</li><li>适用条件<ul><li>死锁发生的频率很小</li><li>预防死锁会付出很大的代价</li></ul></li></ul></blockquote><h3 id="3-4-死锁检测和死锁恢复"><a href="#3-4-死锁检测和死锁恢复" class="headerlink" title="3.4 死锁检测和死锁恢复"></a>3.4 死锁检测和死锁恢复</h3><blockquote><ul><li><p>四种数据结构</p><ul><li>现有资源矩阵<strong>E</strong></li><li>可用资源矩阵<strong>A</strong></li><li>当前分配矩阵<strong>C</strong></li><li>请求矩阵<strong>R</strong></li></ul></li><li><p>死锁检测方法:</p><ol><li>找到一个<strong>C</strong>矩阵中的一行向量C_i(一个进程),它可以被可用资源矩阵<strong>A</strong>满足,标记该向量</li><li>C_i释放资源,矩阵<strong>A</strong>加上C_i释放的资源</li><li>继续寻找下一个向量</li><li>如果最终有没有被满足的向量,系统将进入死锁</li></ol></li><li><p>例子</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20220623235543.png" alt="20210706230211"></p></li><li><p>死锁恢复</p><ul><li><strong>通过抢占恢复</strong></li><li><strong>通过回滚恢复</strong></li><li><strong>通过杀死进程恢复</strong></li></ul></li></ul></blockquote><h3 id="3-5-死锁避免"><a href="#3-5-死锁避免" class="headerlink" title="3.5 死锁避免"></a>3.5 死锁避免</h3><blockquote><ul><li>在资源的动态分配中,用某种方法防止系统进入<strong>不安全状态</strong></li><li><strong>安全状态</strong>:现有的进程资源占有情况下,各进程按某种推进顺序仍然可以使每个进程得到资源最大需求。</li></ul></blockquote><h3 id="避免死锁——银行家算法"><a href="#避免死锁——银行家算法" class="headerlink" title="避免死锁——银行家算法"></a>避免死锁——银行家算法</h3><blockquote><ul><li><p>和死锁检测有类似的地方,不同点在于:</p><ul><li>没有现有资源矩阵,只有<strong>可用资源矩阵(Available)</strong></li><li>没有请求矩阵(Request),只有<strong>需求矩阵(need)</strong></li><li>加入<strong>最大需求矩阵(Max)</strong></li></ul></li><li><p>数量关系:</p></li><li><p>$$<br>Max[i] = Allocation[i] + Need[i]<br>$$</p></li><li><p>试探性分配后进行安全状态判断</p></li></ul></blockquote><h3 id="3-6-死锁预防——破坏必要条件"><a href="#3-6-死锁预防——破坏必要条件" class="headerlink" title="3.6 死锁预防——破坏必要条件"></a>3.6 死锁预防——破坏必要条件</h3><blockquote><ul><li>破坏互斥条件<ul><li>避免分配不必要的资源</li><li>尽可能少的进程真正申请</li><li>比如打印机的假脱机技术</li></ul></li><li>破坏占有且等待条件<ul><li>一次性获取所需的全部资源</li></ul></li><li>破坏不可抢占条件</li><li>破坏环路等待条件</li></ul></blockquote><h3 id="3-7-其他问题"><a href="#3-7-其他问题" class="headerlink" title="3.7 其他问题"></a>3.7 其他问题</h3><blockquote><ul><li>活锁<ul><li>CPU总是被消耗完</li><li>原因是轮询可进入临界区或获取资源</li></ul></li><li>饥饿<ul><li>由于分配资源的算法导致某些进程得不到服务</li><li>原因是系统总是让短进程先执行,短进程源源不断</li><li>解决办法是通过<strong>先来先服务</strong>分配资源</li></ul></li></ul></blockquote><hr><h2 id="第四章-存储管理"><a href="#第四章-存储管理" class="headerlink" title="第四章 存储管理"></a>第四章 存储管理</h2><h3 id="序言"><a href="#序言" class="headerlink" title="序言"></a>序言</h3><blockquote><ul><li>存储管理器(memory manager)<ul><li>内存分配与回收</li><li>内存空间管理</li><li>地址映射(重定位)</li><li>存储保护</li><li>处理内存超载</li></ul></li><li>内存分配方式<ul><li><strong>连续分配</strong><ul><li>单一连续分配</li><li><strong>固定分区分配</strong></li><li><strong>动态分区分配</strong></li></ul></li><li><strong>离散分配</strong><ul><li><strong>分页</strong></li><li><strong>分段</strong></li><li><strong>段页</strong></li></ul></li></ul></li><li>程序链接<ul><li>静态链接<ul><li>装入前链接成为一个完整模块</li></ul></li><li>装入时动态链接<ul><li>运行前边装入边链接</li></ul></li><li>运行时动态链接<ul><li>运行时需要目标模块才进行链接装入</li></ul></li></ul></li><li>地址映射(地址重定位 )<ul><li>用户程序装入内存时,对目标程序有关指令和数据的地址部分的修改</li><li><strong>逻辑地址</strong>–><strong>物理地址</strong></li><li>地址映射的方式<ul><li>静态重定位(可重定位装入)<ul><li>为程序分配内存时,必须分配全部内存空间</li></ul></li><li>动态重定位(动态运行时装入)<ul><li>需要<strong>重定位寄存器</strong>支持</li><li>地址转换推迟在<strong>程序真正要执行时才进行</strong></li></ul></li></ul></li></ul></li><li>存储保护:保存在内存中的多道程序,只能在给定的存储区域内活动并互补产生干扰<ul><li>防止地址越界</li><li>防止越权</li><li>使用<strong>基址寄存器(重定位寄存器)</strong>和<strong>届地址寄存器(界限寄存器)</strong>实现</li></ul></li><li>内存超载:所有进程所需的内存空间总和通常超出物理内存实际能够支持的范围<ul><li><strong>交换技术</strong></li><li><strong>覆盖技术</strong></li><li><strong>虚拟内存</strong></li></ul></li></ul></blockquote><h3 id="4-1-无存储器抽象"><a href="#4-1-无存储器抽象" class="headerlink" title="4.1 无存储器抽象"></a>4.1 无存储器抽象</h3><blockquote><ul><li><p>程序直接访问物理内存</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20220624000133.png" alt="20210707134238"></p></li><li><p>多道系统</p><ul><li>采用<strong>连续分区分配</strong>方式,需要解决<strong>存储保护</strong>和<strong>重定位</strong>问题</li><li>保护键技术:内存每块一个<strong>4位</strong>保护键,通过保护键和<strong>PSW</strong>中对应的4位码来防止进程间的互相干扰</li><li><strong>静态重定位</strong>:<del>程序在装入内存时,由装载器对相关地址进行一次性映射</del>(物理地址=逻辑地址加上偏移量)</li></ul></li><li><p>由于将物理地址完全暴露给用户进程,所以</p><ul><li>用户进程可能破坏操作系统</li><li>多道程序设计困难</li></ul></li><li><p>所以需要进行存储器抽象</p></li></ul></blockquote><h3 id="4-2-存储器抽象-地址空间"><a href="#4-2-存储器抽象-地址空间" class="headerlink" title="4.2 存储器抽象 地址空间"></a>4.2 存储器抽象 地址空间</h3><blockquote><ul><li><strong>进程可用于寻址内存</strong>的一套地址集合</li><li><strong>本质</strong>:内存空间(物理地址空间)的一部分</li><li>每个进程有独立于其他进程的自己的地址空间</li><li>通过<strong>动态重定位</strong>实现</li><li>硬件支持<ul><li><strong>基址寄存器</strong></li><li><strong>界限寄存器</strong></li></ul></li></ul></blockquote><h4 id="交换技术(swapping)"><a href="#交换技术(swapping)" class="headerlink" title="交换技术(swapping)"></a>交换技术(swapping)</h4><blockquote><ul><li><p>处理内存超载的简单方法</p></li><li><p>空闲内存的管理:跟踪内存情况</p></li><li><p><strong>位图</strong>:如图</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221204234610.png" alt="20210707140307"></p></li><li><p><strong>链表</strong>:如图</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221204234638.png" alt="20210707140502"></p></li><li><p>内存分配算法–动态分区分配方式(动态分区存储管理)</p><ul><li><strong>首次适配算法(first fit)</strong></li><li><strong>下次适配算法(next fit)</strong>:对<strong>首次适配</strong>进行调整,记录上一次搜索停止位置,下一次从此开始</li><li><strong>最佳适配算法(best fit)</strong>:找到能够容纳的最小空闲区</li><li><strong>最坏适配算法(worst fit)</strong>:找到能够容纳的最大空闲区</li><li><strong>最快适配算法(quick fit)</strong></li></ul></li><li><p>为<strong>进程结点</strong>和<strong>空闲区结点</strong>建立独立的链表</p><ul><li>搜索速度提高,但复杂度增加且内存回收慢</li></ul></li><li><p>如果空闲链表按<strong>地址递增</strong>排序</p><ul><li>首次适配:低地址区碎片多,每次从低地址区查找增加开销</li><li>下次适配:空闲区分布均匀,减少查询空闲分区的开销,但缺乏大的空闲分区</li><li>最佳适配:查找开销大,能够避免大量难利用的碎片</li><li>最差适配:查找开销大,避免留下大量难利用的碎片,但缺乏大的空闲分区</li></ul></li><li><p>为增加搜索速度</p><ul><li>最佳适配:空闲区按空间大小<strong>递增排序</strong></li><li>最差适配:空闲区按空间大小<strong>递减排序</strong></li></ul></li></ul></blockquote><h4 id="覆盖技术(overlay)"><a href="#覆盖技术(overlay)" class="headerlink" title="覆盖技术(overlay)"></a>覆盖技术(overlay)</h4><blockquote><ul><li>解决<strong>程序大小超过物理内存总和</strong>问题</li><li>发生在同一进程或作业内</li></ul></blockquote><blockquote><p> 思想:将<strong>程序分为多个段(模块)</strong>,常用段常驻内存,不常用段需要时调入内存</p><p> 内存中分配<strong>一个固定区</strong>和<strong>若干个覆盖区</strong></p><p> 常用段调入固定区后不再调出</p><p> 不常用段需要时调入覆盖区,不需要时调出</p><p> <strong>缺点:</strong></p><ul><li>对用户不透明,增加编程负担</li></ul></blockquote><h4 id="分页"><a href="#分页" class="headerlink" title="分页"></a>分页</h4><blockquote></blockquote><h4 id="快表(TLB"><a href="#快表(TLB" class="headerlink" title="快表(TLB)"></a>快表(TLB)</h4><blockquote><p> 局部性原理:</p><ul><li>时间局部性:如果执行了程序中的某个指令,在不久后这条指令很有可能再次执行;访问了程序中的某个数据,不久后该数据可能再次被访问</li><li>空间局部性:访问了某个存储单元,不久后可能会访问该存储单元附近的存储单元</li></ul><p> <strong>快表</strong>,又称<strong>联想寄存器(TLB)</strong>,是一种访问速度比内存快很多的高速缓冲存储器,用来存放当前访问的若干页表项,以及加速地址变换的过程。与此对应,内存中的页表被称为<strong>慢表</strong></p><ul><li>快表命中,一次访存</li><li>快表未命中,两次访存</li></ul></blockquote><h4 id="多级页表"><a href="#多级页表" class="headerlink" title="多级页表"></a>多级页表</h4><blockquote><ul><li>页表必须连续存放,当页表很大时,需要占用多个连续的页框</li></ul></blockquote><h3 id="4-3-虚拟内存"><a href="#4-3-虚拟内存" class="headerlink" title="4.3 虚拟内存"></a>4.3 虚拟内存</h3><blockquote><ul><li>处理内存超载的最重要技术</li><li>内存管理单元(MMU)完成地址映射</li><li>页表:放在内存中,把虚拟页面映射为页框,即给出虚拟地址与物理地址的映射关系<ul><li>有多个页表项组成</li><li>每一个页面对应一个页表项</li></ul></li><li>页框:内存</li><li>页面:进程</li></ul></blockquote><h3 id="4-4-页面置换算法"><a href="#4-4-页面置换算法" class="headerlink" title="4.4 页面置换算法"></a>4.4 页面置换算法</h3><blockquote><ul><li>一般原则:<ul><li>已修改的页面必须首先保存(<strong>需要写回磁盘</strong>),未修改的页面直接由新页面覆盖(<strong>已经是最新的</strong>)</li><li>尽可能不要选择频繁使用的页面置换出内存,很可能很短时间内又要被调入内存</li></ul></li></ul></blockquote><blockquote><ul><li>物理块(页框)分配与置换策略<ul><li><strong>固定</strong>分配<strong>局部</strong>置换<ul><li>进程占据的内存页框数<strong>不变</strong></li><li>缺页时,只能与该进程在内存的页面置换</li><li>由进程类型、程序员确定每个进程分得的物理块数</li><li>但分配时,难以确定某个进程的页框数</li></ul></li><li><strong>可变</strong>分配<strong>全局</strong>置换<ul><li>最易实现,空闲页框由OS管理——空闲物理块队列</li><li>先为进程分配一定物理块</li><li>缺页时,从系统空闲物理块队列中取一块</li><li>空闲物理块取完时,与内存中的一页置换</li></ul></li><li><strong>可变</strong>分配<strong>局部</strong>置换<ul><li>先为进程分配一定物理块</li><li>缺页时,只与该进程在内存的一页置换</li><li>缺页率较高时调整页框数</li></ul></li></ul></li><li><strong>固定</strong>和<strong>可变</strong>:即进程占据的页框数是否会发生变化</li><li><strong>全局</strong>和<strong>局部</strong>:即进程缺页时置换只在该内存中进行叫做<strong>局部</strong>置换,可能置换其他进程的页面叫做<strong>全局</strong>置换</li></ul></blockquote><blockquote><ul><li><strong>最优</strong>(OPT)</li><li><strong>最近未使用</strong>(NRU[not recently used]):当页面被访问或修改时,设置访问位<strong>R</strong>和修改位<strong>M</strong><ul><li>0:R = 0, M = 0;</li><li>1:R = 0, M = 1;</li><li>2:R = 1, M = 0;</li><li>3:R = 1, M = 1(时钟中断R清0,变成1类);</li><li>从编号最小的开始淘汰,即<strong>没访问没修改–>修改了没访问–>访问了没修改–>访问了修改了</strong></li></ul></li><li><strong>先进先出</strong>(FIFO):OS维护一个内存中页面的链表,按照进入内存顺序组织,置换<ul><li>FIFO的<strong>Belady</strong>现象:当分配给进程的物理页面数增加时,缺页次数反而增加</li></ul></li><li><strong>第二次机会</strong>(SCR)<ul><li>对FIFO算法的修改,置换时先看访问位<strong>R</strong>是否为0,为0置换,否则置为0放入队尾</li></ul></li><li><strong>时钟</strong>(clock)<ul><li>避免SCR中移动页面,组织一个环形链表,检查表针指向的页面,R为0置换,否则指针后移</li></ul></li><li><strong>最近最少使用</strong>(LRU):选择最近最少使用的页淘汰<ul><li>根据过去预测未来</li><li>最近最多使用在表头,最近最少使用在表尾</li><li>每次访问时更新链表</li></ul></li><li><strong>最不常用(NFU)</strong>:将每个页面和计时器相关联,初值为0,每次时钟中断将R位加到计数器,淘汰计数器值最小的<ul><li>和LRU粗略近似</li></ul></li></ul></blockquote><h3 id="4-5-分页系统中的设计问题"><a href="#4-5-分页系统中的设计问题" class="headerlink" title="4.5 分页系统中的设计问题"></a>4.5 分页系统中的设计问题</h3><blockquote><ul><li>…</li><li>页面大小:<ul><li>小页面<ul><li>更少的内部碎片</li><li>更加灵活,适合各种程序结构和数据段</li><li>减少内存中局部执行没用的程序</li><li><strong>缺点</strong>:进程需要更多页面,更大页表</li></ul></li><li>大页面<ul><li>页表小</li><li><strong>缺点</strong>:小页面的优点</li></ul></li></ul></li></ul></blockquote><h3 id="4-7-分段"><a href="#4-7-分段" class="headerlink" title="4.7 分段"></a>4.7 分段</h3><blockquote><ul><li>分页缺点:虚地址空间是一维的,不利于动态增长</li><li>解决:根据逻辑关系分段</li><li><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20220706232004.png" alt="20210707214634"></li></ul></blockquote><h2 id="第五章-文件系统"><a href="#第五章-文件系统" class="headerlink" title="第五章 文件系统"></a>第五章 文件系统</h2><h3 id="序言-1"><a href="#序言-1" class="headerlink" title="序言"></a>序言</h3><blockquote><ul><li>文件的根本目标:长期存储信息</li><li>文件的存储介质:磁盘、光盘、磁带等</li><li>文件<ul><li>文件是进程创建的信息逻辑单元</li><li>文件是磁盘上的一种地址空间</li></ul></li><li>文件系统<ul><li>文件是由操作系统管理,操作系统中处理文件的部分叫做文件系统</li></ul></li></ul></blockquote><h3 id="5-1-文件"><a href="#5-1-文件" class="headerlink" title="5.1 文件"></a>5.1 文件</h3><h4 id="5-1-1-文件命名"><a href="#5-1-1-文件命名" class="headerlink" title="5.1.1 文件命名"></a>5.1.1 文件命名</h4><blockquote><ul><li>文件名.扩展名</li></ul></blockquote><h4 id="5-1-2-文件结构"><a href="#5-1-2-文件结构" class="headerlink" title="5.1.2 文件结构"></a>5.1.2 文件结构</h4><blockquote><ul><li>字节序列</li><li>记录序列</li><li>数</li></ul></blockquote><h4 id="5-1-3-文件类型"><a href="#5-1-3-文件类型" class="headerlink" title="5.1.3 文件类型"></a>5.1.3 文件类型</h4><blockquote><ul><li>普通文件<ul><li>ASCII文件</li><li>二进制文件</li><li>Unicode文件</li></ul></li><li>目录<ul><li>管理文件系统结构的系统文件</li></ul></li></ul></blockquote><h4 id="5-1-4-文件存取"><a href="#5-1-4-文件存取" class="headerlink" title="5.1.4 文件存取"></a>5.1.4 文件存取</h4><blockquote><ul><li>顺序存取</li><li>随机存取</li></ul></blockquote><h4 id="5-1-5-文件属性"><a href="#5-1-5-文件属性" class="headerlink" title="5.1.5 文件属性"></a>5.1.5 文件属性</h4><blockquote><ul><li>除了文件名和数据,还会保存<strong>文件属性(元数据)</strong></li></ul></blockquote><h3 id="5-2-目录"><a href="#5-2-目录" class="headerlink" title="5.2 目录"></a>5.2 目录</h3><blockquote><ul><li>目录或文件夹管理的基本要求<ul><li>实现”按名存取“</li><li>提高对目录的检索速度</li><li>文件共享</li><li>允许文件重名</li></ul></li><li>目录项<ul><li>目录的基本组成元素,文件控制块(FCB)</li><li>内容:存放了管理文件所需的所有有关信息,包含文件名、存储地址等基本信息</li><li>目录项是文件存在的标志:目录项和文件一 一对应</li></ul></li><li>文件目录(目录)<ul><li>把文件的目录项(FCB)组织在一起,就构成了文件目录</li></ul></li></ul></blockquote><h3 id="5-3-文件系统的实现"><a href="#5-3-文件系统的实现" class="headerlink" title="5.3 文件系统的实现"></a>5.3 文件系统的实现</h3><blockquote><p> <img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20220706231938.png" alt="20210707225439"></p><p> <img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20220706231934.png" alt="20210707225443"></p></blockquote><h4 id="文件存储的实现"><a href="#文件存储的实现" class="headerlink" title="文件存储的实现"></a>文件存储的实现</h4><blockquote><ul><li>外存三种主要分配方式<ul><li>连续分配——顺序式文件结构</li><li>链接分配——链接式文件结构</li><li>索引分配——索引式文件结构</li></ul></li></ul></blockquote><h4 id="显示链接"><a href="#显示链接" class="headerlink" title="显示链接"></a>显示链接</h4><blockquote><ul><li>这样的表整磁盘仅设一张,称为<strong>文件分配表(FAT)</strong></li><li></li></ul></blockquote><h3 id="5-4-文件系统管理与优化"><a href="#5-4-文件系统管理与优化" class="headerlink" title="5.4 文件系统管理与优化"></a>5.4 文件系统管理与优化</h3><h4 id="磁盘空间管理"><a href="#磁盘空间管理" class="headerlink" title="磁盘空间管理"></a>磁盘空间管理</h4><blockquote><ul><li>盘块<ul><li>盘块大小<ul><li>太大,小文件浪费空间</li><li>太效,大文件访问效率低</li></ul></li><li>记录空闲盘块</li><li>盘块配额</li></ul></li><li>磁盘访问时间 = 寻道时间 + 旋转延迟时间 + 传输时间</li><li><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20220706231922.png" alt="20210707231453"></li></ul></blockquote><h2 id="第六章-IO"><a href="#第六章-IO" class="headerlink" title="第六章 IO"></a>第六章 IO</h2><blockquote><ul><li>IO层次<ul><li>用户级I/O软件</li><li>与设备无关的OS软件</li><li>设备驱动程序</li><li>中断处理程序</li><li>硬件</li></ul></li><li><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20220706231908.png" alt="20210707234756"></li></ul></blockquote><h4 id="磁盘调度算法"><a href="#磁盘调度算法" class="headerlink" title="磁盘调度算法"></a>磁盘调度算法</h4><blockquote><ul><li><strong>先来先服务(FCFS)</strong></li><li><strong>最短寻道优先(SSF)</strong></li><li><strong>电梯算法(SCAN)</strong></li><li><strong>循环扫描(S-SCAN)</strong></li></ul></blockquote>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> 概述 </tag>
<tag> 操作系统 </tag>
</tags>
</entry>
<entry>
<title>计算机网络笔记</title>
<link href="/2022/03/29/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
<url>/2022/03/29/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/</url>
<content type="html"><![CDATA[<h1 id="计算机网络"><a href="#计算机网络" class="headerlink" title="计算机网络"></a>计算机网络</h1><h2 id="第一章-概述"><a href="#第一章-概述" class="headerlink" title="第一章 概述"></a>第一章 概述</h2><h3 id="互联网两个重要基本特点"><a href="#互联网两个重要基本特点" class="headerlink" title="互联网两个重要基本特点"></a>互联网两个重要基本特点</h3><blockquote><ul><li>连通性</li><li>共享(资源共享)<ul><li>信息共享</li><li>软件共享</li><li>硬件共享</li></ul></li></ul></blockquote><h3 id="RFC"><a href="#RFC" class="headerlink" title="RFC"></a>RFC</h3><blockquote><p> <code>Request For Comment</code>请求评论</p><ul><li>互联网草案</li><li>建议标准 –> RFC文档</li><li>互联网标准</li></ul></blockquote><h3 id="互联网的边缘部分"><a href="#互联网的边缘部分" class="headerlink" title="互联网的边缘部分"></a>互联网的边缘部分</h3><blockquote><p> 边缘部分的主机又称为**<code>端系统</code>**</p><p> 端系统之间的通信方式</p><ul><li>客户/服务器(C/S)方式</li><li>对等(P2P)方式</li></ul></blockquote><h3 id="互联网的核心部分"><a href="#互联网的核心部分" class="headerlink" title="互联网的核心部分"></a>互联网的核心部分</h3><blockquote><p> 在网络核心部分起特殊作用的是**<code>路由器(router)</code><strong>,实现</strong>分组转发**</p><p> 采用<strong>网状拓扑结构</strong></p></blockquote><h4 id="电路交换"><a href="#电路交换" class="headerlink" title="电路交换"></a>电路交换</h4><blockquote><ol><li>建立连接</li><li>同话</li><li>释放连接</li></ol><p> 缺点:线路的传输效率低</p></blockquote><h4 id="分组交换"><a href="#分组交换" class="headerlink" title="分组交换"></a>分组交换</h4><blockquote><p> 分组交换采用<strong>存储转发</strong>技术</p><p> <strong>分组(packet)</strong> – <strong>包</strong></p></blockquote><h3 id="计算机网络的性能"><a href="#计算机网络的性能" class="headerlink" title="计算机网络的性能"></a>计算机网络的性能</h3><blockquote><ol><li>速率</li><li>带宽</li><li>吞吐量</li><li>时延</li><li>时延带宽积</li><li>往返时间</li></ol></blockquote><h3 id="协议与划分层次"><a href="#协议与划分层次" class="headerlink" title="协议与划分层次"></a>协议与划分层次</h3><h4 id="协议"><a href="#协议" class="headerlink" title="协议"></a>协议</h4><blockquote><p> 为进行网络中的数据交换而建立的规则、标准或约定称为<strong>网络协议</strong></p><ul><li>语法</li><li>语义</li><li>同步(时序)</li></ul></blockquote><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153520.png" alt="计算机网络体系结构"></p><blockquote><ul><li>应用层<ul><li>通过应用进程间的交互来完成特定网络应用。</li><li>定义的是<strong>应用进程间通信和交互的规则</strong></li></ul></li><li>运输层<ul><li>负责向两台主机中进程之间的通信提供通用的数据传输服务</li><li><strong>传输控制协议TCP</strong></li><li><strong>用户数据报协议UDP</strong></li></ul></li><li>网络层<ul><li>负责为分组交换网上的不同主机提供通信服务</li><li><strong>IP数据报 – 分组</strong></li></ul></li><li>数据链路层<ul><li><strong>帧(frame)</strong></li></ul></li><li>物理层<ul><li><strong>比特(bit)</strong></li></ul></li></ul></blockquote><hr><h2 id="物理层"><a href="#物理层" class="headerlink" title="物理层"></a>物理层</h2><h3 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h3><blockquote><p> 物理层的协议也被称为<strong>规程(procedure)</strong></p><p> 物理层的主要任务</p><ul><li>机械特性</li><li>电气特性</li><li>功能特性</li><li>过程特性</li></ul></blockquote><h3 id="信道"><a href="#信道" class="headerlink" title="信道"></a>信道</h3><blockquote><ul><li><strong>单工通信</strong>:单向通信</li><li><strong>半双工通信</strong>:双向交替通信</li><li><strong>全双工通信</strong>:双向同时通信</li></ul></blockquote><h3 id="调制"><a href="#调制" class="headerlink" title="调制"></a>调制</h3><blockquote><ol><li><strong>基带调制</strong>:仅仅对基带信号的波形进行变换 – <strong>编码(coding)</strong></li><li><strong>带通调制</strong>:使用**载波(carrier)**进行调制,将频率提高并转换为模拟信号。</li></ol></blockquote><h4 id="常用编码方式"><a href="#常用编码方式" class="headerlink" title="常用编码方式"></a>常用编码方式</h4><blockquote><ul><li><strong>不归零制</strong>:正电平代表1,负电平代表0</li><li><strong>归零制</strong>:正脉冲代表1,负脉冲代表0</li><li><strong>曼彻斯特编码</strong>:位周期中心向下跳表示1,向上跳表示0</li><li><strong>差分曼彻斯特</strong>:在每一位的中心都有跳变。位开始边界有跳变代表0,没有代表1</li></ul></blockquote><h4 id="基本带通调制方法"><a href="#基本带通调制方法" class="headerlink" title="基本带通调制方法"></a>基本带通调制方法</h4><blockquote><ul><li><strong>调幅(AM)</strong></li><li><strong>调频(FM)</strong></li><li><strong>调相(PM)</strong></li><li><strong>正交振幅调制QAM</strong></li></ul></blockquote><h3 id="信道的极限容量"><a href="#信道的极限容量" class="headerlink" title="信道的极限容量"></a>信道的极限容量</h3><h4 id="信噪比"><a href="#信噪比" class="headerlink" title="信噪比"></a>信噪比</h4><blockquote><p> 信号的平均功率和噪声的平均功率之比:<strong>S/N</strong>,用分贝(dB)做单位</p><p> <img src="https://gitee.com/xunle1/drawing-bed/raw/master/typora%5C/20211226221220.png" alt="信噪比"></p><p> <strong>香农公式</strong></p><p> <img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153526.png" alt="香农公式"></p><p> W为信道的带宽(Hz),S为信道内所传信号的平均功率,N为信道内部高斯噪声功率</p><p> 香农公式表明:<strong>信道的带宽或信道中的信噪比越大,信息的极限传输速率就越高</strong></p></blockquote><h3 id="传输媒体"><a href="#传输媒体" class="headerlink" title="传输媒体"></a>传输媒体</h3><blockquote><ul><li>双绞线</li><li>同轴电缆</li><li>光缆</li></ul></blockquote><h3 id="码分复用"><a href="#码分复用" class="headerlink" title="码分复用"></a>码分复用</h3><hr><h2 id="网络层"><a href="#网络层" class="headerlink" title="网络层"></a>网络层</h2><h3 id="划分子网"><a href="#划分子网" class="headerlink" title="划分子网"></a>划分子网</h3><blockquote><ul><li>IP 地址利用率有时很低:A 类,B 类</li><li></li></ul></blockquote><h3 id="构造超网"><a href="#构造超网" class="headerlink" title="构造超网"></a>构造超网</h3><h2 id="传输层"><a href="#传输层" class="headerlink" title="传输层"></a>传输层</h2><h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><h4 id="应用层协议主要使用的传输层协议"><a href="#应用层协议主要使用的传输层协议" class="headerlink" title="应用层协议主要使用的传输层协议"></a>应用层协议主要使用的传输层协议</h4><blockquote><ul><li>TCP<ul><li>SMTP、TELNET(远程终端协议)、HTTP、FTP</li></ul></li><li>UDP<ul><li>DNS、TFTP(简单文件传送协议)、RIP(路由信息协议)、DHCP(动态主机配置协议)、SNMP(简单网络管理协议)、NFS(w网络文件系统)</li></ul></li></ul></blockquote><h4 id="应用层使用的传输层协议"><a href="#应用层使用的传输层协议" class="headerlink" title="应用层使用的传输层协议"></a>应用层使用的传输层协议</h4><blockquote><ul><li>TCP:<ul><li>SMTP、TELNET、HTTP、FTP</li></ul></li><li>UDP:<ul><li>DNS、TFTP、RIP、DHCP、SNMP、NFS、IP电话、流式多媒体</li></ul></li></ul></blockquote><h4 id="复用分用"><a href="#复用分用" class="headerlink" title="复用分用"></a>复用分用</h4><blockquote><ul><li><p><strong>复用</strong></p><p>应用层所有的应用进程都可以通过传输层再传到网络层</p></li><li><p><strong>分用</strong></p><p>传输层从网络层收到发送给各应用进程的数据后,必须分别交付指明的各应用进程</p></li></ul></blockquote><h4 id="端口号"><a href="#端口号" class="headerlink" title="端口号"></a>端口号</h4><blockquote><p> TCP/IP的传输层用一个<strong>16</strong>位端口号来表指一个端口,允许有<strong>65535</strong>个不同端口号</p><ol><li><p>服务器端使用的端口号</p><ul><li><p><strong>熟知端口号</strong>:0~1023</p><table><thead><tr><th align="center">应用程序</th><th align="center">FTP</th><th align="center">TELNET</th><th align="center">SMTP</th><th align="center">DNS</th><th align="center">TFTP</th><th align="center">HTTP</th><th align="center">SNMP</th><th align="center">HTTPS</th></tr></thead><tbody><tr><td align="center">熟知端口号</td><td align="center">21</td><td align="center">23</td><td align="center">25</td><td align="center">53</td><td align="center">69</td><td align="center">80</td><td align="center">161</td><td align="center">443</td></tr></tbody></table></li><li><p><strong>登记端口号</strong>:1024~49151</p></li></ul></li><li><p>客户端使用的端口号</p><ul><li>49152~65535</li><li>又称为短暂端口号</li></ul></li></ol></blockquote><hr><h3 id="用户数据报协议UDP"><a href="#用户数据报协议UDP" class="headerlink" title="用户数据报协议UDP"></a>用户数据报协议UDP</h3><h4 id="概述-1"><a href="#概述-1" class="headerlink" title="概述"></a>概述</h4><blockquote><p> UDP只在IP的数据报服务上增加了很少一点功能:<strong>复用分用、差错检测</strong></p><ol><li>UDP是无连接的,即发送数据之前不需要建立连接,不需要使用套接字(ip:port)</li><li>UDP使用<strong>尽最大努力交付</strong>,即不保证可靠交付,因此主机不需要维护复杂的链接状态表</li><li>UDP是<strong>面向报文的</strong>,即应用层交付多少报文,UDP在添加首部后直接交付IP层</li><li>UDP<strong>没有拥塞控制</strong></li><li>UDP支持一对一、一对多、多对一和多对多的交互通信</li><li>UDP的首部开销小</li></ol></blockquote><h4 id="UDP的首部格式"><a href="#UDP的首部格式" class="headerlink" title="UDP的首部格式"></a>UDP的首部格式</h4><blockquote><p> UDP的首部只有<strong>8</strong>个字节,每两个字节一个字段</p><ol><li><strong>源端口</strong></li><li><strong>目的端口</strong></li><li><strong>长度</strong>:其最小值是<strong>8</strong>(仅包含首部)</li><li><strong>检验和</strong>:检测数据报是否有差错</li></ol></blockquote><h4 id="检验和"><a href="#检验和" class="headerlink" title="检验和"></a>检验和</h4><blockquote><p> 在首部之前添加<strong>12</strong>个字节的<strong>伪首部</strong>,</p></blockquote><hr><h3 id="传输控制协议TCP"><a href="#传输控制协议TCP" class="headerlink" title="传输控制协议TCP"></a>传输控制协议TCP</h3><h4 id="概述-2"><a href="#概述-2" class="headerlink" title="概述"></a>概述</h4><blockquote><ol><li>TCP是<strong>面向连接</strong>的传输层协议,即传输数据前要建立TCP连接,之后要释放TCP连接</li><li>TCP连接只有两个端点</li><li>TCP提供<strong>可靠交付</strong>的服务:<strong>无差错、不丢失、不重复、按序到达</strong></li><li>TCP提供<strong>全双工</strong>通信</li><li><strong>面向字节流</strong></li></ol></blockquote><h4 id="可靠传输的工作原理"><a href="#可靠传输的工作原理" class="headerlink" title="可靠传输的工作原理"></a>可靠传输的工作原理</h4><blockquote><p> 理想的传输条件有以下两个特点:</p><ul><li>传输信道不产生差错</li><li>接收方来得及处理收到的数据</li></ul><p> 但实际网络传输不是理想状态,故:</p><ul><li>可以在出现差错时让发送方重传出现差错的数据</li><li>在接收方来不及处理数据时,及时告诉发送方降低发送速度</li></ul></blockquote><h4 id="停止等待协议"><a href="#停止等待协议" class="headerlink" title="停止等待协议"></a>停止等待协议</h4><blockquote><p> 每发送完一个分组就停止发送,等待对方的确认,在收到确认后再发送下一个分组</p><ul><li>出现差错使用超时重传,超时计时器的重传时间应当比数据再分组传输的平均往返时间更长一点</li><li>确认丢失或迟到时,发送方会重传分组,接收方收到后丢弃重复分组并返回确认。</li><li>此即为<strong>自动重传协议ARQ</strong></li><li>信道利用率低</li></ul></blockquote><h4 id="连续ARQ协议"><a href="#连续ARQ协议" class="headerlink" title="连续ARQ协议"></a>连续ARQ协议</h4><h4 id="TCP的首部格式"><a href="#TCP的首部格式" class="headerlink" title="TCP的首部格式"></a>TCP的首部格式</h4><blockquote><p> 首部固定<strong>20</strong>字节,后面有 4n 字节是根据需要而增加的选项</p><ol><li>源端口和目的端口,<strong>4</strong>字节</li><li>序号,<strong>4</strong>字节。范围是[0,2^32 - 1]。该序号为报文段的数据的第一个字节的序号(面向字节流)</li><li>确认号,<strong>4</strong>字节。期望收到对方下一个报文段的第一个数据字节的序号</li><li>数据偏移,<strong>1</strong>字节(4位)。</li><li>保留,<strong>6</strong>位。</li><li>URG ACK PSH RET SYN FIN,<strong>6</strong>位。</li><li>窗口,<strong>2</strong>字节。</li><li>检验和,<strong>2</strong>字节。</li><li>紧急指针,<strong>2</strong>字节。</li><li>选项,长度可变,最长可达<strong>40</strong>字节。填充字段是为了使整个TCP首部长度使<strong>4</strong>字节的整数倍</li></ol></blockquote><h4 id="TCP的流量控制"><a href="#TCP的流量控制" class="headerlink" title="TCP的流量控制"></a>TCP的流量控制</h4><blockquote><p> 设A向B发送数据,在连接建立时B向A发送<strong>接收窗口大小 rwnd = xx</strong></p><p> TCP为每一个连接设有一个<strong>持续计时器</strong>,只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器,时间到了就发送一个零窗口<strong>探测报文段</strong>(仅携带1字节的数据)。</p><p> <strong>糊涂窗口综合征</strong>:接收方在缓存刚有一点小空间的时候就急忙把窗口大小信息发送给发送方</p></blockquote><h4 id="TCP的拥塞控制"><a href="#TCP的拥塞控制" class="headerlink" title="TCP的拥塞控制"></a>TCP的拥塞控制</h4><blockquote><p> TCP拥塞控制的算法有四种:<strong>慢开始、拥塞避免、快重传、快恢复</strong></p><p> <strong>慢开始:</strong></p><ul><li><p>由小到大逐渐增大拥塞窗口数值</p></li><li><table><thead><tr><th align="center">SMSS数值</th><th align="center">大于2190</th><th align="center">小于2190大于1095</th><th align="center">小于1095</th></tr></thead><tbody><tr><td align="center">窗口大小</td><td align="center">2 * SMSS</td><td align="center">3 * SMSS</td><td align="center">4 * SMSS</td></tr></tbody></table></li><li><p>每收到一个对新的报文段的确认,拥塞窗口增加最多一个SMSS。</p></li><li><p>拥塞窗口每次增加量 cwnd = min(N, SMSS),N为确认报文段所确认的字节数</p></li></ul><p> <strong>拥塞避免:</strong></p><ul><li>为了防止 cwnd 增长过大,还需要设置一个**慢开始门限(ssthresh)**。</li><li>当 cwnd < ssthresh 时,使用慢开始;当 cwnd > ssthresh 时改用拥塞避免;相等时两者皆可</li><li>拥塞避免算法是让 cwnd 使用**加法增大(Additive Increase)**,即经过一个 RTT 后 cwnd + 1</li><li>当网络出现超时的时候,调整门限 ssthresh = cwnd / 2,同时设置 cwnd = 1,进入慢开始阶段</li></ul><p> <strong>快重传:</strong></p><ul><li>采用快重传可以让发送方<strong>尽早发现个别报文段的丢失</strong></li><li>接收方即使收到了失序的报文段,也要立即发出对已收到报文段的确认。</li><li>当发送方收到**连续三个对同一报文段的重复确认(3-ACK)**时,立即进行重传</li></ul><p> <strong>快恢复:</strong></p><ul><li>当发送方知道只是丢失了个别报文段时,执行快恢复算法</li><li>设置 ssthresh = cwnd / 2,cwnd = ssthresh,并开始执行拥塞避免算法</li></ul></blockquote><h4 id="TCP的连接建立"><a href="#TCP的连接建立" class="headerlink" title="TCP的连接建立"></a>TCP的连接建立</h4><blockquote><p> 设 A 是客户主机,B 是服务主机。发送数据TCP建立连接过程要经过<strong>三次握手</strong>,即 A 和 B 之间交换三个TCP报文段</p><ul><li>B 的TCP进程创建传输控制快 <strong>TCB</strong>,服务器从 <strong>CLOSED</strong>到 <strong>LISTEN(收听)</strong> 状态</li><li>A 的客户进程也创建TCB,向 B 发出连接建立请求:<strong>SYN = 1, seq = x</strong>,消耗一个序号。此时 A 进入 **SYN-SENT(同步已发送)**状态</li><li>B 收到连接建立请求报文段后,如同意建立连接,则向 A 发送确认:<strong>SYN = 1, ACK = 1, ack = x + 1, seq = y</strong>,同样消耗一个序号。此时 B 进入 <strong>SYN-RCVD(同步收到)</strong> 状态</li><li>A 收到 B 的确认后,还要向 B 发送确认:<strong>ACK = 1, seq = x + 1, ack = y + 1</strong>,ACK 报文段可以携带数据,如果携带则不消耗序号。此时 A 进入 <strong>ESTABLISHED(连接已建立)</strong> 状态</li><li>B 收到 A 的确认后,也进入 <strong>ESTABLISHED</strong> 状态</li></ul><p> <img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153536.png" alt="使用三报文握手建立TCP连接"></p><p> 为什么不使用两次握手?</p><ul><li>如果 A 在向 B 发送连接建立请求时,第一次请求在网络滞留,第二次发送成功建立,在释放连接后,第一次请求又到达 B。如果采用两次握手,B 收到连接建立请求后就进入 <strong>ESTABLISHED</strong> 状态,浪费了 B 的资源</li><li>采用三次握手 A 就不会理睬 B 发送的数据</li></ul><p> 为什么不适用四次握手?</p><ul><li>三次握手足够建立连接</li></ul></blockquote><h4 id="TCP的连接释放"><a href="#TCP的连接释放" class="headerlink" title="TCP的连接释放"></a>TCP的连接释放</h4><blockquote><p> 设 A 是客户主机,B 是服务主机。TCP的连接释放需要经过<strong>四次挥手</strong>过程。</p><ul><li>假设 A 先向 B 发送连接释放报文段,并停止发送数据:<strong>FIN = 1, seq = u</strong>,u 是发送的最后一个字节的序号加1,此时 A 进入 **FIN-WAIT-1(终止等待1) **状态,等待 B 的确认。</li><li>B 收到连接释放请求后发出确认:<strong>ACK = 1, ack = u + 1, seq = v</strong>,v 等于 B 发送的最后一个字节的序号加1。此时 B 进入 <strong>CLOSE-WAIT(关闭等待)</strong> 状态,此时的 TCP 连接处于<strong>半关闭</strong> 状态,即 A 已经没有数据要发送,如果 B 要发送数据,A 仍要接受。</li><li>A 收到 B 的确认后,进入 <strong>FIN-WAIT-2(终止等待2)</strong> 状态,等待 B 发出的连接释放报文段。</li><li>若 B 已经没有要发送的数据,则应用进程通知 TCP 释放连接:<strong>FIN = 1, ACK = 1, ack = u + 1, seq = w</strong>,此时 B 进入 <strong>LAST-ACK(最后确认)</strong> 状态。</li><li>A 在收到 B 的连接释放报文段后,对此发出确认:<strong>ACK = 1, seq = u + 1, ack = w + 1</strong>,前面 FIN 消耗了一个序号,此时 A 进入 <strong>TIME-WAIT(时间等待)</strong> 状态,经过<strong>时间等待计时器</strong>设置的时间 <strong>2MSL</strong> 后,A 进入 <strong>CLOSED</strong> 状态</li><li>B 收到 A 的确认后进入 <strong>CLOSED</strong> 状态。</li></ul><p> <img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153541.png" alt="TCP连接释放过程"></p><p> 为什么要等待 <strong>2MSL(最长报文段寿命)</strong></p></blockquote><hr><h2 id="应用层"><a href="#应用层" class="headerlink" title="应用层"></a>应用层</h2><h3 id="域名系统DNS"><a href="#域名系统DNS" class="headerlink" title="域名系统DNS"></a>域名系统DNS</h3><h4 id="概述-3"><a href="#概述-3" class="headerlink" title="概述"></a>概述</h4><blockquote><p> 域名系统DNS(Domain Name System)能够把互联网上的主机名字转换为IP地址</p></blockquote><h4 id="域名服务器"><a href="#域名服务器" class="headerlink" title="域名服务器"></a>域名服务器</h4><blockquote><ul><li>根域名服务器</li><li>顶级域名服务器</li><li>权限域名服务器</li><li>本地域名服务器</li></ul></blockquote><h3 id="文件传送协议"><a href="#文件传送协议" class="headerlink" title="文件传送协议"></a>文件传送协议</h3><h3 id="HTTP协议"><a href="#HTTP协议" class="headerlink" title="HTTP协议"></a>HTTP协议</h3><h4 id="HTTP1-0和HTTP1-1"><a href="#HTTP1-0和HTTP1-1" class="headerlink" title="HTTP1.0和HTTP1.1"></a>HTTP1.0和HTTP1.1</h4><blockquote><p> HTTP协议本身是无连接的,建立在TCP连接上。</p><p> <strong>HTTP1.0</strong></p><ul><li>每请求一个文档就有两倍RTT的开销(建立TCP连接+请求响应)</li></ul><p> <strong>HTTP1.1</strong></p><p> HTTP/1.1使用<strong>持续链接</strong>,有<strong>非流水线方式</strong>和<strong>流水线方式</strong>两种工作方式</p><ul><li>非流水线方式<ul><li>节省了建立TCP连接的开销</li><li>收到前一个的响应后才返回下一个请求</li><li>当服务器发送一个响应后,TCP连接处于空闲状态,浪费服务器资源</li></ul></li><li>流水线方式<ul><li>客户在收到响应报文前就能够接着发送新的请求报文</li><li>所有对象只需花费一个RTT时间</li></ul></li></ul></blockquote><h4 id="报文结构"><a href="#报文结构" class="headerlink" title="报文结构"></a>报文结构</h4><blockquote><p> <strong>请求报文</strong></p><ul><li>请求行<ul><li>方法 URL HTTP版本</li></ul></li><li>首部行</li><li>空行</li><li>请求体</li></ul><p> <strong>响应报文</strong></p><ul><li>状态行<ul><li>版本 状态码 短语</li></ul></li><li>首部行</li><li>空行</li><li>响应体</li></ul></blockquote><h4 id="状态码"><a href="#状态码" class="headerlink" title="状态码"></a>状态码</h4><blockquote><table><thead><tr><th align="left">状态码</th><th>短语</th><th>含义</th></tr></thead><tbody><tr><td align="left">200</td><td>OK</td><td>响应成功</td></tr><tr><td align="left">301</td><td>Moved Permanently</td><td>请求对象已被永久转移,新的URL定义在响应报文的首部行,客户端将自动获取</td></tr><tr><td align="left">400</td><td>Bad Request</td><td>通用的差错代码,请求不能被服务器理解。</td></tr><tr><td align="left">401</td><td>Unauthorized</td><td>未认证,缺乏相关权限</td></tr><tr><td align="left">402</td><td>Payment Required</td><td>保留,将来使用</td></tr><tr><td align="left">403</td><td>Forbidden</td><td>通用的差错代码,请求不能被服务器理解。</td></tr><tr><td align="left">404</td><td>Not Found</td><td>被请求的文档不在服务器上,有可能因为请求 URL 出错。</td></tr><tr><td align="left">405</td><td>Method Not Allowed</td><td>客户端中请求的方法被禁止,例如限制 POST 方式但使用了 GET 访问。</td></tr><tr><td align="left">500</td><td>Internal Server Error</td><td>客户端中请求的方法被禁止,例如限制 POST 方式但使用了 GET 访问。</td></tr><tr><td align="left">502</td><td>Bad Gateway</td><td>作为网关或代理工作的服务器尝试执行请求时,从远程服务器收到了一个无效响应。</td></tr></tbody></table></blockquote><h4 id="HTTPS"><a href="#HTTPS" class="headerlink" title="HTTPS"></a>HTTPS</h4><blockquote><p> HTTP over SSL</p><p> 客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。</p><p> 所以这里就需要借助第三方权威机构 CA (数字证书认证机构),将<strong>「服务器公钥放在数字证书」</strong>(由数字证书认证机构颁发)中,只要证书是可信的,公钥就是可信的。</p></blockquote><blockquote><p> <strong>SSL连接过程</strong> </p><ol><li><p>Client发起一个HTTPS的请求</p></li><li><p>Server把事先配置好的公钥证书返回给客户端。</p></li><li><p>Client验证公钥证书:比如是否在有效期内,证书的用途是不是匹配Client请求的站点,是不是在CRL吊销列表里面,它的上一级证书是否有效,这是一个递归的过程,直到验证到根证书(操作系统内置的Root证书或者Client内置的Root证书),如果验证通过则继续,不通过则显示警告信息。</p></li><li><p>Client使用伪随机数生成器生成加密所使用的对称密钥,然后用证书的公钥加密这个对称密钥,发给Server。</p></li><li><p>Client使用伪随机数生成器生成加密所使用的对称密钥,然后用证书的公钥加密这个对称密钥,发给Server。</p></li><li><p>Server使用对称密钥加密明文内容A,发送给Client。</p></li><li><p>Client使用对称密钥解密响应的密文,得到明文内容A。<strong>SSL建立</strong></p></li><li><p>Client再次发起HTTPS的请求,使用对称密钥加密请求的明文内容B,然后Server使用对称密钥解密密文,得到明文内容B。</p></li></ol></blockquote>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> 计算机网络 </tag>
<tag> 概述 </tag>
</tags>
</entry>
<entry>
<title>Java 类加载过程</title>
<link href="/2022/03/28/Java%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B/"/>
<url>/2022/03/28/Java%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B/</url>
<content type="html"><![CDATA[<h1 id="Java类加载过程"><a href="#Java类加载过程" class="headerlink" title="Java类加载过程"></a>Java类加载过程</h1><p>Java中类的加载过程如下图:</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206154017.png" alt="类加载过程"></p><p>其中<strong>连接</strong>过程又分三步:<strong>验证–>准备–>解析</strong></p><blockquote><ul><li><strong>加载:</strong><ul><li>通过一个类的<strong>完全限定</strong>查找此类字节码文件,并利用字节码文件创建一个 Class 文件</li></ul></li><li><strong>验证:</strong><ul><li>确保 Class 文件字节流中包含的信息符合当前虚拟机要求,不会危害虚拟机安全。主要包括<strong>文件格式验证</strong>、<strong>元数据验证</strong>、<strong>字节码验证</strong>、<strong>符号引用验证</strong></li></ul></li><li><strong>准备:</strong><ul><li>为<strong>类变量</strong>分配内存并赋初值,此处不包括 final 修饰的变量</li></ul></li><li><strong>解析:</strong><ul><li>主要将常量池中的符号引用转变为直接引用</li></ul></li><li><strong>初始化:</strong><ul><li>若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化变量。</li></ul></li></ul></blockquote><hr><h2 id="双亲委派模型"><a href="#双亲委派模型" class="headerlink" title="双亲委派模型"></a>双亲委派模型</h2><p><strong>三层类加载器</strong></p><blockquote><ul><li>启动类加载器 BootStrap ClassLoader:负责加载存放在 \lib 目录或被 -Xbootclaspath 参数,启动类加载器无法被 Java 程序直接引用,用户在自定义类加载器时,需要给引导类加载器处理</li><li>扩展类加载器 Extension Classloader:负责加载 \lib\ext 目录或者被 java.ext.dirs 系统变量所指定的目录中的所有类库</li><li>应用程序类加载器 Application Classloader(也叫系统类加载器):负责加载用户类路径 ClassPath 上的所有类库</li></ul></blockquote><p><strong>两个 class 对象是否是同一个类对象</strong></p><blockquote><ul><li>类的全限定名,包括包名必须一致</li><li>加载这个类的 ClassLoader 必须相同,即 ClassLoader 实例对象</li></ul></blockquote><p><strong>双亲委派模型加载过程</strong></p><blockquote><ol><li>如果一个类加载器收到类加载请求,它首先不会自己尝试加载这个类,而是把请求委托到父类执行。</li><li>每一层次的类加载器都会委托其父类加载器去完成,最终传回最顶层的启动类加载器中。</li><li>只有当所有父类加载器都无法完成这个类的加载请求时,子类加载器才会进行加载</li></ol><p> <strong>作用</strong>:</p><ul><li>这样可以避免重复加载类,当父类已经加载了该类时,就没必要再加载一次</li><li>考虑到安全因素,双亲委派模型可以避免子类加载器加载已经被父类加载器加载过的类。</li></ul></blockquote><p><strong>破坏双亲委派模型</strong></p><blockquote><p> 为什么要破坏双亲委派模型?</p><ul><li>因为类加载器受到加载范围的限制,在某些情况下父类加载器无法加载到需要的文件,这是就需要打破双亲委派模型,委托子类加载器去加载 Class 文件</li></ul><p> <strong>怎么打破双亲委派模型</strong></p><ul><li>使用<strong>线程上下文类加载器(ContextClassLoader)</strong>加载类,一般 ContexClassLoader 默认为应用类加载器</li></ul><p> <strong>打破实例:</strong></p><ul><li><p><strong>JDBC</strong> 各种驱动的实现</p><ul><li>加载 Driver 接口实现类的时候,DriverManager 由启动类加载器加载,而实现类由服务提供商加载</li></ul><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153801.png" alt="image-20220317012236388"></p></li><li><p><strong>Tomcat</strong></p><ul><li>各个 webapps 需要隔离的环境,也需要一些共享的类</li><li>Tomcat 使用自定义类加载器去加载类,以免恶意或无意破坏</li><li>热部署</li></ul></li></ul></blockquote>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> JVM </tag>
<tag> 类加载 </tag>
</tags>
</entry>
<entry>
<title>WebSocket</title>
<link href="/2022/03/28/WebSocket/"/>
<url>/2022/03/28/WebSocket/</url>
<content type="html"><![CDATA[<h1 id="WebSocket"><a href="#WebSocket" class="headerlink" title="WebSocket"></a>WebSocket</h1><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p><strong>WebSocket</strong>是一种在单个<a href="https://baike.baidu.com/item/TCP">TCP</a>连接上进行<a href="https://baike.baidu.com/item/%E5%85%A8%E5%8F%8C%E5%B7%A5">全双工</a>通信的协议。<strong>HTTP</strong>协议有一个缺陷,即通信只能由客户端发起,如果服务器有状态变化,只能通过客户端<strong>轮询</strong>每隔一段时间给服务端发起请求,询问是否有新消息。而<strong>WebSocket</strong>协议中,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息。</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153228.png" alt="img"></p><p><strong>WebSocket</strong>协议由两部分组成</p><ul><li>握手</li><li>数据传输</li></ul><hr><p><strong>握手</strong></p><p>握手是基于http协议的。</p><p>来自客户端的握手</p><figure class="highlight http"><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">GET</span> <span class="string">ws://localhost/char</span> <span class="meta">HTTP/1.1</span></span><br><span class="line"><span class="attribute">HOST</span><span class="punctuation">: </span>localhost</span><br><span class="line"><span class="attribute">Upgrade</span><span class="punctuation">: </span>websocket</span><br><span class="line"><span class="attribute">Connection</span><span class="punctuation">: </span>Upgrade</span><br><span class="line"><span class="attribute">Sec-WebSocket-key</span><span class="punctuation">: </span>key1</span><br><span class="line"><span class="attribute">Sec-WebSocket-Extensions</span><span class="punctuation">: </span>permessage-deflate</span><br><span class="line"><span class="attribute">Sec-WebSocket-Version</span><span class="punctuation">: </span>13</span><br></pre></td></tr></table></figure><p>来自服务器的握手</p><figure class="highlight http"><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="meta">HTTP/1.1</span> <span class="number">101</span> Switching Protocols</span><br><span class="line"><span class="attribute">Upgrade</span><span class="punctuation">: </span>webscocket</span><br><span class="line"><span class="attribute">Connection</span><span class="punctuation">: </span>Upgrade</span><br><span class="line"><span class="attribute">Sec-WebSocket-Accept</span><span class="punctuation">: </span>acc1</span><br><span class="line"><span class="attribute">Sec-WebSocket-Extensions</span><span class="punctuation">: </span>permessage-deflate</span><br></pre></td></tr></table></figure><p>字段说明</p><table><thead><tr><th>头名称</th><th>说明</th></tr></thead><tbody><tr><td>Connection: Upgrade</td><td>标识该HTTP请求是一个协议升级请求</td></tr><tr><td>Upgrade: websocket</td><td>协议升级为WebScoket协议</td></tr><tr><td>Sec-WbSocket-Versions: 13</td><td>客户端支持WebSocket的版本</td></tr><tr><td>Sec-WbSocket-key: xxx</td><td>客户端采用base64编码的24位随机字符序列,服务器接受客户端HTTP协议升级的证明。要求服务端响应一个对应的加密的Sec-WebSocket-Accept头信息作为应答</td></tr><tr><td>Sec-WbSocket-Extensions: permessage-deflate</td><td>协议拓展类型</td></tr></tbody></table><hr><h3 id="客户端(浏览器)实现"><a href="#客户端(浏览器)实现" class="headerlink" title="客户端(浏览器)实现"></a>客户端(浏览器)实现</h3><p><strong>WebSocket对象</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> ws = <span class="keyword">new</span> <span class="title class_">WebSocket</span>(url)</span><br></pre></td></tr></table></figure><blockquote><p> 通过上面的语句创建一个WebSocket对象,需要传入参数<code>url</code></p><p> <code>url</code>:ws://ip:prot/resources</p></blockquote><p><strong>WebSocket事件</strong></p><table><thead><tr><th>事件</th><th>事件处理程序</th><th>描述</th></tr></thead><tbody><tr><td>open</td><td>websocket.onopen</td><td>连接建立时触发</td></tr><tr><td>message</td><td>websocket.onmessage</td><td>客户端接收服务端数据时触发</td></tr><tr><td>error</td><td>websocket.onerror</td><td>通信发生错误时触发</td></tr><tr><td>close</td><td>websocket.onclose</td><td>连接关闭时触发</td></tr></tbody></table><p><strong>WebSocket方法</strong></p><table><thead><tr><th>方法</th><th>描述</th></tr></thead><tbody><tr><td>send()</td><td>使用连接发送数据</td></tr></tbody></table><hr><h3 id="服务端实现"><a href="#服务端实现" class="headerlink" title="服务端实现"></a>服务端实现</h3><p>Tomcat的7.0.5版本开始支持<strong>WebSocket</strong>协议,并且实现了<strong>Java WebSocket</strong>规范</p><p><strong>Java WebSocket</strong>应用有一系列的<strong>WebSocketEndpoint</strong>组成。<strong>EndPoint</strong>是一个Java对象,代表<strong>WebSocket</strong>连接的一端。</p><p>两种方式定义<strong>EndPoint</strong></p><ul><li>编程式:继承类<code>javax.websocket.EndPoint</code>并实现方法</li><li>注解式:定义一个POJO,并添加<code>@ServerEndpoint</code>相关注解</li></ul><p><strong>服务端接受客户端发送的数据</strong></p><blockquote><p> 通过为<code>Session</code>添加<code>MessageHandler</code>消息处理器来接受消息,当采用注解方式时,通过注解<code>@OnMessage</code>注解指定接受消息的方法。</p></blockquote><p><strong>服务端推送数据给客户端</strong></p><blockquote><p> 发送消息由<code>RemoteEndPoint</code>完成,其实例由<code>Session</code>维护。根据使用情况,可以通过<code>Session.getBasicRemote</code>获取发送同步消息的实例,调用<code>sendXxx()</code>方法发送消息;可以通过<code>Session.getAsyncRemote</code>获取发送异步消息的实例</p></blockquote>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> 协议 </tag>
<tag> 计算机网络 </tag>
</tags>
</entry>
<entry>
<title>Java 垃圾回收</title>
<link href="/2021/12/29/Java%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/"/>
<url>/2021/12/29/Java%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/</url>
<content type="html"><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Java虚拟机中,<strong>程序计数器</strong>、<strong>虚拟机栈</strong>、<strong>本地方法栈</strong>三个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而有条不紊地执行着入栈和出栈操作。每一个栈帧分配多少内存基本在编译期可以确定,因此这几个区域的内存分配和回收都具备确定性,不需要过多的考虑如何回收的问题,线程结束时内存自然跟着回收了。</p><p>但<strong>Java堆</strong>和<strong>方法区</strong>则不同:一个接口的多个实现类需要的内存可能不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有处于运行期才能知道程序会创建多少对象,这部分的内存的分配与回收是动态的。所以垃圾收集器关注的就是这部分内存该如何管理。</p><hr><h2 id="对象的消亡"><a href="#对象的消亡" class="headerlink" title="对象的消亡"></a>对象的消亡</h2><p>判断对象是否存活有两种算法,一是<strong>引用计数算法</strong>,另一个是<strong>可达性分析算法</strong></p><ul><li><p><strong>引用计数算法:</strong></p><p>简单来说,在对象中添加一个引用计数器,当有一个指向对象的引用时+1,当引用失效时-1,任何时刻计数器为零的对象就是不可能再被使用的。</p><p>但主流的Java虚拟机中并没有选用该算法,因为该算法需要大量额外处理才能保证正确地工作。举例来讲它很难解决<strong>对象之间相互引用</strong>的问题。</p></li><li><p><strong>可达性分析算法:</strong></p><p>该算法的基本思路是通过一系列称为“<strong>GC Roots</strong>”的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索路径称为“引用链”。如果一个对象到GC Roots间没有任何引用链相连,则证明此对象是不可能再被使用的。</p><p>在Java技术体系中,固定可作为GC Roots的对象包括以下几种:</p><ul><li>在虚拟机栈(栈帧中的本地变量表)中引用的对象,比如当前正在运行的方法中用到的参数、局部变量、临时变量等。</li><li>在方法区中类静态属性引用的对象,比如Java类的引用类型静态变量</li><li>在方法区中常量引用的对象,比如字符串常量池(String Table)里的引用</li><li>在本地方法栈中<code>Native</code>方法引用的对象</li><li>所有被同步锁持有的对象</li></ul></li></ul><h3 id="Java中引用的概念"><a href="#Java中引用的概念" class="headerlink" title="Java中引用的概念"></a>Java中引用的概念</h3><ul><li>JDK 1.2之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。</li><li>JDK 1.2之后,Java 对引用概念进行了扩充:将引用分为<strong>强引用</strong>、<strong>软引用</strong>、<strong>弱引用</strong>、<strong>虚引用</strong><ul><li>强引用</li><li>软引用</li><li>弱引用</li><li>虚引用</li></ul></li></ul><h3 id="如何判断一个常量是废弃常量?"><a href="#如何判断一个常量是废弃常量?" class="headerlink" title="如何判断一个常量是废弃常量?"></a>如何判断一个常量是废弃常量?</h3><p>当常量池中存在某一常量,而系统有没有任何一个对象引用这个常量,若在这时需要内存回收,而垃圾收集器判断确有必要的话,这个常量就会被清理出常量池。</p><h3 id="如何判断一个类是无用的类?"><a href="#如何判断一个类是无用的类?" class="headerlink" title="如何判断一个类是无用的类?"></a>如何判断一个类是无用的类?</h3><p>判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 <strong>“无用的类”</strong> :</p><ul><li>该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。</li><li>加载该类的 <code>ClassLoader</code> 已经被回收。</li><li>该类对应的 <code>java.lang.Class</code> 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。</li></ul><p>虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。</p><hr><h2 id="分代收集理论"><a href="#分代收集理论" class="headerlink" title="分代收集理论"></a>分代收集理论</h2><p>当今商业虚拟机的垃圾收集器,大多都遵循了“分代收集”的理论进行设计,它建立在两个分代假说上:</p><ul><li>弱分代假说:绝大多数对象都是朝生夕灭的</li><li>强分代假说:熬过越多次垃圾收集过程的对象越难以消亡</li></ul><p>据此,垃圾收集器通常将Java堆划分出不同的区域,将回收对象按照年龄分配到不同的区域存储。</p><p>在划分出不同的区域之后,垃圾收集器才可以针对某一区域或某些部分区域进行回收。</p><p>由此,划分出了不同的回收类型:</p><ul><li>新生代收集(<strong>Minor GC/Young GC</strong>):目标只是新生代的垃圾收集。</li><li>老年代收集(<strong>Major GC/Old GC</strong>):只有CMS收集器会有单独收集老年代的行为</li><li>混合收集(<strong>Mixed GC</strong>):目标是收集整个新生代及部分老年代,目前只有G1收集器有这个行为</li><li>整堆收集(<strong>Full GC</strong>):收集整个Java堆和方法区的垃圾收集。</li></ul><p>Java堆结构如图:</p><p><img src="https://gitee.com/xunle1/drawing-bed/raw/master/typora%5C/20210908214450.png" alt="Java堆结构"></p><hr><h2 id="垃圾收集算法"><a href="#垃圾收集算法" class="headerlink" title="垃圾收集算法"></a>垃圾收集算法</h2><p>Java虚拟机中不同的垃圾回收器有不同的垃圾收集算法。可简单分为三种</p><ul><li><strong>标记——清除:</strong>该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。这种垃圾收集算法会带来两个明显的问题:<ol><li><strong>效率问题</strong></li><li><strong>空间问题(标记清除后会产生大量不连续的碎片)</strong></li></ol></li><li><strong>标记——复制:</strong>为了解决效率问题,“标记-复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。</li><li><strong>标记——整理:</strong>根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。</li></ul><p>HotSpot 为什么要分为新生代和老年代?</p><ul><li>在新生代每次收集都有大量对象消亡,所以可以选择“<strong>标记——复制</strong>算法,提高效率</li><li>老年代对象存活几率较高,所以可以采用”<strong>标记——清除</strong>“和”<strong>标记——整理</strong>”算法进行收集</li></ul><hr><h2 id="垃圾收集器"><a href="#垃圾收集器" class="headerlink" title="垃圾收集器"></a>垃圾收集器</h2><ol><li><p><strong>Serial(串行)收集器</strong></p><p>单线程收集器,它只会使用一条垃圾收集线程去完成垃圾收集工作,并且在进行垃圾收集时需要暂停其他工作线程。</p><p><strong>新生代采用标记——复制算法,老年代采用标记——整理算法</strong></p><p>简单高效(与其他收集器的单线程相比)</p></li><li><p><strong>ParNew收集器</strong></p><p>ParNew收集器是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为和Serial收集器一样</p><p><strong>新生代采用标记——复制算法,老年代采用标记——整理算法</strong></p></li><li><p><strong>Parallel Scavenge收集器</strong></p><p>Parallel Scavenge收集器也是多线程收集器,和ParNew收集器的区别在于Parallel Scavenge收集器更多关注的是<strong>吞吐量</strong>,而CMS等垃圾收集器更多关注的是用户线程的停顿时间。这也是Jdk1.8默认垃圾收集器</p><p><strong>新生代采用标记——复制算法,老年代采用标记——整理算法</strong></p></li><li><p><strong>Serial Old收集器</strong></p><p>Serial收集器的老年代版本,一种用途是在JDK1.5以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案</p></li><li><p><strong>Parallel Old收集器</strong></p><p>Parallel Scavenge收集器的老年代版本</p></li><li><p><strong>CMS收集器</strong></p><p>CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器实现了<strong>让垃圾回收线程与用户线程同时工作</strong></p><p>CMS收集器采用的是“<strong>标记——清除</strong>”算法,运作过程分四个步骤:</p><ol><li><strong>初始标记</strong></li><li><strong>并发标记</strong></li><li><strong>重新标记</strong></li><li><strong>并发清除</strong></li></ol></li><li><p><strong>G1收集器</strong></p><p><strong>G1 (Garbage-First)</strong> 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.</p></li><li><p><strong>ZGC收集器</strong></p></li></ol><h3 id="CMS收集器"><a href="#CMS收集器" class="headerlink" title="CMS收集器"></a>CMS收集器</h3><blockquote><p> CMS 前的垃圾收集器在 GC 线程工作时都会 **STW(Stop The World)**,即停止用户线程。而 CMS 的 GC 线程在部分场景下可以与用户线程并行。</p><ul><li><p><strong>初始标记</strong></p><p>初始标记阶段会标记 <strong>GCRoots 直接关联的对象</strong>以及 <strong>年轻代指向老年代的对象</strong>,会发生短暂的 STW ,但由于没有向下追溯(只标记一层),所以速度很快</p></li><li><p><strong>并发标记</strong></p><p>并发标记阶段主要<strong>从 GCRoots 向下追溯,标记所有可达对象</strong>,该阶段由于与用户线程并行,所以可能有对象发生变化</p></li><li><p><strong>重新标记</strong></p><p>该阶段会发生 STW 暂停用户线程,扫描 <strong>老年代(dirty card)<strong>和</strong>年轻代</strong>,找出存活的老年代对象</p></li><li><p><strong>并发清除</strong></p><p>与用户线程并行,回收所有<strong>不可达对象</strong>,这个过程中可能产生浮动垃圾</p></li></ul><p> 缺点:</p><ul><li>使用标记清除,产生内存碎片,空间利用率减小</li><li>并发过程需要预留空间,如果预留空间不足,会报 <strong>Concurrent Mode Failure</strong>,然后采用 <strong>Serial Old</strong> 进行老年代回收</li><li>要处理内存碎片,需要 STW</li></ul></blockquote><h3 id="G1收集器"><a href="#G1收集器" class="headerlink" title="G1收集器"></a>G1收集器</h3><blockquote><p> CMS 垃圾收集器会出现<strong>停顿时间不可预知</strong>的情况,G1 收集器会让用户提前设定一个可以接受的 STW 时间,根据这个时间尽可能满足。</p><p> G1 收集器将堆空间以逻辑形式划分为一个个 <strong>Region</strong></p><p> G1 收集器的运作大致分为以下几个步骤:</p><ul><li><strong>初始标记</strong></li><li><strong>并发标记</strong></li><li><strong>最终标记</strong></li><li><strong>筛选回收</strong></li></ul></blockquote>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> JVM </tag>
<tag> 垃圾回收 </tag>
</tags>
</entry>
<entry>
<title>Java 单例模式</title>
<link href="/2021/11/04/Java%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/"/>
<url>/2021/11/04/Java%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/</url>
<content type="html"><![CDATA[<h1 id="单例模式"><a href="#单例模式" class="headerlink" title="单例模式"></a>单例模式</h1><p><strong>单例(Singleton)模式</strong>:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。</p><p>单例模式有 3 个特点:</p><ol><li>单例类只有一个实例对象;</li><li>该单例对象必须由单例类自行创建;</li><li>单例类对外提供一个访问该单例的全局访问点。</li></ol><p>对于Java来说,通过将构造方法私有化使外部无法调用构造方法生成多个实例,提供一个静态函数供外部访问单例。单例模式可以保证在一个 JVM 中只存在单一实例。</p><p>单例模式通常有两种实现方式</p><ul><li>懒汉式</li><li>饿汉式</li></ul><hr><h2 id="饿汉式单例"><a href="#饿汉式单例" class="headerlink" title="饿汉式单例"></a>饿汉式单例</h2><p>类一旦加载就创建一个实例,保证在调用<code>getInstance()</code>方法前已经创建实例</p><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HungrySingleton</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">HungrySingleton</span> <span class="variable">instance</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HungrySingleton</span>();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="title function_">HungrySingleton</span><span class="params">()</span> {}</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> HungrySingleton <span class="title function_">getInstance</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> instance;</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>加载时没有生成实例,只有第一次调用时才创建实例。</p><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LazySingleton</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">LazySingleton</span> <span class="variable">instance</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="title function_">LazySingleton</span><span class="params">()</span> {}</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> LazySingleton <span class="title function_">getInstance</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">if</span> (instance == <span class="literal">null</span>) {</span><br><span class="line"> instance = <span class="keyword">new</span> <span class="title class_">LazySingleton</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>懒汉式对于实例化类呈懒惰态度,即实例化的时机在第一被引用时触发,在类进行加载时,不会将自己实例化(延迟加载技术)。</p><h2 id="懒汉式单例的并发问题"><a href="#懒汉式单例的并发问题" class="headerlink" title="懒汉式单例的并发问题"></a>懒汉式单例的并发问题</h2><p>上述<strong>懒汉式单例</strong>代码存在并发问题,如果多个线程同时访问<code>LazySingleton.getInstance()</code>可能导致类被实例化多次。而饿汉式调用<code>getInstance()</code>前就已经创建实例,不存在并发问题。</p><p><strong>并发测试</strong>:三个线程调用<code>LazySingleton.getInstance()</code>方法,三个线程调用<code>HungrySingleton.getInstance()</code>方法</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestSingleton</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="type">Thread</span> <span class="variable">t1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="type">LazySingleton</span> <span class="variable">instance</span> <span class="operator">=</span> LazySingleton.getInstance();</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + <span class="string">"->"</span> + instance);</span><br><span class="line"> }, <span class="string">"t1"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="type">Thread</span> <span class="variable">t2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="type">LazySingleton</span> <span class="variable">instance</span> <span class="operator">=</span> LazySingleton.getInstance();</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + <span class="string">"->"</span> + instance);</span><br><span class="line"> }, <span class="string">"t2"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="type">Thread</span> <span class="variable">t3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="type">LazySingleton</span> <span class="variable">instance</span> <span class="operator">=</span> LazySingleton.getInstance();</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + <span class="string">"->"</span> + instance);</span><br><span class="line"> }, <span class="string">"t3"</span>);</span><br><span class="line"></span><br><span class="line"> t1.start();</span><br><span class="line"> t2.start();</span><br><span class="line"> t3.start();</span><br><span class="line"></span><br><span class="line"> <span class="type">Thread</span> <span class="variable">t4</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="type">HungrySingleton</span> <span class="variable">instance</span> <span class="operator">=</span> HungrySingleton.getInstance();</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + <span class="string">"->"</span> + instance);</span><br><span class="line"> }, <span class="string">"t4"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="type">Thread</span> <span class="variable">t5</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="type">HungrySingleton</span> <span class="variable">instance</span> <span class="operator">=</span> HungrySingleton.getInstance();</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + <span class="string">"->"</span> + instance);</span><br><span class="line"> }, <span class="string">"t5"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="type">Thread</span> <span class="variable">t6</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="type">HungrySingleton</span> <span class="variable">instance</span> <span class="operator">=</span> HungrySingleton.getInstance();</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + <span class="string">"->"</span> + instance);</span><br><span class="line"> }, <span class="string">"t6"</span>);</span><br><span class="line"></span><br><span class="line"> t4.start();</span><br><span class="line"> t5.start();</span><br><span class="line"> t6.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>测试结果:</strong></p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153618.png" alt="并发测试结果"></p><p>懒汉式获取单例时产生了多个实例,饿汉式获取单例时没有影响。</p><hr><h2 id="解决线程安全问题"><a href="#解决线程安全问题" class="headerlink" title="解决线程安全问题"></a>解决线程安全问题</h2><h3 id="对方法加锁"><a href="#对方法加锁" class="headerlink" title="对方法加锁"></a>对方法加锁</h3><p>对<code>getInstance()</code>方法加锁使同一时间只有一个线程获得单例</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadSafeSingleton</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> ThreadSafeSingleton instance;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="title function_">ThreadSafeSingleton</span><span class="params">()</span> {}</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> ThreadSafeSingleton <span class="title function_">getInstance</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">if</span> (instance == <span class="literal">null</span>) {</span><br><span class="line"> instance = <span class="keyword">new</span> <span class="title class_">ThreadSafeSingleton</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样可以保证只会创建一个实例,<code>volatile</code>防止指令重排序,但<code>synchronized</code>锁住了整个方法,多线程访问时性能会降低。</p><h3 id="双重检查锁"><a href="#双重检查锁" class="headerlink" title="双重检查锁"></a>双重检查锁</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DoubleCheckSingleton</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> DoubleCheckSingleton instance;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="title function_">DoubleCheckSingleton</span><span class="params">()</span> {}</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> DoubleCheckSingleton <span class="title function_">getInstance</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">if</span> (instance == <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">synchronized</span> (DoubleCheckSingleton.class) {</span><br><span class="line"> <span class="keyword">if</span> (instance == <span class="literal">null</span>) {</span><br><span class="line"> System.out.println(Thread.currentThread().getName()+ <span class="string">"正在初始化"</span>);</span><br><span class="line"> instance = <span class="keyword">new</span> <span class="title class_">DoubleCheckSingleton</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果多个线程进入<code>getInstance()</code>方法</p><ul><li>先判断是否已存在实例,若已存在则直接返回,否则获得锁创建实例</li><li>线程获得锁后先判断是否已存在实例。原因是如果线程A和线程B同时竞争锁,线程A获得锁进入并创建实例后释放锁,此时线程B获得锁且并不知道实例已被创建的话,会继续创建新实例,所以需要第二次判断。</li></ul><h3 id="内部类"><a href="#内部类" class="headerlink" title="内部类"></a>内部类</h3><h3 id="枚举"><a href="#枚举" class="headerlink" title="枚举"></a>枚举</h3>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> 设计模式 </tag>
<tag> singleton </tag>
</tags>
</entry>
<entry>
<title>Java 代理模式</title>
<link href="/2021/11/03/Java%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/"/>
<url>/2021/11/03/Java%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/</url>
<content type="html"><![CDATA[<h1 id="代理模式"><a href="#代理模式" class="headerlink" title="代理模式"></a>代理模式</h1><p>代理通常指的通过一个代理类实现对真实类的控制访问。</p><p>在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。比如租房子时与中介沟通租房的一切事宜,不直接接触房东。</p><p>Java中根据代理的创建时期,代理的类型分为两种。</p><ul><li>静态代理:程序员创建代理类或特定工具类源码对其编译,在程序运行前就生成了.class文件</li><li>动态代理:在程序运行时通过反射机制动态创建</li></ul><h1 id="静态代理"><a href="#静态代理" class="headerlink" title="静态代理"></a>静态代理</h1><p>创建一个<code>RentService</code>接口</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">RentService</span> {</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">void</span> <span class="title function_">pay</span><span class="params">()</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RentServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">RentService</span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">pay</span><span class="params">()</span> {</span><br><span class="line"> System.out.println(<span class="string">"房客付钱咯..."</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RentServiceProxy</span> <span class="keyword">implements</span> <span class="title class_">RentService</span>{</span><br><span class="line"> <span class="keyword">private</span> RentService rentService;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">RentServiceProxy</span><span class="params">(RentService rentService)</span> {</span><br><span class="line"> <span class="built_in">this</span>.rentService = rentService;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">see</span><span class="params">()</span> {</span><br><span class="line"> System.out.println(<span class="string">"房客看房咯..."</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="keyword">public</span> <span class="keyword">void</span> <span class="title function_">pay</span><span class="params">()</span> {</span><br><span class="line"> see();</span><br><span class="line"> rentService.pay();</span><br><span class="line"> sign();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sign</span><span class="params">()</span> {</span><br><span class="line"> System.out.println(<span class="string">"房客签名咯..."</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestProxy</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="type">RentService</span> <span class="variable">rentService</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RentServiceProxy</span>(<span class="keyword">new</span> <span class="title class_">RentServiceImpl</span>());</span><br><span class="line"> rentService.pay();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>结果如图</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206154028.png" alt="静态代理"></p><p>通过<code>RentServiceProxy</code>实现了对<code>RentService</code>类的拓展,在测试类中代码量没有增加但是扩展了功能。</p><p>但静态代理的缺点在于:</p><ul><li>当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:<ul><li>只维护一个代理类,由这个代理类实现多个接口,但是这样就导致<strong>代理类过于庞大</strong>。</li><li>新建多个代理类,每个目标对象对应一个代理类,但是这样会**产生过多的代理类。</li></ul></li><li>当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,<strong>不易维护</strong>。</li></ul><h1 id="动态代理"><a href="#动态代理" class="headerlink" title="动态代理"></a>动态代理</h1><p>Java中动态代理有两种实现方法:</p><ul><li>JDK动态代理</li></ul><p>涉及两个类 <code>java.lang.reflect.Proxy</code> 和 <code>java.lang.reflect.InvocationHandler</code>。</p><p><code>Proxy</code> 提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。</p><p><code>InvocationHandler</code> 是由代理实例的调用处理程序实现的接口。</p><p>JDK 动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用CGLIB实现。JDK 动态代理之所以<strong>只能代理接口</strong>是因为<strong>代理类本身已经 extends 了 Proxy,而java是不允许多重继承的</strong>,但是允许实现多个接口。</p><ul><li>Cglib动态代理</li></ul><p>使用cglib代理的对象则无需实现接口,可以在运行时动态的生成某个类的子类,故需要某个类不能被标记为final</p><h2 id="JDK动态代理"><a href="#JDK动态代理" class="headerlink" title="JDK动态代理"></a>JDK动态代理</h2><p>使用<code>Proxy.newProxyInstance</code>方法生成代理对象</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title function_">newProxyInstance</span><span class="params">(ClassLoader loader,</span></span><br><span class="line"><span class="params"> Class<?>[] interfaces,</span></span><br><span class="line"><span class="params"> InvocationHandler h)</span></span><br></pre></td></tr></table></figure><ul><li><code>loader</code> 为代理类的类加载器</li><li><code>interfaces</code> 为代理类的接口数组</li><li><code>h</code> 为方法调用处理器</li></ul><p>使用 <code>InvocationHandler</code> 的 <code>invoke</code> 方法实现方法调用</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Object <span class="title function_">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span></span><br></pre></td></tr></table></figure><ul><li><code>proxy</code>:触发方法的代理类实例</li><li><code>method</code>:代理类实现的接口中的方法</li><li><code>args</code>:方法参数</li></ul><p>实现一个Handler,继承 <code>InvocationHandler</code></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DynamicProxyHandler</span> <span class="keyword">implements</span> <span class="title class_">InvocationHandler</span> {</span><br><span class="line"> <span class="keyword">private</span> RentService rentService;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">DynamicProxyHandler</span><span class="params">(RentService rentService)</span> {</span><br><span class="line"> <span class="built_in">this</span>.rentService = rentService;</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="keyword">public</span> Object <span class="title function_">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable {</span><br><span class="line"> System.out.println(<span class="string">"房客看房咯..."</span>);</span><br><span class="line"> <span class="type">Object</span> <span class="variable">o</span> <span class="operator">=</span> method.invoke(rentService, args);</span><br><span class="line"> System.out.println(<span class="string">"房客签名咯..."</span>);</span><br><span class="line"> <span class="keyword">return</span> o;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestProxy</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> </span><br><span class="line"> <span class="type">RentService</span> <span class="variable">target</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RentServiceImpl</span>();</span><br><span class="line"> <span class="comment">//自定义方法调用处理器</span></span><br><span class="line"> <span class="type">DynamicProxyHandler</span> <span class="variable">handler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DynamicProxyHandler</span>(target);</span><br><span class="line"> <span class="comment">//获得代理类实例</span></span><br><span class="line"> <span class="type">RentService</span> <span class="variable">rentServiceProxy</span> <span class="operator">=</span> (RentService) Proxy.newProxyInstance(</span><br><span class="line"> target.getClass().getClassLoader(),</span><br><span class="line"> target.getClass().getInterfaces(),</span><br><span class="line"> handler);</span><br><span class="line"> rentServiceProxy.pay();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>结果和静态代理类相同。</p><h2 id="CGLIB动态代理"><a href="#CGLIB动态代理" class="headerlink" title="CGLIB动态代理"></a><a href="https://github.com/cglib/cglib">CGLIB</a>动态代理</h2><p> JDK中提供的生成动态代理类的机制有个鲜明的特点是: 某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法。比如上面例子中如果<code>RentServiceImpl</code>有其他方法<code>live()</code>,JDK动态代理类中就没有这个方法。</p><p>而<code>CGLIB(Code Generation Library)</code>没有接口也能实现动态代理</p><p><strong>示例代码</strong></p><p>自定义<code>Interceptor</code></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RentInterceptor</span> <span class="keyword">implements</span> <span class="title class_">MethodInterceptor</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 class="doctag">@param</span> object 增强的对象</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> method 拦截的方法</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> args 方法参数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> methodProxy 对方法的代理,invokeSuper方法表示对被代理对象方法的调用</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 执行结果</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> Throwable</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> Object <span class="title function_">intercept</span><span class="params">(Object object, Method method, Object[] args, MethodProxy methodProxy)</span> <span class="keyword">throws</span> Throwable {</span><br><span class="line"> System.out.println(<span class="string">"房客看房咯..."</span>);</span><br><span class="line"> <span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> methodProxy.invokeSuper(object, args);</span><br><span class="line"> System.out.println(<span class="string">"房客签名咯..."</span>);</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestProxy</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> </span><br><span class="line"> <span class="type">RentInterceptor</span> <span class="variable">interceptor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RentInterceptor</span>();</span><br><span class="line"> <span class="type">Enhancer</span> <span class="variable">enhancer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Enhancer</span>();</span><br><span class="line"> enhancer.setSuperclass(RentServiceImpl.class);</span><br><span class="line"> enhancer.setCallback(interceptor);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//创建代理类,可以对类进行代理</span></span><br><span class="line"> <span class="type">RentServiceImpl</span> <span class="variable">proxy</span> <span class="operator">=</span> (RentServiceImpl)enhancer.create();</span><br><span class="line"> System.out.println(<span class="string">"cglib: "</span> + proxy.getClass());</span><br><span class="line"> proxy.pay();</span><br><span class="line"> proxy.decorate();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行结果</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206154032.png" alt="cglib"></p>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> proxy </tag>
<tag> 设计模式 </tag>
</tags>
</entry>
<entry>
<title>Java 内存区域</title>
<link href="/2021/09/07/Java%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F/"/>
<url>/2021/09/07/Java%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F/</url>
<content type="html"><![CDATA[<h1 id="Java内存区域"><a href="#Java内存区域" class="headerlink" title="Java内存区域"></a>Java内存区域</h1><p><strong>JDK 1.8以前:</strong></p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153655.png" alt="JVM运行时数据区域"></p><p><strong>JDK 1.8以后:</strong></p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153701.png" alt="Java运行时数据区域JDK1.8"></p><p><strong>线程私有:</strong></p><ul><li><p><strong>虚拟机栈</strong></p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153704.jpeg" alt="虚拟机栈"></p><p>Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。</p><p><strong>局部变量表主要存放了编译期可知的各种数据类型</strong>(boolean、byte、char、short、int、float、long、double)、<strong>对象引用</strong>(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。</p><p><strong>Java 虚拟机栈会出现两种错误:<code>StackOverFlowError</code> 和 <code>OutOfMemoryError</code>。</strong></p><ul><li><strong><code>StackOverFlowError</code>:</strong> 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。</li><li><strong><code>OutOfMemoryError</code>:</strong> Java 虚拟机栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出<code>OutOfMemoryError</code>异常。</li></ul></li><li><p><strong>本地方法栈</strong></p><p>本地方法栈与虚拟机栈发挥的作用相似,区别在于虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈为虚拟机使用到的本地(Native)方法服务</p></li><li><p><strong>程序计数器</strong></p><p>程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。</p><p>程序计数器的作用:</p><ol><li>字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。</li><li>在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。</li></ol><p>程序计数器是唯一一个没有规定任何<code>OutOfMemoryError</code>的内存区域</p></li></ul><p><strong>线程共享:</strong></p><ul><li><p><strong>堆</strong></p><p>Java堆的目的是存放对象实例,几乎所有对象实例都在堆上分配内存。</p><p>Java堆也是垃圾收集器管理的主要区域。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代;再细致一点有:<code>Eden</code> 空间、<code>From Survivor</code>、<code>To Survivor</code> 空间等。<strong>进一步划分的目的是更好地回收内存,或者更快地分配内存。</strong></p><p>JDK 7及以前堆内存区域:</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153708.png" alt="JVM堆内存结构-JDK7"></p><p>JDK 8以后堆内存区域:</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206153711.png" alt="JVM对内存区域-jdk8"></p></li><li><p><strong>方法区</strong></p><p>方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。</p></li><li><p><strong>运行时常量池</strong></p><p>运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池表(用于存放编译期生成的各种字面量和符号引用)</p></li><li><p><strong>直接内存</strong></p><p>直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 <code>OutOfMemoryError</code> 错误出现。</p></li></ul><hr><p><em>参考阅读:</em></p><p><a href="https://book.douban.com/subject/34907497/">深入理解Java虚拟机(第3版) (豆瓣) (douban.com)</a></p><p><a href="https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/jvm/Java%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F.md">JavaGuide</a></p>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> 内存区域 </tag>
<tag> JVM </tag>
</tags>
</entry>
<entry>
<title>浅谈 LRU 缓存机制算法题</title>
<link href="/2021/07/23/%E6%B5%85%E8%B0%88LRU%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6%E7%AE%97%E6%B3%95%E9%A2%98/"/>
<url>/2021/07/23/%E6%B5%85%E8%B0%88LRU%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6%E7%AE%97%E6%B3%95%E9%A2%98/</url>
<content type="html"><![CDATA[<h1 id="146-LRU-缓存机制"><a href="#146-LRU-缓存机制" class="headerlink" title="146. LRU 缓存机制"></a><a href="https://leetcode-cn.com/problems/lru-cache/">146. LRU 缓存机制</a></h1><p>运用你所掌握的数据结构,设计和实现一个 <a href="https://baike.baidu.com/item/LRU">LRU (最近最少使用) 缓存机制</a> 。</p><p>实现 <code>LRUCache</code> 类:</p><ul><li><code>LRUCache(int capacity)</code> 以正整数作为容量 <code>capacity</code> 初始化 LRU 缓存</li><li><code>int get(int key)</code> 如果关键字 <code>key</code> 存在于缓存中,则返回关键字的值,否则返回 <code>-1</code> 。</li><li><code>void put(int key, int value)</code> 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。</li><li>要求在<code>O(1)</code>时间复杂度内完成<code>get</code>和<code>put</code>操作</li></ul><hr><h2 id="实现思路及参考代码"><a href="#实现思路及参考代码" class="headerlink" title="实现思路及参考代码"></a>实现思路及参考代码</h2><p>要在<code>O(1)</code>时间内完成<code>get</code>和<code>put</code>操作,很自然的想到哈希表结构,但是很明显页面在置换时对顺序是有要求的。如果使用链表结构,在<code>get</code>操作上是<code>O(n)</code>的复杂度。故可以考虑采用哈希表加上双向链表的结构,哈希表存储对应的结点。</p><p><strong>原生实现</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Node</span>{</span><br><span class="line"> <span class="comment">//定义key的原因是需要key对哈希表进行操作</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> key, val;</span><br><span class="line"> <span class="keyword">public</span> Node next, prev;</span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">Node</span><span class="params">(<span class="type">int</span> k, <span class="type">int</span> v)</span> {</span><br><span class="line"> <span class="built_in">this</span>.key = k;</span><br><span class="line"> <span class="built_in">this</span>.val = v;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用<code>Node</code>构建双向链表</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">DoubleList</span>{</span><br><span class="line"> <span class="comment">//头尾节点</span></span><br><span class="line"><span class="keyword">private</span> Node head, tail;</span><br><span class="line"> <span class="comment">//链表节点数</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> size;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//构造方法,初始化头尾节点</span></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">DoubleList</span><span class="params">()</span> {</span><br><span class="line"> <span class="built_in">this</span>.head = <span class="keyword">new</span> <span class="title class_">Node</span>(<span class="number">0</span>,<span class="number">0</span>);</span><br><span class="line"> <span class="built_in">this</span>.tail = <span class="keyword">new</span> <span class="title class_">Node</span>(<span class="number">0</span>,<span class="number">0</span>);</span><br><span class="line"> head.next = tail;</span><br><span class="line"> tail.prev = head;</span><br><span class="line"> size = <span class="number">0</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="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addLast</span><span class="params">(Node node)</span> {</span><br><span class="line"> node.prev = tail.prev;</span><br><span class="line"> node.next = tail;</span><br><span class="line"> tail.prev.next = node;</span><br><span class="line"> tail.prev = node;</span><br><span class="line"> size++;</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="keyword">public</span> <span class="keyword">void</span> <span class="title function_">remove</span><span class="params">(Node node)</span> {</span><br><span class="line"> node.prev.next = node.next;</span><br><span class="line"> node.next.prev = node.prev;</span><br><span class="line"> size--;</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="keyword">public</span> Node <span class="title function_">removeFirst</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">if</span> (head.next == tail) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="type">Node</span> <span class="variable">node</span> <span class="operator">=</span> head.next;</span><br><span class="line"> remove(node);</span><br><span class="line"> <span class="keyword">return</span> node;</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="keyword">public</span> <span class="type">int</span> <span class="title function_">size</span><span class="params">()</span>{</span><br><span class="line"> <span class="keyword">return</span> size;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其中删除操作的时间复杂度是<code>O(1)</code>,因为是双向链表所以可以直接找到前驱节点,如果是单链表删除操作会需要<code>O(n)</code>时间复杂度。</p><p>双链表结合哈希表实现<code>LRUCache</code>类</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LRUCache</span>{</span><br><span class="line"> <span class="keyword">private</span> HashMap<Integer, Node> map;</span><br><span class="line"> <span class="keyword">private</span> DoubleList cache;</span><br><span class="line"> <span class="comment">//缓存容量</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> capacity;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">LRUCache</span><span class="params">(<span class="type">int</span> capacity)</span> {</span><br><span class="line"> <span class="built_in">this</span>.map = <span class="keyword">new</span> <span class="title class_">HashMap</span><>();</span><br><span class="line"> <span class="built_in">this</span>.cache = <span class="keyword">new</span> <span class="title class_">DoubleList</span>();</span><br><span class="line"> <span class="built_in">this</span>.capacity = capacity;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//get操作</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">(<span class="type">int</span> key)</span> {</span><br><span class="line"> <span class="comment">//先查看是否存在该节点</span></span><br><span class="line"> <span class="keyword">if</span> (!map.containsKey(key)) <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line"> <span class="comment">//如果存在,将该节点移动到链表尾部成为最近最常使用节点</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">val</span> <span class="operator">=</span> map.get(key).val;</span><br><span class="line"> makeRecently(key);</span><br><span class="line"> <span class="keyword">return</span> val;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//put操作</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">put</span><span class="params">(<span class="type">int</span> key, <span class="type">int</span> value)</span> {</span><br><span class="line"> <span class="comment">//如果链表中已存在该节点,将该节点直接移动到尾部</span></span><br><span class="line"> <span class="keyword">if</span> (map.containsKey(key)) {</span><br><span class="line"> map.put(key, value);</span><br><span class="line"> makeRecently(key);</span><br><span class="line"> <span class="keyword">return</span> ;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//如果不存在该节点,则首先要判断缓存是否已满;</span></span><br><span class="line"> <span class="keyword">if</span> (cache.size() == <span class="built_in">this</span>.capacity) {</span><br><span class="line"> <span class="comment">//如果满了删除第一个节点</span></span><br><span class="line"> <span class="type">Node</span> <span class="variable">oldestNode</span> <span class="operator">=</span> cache.removeFirst();</span><br><span class="line"> map.remove(oldestNode.key);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">Node</span> <span class="variable">newNode</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Node</span>(key, value);</span><br><span class="line"> map.put(key, newNode);</span><br><span class="line"> cache.addLast(newNode);</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="keyword">public</span> <span class="keyword">void</span> <span class="title function_">makeRecently</span><span class="params">(<span class="type">int</span> key)</span> {</span><br><span class="line"> <span class="type">Node</span> <span class="variable">newestNode</span> <span class="operator">=</span> map.get(key);</span><br><span class="line"> cache.remove(newestNode);</span><br><span class="line"> cache.addLast(newestNode);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><p><strong>使用<code>LinkedHashMap</code></strong></p><p>Java 内置集合类型<code>LinkedHashMap</code>是一个哈希链表。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LRUCache</span>{</span><br><span class="line"> LinkedHashMap<Integer, Integer> cache;</span><br><span class="line"> <span class="type">int</span> capacity;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">LRUCache</span><span class="params">(<span class="type">int</span> capacity)</span> {</span><br><span class="line"> cache = <span class="keyword">new</span> <span class="title class_">LinkedHashMap</span><>();</span><br><span class="line"> <span class="built_in">this</span>.capacity = capacity;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//get操作</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">(<span class="type">int</span> key)</span> {</span><br><span class="line"> <span class="keyword">if</span> (!cache.containsKey(key)) <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line"> makeRecently(key);</span><br><span class="line"> <span class="keyword">return</span> cache.get(key);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//put操作</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">put</span><span class="params">(<span class="type">int</span> key, <span class="type">int</span> value)</span> {</span><br><span class="line"> <span class="keyword">if</span> (cache.containsKey(key)) {</span><br><span class="line"> cache.put(key, value);</span><br><span class="line"> makeRecently(key);</span><br><span class="line"> <span class="keyword">return</span> ;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">this</span>.capacity == cache.size()) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">oldestNode</span> <span class="operator">=</span> cache.keySet().iterator().next();</span><br><span class="line"> cache.remove(oldestNode);</span><br><span class="line"> }</span><br><span class="line"> cache.put(key, value);</span><br><span class="line"> makeRecently(key);</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="keyword">public</span> <span class="keyword">void</span> <span class="title function_">makeRecently</span><span class="params">(<span class="type">int</span> key)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">val</span> <span class="operator">=</span> cache.get(key);</span><br><span class="line"> cache.remove(key);</span><br><span class="line"> cache.put(key, val);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>最简实现</strong></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="keyword">class</span> <span class="title class_">LRUCache</span> <span class="keyword">extends</span> <span class="title class_">LinkedHashMap</span><Integer, Integer>{</span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> capacity;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">LRUCache</span><span class="params">(<span class="type">int</span> capacity)</span> {</span><br><span class="line"> <span class="built_in">super</span>(capacity, <span class="number">0.75F</span>, <span class="literal">true</span>);</span><br><span class="line"> <span class="built_in">this</span>.capacity = capacity;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">(<span class="type">int</span> key)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">super</span>.getOrDefault(key, -<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">put</span><span class="params">(<span class="type">int</span> key, <span class="type">int</span> value)</span> {</span><br><span class="line"> <span class="built_in">super</span>.put(key, value);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//会在put调用时自动调用</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">removeEldestEntry</span><span class="params">(Map.Entry<Integer, Integer> eldest)</span> {</span><br><span class="line"> <span class="keyword">return</span> size() > capacity;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><p>本文参考:<a href="https://github.com/labuladong/fucking-algorithm#labuladong-%E7%9A%84%E7%AE%97%E6%B3%95%E5%B0%8F%E6%8A%84">labuladong 的算法小抄</a></p>]]></content>
<categories>
<category> 算法题 </category>
</categories>
<tags>
<tag> leetcode </tag>
<tag> 算法 </tag>
<tag> LRU </tag>
</tags>
</entry>
<entry>
<title>LeetCode 刷题记录</title>
<link href="/2021/07/09/LeetCode%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95/"/>
<url>/2021/07/09/LeetCode%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95/</url>
<content type="html"><![CDATA[<h1 id="Leetcode"><a href="#Leetcode" class="headerlink" title="Leetcode"></a>Leetcode</h1><h2 id="1-两数之和"><a href="#1-两数之和" class="headerlink" title="1. 两数之和"></a><a href="https://leetcode-cn.com/problems/two-sum/">1. 两数之和</a></h2><p>给定一个整数数组 <code>nums</code> 和一个整数目标值 <code>target</code>,请你在该数组中找出 <strong>和为目标值</strong> <em><code>target</code></em> 的那 <strong>两个</strong> 整数,并返回它们的数组下标。</p><p><strong>示例 1:</strong></p><figure class="highlight plaintext"><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">输入:nums = [2,7,11,15], target = 9</span><br><span class="line">输出:[0,1]</span><br><span class="line">解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。</span><br></pre></td></tr></table></figure><h3 id="解题思路"><a href="#解题思路" class="headerlink" title="解题思路"></a>解题思路</h3><p><strong>1.暴力法</strong></p><p>对两两数字和加起来进行比较</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(n^2),n 为数组长度</li><li>空间复杂度:O(1)</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span>[] twoSum(<span class="type">int</span>[] nums, <span class="type">int</span> target) {</span><br><span class="line"> <span class="type">int</span>[] ret = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">2</span>];</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < nums.length - <span class="number">1</span>; i++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i + <span class="number">1</span>; j < nums.length; j++) {</span><br><span class="line"> <span class="keyword">if</span> (nums[i] + nums[j] == target) {</span><br><span class="line"> ret[<span class="number">0</span>] = i;</span><br><span class="line"> ret[<span class="number">1</span>] = j;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><p><strong>2.哈希表</strong></p><p>对于数组中的每一个<code>x</code>要找到<code>target - x</code>时间复杂度是 O(n^2),如果使用哈希表将数组中的每一个数保存下来,寻找<code>target - x</code>的时间复杂度就可以降到 O(1)</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(n),n 为数组长度</li><li>空间复杂度:O(n),n 为数组长度</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span>[] twoSum(<span class="type">int</span>[] nums, <span class="type">int</span> target) {</span><br><span class="line"> Map<Integer, Integer> map = <span class="keyword">new</span> <span class="title class_">HashMap</span><>();</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < nums.length; i++) {</span><br><span class="line"> <span class="keyword">if</span> (map.containsKey(target - nums[i])) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[]{i, map.get(target - nums[i])};</span><br><span class="line"> }</span><br><span class="line"> map.put(nums[i], i);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="88-合并两个有序数组"><a href="#88-合并两个有序数组" class="headerlink" title="88. 合并两个有序数组"></a><a href="https://leetcode-cn.com/problems/merge-sorted-array/">88. 合并两个有序数组</a></h2><p>给你两个有序整数数组 <code>nums1</code> 和 <code>nums2</code>,请你将 <code>nums2</code> 合并到 <code>nums1</code> 中<em>,</em>使 <code>nums1</code> 成为一个有序数组。</p><p>初始化 <code>nums1</code> 和 <code>nums2</code> 的元素数量分别为 <code>m</code> 和 <code>n</code>。你可以假设 <code>nums1</code> 的空间大小等于 <code>m + n</code>,这样它就有足够的空间保存来自 <code>nums2</code> 的元素。</p><p><strong>示例 1:</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3</span><br><span class="line">输出:[1,2,2,3,5,6]</span><br></pre></td></tr></table></figure><h3 id="解题思路-1"><a href="#解题思路-1" class="headerlink" title="解题思路"></a>解题思路</h3><p><strong>1.先合并,再排序</strong></p><p>将<code>nums2</code>中的元素加入<code>nums1</code>,再对<code>nums1</code>排序</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O( (m+n) log(m+n) ),快排的时间复杂度</li><li>空间复杂度:O( log(m+n) ),快排的空间复杂度</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">solution</span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">merge</span><span class="params">(<span class="type">int</span>[] nums1, <span class="type">int</span> m, <span class="type">int</span>[] nums2, <span class="type">int</span> n)</span> {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> nums1[m + i] = nums2[i];</span><br><span class="line"> }</span><br><span class="line"> Arrays.sort(nums1);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><p><strong>2.双指针法</strong></p><p>方法 1 没有用到题目中<code>nums1</code>和<code>nums2</code>有序的性质,可以用两个指针<code>i、j</code>分别指向<code>nums1、nums2</code>的下标,并根据下标进行比较,但是需要一个新数组保存结果</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(m + n)</li><li>空间复杂度:O(m + n)</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">merge</span><span class="params">(<span class="type">int</span>[] nums1, <span class="type">int</span> m, <span class="type">int</span>[] nums2, <span class="type">int</span> n)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="type">int</span>[] sorted = <span class="keyword">new</span> <span class="title class_">int</span>[nums1.length];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (i < m && j < n) {</span><br><span class="line"> <span class="keyword">if</span> (nums1[i] < nums2[j]) {</span><br><span class="line"> sorted[i + j] = nums1[i++];</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> sorted[i + j] = nums2[j++];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (i < m) {</span><br><span class="line"> <span class="keyword">while</span> (i < m) {</span><br><span class="line"> sorted[i + j] = nums1[i++];</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">while</span> (j < n) {</span><br><span class="line"> sorted[i + j] = nums2[j++];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">k</span> <span class="operator">=</span> <span class="number">0</span>; k < m + n; k++) {</span><br><span class="line"> nums1[k] = sorted[k];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><p><strong>3.逆向双指针</strong></p><p>方法 2 中新建了一个<code>sorted</code>数组保存排序结果,但是题目中<code>nums1</code>中已经预留了空间,所以可以从大的开始排序,并放入<code>nums1</code>的空闲区</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(m + n)</li><li>空间复杂度:O(1)</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">merge</span><span class="params">(<span class="type">int</span>[] nums1, <span class="type">int</span> m, <span class="type">int</span>[] nums2, <span class="type">int</span> n)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> m - <span class="number">1</span>;</span><br><span class="line"> <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> n - <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (i >= <span class="number">0</span> && j >= <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (nums1[i] > nums2[j]) {</span><br><span class="line"> nums1[i + j + <span class="number">1</span>] = nums1[i--];</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> nums1[i + j + <span class="number">1</span>] = nums2[j--];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (i > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">while</span> (i >= <span class="number">0</span>) {</span><br><span class="line"> nums1[i + j + <span class="number">1</span>] = nums1[i--];</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">while</span> (j >= <span class="number">0</span>) {</span><br><span class="line"> nums1[i + j + <span class="number">1</span>] = nums2[j--];</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><hr><h2 id="53-最大子序和"><a href="#53-最大子序和" class="headerlink" title="53. 最大子序和"></a><a href="https://leetcode-cn.com/problems/maximum-subarray/">53. 最大子序和</a></h2><p>给定一个整数数组 <code>nums</code> ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。</p><p><strong>示例 1:</strong></p><figure class="highlight plaintext"><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">输入:nums = [-2,1,-3,4,-1,2,1,-5,4]</span><br><span class="line">输出:6</span><br><span class="line">解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。</span><br></pre></td></tr></table></figure><h3 id="解题思路-2"><a href="#解题思路-2" class="headerlink" title="解题思路"></a>解题思路</h3><p>假设 dp[i]是以 nums[i]结尾的最大子序列和,则<br><img src="https://gitee.com/xunle1/drawing-bed/raw/master/typora%5C/20210709145452.png"><br>最大子序列和 max 就是 dp 数组中的最大值</p><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxSubArray</span><span class="params">(<span class="type">int</span>[] nums)</span> {</span><br><span class="line"><span class="comment">//dp[i]表示以nums[i]结尾的最大子序列和</span></span><br><span class="line"><span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[nums.length];</span><br><span class="line">dp[<span class="number">0</span>] = nums[<span class="number">0</span>];</span><br><span class="line"><span class="type">int</span> <span class="variable">max</span> <span class="operator">=</span> nums[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i < nums.length; i++) {</span><br><span class="line"><span class="comment">//以 nums[i] 结尾的最大子序列和 == 以 nums[i-1] 结尾的最大子序列和 + nums[i] 或者是 nums[i]</span></span><br><span class="line">dp[i] = Math.max(dp[i-<span class="number">1</span>] + nums[i], nums[i]);</span><br><span class="line">max = Math.max(dp[i], max);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> max;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="26-删除有序数组中的重复项"><a href="#26-删除有序数组中的重复项" class="headerlink" title="26. 删除有序数组中的重复项"></a><a href="https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/">26. 删除有序数组中的重复项</a></h2><p>给你一个有序数组 <code>nums</code> ,请你**<a href="http://baike.baidu.com/item/%E5%8E%9F%E5%9C%B0%E7%AE%97%E6%B3%95"> 原地</a>** 删除重复出现的元素,使每个元素 <strong>只出现一次</strong> ,返回删除后数组的新长度。</p><p><strong>示例 1:</strong></p><figure class="highlight plaintext"><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">输入:nums = [0,0,1,1,1,2,2,3,3,4]</span><br><span class="line">输出:5, nums = [0,1,2,3,4]</span><br><span class="line">解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。</span><br></pre></td></tr></table></figure><h3 id="解题思路-3"><a href="#解题思路-3" class="headerlink" title="解题思路"></a>解题思路</h3><p><strong>1.暴力法</strong></p><p>对于<code>nums[i]</code>,找到第一个比<code>nums[i]</code>大的数放在<code>nums[i+1]</code>的位置,直到找不到比<code>nums[i]</code>还要大的数。最后返回<code>i+1</code>即数组的新长度</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(n^2)</li><li>空间复杂度:O(1)</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">removeDuplicates</span><span class="params">(<span class="type">int</span>[] nums)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> nums.length;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < len - <span class="number">1</span>; i++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i + <span class="number">1</span>; j < len; j++) {</span><br><span class="line"> <span class="keyword">if</span> (nums[j] > nums[i]) {</span><br><span class="line"> nums[i + <span class="number">1</span>] = nums[j];</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (j == len - <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">return</span> 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="keyword">return</span> len;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><p><strong>2.双指针</strong></p><p>如果存在<code>nums[i] = nums[j]</code>,则对于 $$i \leq k \leq j$$ 有<code>nums[i] = nums[k] = nums[j]</code>,即相等的元素下标一定连续。通过维护<code>slow</code>和<code>fast</code>快慢指针,可以将空间复杂度降低到 O(n)</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(n)</li><li>空间复杂度:O(1)</li></ul><p><strong>参考代码</strong></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="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">removeDuplicates</span><span class="params">(<span class="type">int</span>[] nums)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">slow</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="type">int</span> <span class="variable">fast</span> <span class="operator">=</span> slow + <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">while</span> (fast < nums.length) {</span><br><span class="line"> <span class="keyword">if</span> (nums[slow] == nums[fast]) {</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> fast++;</span><br><span class="line"> } <span class="keyword">while</span> (fast < nums.length && nums[slow] == nums[fast]);</span><br><span class="line"> <span class="keyword">if</span> (fast == nums.length) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> nums[slow+<span class="number">1</span>] = nums[fast];</span><br><span class="line"> slow++;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> slow++;</span><br><span class="line"> fast++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> slow + <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">removeDuplicates</span><span class="params">(<span class="type">int</span>[] nums)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j < nums.length; j++) {</span><br><span class="line"> <span class="keyword">if</span> (nums[i] != nums[j]) {</span><br><span class="line"> nums[++i] = nums[j];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ++i;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="350-两个数组的交集-II"><a href="#350-两个数组的交集-II" class="headerlink" title="350. 两个数组的交集 II"></a><a href="https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/">350. 两个数组的交集 II</a></h2><p>给定两个数组,编写一个函数来计算它们的交集。</p><p><strong>示例 1:</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:nums1 = [1,2,2,1], nums2 = [2,2]</span><br><span class="line">输出:[2,2]</span><br></pre></td></tr></table></figure><h3 id="解题思路-4"><a href="#解题思路-4" class="headerlink" title="解题思路"></a>解题思路</h3><p><strong>1.哈希表</strong></p><p>对<code>nums1</code>和<code>nums2</code>创建两个哈希表<code>map1</code>和<code>map2</code>,以数组中出现的数字为 Key,出现的次数为 value,如果两个表存在相同的<code>key</code>值,则返回较小的<code>value</code>,此<code>value</code>就是两个数组中<code>key</code>的交集</p><p><strong>复杂度</strong></p><ul><li>时间复杂度:O(n + m)</li><li>空间复杂度:O(n + m)</li></ul><p><strong>参考代码</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span>[] intersect(<span class="type">int</span>[] nums1, <span class="type">int</span>[] nums2) {</span><br><span class="line"> Map<Integer, Integer> map1 = <span class="keyword">new</span> <span class="title class_">HashMap</span><>();</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < nums1.length; i++) {</span><br><span class="line"> <span class="keyword">if</span> (!map1.containsKey(nums1[i])) {</span><br><span class="line"> map1.put(nums1[i], <span class="number">1</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> map1.get(nums1[i]);</span><br><span class="line"> count++;</span><br><span class="line"> map1.put(nums1[i], count);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Map<Integer, Integer> map2 = <span class="keyword">new</span> <span class="title class_">HashMap</span><>();</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < nums2.length; i++) {</span><br><span class="line"> <span class="keyword">if</span> (!map2.containsKey(nums2[i])) {</span><br><span class="line"> map2.put(nums2[i], <span class="number">1</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> map2.get(nums2[i]);</span><br><span class="line"> count++;</span><br><span class="line"> map2.put(nums2[i], count);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> List<Integer> ret = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> Set<Integer> keySet = map1.keySet();</span><br><span class="line"> <span class="keyword">for</span> (Integer key : keySet) {</span><br><span class="line"> <span class="keyword">if</span> (map2.containsKey(key)) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> map1.get(key) > map2.get(key) ? map2.get(key) : map1.get(key);</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < count; i++) {</span><br><span class="line"> ret.add(key);</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="type">int</span>[] result = <span class="keyword">new</span> <span class="title class_">int</span>[ret.size()];</span><br><span class="line"> <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (Integer integer : ret) {</span><br><span class="line"> result[i++] = integer;</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></pre></td></tr></table></figure><p>用较小的数组建哈希表降低空间复杂度</p><p><strong>复杂度</strong></p><ul><li>时间复杂度:O(n + m)</li><li>空间复杂度:O($$min\lbrace n,m\rbrace$$)</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span>[] intersect(<span class="type">int</span>[] nums1, <span class="type">int</span>[] nums2) {</span><br><span class="line"> <span class="keyword">if</span> (nums1.length > nums2.length) {</span><br><span class="line"> <span class="keyword">return</span> intersect(nums2,nums1);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Map<Integer, Integer> map = <span class="keyword">new</span> <span class="title class_">HashMap</span><>();</span><br><span class="line"> <span class="comment">//map.getOrDefault(key, defaultValue) 如果存在key,返回key的value;如果不存在返回defaultValue</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i : nums1) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> map.getOrDefault(i, <span class="number">0</span>) + <span class="number">1</span>;</span><br><span class="line"> map.put(i, count);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span>[] intersection = <span class="keyword">new</span> <span class="title class_">int</span>[nums1.length];</span><br><span class="line"> <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i : nums2) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> map.getOrDefault(i, <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (count > <span class="number">0</span>) {</span><br><span class="line"> intersection[index++] = i;</span><br><span class="line"> count--;</span><br><span class="line"> <span class="keyword">if</span> (count > <span class="number">0</span>) {</span><br><span class="line"> map.put(i,count);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> map.remove(i);</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="keyword">return</span> Arrays.copyOfRange(intersection,<span class="number">0</span>,index);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="121-买卖股票的最佳时机"><a href="#121-买卖股票的最佳时机" class="headerlink" title="121. 买卖股票的最佳时机"></a><a href="https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/">121. 买卖股票的最佳时机</a></h2><p>给定一个数组 <code>prices</code> ,它的第 <code>i</code> 个元素 <code>prices[i]</code> 表示一支给定股票第 <code>i</code> 天的价格。</p><p>你只能选择 <strong>某一天</strong> 买入这只股票,并选择在 <strong>未来的某一个不同的日子</strong> 卖出该股票。设计一个算法来计算你所能获取的最大利润。</p><p>返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 <code>0</code> 。</p><p><strong>示例 1:</strong></p><figure class="highlight plaintext"><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">输入:[7,1,5,3,6,4]</span><br><span class="line">输出:5</span><br><span class="line">解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。</span><br><span class="line"> 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。</span><br></pre></td></tr></table></figure><h3 id="解题思路-5"><a href="#解题思路-5" class="headerlink" title="解题思路"></a>解题思路</h3><p><strong>1.蛮力法(超出时间限制)</strong></p><p>对第<code>i</code>天买入,计算之后每天的利润,并保存最大值。返回最后的最大值。</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(n^2)</li><li>空间复杂度:O(1)</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxProfit</span><span class="params">(<span class="type">int</span>[] prices)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">max</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < prices.length - <span class="number">1</span>; i++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i + <span class="number">1</span>; j < prices.length; j++) {</span><br><span class="line"> <span class="keyword">if</span> (prices[j] - prices[i] > max) {</span><br><span class="line"> max = prices[j] - prices[i];</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="keyword">return</span> max;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><p><strong>2.单调栈</strong></p><p>维护一个单调栈</p><ul><li>入栈元素大于等于栈顶元素时,直接入栈</li><li>入栈元素小于栈顶元素时,将栈顶弹出,并计算它与栈底元素的差值,直到找到一个元素小于等于入栈元素</li><li>在数组最后设置一个哨兵,为了使栈中元素得到计算</li></ul><p>第二条可以这样理解,栈顶元素为<code>a</code>,入栈元素为<code>b</code>,当<code>b</code>入栈时,之后入栈的元素<code>C</code>一定存在 $$C-b > C-a$$</p><p>此时<code>a</code>已经没用了,计算一下与利润即可弹出。</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(n)</li><li>空间复杂度:O(n)</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxProfit</span><span class="params">(<span class="type">int</span>[] prices)</span> {</span><br><span class="line"> Stack<Integer> stack = <span class="keyword">new</span> <span class="title class_">Stack</span><>();</span><br><span class="line"></span><br><span class="line"> stack.push(prices[<span class="number">0</span>]);</span><br><span class="line"> <span class="type">int</span> <span class="variable">low</span> <span class="operator">=</span> prices[<span class="number">0</span>];</span><br><span class="line"> <span class="type">int</span> <span class="variable">max</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i < prices.length; i++) {</span><br><span class="line"> <span class="keyword">if</span> (stack.peek() < prices[i]) {</span><br><span class="line"> stack.push(prices[i]);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (stack.size() == <span class="number">1</span> && stack.peek() > prices[i]) {</span><br><span class="line"> stack.pop();</span><br><span class="line"> low = prices[i];</span><br><span class="line"> stack.push(prices[i]);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (stack.peek() > prices[i]) {</span><br><span class="line"> <span class="keyword">while</span> (!stack.isEmpty() && stack.peek() > prices[i] ) {</span><br><span class="line"> <span class="type">Integer</span> <span class="variable">pop</span> <span class="operator">=</span> stack.pop();</span><br><span class="line"> <span class="keyword">if</span> (pop - low > max) max = pop - low;</span><br><span class="line"> }</span><br><span class="line"> stack.push(prices[i]);</span><br><span class="line"> <span class="keyword">if</span> (stack.size() == <span class="number">1</span>) low = stack.peek();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span> (stack.size() > <span class="number">0</span>) {</span><br><span class="line"> <span class="type">Integer</span> <span class="variable">pop</span> <span class="operator">=</span> stack.pop();</span><br><span class="line"> <span class="keyword">if</span> (pop - low > max) max = pop - low;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> max;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><p><strong>3.一次遍历</strong></p><p>第<code>i</code>天的最大收益是<code>maxProfig = price[i] - minPrice</code>,即第<code>i</code>天的价格减去第<code>i</code>天前的历史最低价。</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(n)</li><li>空间复杂度:O(1)</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxProfit</span><span class="params">(<span class="type">int</span>[] prices)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">minPrice</span> <span class="operator">=</span> Integer.MAX_VALUE;</span><br><span class="line"> <span class="type">int</span> <span class="variable">maxProfit</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < prices.length; i++) {</span><br><span class="line"> <span class="keyword">if</span> (prices[i] < minPrice) {</span><br><span class="line"> minPrice = prices[i];</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (prices[i] - minPrice > maxProfit) {</span><br><span class="line"> maxProfit = prices[i] - minPrice;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> maxProfit;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="566-重塑矩阵"><a href="#566-重塑矩阵" class="headerlink" title="566. 重塑矩阵"></a><a href="https://leetcode-cn.com/problems/reshape-the-matrix/">566. 重塑矩阵</a></h2><p>给出一个由二维数组表示的矩阵,以及两个正整数<code>r</code>和<code>c</code>,分别表示想要的重构的矩阵的行数和列数。</p><p>重构后的矩阵需要将原始矩阵的所有元素以相同的<strong>行遍历顺序</strong>填充。</p><p>如果具有给定参数的<code>reshape</code>操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。</p><p><strong>示例 1:</strong></p><figure class="highlight plaintext"><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><br><span class="line">nums =</span><br><span class="line">[[1,2],</span><br><span class="line"> [3,4]]</span><br><span class="line">r = 1, c = 4</span><br><span class="line">输出:</span><br><span class="line">[[1,2,3,4]]</span><br></pre></td></tr></table></figure><p><strong>示例 2:</strong></p><figure class="highlight plaintext"><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><br><span class="line">nums =</span><br><span class="line">[[1,2],</span><br><span class="line"> [3,4]]</span><br><span class="line">r = 2, c = 4</span><br><span class="line">输出:</span><br><span class="line">[[1,2],</span><br><span class="line"> [3,4]]</span><br></pre></td></tr></table></figure><h3 id="解题思路-6"><a href="#解题思路-6" class="headerlink" title="解题思路"></a>解题思路</h3><p><strong>1.蛮力法</strong></p><p>将输入的数组<code>nums</code>先转换为一维数组,再将一维数组转换为二维数组</p><p><strong>复杂度</strong></p><ul><li>时间复杂度:O(m * n)</li><li>空间复杂度:O(m * n)</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span>[][] matrixReshape(<span class="type">int</span>[][] mat, <span class="type">int</span> r, <span class="type">int</span> c) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">total</span> <span class="operator">=</span> mat.length * mat[<span class="number">0</span>].length;</span><br><span class="line"> <span class="keyword">if</span> (total != r * c) {</span><br><span class="line"> <span class="keyword">return</span> mat;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span>[] source = <span class="keyword">new</span> <span class="title class_">int</span>[total];</span><br><span class="line"> <span class="type">int</span> <span class="variable">k</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < mat.length; i++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j < mat[<span class="number">0</span>].length; j++) {</span><br><span class="line"> source[k++] = mat[i][j];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span>[][] result = <span class="keyword">new</span> <span class="title class_">int</span>[r][c];</span><br><span class="line"> k = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < r; i++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j < c; j++) {</span><br><span class="line"> result[i][j] = source[k++];</span><br><span class="line"> }</span><br><span class="line"> }</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></pre></td></tr></table></figure><hr><p><strong>2.二维数组的一维表示</strong></p><p>对于 $$ m×n $$ 的数组,映射为一维数组<code>[0,mn)</code>:</p><p>$$ (i,j) = i × n + j$$,</p><p>而对于一维数组中的<code>x</code>,映射到二维数组</p><p>$$ \begin{cases} i = x <del>/</del>n \ j = x % n \end{cases}$$ ,</p><p>剩下的思路和第一种方法一样,先转换为一维数组再变为二维数组</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(m * n)</li><li>空间复杂度:O(1)</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span>[][] matrixReshape(<span class="type">int</span>[][] mat, <span class="type">int</span> r, <span class="type">int</span> c) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">m</span> <span class="operator">=</span> mat.length;</span><br><span class="line"> <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> mat[<span class="number">0</span>].length;</span><br><span class="line"> <span class="type">int</span> <span class="variable">total</span> <span class="operator">=</span> m * n;</span><br><span class="line"> <span class="keyword">if</span> (total != r * c) {</span><br><span class="line"> <span class="keyword">return</span> mat;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span>[][] result = <span class="keyword">new</span> <span class="title class_">int</span>[r][c];</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> <span class="number">0</span>; x < total; x++) {</span><br><span class="line"> result[x / c][x % c] = mat[x / n][x % n];</span><br><span class="line"> }</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></pre></td></tr></table></figure><hr><h2 id="118-杨辉三角"><a href="#118-杨辉三角" class="headerlink" title="118. 杨辉三角"></a><a href="https://leetcode-cn.com/problems/pascals-triangle/">118. 杨辉三角</a></h2><p>给定一个非负整数 <code>numRows</code>,生成杨辉三角的前 <code>numRows</code> 行。</p><p><img src="https://gitee.com/xunle1/drawing-bed/raw/master/typora%5C/20210712132756.gif" alt="PascalTriangleAnimated2"></p><p>在杨辉三角中,每个数是它左上方和右上方的数的和。</p><h3 id="解题思路-7"><a href="#解题思路-7" class="headerlink" title="解题思路"></a>解题思路</h3><p>根据图示可知,从第三行开始,除了第一列和最后一列,第<code>i</code>行第<code>j</code>列应该等于第<code>i-1</code>行的<code>j-1 + j</code></p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(numRows ^ 2)</li><li>空间复杂度:O(1)</li></ul><p><strong>参考代码</strong></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="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> List<List<Integer>> <span class="title function_">generate</span><span class="params">(<span class="type">int</span> numRows)</span> {</span><br><span class="line"> List<List<Integer>> list = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < numRows; i++) {</span><br><span class="line"> List<Integer> innerList = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j < i + <span class="number">1</span>; j++) {</span><br><span class="line"> <span class="keyword">if</span> (j == <span class="number">0</span>) {</span><br><span class="line"> innerList.add(<span class="number">1</span>);</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (j == i) {</span><br><span class="line"> innerList.add(<span class="number">1</span>);</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> innerList.add(list.get(i-<span class="number">1</span>).get(j-<span class="number">1</span>) + list.get(i-<span class="number">1</span>).get(j));</span><br><span class="line"> }</span><br><span class="line"> list.add(innerList);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> list;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="387-字符串中的第一个唯一字符"><a href="#387-字符串中的第一个唯一字符" class="headerlink" title="387. 字符串中的第一个唯一字符"></a><a href="https://leetcode-cn.com/problems/first-unique-character-in-a-string/">387. 字符串中的第一个唯一字符</a></h2><p>给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。</p><p><strong>示例:</strong></p><figure class="highlight plaintext"><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">s = "leetcode"</span><br><span class="line">返回 0</span><br><span class="line"></span><br><span class="line">s = "loveleetcode"</span><br><span class="line">返回 2</span><br></pre></td></tr></table></figure><h3 id="解题思路-8"><a href="#解题思路-8" class="headerlink" title="解题思路"></a>解题思路</h3><p><strong>二次遍历</strong></p><p>第一次遍历记录<code>s</code>中的字符出现次数,第二次遍历返回较小的索引值</p><p><strong>复杂度</strong></p><ul><li>时间复杂度:O(n),n 为<code>s</code>长度,遍历两次</li><li>空间复杂度:O(1),需要一个 26 个字母的字典</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">firstUniqChar</span><span class="params">(String s)</span> {</span><br><span class="line"> <span class="type">int</span>[] table = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">26</span>];</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < s.length(); i++) {</span><br><span class="line"> table[s.charAt(i) - <span class="string">'a'</span>]++;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < s.length(); i++) {</span><br><span class="line"> <span class="keyword">if</span> (table[s.charAt(i) - <span class="string">'a'</span>] == <span class="number">1</span>) <span class="keyword">return</span> i;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="98-验证二叉搜索树"><a href="#98-验证二叉搜索树" class="headerlink" title="98. 验证二叉搜索树"></a><a href="https://leetcode-cn.com/problems/validate-binary-search-tree/">98. 验证二叉搜索树</a></h2><p>给定一个二叉树,判断其是否是一个有效的二叉搜索树。</p><p>假设一个二叉搜索树具有如下特征:</p><ul><li>节点的左子树只包含<strong>小于</strong>当前节点的数。</li><li>节点的右子树只包含<strong>大于</strong>当前节点的数。</li><li>所有左子树和右子树自身必须也是二叉搜索树。</li></ul><h3 id="解题思路-9"><a href="#解题思路-9" class="headerlink" title="解题思路"></a>解题思路</h3><p><strong>1.递归</strong></p><p>这道题容易出错的地方在于只根据当前节点判断左儿子和右儿子是否满足条件,忽略左子树中<strong>所有节点</strong>都必须小于当前节点,右子树中<strong>所有节点</strong>都必须大于当前节点。故设计一个函数<code>isValid(root, lower, upper)</code>,判断<code>root</code>的节点值是否满足<code>lower < root.val < upper</code>。</p><p><strong>复杂度</strong></p><ul><li>时间复杂度:O(n)</li><li>空间复杂度:O(n)</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isValidBST</span><span class="params">(TreeNode root)</span> {</span><br><span class="line"> <span class="type">Long</span> <span class="variable">lower</span> <span class="operator">=</span> Long.MIN_VALUE, upper = Long.MAX_VALUE;</span><br><span class="line"> <span class="keyword">return</span> isValid(root, lower, upper);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isValid</span><span class="params">(TreeNode root, <span class="type">long</span> lower, <span class="type">long</span> upper)</span> {</span><br><span class="line"> <span class="keyword">if</span> (root == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">if</span> (root.val <= lower || root.val >= upper) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> <span class="comment">//对于左子树,上界是当前节点;对于右子树,下界是当前节点</span></span><br><span class="line"> <span class="keyword">return</span> isValid(root.left, lower, root.val) && isValid(root.right, root.val, upper);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><p><strong>2.中序遍历</strong></p><p>二叉搜索树的中序遍历是个有序序列,可以根据中序遍历判断该序列是否有序从而判断是否为二叉搜索树</p><p><strong>复杂度</strong></p><ul><li>时间复杂度:O(n)</li><li>空间复杂度:O(n)</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span>{</span><br><span class="line"> <span class="type">long</span> <span class="variable">pre</span> <span class="operator">=</span> Long.MIN_VALUE;</span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isValidBST</span><span class="params">(TreeNode root)</span> {</span><br><span class="line"> <span class="keyword">return</span> inorder(root);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">inorder</span><span class="params">(TreeNode root)</span> {</span><br><span class="line"> <span class="keyword">if</span> (root == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"> <span class="type">boolean</span> <span class="variable">l</span> <span class="operator">=</span> inorder(root.left);</span><br><span class="line"> <span class="keyword">if</span> (root.val <= pre) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> pre = root.val;</span><br><span class="line"> <span class="type">boolean</span> <span class="variable">r</span> <span class="operator">=</span> inorder(root.right);</span><br><span class="line"> <span class="keyword">return</span> l && r;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>迭代</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isValidBST</span><span class="params">(TreeNode root)</span> {</span><br><span class="line"> LinkedList<TreeNode> stack = <span class="keyword">new</span> <span class="title class_">LinkedList</span><>();</span><br><span class="line"> <span class="type">long</span> <span class="variable">pre</span> <span class="operator">=</span> Long.MIN_VALUE;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(!stack.isEmpty() || root != <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">while</span> (root != <span class="literal">null</span>) {</span><br><span class="line"> stack.push(root);</span><br><span class="line"> root = root.left;</span><br><span class="line"> }</span><br><span class="line"> root = stack.pop();</span><br><span class="line"> <span class="keyword">if</span> (root.val <= pre) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> pre = root.val;</span><br><span class="line"> root = root.right;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="230-二叉搜索树中第-K-小的元素"><a href="#230-二叉搜索树中第-K-小的元素" class="headerlink" title="230. 二叉搜索树中第 K 小的元素"></a><a href="https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/">230. 二叉搜索树中第 K 小的元素</a></h2><p>给定一个二叉搜索树的根节点 <code>root</code> ,和一个整数 <code>k</code> ,请你设计一个算法查找其中第 <code>k</code> 个最小元素(从 1 开始计数)。</p><p><strong>示例 1:</strong></p><p><img src="https://assets.leetcode.com/uploads/2021/01/28/kthtree1.jpg" alt="img"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:root = [3,1,4,null,2], k = 1</span><br><span class="line">输出:1</span><br></pre></td></tr></table></figure><p><strong>示例 2:</strong></p><p><img src="https://assets.leetcode.com/uploads/2021/01/28/kthtree2.jpg" alt="img"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:root = [5,3,6,2,4,null,null,1], k = 3</span><br><span class="line">输出:3</span><br></pre></td></tr></table></figure><h3 id="解题思路-10"><a href="#解题思路-10" class="headerlink" title="解题思路"></a>解题思路</h3><p>**二叉搜索树(BST)**的特点:</p><ul><li>每个节点的左儿子比父节点小,右儿子比父节点大</li><li>每个节点的左子树和右子树也是二叉搜索树</li><li>二叉搜索树中序遍历的序列是一个升序序列</li></ul><p>为了找到树中第<code>K</code>小的元素,可以按照中序遍历得到升序序列,第<code>k-1</code>个元素就是第<code>k</code>小的节点</p><p>采用<strong>迭代</strong>和<strong>递归</strong>两种方法实现</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(n)</li><li>空间复杂度:O(n)</li></ul><p><strong>参考代码:</strong></p><p><strong>迭代</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">kthSmallest</span><span class="params">(TreeNode root, <span class="type">int</span> k)</span> {</span><br><span class="line"> LinkedList<TreeNode> stack = <span class="keyword">new</span> <span class="title class_">LinkedList</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> (<span class="literal">true</span>) {</span><br><span class="line"> <span class="keyword">while</span> (root != <span class="literal">null</span>) {</span><br><span class="line"> stack.push(root);</span><br><span class="line"> root = root.left;</span><br><span class="line"> }</span><br><span class="line"> root = stack.pop();</span><br><span class="line"> <span class="comment">//弹出一个节点,k-1,当k等于0时节点值就是第k小的节点</span></span><br><span class="line"> <span class="keyword">if</span> (--k == <span class="number">0</span>) <span class="keyword">return</span> root.val;</span><br><span class="line"> root = root.right;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>递归</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">res</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="type">int</span> <span class="variable">rank</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">kthSmallest</span><span class="params">(TreeNode root, <span class="type">int</span> k)</span> {</span><br><span class="line"> traverse(root, k);</span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">traverse</span><span class="params">(TreeNode root, <span class="type">int</span> k)</span> {</span><br><span class="line"> <span class="keyword">if</span> (root == <span class="literal">null</span>) <span class="keyword">return</span> ;</span><br><span class="line"> traverse(root.left, k);</span><br><span class="line"> rank++;</span><br><span class="line"> <span class="keyword">if</span> (rank == k) {</span><br><span class="line"> res = root.val;</span><br><span class="line"> <span class="keyword">return</span> ;</span><br><span class="line"> }</span><br><span class="line"> traverse(root.right, k);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="538-把二叉搜索树转换为累加树"><a href="#538-把二叉搜索树转换为累加树" class="headerlink" title="538. 把二叉搜索树转换为累加树"></a><a href="https://leetcode-cn.com/problems/convert-bst-to-greater-tree/">538. 把二叉搜索树转换为累加树</a></h2><p>给出二叉 <strong>搜索</strong> 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 <code>node</code> 的新值等于原树中大于或等于 <code>node.val</code> 的值之和。</p><p><strong>示例 1:</strong></p><p><img src="https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/05/03/tree.png" alt="img"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]</span><br><span class="line">输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]</span><br></pre></td></tr></table></figure><h3 id="解题思路-11"><a href="#解题思路-11" class="headerlink" title="解题思路"></a>解题思路</h3><p>反向遍历树,得到降序的序列,使用全局变量<code>sum</code>记录节点值</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(n)</li><li>空间复杂度:O(n)</li></ul><p><strong>参考代码:</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span>{</span><br><span class="line"> <span class="keyword">public</span> TreeNode <span class="title function_">convertBST</span><span class="params">(TreeNode root)</span> {</span><br><span class="line"> traverse(root);</span><br><span class="line"> <span class="keyword">return</span> root;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">traverse</span><span class="params">(TreeNode root)</span> {</span><br><span class="line"> <span class="keyword">if</span> (root == <span class="literal">null</span>) <span class="keyword">return</span> ;</span><br><span class="line"> <span class="comment">//反向遍历</span></span><br><span class="line"> traverse(root.right);</span><br><span class="line"> sum += root.val;</span><br><span class="line"> root.val = sum;</span><br><span class="line"> traverse(root.left);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="剑指-Offer-II-024-反转链表"><a href="#剑指-Offer-II-024-反转链表" class="headerlink" title="剑指 Offer II 024. 反转链表"></a><a href="https://leetcode-cn.com/problems/UHnkqh/">剑指 Offer II 024. 反转链表</a></h2><p>给定单链表的头节点 <code>head</code> ,请反转链表,并返回反转后的链表的头节点。</p><p><strong>示例 1:</strong></p><p><img src="https://gitee.com/xunle1/drawing-bed/raw/master/typora%5C/20211102143406.jpeg" alt="img"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:head = [1,2,3,4,5]</span><br><span class="line">输出:[5,4,3,2,1]</span><br></pre></td></tr></table></figure><h3 id="解题思路-12"><a href="#解题思路-12" class="headerlink" title="解题思路"></a>解题思路</h3><p><strong>1.迭代</strong></p><p>从链表头部开始一个一个改变<code>next</code>指针指向。操作过程如下:</p><ol><li>保存当前节点的下一个节点</li><li>改变当前节点<code>next</code>指向前一个节点</li><li>向后移动当前节点</li><li>向后移动前一个节点</li></ol><p><strong>参考代码:</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ListNode <span class="title function_">reverseList</span><span class="params">(ListNode head)</span> {</span><br><span class="line"><span class="keyword">if</span> (head == <span class="literal">null</span> || head.next == <span class="literal">null</span>) <span class="keyword">return</span> head;</span><br><span class="line"><span class="type">ListNode</span> <span class="variable">pre</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"><span class="type">ListNode</span> <span class="variable">cur</span> <span class="operator">=</span> head;</span><br><span class="line"><span class="type">ListNode</span> <span class="variable">nxt</span> <span class="operator">=</span> cur;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (cur != <span class="literal">null</span>) {</span><br><span class="line">nxt = cur.next;</span><br><span class="line">cur.next = pre;</span><br><span class="line">pre = cur;</span><br><span class="line">cur = nxt;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> pre;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>2.递归</strong></p><p><strong>参考代码:</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ListNode <span class="title function_">reverseList</span><span class="params">(ListNode head)</span> {</span><br><span class="line"> <span class="keyword">if</span> (head == <span class="literal">null</span> || head.next == <span class="literal">null</span>) <span class="keyword">return</span> head;</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">last</span> <span class="operator">=</span> reverseList(head.next);</span><br><span class="line"> head.next.next = head;</span><br><span class="line"> head.next = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">return</span> last;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="92-反转链表-II"><a href="#92-反转链表-II" class="headerlink" title="92. 反转链表 II"></a><a href="https://leetcode-cn.com/problems/reverse-linked-list-ii/">92. 反转链表 II</a></h2><p>给你单链表的头指针 <code>head</code> 和两个整数 <code>left</code> 和 <code>right</code> ,其中 <code>left <= right</code> 。请你反转从位置 <code>left</code> 到位置 <code>right</code> 的链表节点,返回 <strong>反转后的链表</strong> 。</p><p><strong>示例 1:</strong></p><p><img src="https://gitee.com/xunle1/drawing-bed/raw/master/typora%5C/20211108155058.jpeg" alt="img"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:head = [1,2,3,4,5], left = 2, right = 4</span><br><span class="line">输出:[1,4,3,2,5]</span><br></pre></td></tr></table></figure><h3 id="解题思路-13"><a href="#解题思路-13" class="headerlink" title="解题思路"></a>解题思路</h3><ul><li>先找到反转开始节点<code>leftNode</code>,保存反转开始节点的前一个节点<code>prev</code>。</li><li>反转<code>[leftNode,rightNode]</code>,保存<code>rightNode</code>的下一个节点<code>next</code>。</li><li><code>prev.next</code>指向<code>rightNode</code>,<code>leftNode.next</code>指向<code>next</code>。</li><li>返回头节点</li></ul><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"> <span class="keyword">public</span> ListNode <span class="title function_">reverseBetween</span><span class="params">(ListNode head, <span class="type">int</span> left, <span class="type">int</span> right)</span> {</span><br><span class="line"> <span class="comment">//头节点可能会变化,定义伪头节点</span></span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">dummy</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ListNode</span>();</span><br><span class="line"> dummy.next = head;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//找到leftNode</span></span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">leftNode</span> <span class="operator">=</span> dummy.next;</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">prev</span> <span class="operator">=</span> dummy;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i < left; i++) {</span><br><span class="line"> prev = leftNode;</span><br><span class="line"> leftNode = leftNode.next;</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="type">ListNode</span> <span class="variable">pre</span> <span class="operator">=</span> prev, cur = leftNode, nxt = cur;</span><br><span class="line"> <span class="keyword">while</span> (left <= right && cur != <span class="literal">null</span>) {</span><br><span class="line"> nxt = cur.next;</span><br><span class="line"> cur.next = pre;</span><br><span class="line"> pre = cur;</span><br><span class="line"> cur = nxt;</span><br><span class="line"> left++;</span><br><span class="line"> }</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">next</span> <span class="operator">=</span> cur;</span><br><span class="line"></span><br><span class="line"> prev.next = pre;</span><br><span class="line"> leftNode.next = next;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> dummy.next;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="剑指-Offer-II-027-回文链表"><a href="#剑指-Offer-II-027-回文链表" class="headerlink" title="剑指 Offer II 027. 回文链表"></a><a href="https://leetcode-cn.com/problems/aMhZSa/">剑指 Offer II 027. 回文链表</a></h2><p>给定一个链表的 <strong>头节点</strong> <code>head</code> <strong>,</strong>请判断其是否为回文链表。</p><p>如果一个链表是回文,那么链表节点序列从前往后看和从后往前看是相同的。</p><p><strong>示例 1:</strong></p><p><img src="https://gitee.com/xunle1/drawing-bed/raw/master/typora%5C/20211102135326.png" alt="img"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入: head = [1,2,3,3,2,1]</span><br><span class="line">输出: true</span><br></pre></td></tr></table></figure><h3 id="解题思路-14"><a href="#解题思路-14" class="headerlink" title="解题思路"></a>解题思路</h3><p><strong>1.快慢指针</strong></p><p>先找到链表中间节点,对链表后半部分进行反转,再一一比较。</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(n)</li><li>空间复杂度:O(n)或者 O(1),取决于使用递归还是迭代</li></ul><p><strong>参考代码:</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isPalindrome</span><span class="params">(ListNode head)</span> {</span><br><span class="line"><span class="keyword">if</span> (head.next == <span class="literal">null</span> || head == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"><span class="type">ListNode</span> <span class="variable">fast</span> <span class="operator">=</span> head.next;</span><br><span class="line"><span class="type">ListNode</span> <span class="variable">slow</span> <span class="operator">=</span> head;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (fast.next != <span class="literal">null</span> && fast.next.next != <span class="literal">null</span>) {</span><br><span class="line">fast = fast.next.next;</span><br><span class="line">slow = slow.next;</span><br><span class="line">}</span><br><span class="line"><span class="type">ListNode</span> <span class="variable">mid</span> <span class="operator">=</span> reverse(slow.next);</span><br><span class="line"><span class="keyword">while</span> (mid != <span class="literal">null</span>) {</span><br><span class="line"><span class="keyword">if</span> (head.val != mid.val) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">mid = mid.next;</span><br><span class="line">head = head.next;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</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="keyword">public</span> ListNode <span class="title function_">reverse</span><span class="params">(ListNode head)</span> {</span><br><span class="line"><span class="keyword">if</span> (head.next == <span class="literal">null</span>) <span class="keyword">return</span> head;</span><br><span class="line"><span class="type">ListNode</span> <span class="variable">ret</span> <span class="operator">=</span> reverse(head.next);</span><br><span class="line">head.next.next = head;</span><br><span class="line">head.next = <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">return</span> ret;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="剑指-Offer-II-023-两个链表的第一个重合节点"><a href="#剑指-Offer-II-023-两个链表的第一个重合节点" class="headerlink" title="剑指 Offer II 023. 两个链表的第一个重合节点"></a><a href="https://leetcode-cn.com/problems/3u1WK4/">剑指 Offer II 023. 两个链表的第一个重合节点</a></h2><p>给定两个单链表的头节点 headA 和 headB ,请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。</p><p>图示两个链表在节点 c1 开始相交:</p><p><img src="https://gitee.com/xunle1/drawing-bed/raw/master/typora%5C/20211102150638.png" alt="img"></p><h3 id="解题思路-15"><a href="#解题思路-15" class="headerlink" title="解题思路"></a>解题思路</h3><p><strong>1.哈希集合</strong></p><p>将单链表 A 中的节点放入集合中,遍历单链表 B 判断集合中是否存在相交节点</p><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(m+n)</li><li>空间复杂度:O(m)</li></ul><p><strong>参考代码:</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ListNode <span class="title function_">getIntersectionNode</span><span class="params">(ListNode headA, ListNode headB)</span> {</span><br><span class="line">Set<ListNode> set = <span class="keyword">new</span> <span class="title class_">HashSet</span><>();</span><br><span class="line"><span class="keyword">while</span> (headA != <span class="literal">null</span>) {</span><br><span class="line">set.add(headA);</span><br><span class="line">headA = headA.next;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">while</span> (headB != <span class="literal">null</span>) {</span><br><span class="line"><span class="keyword">if</span> (set.contains(headB)) <span class="keyword">return</span> headB;</span><br><span class="line">headB = headB.next;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>2.双指针</strong></p><p>定义两个指针<code>pA</code>和<code>pB</code>分别指向单链表 A 和 B 的头部。则会出现三种情况:</p><ol><li><p>两个链表有交点,且 A 和 B 的头节点到交点步数相同</p><p><img src="https://gitee.com/xunle1/drawing-bed/raw/master/typora%5C/20211102151431.png" alt="image.png"></p><p>则易得<code>pA</code>和<code>pB</code>第一次遍历到达交点</p></li><li><p>两个链表有交点,但 A 和 B 的头节点到交点步数不同</p><p><img src="https://gitee.com/xunle1/drawing-bed/raw/master/typora%5C/20211102151454.png" alt="image.png"></p><p>当<code>pA</code>遍历完 A 链表后继续遍历 B,经过<strong>a+c+b</strong>步到达交点;同样的<code>pB</code>经过<code>b+c+a</code>步到达交点。</p></li><li><p>两个链表没有交点。</p><p><img src="https://gitee.com/xunle1/drawing-bed/raw/master/typora%5C/20211102152034.png" alt="image.png"></p><p><code>pA</code>和<code>pB</code>最终都指向 null,但经过的步数也是相同的。</p></li></ol><p><strong>复杂度:</strong></p><ul><li>时间复杂度:O(m+n)</li><li>空间复杂度:O(1)</li></ul><p><strong>参考代码:</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ListNode <span class="title function_">getIntersectionNode</span><span class="params">(ListNode headA, ListNode headB)</span> {</span><br><span class="line"><span class="type">ListNode</span> <span class="variable">pA</span> <span class="operator">=</span> headA;</span><br><span class="line"><span class="type">ListNode</span> <span class="variable">pB</span> <span class="operator">=</span> headB;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (pA != pB) {</span><br><span class="line">pA = pA == <span class="literal">null</span> ? headB : pA.next;</span><br><span class="line">pB = pB == <span class="literal">null</span> ? headA : pB.next;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> pA;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="剑指-Offer-25-合并两个排序的链表"><a href="#剑指-Offer-25-合并两个排序的链表" class="headerlink" title="剑指 Offer 25. 合并两个排序的链表"></a><a href="https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/">剑指 Offer 25. 合并两个排序的链表</a></h2><p>输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。</p><p><strong>示例 1:</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:1->2->4, 1->3->4</span><br><span class="line">输出:1->1->2->3->4->4</span><br></pre></td></tr></table></figure><h3 id="解题思路-16"><a href="#解题思路-16" class="headerlink" title="解题思路"></a><strong>解题思路</strong></h3><p>使用伪头节点连接两个链表</p><p><strong>参考代码:</strong></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="keyword">public</span> ListNode <span class="title function_">mergeTwoLists</span><span class="params">(ListNode l1, ListNode l2)</span> {</span><br><span class="line"><span class="type">ListNode</span> <span class="variable">head</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ListNode</span>();</span><br><span class="line"><span class="type">ListNode</span> <span class="variable">dummy</span> <span class="operator">=</span> head;</span><br><span class="line"><span class="keyword">while</span> (l1 != <span class="literal">null</span> && l2 != <span class="literal">null</span>) {</span><br><span class="line"><span class="keyword">if</span> (l1.val > l2.val) {</span><br><span class="line">dummy.next = l2;</span><br><span class="line">l2 = l2.next;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line">dummy.next = l1;</span><br><span class="line">l1 = l1.next;</span><br><span class="line">}</span><br><span class="line">dummy = dummy.next;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (l1 == <span class="literal">null</span>) {</span><br><span class="line">dummy.next = l2;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line">dummy.next = l1;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> head;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="1171-从链表中删去总和值为零的连续节点"><a href="#1171-从链表中删去总和值为零的连续节点" class="headerlink" title="1171. 从链表中删去总和值为零的连续节点"></a><a href="https://leetcode-cn.com/problems/remove-zero-sum-consecutive-nodes-from-linked-list/">1171. 从链表中删去总和值为零的连续节点</a></h2><p>给你一个链表的头节点 <code>head</code>,请你编写代码,反复删去链表中由 <strong>总和</strong> 值为 <code>0</code> 的连续节点组成的序列,直到不存在这样的序列为止。</p><p>删除完毕后,请你返回最终结果链表的头节点。</p><p><strong>示例 1:</strong></p><figure class="highlight plaintext"><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">输入:head = [1,2,-3,3,1]</span><br><span class="line">输出:[3,1]</span><br><span class="line">提示:答案 [1,2,1] 也是正确的。</span><br></pre></td></tr></table></figure><h3 id="解题思路-17"><a href="#解题思路-17" class="headerlink" title="解题思路"></a>解题思路</h3><p>定义两个指针<code>pre</code>和<code>p</code>,<code>pre</code>用于保存节点方便删除区间,<code>p</code>遍历链表找到前缀和为 0 的节点将其删去。</p><p><code>pre</code>一开始指向伪头节点,只有当<code>p</code>走到结尾时才移动到下一个节点,而每当<code>p</code>走到前缀和<code>sum=0</code>时,更新<code>pre</code>的指向<code>pre.next = p.next</code></p><p><strong>参考代码</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Solution</span>{</span><br><span class="line"> <span class="keyword">public</span> ListNode <span class="title function_">removeZeroSumSublists</span><span class="params">(ListNode head)</span> {</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">dummy</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ListNode</span>(<span class="number">0</span>);</span><br><span class="line"> dummy.next = head;</span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">pre</span> <span class="operator">=</span> dummy;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (pre != <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">//指针p指向pre后面的节点,计算前缀和</span></span><br><span class="line"> <span class="type">ListNode</span> <span class="variable">p</span> <span class="operator">=</span> pre.next;</span><br><span class="line"> <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (p != <span class="literal">null</span>) {</span><br><span class="line"></span><br><span class="line"> sum += p.val;</span><br><span class="line"> <span class="comment">//如果此时前缀和为0,删去(pre,p]区间的节点</span></span><br><span class="line"> <span class="keyword">if</span> (sum == <span class="number">0</span>) {</span><br><span class="line"> pre.next = p.next;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> p = p.next;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//当p走到结尾时,pre移动到下一个节点,否则不移动。</span></span><br><span class="line"> <span class="keyword">if</span> (p == <span class="literal">null</span>) pre = pre.next;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> dummy.next;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="232-用栈实现队列"><a href="#232-用栈实现队列" class="headerlink" title="232. 用栈实现队列"></a><a href="https://leetcode-cn.com/problems/implement-queue-using-stacks/">232. 用栈实现队列</a></h2><p>请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(<code>push</code>、<code>pop</code>、<code>peek</code>、<code>empty</code>):</p><h3 id="解题思路-18"><a href="#解题思路-18" class="headerlink" title="解题思路"></a>解题思路</h3><p>解题关键在于如何保证入队的元素插入到<strong>设计的结构的尾部</strong>即<strong>栈的底部</strong>,出队的元素在<strong>栈的顶部</strong>。</p><p>可以将一个栈设置为<strong>入队栈</strong>,入队元素都进入这个栈;另一个栈为<strong>出队栈</strong>。</p><ul><li><strong>入队:入队栈</strong>直接<code>push</code></li><li><strong>出队:</strong>当<strong>出队栈</strong>为空时,需要将<strong>入队栈</strong>中的元素一个一个<code>pop</code>到<strong>出队栈</strong>中,这样可以保证<strong>入队栈</strong>中最后进入的元素被<code>push</code>到<strong>出队栈的底部</strong>,最先进入的元素进入<strong>出队栈</strong>的头部;当<strong>出队栈</strong>不为空时,出队栈直接<code>pop</code>。</li></ul><p><strong>示例代码:</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyQueue</span> {</span><br><span class="line">Stack<Integer> inStack;</span><br><span class="line"> Stack<Integer> outStack;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">MyQueue</span><span class="params">()</span> {</span><br><span class="line">inStack = <span class="keyword">new</span> <span class="title class_">Stack</span><>();</span><br><span class="line"> outStack = <span class="keyword">new</span> <span class="title class_">Stack</span><>();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">push</span><span class="params">(<span class="type">int</span> x)</span> {</span><br><span class="line">inStack.push(x);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">pop</span><span class="params">()</span> {</span><br><span class="line"><span class="keyword">if</span> (outStack.isEmpty()) {</span><br><span class="line"> <span class="keyword">while</span> (!inStack.isEmpty()) {</span><br><span class="line"> outStack.push(inStack.pop());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> outStack.pop();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">peek</span><span class="params">()</span> {</span><br><span class="line"><span class="keyword">if</span> (outStack.isEmpty()) {</span><br><span class="line"> <span class="keyword">while</span> (!inStack.isEmpty()) {</span><br><span class="line"> outStack.push(inStack.pop());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> outStack.peek();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">empty</span><span class="params">()</span> {</span><br><span class="line"><span class="keyword">return</span> inStack.isEmpty() && outStack.isEmpty();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr>]]></content>
<categories>
<category> 算法题 </category>
</categories>
<tags>
<tag> leetcode </tag>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>Spring Boot 集成 Swagger</title>
<link href="/2021/07/06/SpringBoot%E9%9B%86%E6%88%90Swagger/"/>
<url>/2021/07/06/SpringBoot%E9%9B%86%E6%88%90Swagger/</url>
<content type="html"><![CDATA[<h1 id="SpringBoot集成Swagger"><a href="#SpringBoot集成Swagger" class="headerlink" title="SpringBoot集成Swagger"></a>SpringBoot集成Swagger</h1><p>这是我参与新手入门的第一篇文章</p><hr><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在Web后端人员开发接口时,需要对接口进行测试,这是我们可以使用到今天要提到的工具Swagger。</p><h2 id="Swagger是什么?"><a href="#Swagger是什么?" class="headerlink" title="Swagger是什么?"></a>Swagger是什么?</h2><ul><li>Swagger官网网址:<a href="https://swagger.io/">API Documentation & Design Tools for Teams | Swagger</a></li></ul><p>进入官网我们就可以看见官方对Swagger的简介</p><blockquote><p> Simplify API development for users, teams, and enterprises with the Swagger open source and professional toolset. Find out how Swagger can help you design and document your APIs at scale.</p></blockquote><p>简言之就是帮助用户简化API开发的工具,它可以将API可视化从而方便开发人员进行设计于调试。</p><p>由于笔者主要使用Java,故今天想要介绍的是在SpringBoot中如何集成Swagger。</p><h2 id="新建SpringBoot项目并引入依赖"><a href="#新建SpringBoot项目并引入依赖" class="headerlink" title="新建SpringBoot项目并引入依赖"></a>新建SpringBoot项目并引入依赖</h2><ul><li><p>将Swagger的依赖引入SpringBoot项目</p><figure class="highlight xml"><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="comment"><!-- https://mvnrepository.com/artifact/io.springfox/springfox-boot-starter --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">groupId</span>></span>io.springfox<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>springfox-boot-starter<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">version</span>></span>3.0.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></li><li><p>项目结构如图</p></li><li><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206154111.png" alt="image-20210706155932304"></p></li></ul><h2 id="新建Swagger配置类"><a href="#新建Swagger配置类" class="headerlink" title="新建Swagger配置类"></a>新建Swagger配置类</h2><p>Swagger配置类位置如上图</p><p>Swagger配置类用于配置Swagger扫描的接口、作者的信息</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> xunle</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@EnableSwagger2</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SwaggerConfig</span> {</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 配置Swagger的信息</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> ApiInfo <span class="title function_">getApiInfo</span><span class="params">()</span> {</span><br><span class="line"> <span class="comment">//作者信息</span></span><br><span class="line"> <span class="type">Contact</span> <span class="variable">contact</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Contact</span>(<span class="string">"xunle"</span>,<span class="string">"https://xunle1.github.io/"</span>,<span class="string">"1601315809@qq.com"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ApiInfoBuilder</span>()</span><br><span class="line"> <span class="comment">//标题</span></span><br><span class="line"> .title(<span class="string">"Swagger Test"</span>)</span><br><span class="line"> <span class="comment">//描述信息</span></span><br><span class="line"> .description(<span class="string">"SpringBoot集成Swagger"</span>)</span><br><span class="line"> <span class="comment">//版本号</span></span><br><span class="line"> .version(<span class="string">"v1.0"</span>)</span><br><span class="line"> <span class="comment">//作者信息</span></span><br><span class="line"> .contact(contact)</span><br><span class="line"> .build();</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"> * 用于配置Swagger扫描接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="keyword">public</span> Docket <span class="title function_">docket</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Docket</span>(DocumentationType.SWAGGER_2)</span><br><span class="line"> .apiInfo(getApiInfo())</span><br><span class="line"> .select()</span><br><span class="line"> <span class="comment">//apis用于配置扫描哪些接口</span></span><br><span class="line"> .apis(RequestHandlerSelectors.basePackage(<span class="string">"com.example.swagger.controller"</span>))</span><br><span class="line"> <span class="comment">//paths用于配置路径选择</span></span><br><span class="line"> .paths(PathSelectors.ant(<span class="string">"/hello/**"</span>))</span><br><span class="line"> .build()</span><br><span class="line"> <span class="comment">//是否开启Swagger</span></span><br><span class="line"> .enable(<span class="literal">true</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="新建Controller"><a href="#新建Controller" class="headerlink" title="新建Controller"></a>新建Controller</h2><p>Controller位置如上图</p><p>Controller类中定义接口</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"> * <span class="doctag">@author</span> xunle</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="comment">//Api 类信息</span></span><br><span class="line"><span class="meta">@Api(tags = "Swagger测试Controller")</span></span><br><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SwaggerController</span> {</span><br><span class="line"> <span class="comment">//ApiOperation 方法描述</span></span><br><span class="line"> <span class="meta">@ApiOperation("Swagger测试Get方法")</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="meta">@GetMapping("/hello/test")</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">test</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Swagger测试"</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@ApiOperation("Swagger是否扫描路径")</span></span><br><span class="line"> <span class="meta">@ResponseBody</span></span><br><span class="line"> <span class="meta">@GetMapping("/hi/test")</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">testPathSelector</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"selected!"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Swagger-ui基本信息"><a href="#Swagger-ui基本信息" class="headerlink" title="Swagger-ui基本信息"></a>Swagger-ui基本信息</h2><p>一般情况下访问 ==工程名/swagger-ui.html==</p><p>笔者本机访问 <a href="http://localhost:8080/swagger-ui.html">http://localhost:8080/swagger-ui.html</a></p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206154131.png" alt="image-20210706162908770"></p><p>前两步的配置都在Swagger-ui中显示出来了,可以对照着看配置对应的显示</p><p>注意Controller中配置的Path,第二个Get方法路径==/hi/test==不匹配Swagger配置类中的路径==/hello/**==,故不会显示在Swagger-ui中</p><h2 id="接口测试"><a href="#接口测试" class="headerlink" title="接口测试"></a>接口测试</h2><p>在点击对应API后</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206154136.png" alt="image-20210706163636565"></p><blockquote><ul><li>Parameters为参数列表</li><li>Response为返回信息<ul><li>Code:状态码</li><li>Description:访问成功时Example Value中显示数据类型,Model表示返回的是实体类</li></ul></li></ul></blockquote><p>点击Try it out后显示如下:</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206154139.png" alt="image-20210706164104575"></p><p>点击Execute进行接口测试后显示如下:</p><p><img src="https://xunle-picture-bed.oss-cn-hangzhou.aliyuncs.com/20221206154143.png" alt="image-20210706164349701"></p><blockquote><ul><li>Response body:返回信息</li><li>Response headers:响应头</li></ul></blockquote>]]></content>
<categories>
<category> 技术 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> Swagger </tag>
<tag> SpringBoot </tag>
</tags>
</entry>
<entry>
<title>tags</title>
<link href="/tags/index.html"/>
<url>/tags/index.html</url>
<content type="html"><![CDATA[]]></content>
</entry>
<entry>
<title>about</title>
<link href="/about/index.html"/>
<url>/about/index.html</url>
<content type="html"><![CDATA[<h2 id="荀乐"><a href="#荀乐" class="headerlink" title="荀乐"></a>荀乐</h2><p>这个名字的由来是玩《古剑奇谭网络版》的时候随机的,直接一个<del>偷</del>用。</p><ul><li>重邮计算机专业本科毕业生</li><li>熟悉 Web 后台开发</li><li>常用语言:Java、C++、Go</li></ul><h2 id="常用软件"><a href="#常用软件" class="headerlink" title="常用软件"></a>常用软件</h2><h3 id="macOS"><a href="#macOS" class="headerlink" title="macOS"></a>macOS</h3><ul><li><a href="https://www.u.tools/">uTools</a>: 工具合集</li><li><a href="https://www.snipaste.com/">Snipaste</a>: 截图</li><li><a href="https://kyome.io/runcat/index.html">Runcat</a>: 猫</li><li><a href="https://www.vmware.com/products/fusion.html">VMware Fusion</a>: 虚拟机</li><li><a href="https://github.com/alienator88/Pearcleaner">Pearcleaner</a>: 清理</li><li>[Clash X Pro] 代理</li></ul><h4 id="CLI"><a href="#CLI" class="headerlink" title="CLI"></a>CLI</h4><ul><li><a href="https://formulae.brew.sh/formula/bandwhich">bandwhich</a>: 网络监控</li><li><a href="https://formulae.brew.sh/formula/cloc">cloc</a>: 代码统计</li><li><a href="https://formulae.brew.sh/formula/dust">dust</a>: 文件大小展示</li><li><a href="https://formulae.brew.sh/formula/tree">tree</a>: 树形目录</li><li><a href="https://formulae.brew.sh/formula/bottom">bottom</a>: 系统监控</li><li><a href="https://formulae.brew.sh/formula/fastfetch">fastfetch</a>: 系统信息</li></ul><h3 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h3><ul><li><a href="https://www.u.tools/">uTools</a>: 快速启动</li><li><a href="https://learn.microsoft.com/en-us/sysinternals/downloads/tcpview">TcpView</a>: 查看连接</li><li><a href="https://www.jam-software.com/treesize">TreeSize</a>: 查看磁盘文件</li><li>Clash for Windows</li><li><a href="https://www.internetdownloadmanager.com/">IDM</a>: 下载</li><li><a href="https://github.com/TranslucentTB/TranslucentTB">TranslucentTB</a>: 任务栏美化</li><li><a href="https://hovancik.net/stretchly/">Stretchly</a>: 久坐提醒</li></ul><h3 id="笔记工具"><a href="#笔记工具" class="headerlink" title="笔记工具"></a>笔记工具</h3><ul><li><a href="https://obsidian.md/">Obsidian</a></li></ul><h3 id="Obsidian-插件"><a href="#Obsidian-插件" class="headerlink" title="Obsidian 插件"></a>Obsidian 插件</h3><ul><li><a href="https://github.com/denolehov/obsidian-git">Git</a></li><li><a href="https://github.com/kepano/obsidian-minimal">Minimal</a>: 主题</li><li><a href="https://github.com/kepano/obsidian-minimal-settings">Minimal Theme Settings</a>: 修改主题颜色</li><li><a href="https://github.com/kepano/obsidian-hider">Hider</a>: 隐藏工具栏</li><li><a href="https://github.com/davisriedel/obsidian-typewriter-mode">Typewriter Mode</a>: 打字机模式</li><li><a href="https://github.com/wakatime/obsidian-wakatime">Wakatime</a>: 记录时间</li><li><a href="https://excalidraw.com/">Excalidraw</a>: 画图</li></ul><h3 id="Chrome-插件"><a href="#Chrome-插件" class="headerlink" title="Chrome 插件"></a>Chrome 插件</h3><ul><li><a href="https://feeder.co/">RSS Feed Reader</a>: RSS 订阅</li><li><a href="https://github.com/gdh1995/vimium-c">Vimium C</a>: Vim 模拟</li><li><a href="https://www.wappalyzer.com/">Wappalyzer</a>: web 页面技术识别</li><li><a href="https://www.tampermonkey.net/">Tampermonkey</a>: 脚本运行工具</li><li><a href="https://chromewebstore.google.com/detail/zlibrary-searcher">ZLibrary Searcher</a>: 电子书</li></ul>]]></content>
</entry>
<entry>
<title>categories</title>
<link href="/categories/index.html"/>
<url>/categories/index.html</url>
<content type="html"><![CDATA[]]></content>
</entry>
</search>