Repository navigation
Expand file tree
/
Copy pathsearch.xml
More file actions
537 lines (258 loc) · 291 KB
/
Copy pathsearch.xml
File metadata and controls
537 lines (258 loc) · 291 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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>如何上传自己的工具类到MavenCentral里?</title>
<link href="/2023/09/21/how-to-publish-your-code-to-maven-central/"/>
<url>/2023/09/21/how-to-publish-your-code-to-maven-central/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近工作也比较忙,难得有时间闲下来在自己的博客中写篇文章。距离上次发表文章已经有 <strong>10个月了</strong>,故赶紧水一篇文章出来。</p><p>这篇文章将讲述如何将自己写的工具类或者开源框架打包发布到 <code>MavenCentral</code> 中,让自己的朋友或者同事能够远程使用你的代码。</p><div class="note blue icon modern"><i class="note-icon fas fa-bullhorn"></i><p>比如,在 <code>build.gradle</code> 中加入依赖:<code>implementation("io.github.cofbro:mvvmutils:1.0.0")</code>,就能够将你的 <code>jar包</code> 下载到本地使用。</p></div><p>☑️ 接下来,我将以成本最小、效率最高的方法带你完成整个过程,那么进入正题。</p><h2 id="准备好原材料"><a href="#准备好原材料" class="headerlink" title="准备好原材料"></a>准备好原材料</h2><p>现在你想把你写的完美无敌精妙的代码发布到 <code>MavenCentral</code> 中,首先需要上传到 <code>github</code> 中。</p><p><em>ps:这一步不会的同学请自行百度⚡</em></p><h2 id="注册一个-sonatype-账号"><a href="#注册一个-sonatype-账号" class="headerlink" title="注册一个 sonatype 账号"></a>注册一个 <code>sonatype</code> 账号</h2><p>进入官网 <a href="https://issues.sonatype.org/" title="赶紧注册吧">sonatype</a></p><p><img src="https://cofbro.github.io/2023/09/21/how-to-publish-your-code-to-maven-central/issue.png"><br>点击 <code>新建</code> ,然后 <code>问题类型</code> 选择 <strong>New Project</strong>,<code>概要</code> 就随便输一个相关的名字就ok。 </p><p><img src="https://cofbro.github.io/2023/09/21/how-to-publish-your-code-to-maven-central/issue_detail.png"><br>接下来:</p><ul><li><code>Group Id: io.github.<Your github username></code></li><li><code>Project URL: 这里就是你push到github仓库的地址</code></li><li><code>SCM url: <Project URL>.git</code> </li><li><code>Username(s): Username of your sonatype</code></li></ul><p>全部配置完后点击右下角完成。</p><h2 id="认证你的Github"><a href="#认证你的Github" class="headerlink" title="认证你的Github"></a>认证你的Github</h2><p>上述步骤完成后,会有相关人员在你的 <code>issue</code> 下进行评论。<br><img src="https://cofbro.github.io/2023/09/21/how-to-publish-your-code-to-maven-central/comment1.png"></p><p>大概意思是:</p><div class="note blue icon modern"><i class="note-icon fas fa-bullhorn"></i><p>1️⃣. 在你的 <code>Github</code> 账号下创建一个名为 <code>OSSRH-xxxxx</code> 的仓库以证明你是账号拥有者。</p><p>2️⃣. 完成后回复此评论。</p></div><p><strong>照做后,出现下述评论即代表通过验证</strong><br><img src="https://cofbro.github.io/2023/09/21/how-to-publish-your-code-to-maven-central/comment2.png"></p><h2 id="将代码推送到-Maven-Central"><a href="#将代码推送到-Maven-Central" class="headerlink" title="将代码推送到 Maven Central"></a>将代码推送到 <code>Maven Central</code></h2><p>在你想要上传至中央仓库的模块中的 <code>build.gradle</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><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><br><span class="line">task androidSourcesJar(type: Jar) {</span><br><span class="line"> archiveClassifier.set('sources')</span><br><span class="line"> from android.sourceSets.main.java.srcDirs</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 用于把aar打包到maven仓库的任务</span><br><span class="line">afterEvaluate {</span><br><span class="line"> publishing {</span><br><span class="line"> publications {</span><br><span class="line"> release(MavenPublication) {</span><br><span class="line"> from components.release</span><br><span class="line"> artifact androidSourcesJar</span><br><span class="line"> groupId = 'io.github.cofbro' // group id</span><br><span class="line"> artifactId = 'mvvmutils' // 库名称</span><br><span class="line"> version = '1.0.0' // 库版本</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> repositories {</span><br><span class="line"> maven {</span><br><span class="line"> allowInsecureProtocol = true // 仓库默认不允许使用非https协议,所以这里设置为允许</span><br><span class="line"> url 'https://s01.oss.sonatype.org/content/repositories/releases/' // maven仓库地址</span><br><span class="line"> credentials {</span><br><span class="line"> username 'cofbro' // maven仓库账号-sonatype</span><br><span class="line"> password 'xxxxxxxx' // maven仓库密码-sonatype</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><p>最后对模块进行<code>build</code> ,成功后即可在 <a href="https://s01.oss.sonatype.org/" title="点击试试">Maven仓库</a> 中找到你的 <code>Jar包</code> 。<br><img src="https://cofbro.github.io/2023/09/21/how-to-publish-your-code-to-maven-central/success.png"></p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>学会了吗?学会了还不赶紧发布一次你的代码?😍😍</p>]]></content>
<categories>
<category> 技能树 </category>
</categories>
<tags>
<tag> Android </tag>
<tag> Maven </tag>
</tags>
</entry>
<entry>
<title>ThreadLocal详解</title>
<link href="/2022/10/24/example/"/>
<url>/2022/10/24/example/</url>
<content type="html"><![CDATA[<h2 id="ThreadLocal是什么?"><a href="#ThreadLocal是什么?" class="headerlink" title="ThreadLocal是什么?"></a>ThreadLocal是什么?</h2><p>ThreadLocal是线程之间进行协作和共享的重要工具,可以保证并发中的线程安全。</p><p>当我们定义好了ThreadLocal变量后,每个线程就会持有这个变量的副本,各个线程就可以单独操作这些变量而不会互相影响,当然我们也可以使用锁的方式来代替,用ThreadLocal这种方案不过是用了空间来换时间了。</p><p><img src="https://c2.im5i.com/2022/11/17/2mmj6.png"></p><div class="note blue icon modern"><i class="note-icon fas fa-bullhorn"></i><p>一句话,ThreadLocal可以避免并发场景下的线程安全问题,因为你访问ThreadLocal变量实际操作的是自己线程中保存的副本。</p></div><h2 id="ThreadLocal的使用"><a href="#ThreadLocal的使用" class="headerlink" title="ThreadLocal的使用"></a>ThreadLocal的使用</h2><p>举个例子,在main线程中开启五个线程,每个线程都去修改 <strong>express对象</strong> 中的ThreadLocal变量的值,我们来看看是个什么情况。</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><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">public class Express {</span><br><span class="line"> //定义 ThreadLocal 变量,并将它的值初始化成100</span><br><span class="line"> ThreadLocal<Integer> tl = new ThreadLocal<Integer>() {</span><br><span class="line"> //重写 initialVlaue() 方法,将其赋值100</span><br><span class="line"> @Override</span><br><span class="line"> protected Integer initialValue() {</span><br><span class="line"> return 100;</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> public Express(){}</span><br><span class="line"></span><br><span class="line"> public void changeThreadLocalVariable(int num) {</span><br><span class="line"> //set() 方法,用来设置 ThreadLocal 的值</span><br><span class="line"> tl.set(num);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><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><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">public class Test {</span><br><span class="line"> //静态变量 express</span><br><span class="line"> private static final Express express = new Express();</span><br><span class="line"> public static class A extends Thread {</span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> //以每个 Thread 的 id 作为值,重新设置当前线程中的 ThreadLocal 变量副本的值</span><br><span class="line"> int id = (int) Thread.currentThread().getId();</span><br><span class="line"> //副本设值</span><br><span class="line"> express.changeThreadLocalVariable(id);</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + "修改后的值为:" + express.tl.get());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public static void main(String[] args) throws InterruptedException {</span><br><span class="line"> for (int i = 0; i < 5; i++) {</span><br><span class="line"> //以此启动五个线程</span><br><span class="line"> new A().start();</span><br><span class="line"> }</span><br><span class="line"> System.out.println("num实际的值为:" + express.tl.get());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行后结果如下:我们发现实际的num值依然是100,而其他五个子线程中的num值则已经被修改了,这便证实了 ThreadLocal 变量,会让每个线程拥有其一个副本,在子线程中都是访问和操作这个副本。</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></pre></td><td class="code"><pre><span class="line">Thread-0修改后的值为:20</span><br><span class="line">Thread-4修改后的值为:24</span><br><span class="line">Thread-1修改后的值为:21</span><br><span class="line">Thread-2修改后的值为:22</span><br><span class="line">Thread-3修改后的值为:23</span><br><span class="line">num实际的值为:100</span><br></pre></td></tr></table></figure><h2 id="ThreadLocal的原理解析"><a href="#ThreadLocal的原理解析" class="headerlink" title="ThreadLocal的原理解析"></a>ThreadLocal的原理解析</h2><p>在 Thread 类中有一个成员变量是 threadLocals</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ThreadLocal.ThreadLocalMap threadLocals = null;</span><br></pre></td></tr></table></figure><p>这个变量的作用就是用来保存其它线程中 <strong>ThreadLocal</strong> 变量的副本。因此每个线程中都会有一个 <code>threadLocals</code>,不过最开始都是 null,等待着你给它设置。下面给大家画了一张图,便于整体上对 <code>ThreadLocal</code> 一个总体的认识。</p><p><img src="https://c2.im5i.com/2022/11/18/2x9z2.png"></p><p>从上面这张图我们可以大概知道:</p><p>1.每个线程中都会有一个 <code>ThreadLocalMap</code> 变量。</p><p>2.<code>ThreadLocalMap</code> 中内部是一个个 <code>Entry</code> ,其中的 <strong>key</strong> 是 <code>ThreadLocal</code> 本身,而 <strong>value</strong> 是创建 <code>ThreadLocal</code> 时的值。</p><h3 id="ThreadLocalMap-源码"><a href="#ThreadLocalMap-源码" class="headerlink" title="ThreadLocalMap 源码"></a>ThreadLocalMap 源码</h3><div class="note red icon modern"><i class="note-icon fas fa-battery-half"></i><p>以下仅展示部分相关源码,已省略掉无关代码,便于大家理解。</p></div><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><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">static class ThreadLocalMap {</span><br><span class="line"> </span><br><span class="line"> //弱引用</span><br><span class="line"> static class Entry extends WeakReference<ThreadLocal<?>> {</span><br><span class="line"></span><br><span class="line"> private static final int INITIAL_CAPACITY = 16;</span><br><span class="line"></span><br><span class="line"> Object value;</span><br><span class="line"></span><br><span class="line"> Entry(ThreadLocal<?> k, Object v) {</span><br><span class="line"> super(k);</span><br><span class="line"> value = v;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //Entry数组,用于多个 ThreadLocal<T> 的储存</span><br><span class="line"> private Entry[] table;</span><br><span class="line"> </span><br><span class="line"> //创建ThreadLocalMap,ThreadLocal作为key,以ThreadLocal中的泛型为值</span><br><span class="line"> ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {</span><br><span class="line"> table = new Entry[INITIAL_CAPACITY];</span><br><span class="line"> int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);</span><br><span class="line"> //创建 Entry</span><br><span class="line"> table[i] = new Entry(firstKey, firstValue);</span><br><span class="line"> size = 1;</span><br><span class="line"> setThreshold(INITIAL_CAPACITY);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="set-方法源码"><a href="#set-方法源码" class="headerlink" title="set() 方法源码"></a>set() 方法源码</h3><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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line">public void set(T value) {</span><br><span class="line"> //获取当前线程</span><br><span class="line"> Thread t = Thread.currentThread();</span><br><span class="line"> //拿到当前线程的 ThreadLocal.ThreadLocalMap 变量 threadLocals</span><br><span class="line"> ThreadLocalMap map = getMap(t);</span><br><span class="line"> if (map != null)</span><br><span class="line"> //如果不为 threadLocals 不为空就调用 ThreadLocalMap 中的 set 方法</span><br><span class="line"> map.set(this, value);</span><br><span class="line"> else</span><br><span class="line"> //空的话就创建一个 ThreadLocalMap 变量,并把 value 保存进去</span><br><span class="line"> createMap(t, value);</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">//ThreadLocalMap 中的 set() 方法</span><br><span class="line">//这里是真正给entry设置的地方</span><br><span class="line">private void set(ThreadLocal<?> key, Object value) {</span><br><span class="line"></span><br><span class="line"> // We don't use a fast path as with get() because it is at</span><br><span class="line"> // least as common to use set() to create new entries as</span><br><span class="line"> // it is to replace existing ones, in which case, a fast</span><br><span class="line"> // path would fail more often than not.</span><br><span class="line"></span><br><span class="line"> Entry[] tab = table;</span><br><span class="line"> int len = tab.length;</span><br><span class="line"> //经过复杂位运算获得entry数组中的对应的下表</span><br><span class="line"> int i = key.threadLocalHashCode & (len-1);</span><br><span class="line"></span><br><span class="line"> for (Entry e = tab[i];</span><br><span class="line"> e != null;</span><br><span class="line"> e = tab[i = nextIndex(i, len)]) {</span><br><span class="line"> ThreadLocal<?> k = e.get();</span><br><span class="line"></span><br><span class="line"> if (k == key) {</span><br><span class="line"> e.value = value;</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //对 key 为 null 的entry清除,但是清除不是非常及时,会造成内存泄漏,因此当我们用完ThreadLocal时一定要记得 remove()</span><br><span class="line"> if (k == null) {</span><br><span class="line"> replaceStaleEntry(key, value, i);</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //创建entry并保存值</span><br><span class="line"> tab[i] = new Entry(key, value);</span><br><span class="line"> int sz = ++size;</span><br><span class="line"> if (!cleanSomeSlots(i, sz) && sz >= threshold)</span><br><span class="line"> rehash();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="get-方法源码"><a href="#get-方法源码" class="headerlink" title="get() 方法源码"></a>get() 方法源码</h3><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><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></pre></td><td class="code"><pre><span class="line">public T get() {</span><br><span class="line"> //获取当前线程</span><br><span class="line"> Thread t = Thread.currentThread();</span><br><span class="line"> //拿到当前线程的 ThreadLocal.ThreadLocalMap 变量 threadLocals</span><br><span class="line"> ThreadLocalMap map = getMap(t);</span><br><span class="line"> if (map != null) {</span><br><span class="line"> //如果不为 threadLocals 不为空就调用 getEntry() 方法</span><br><span class="line"> ThreadLocalMap.Entry e = map.getEntry(this);</span><br><span class="line"> if (e != null) {</span><br><span class="line"> @SuppressWarnings("unchecked")</span><br><span class="line"> //如果 e 不为 null,就返回entry的value</span><br><span class="line"> T result = (T)e.value;</span><br><span class="line"> return result;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> //如果map是空,就调用setInitialValue()方法,实际上最终返回的是 null</span><br><span class="line"> return setInitialValue();</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><span class="line">//getEntry() 方法</span><br><span class="line">//真正得到 value 的方法</span><br><span class="line">private Entry getEntry(ThreadLocal<?> key) {</span><br><span class="line"> //经过复杂位运算获得entry数组中的对应的下标</span><br><span class="line"> int i = key.threadLocalHashCode & (table.length - 1);</span><br><span class="line"> Entry e = table[i];</span><br><span class="line"> if (e != null && e.get() == key)</span><br><span class="line"> //entry 不为 null 且有相应的 key 就返回对应数组的元素值</span><br><span class="line"> return e;</span><br><span class="line"> else</span><br><span class="line"> //不满足上述条件的话就会调用 getEntryAfterMiss()</span><br><span class="line"> return getEntryAfterMiss(key, i, e);</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>getEntryAfterMiss()这个方法会判断 <code>entry</code> 是否为空,如果为空就会直接返回 <code>null</code>,否则将进一步判断 <strong>key</strong> 是否为空,若不为空就返回此 <code>entry</code>,如果为空那么就会做一波清理,清理掉过时的 <code>entry</code> 。</p><h3 id="remove-方法源码"><a href="#remove-方法源码" class="headerlink" title="remove() 方法源码"></a>remove() 方法源码</h3><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></pre></td><td class="code"><pre><span class="line">//这个方法就是用来清除 threadLocals 的,防止内存泄漏</span><br><span class="line">public void remove() {</span><br><span class="line"> ThreadLocalMap m = getMap(Thread.currentThread());</span><br><span class="line"> if (m != null)</span><br><span class="line"> m.remove(this);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="ThreadLocal-的-Entry-中的-Key-为什么要用弱引用?"><a href="#ThreadLocal-的-Entry-中的-Key-为什么要用弱引用?" class="headerlink" title="ThreadLocal 的 Entry 中的 Key 为什么要用弱引用?"></a>ThreadLocal 的 Entry 中的 Key 为什么要用弱引用?</h2><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><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">//WeakReference --------------> 弱引用</span><br><span class="line">static class Entry extends WeakReference<ThreadLocal<?>> {</span><br><span class="line"> /** The value associated with this ThreadLocal. */</span><br><span class="line"> Object value;</span><br><span class="line"></span><br><span class="line"> Entry(ThreadLocal<?> k, Object v) {</span><br><span class="line"> super(k);</span><br><span class="line"> value = v;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在说这个问题之前我们先来看看 <code>JDK</code> 为我们提供了哪些引用:</p><ul><li><strong>强引用</strong> : 像 <code>Objective o = new Object()</code> 这种直接用 <code>=</code> 引用的就属于强引用,在每次 <code>GC</code> 时会进行 <strong>根可达分析</strong>,如果能够与 <strong>GCRoots</strong> 相关联,那么就永远不会被垃圾回收。</li><li><strong>软引用</strong>:一些有用但是并非必需。用软引用关联的对象,系统将要发生内存溢出(OuyOfMemory)之前,这些对象就会被回收,如果这次回收后还是没有足够的空间,才会抛出内存溢出。</li><li><strong>弱引用</strong>:一些有用(程度比软引用更低)但是并非必需。用弱引用关联的对象,只能生存到下一次垃圾回收之前,GC 发生时,不管内存够不够,都会被回收。</li><li><strong>虚引用</strong>:最弱,它随时可能被回收掉。垃圾回收的时候收到一个通知,主要用来监控垃圾回收器是否正常工作。</li></ul><p>照这么说的话弱引用能够在 <code>GC</code> 时被清除掉,那用在 <code>Entry中的Key</code> 中有什么用呢,用强引用难道就不行了吗?</p><p>诶,答案是还真不行。我们来看看 <code>Entry</code> 在堆里的结构:<br><img src="https://c2.im5i.com/2022/11/18/2zDyj.png"></p><p>如果 <code>Entry</code> 中的 <code>key</code> 是强引用,那么当 <code>ThreadLocal</code> 变量 <code>tl</code> 手动设置为 null 时,由于 <code>Entry</code> 中还有对 <code>ThreadLocal</code> 对象的强引用,因此它无法被回收。而我们栈中 <code>tl</code> 变量已经是 <code>null</code>,无法通过 <code>key</code> 来访问 <code>Entry</code> 中的变量了,但这个 <code>Entry</code> 由于持有一个强引用又不会被垃圾回收,因此这就造成了 <strong>内存泄漏</strong> 。</p><p>那我们来看看用 <strong>弱引用</strong> 是什么情况,当我们使用完 <code>tl</code> 变量后将其设置为 <code>null</code> 后,因为是 <code>弱引用</code>,因此 <code>ThreadLocal</code> 会被垃圾回收。当被回收之后, <code>Entry</code> 中的 <code>key</code> 就是 <code>null</code> 了,而之前我们讲到过在 <code>get()</code>,<code>set()</code>,<code>remove()</code>方法中都会对 <code>key</code> 为 <code>null</code> 的 <code>entry</code> 进行清除处理,因此不会造成内存泄漏,这就是 <code>JDK</code> 用 <strong>虚引用</strong> 的精妙之处。</p><div class="note pink icon modern"><i class="note-icon fas fa-car"></i><p>因此建议大家在用完 <code>ThreadLocal</code> 变量之后一定记得要进行 <code>tl.remove()</code> 操作,用来避免内存泄漏这一问题。</p></div>]]></content>
<categories>
<category> Java多线程 </category>
</categories>
<tags>
<tag> Java </tag>
</tags>
</entry>
<entry>
<title>Android九宫格解锁视图</title>
<link href="/2022/07/20/android-unlockView/"/>
<url>/2022/07/20/android-unlockView/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>今天我们来手写一个关于手机九宫格解锁的一个 <code>view</code> ,我们将各种方法包括向外部传出密码的功能进行封装,写成我们自己的一个 <code>view</code> 。</p><h2 id="效果展示"><a href="#效果展示" class="headerlink" title="效果展示"></a>效果展示</h2><p><img src="https://s3.bmp.ovh/imgs/2022/07/20/c8bc218def416211.png"></p><blockquote><p>说明:此视图中的子控件全部都是 <code>ImageView</code> ,这里没有用到绘制</p></blockquote><h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><p>我们首先来说明一下这个小demo的一些细节。</p><p>1.已经点亮的点不能重复点亮</p><p>2.已经点亮的点不能与其他任意一点之间形成连线</p><p>3.当手指完成九宫格解锁时,要向外部,如 <code>Activity</code> 传递 <code>password</code> ,由外部来判断密码是否正确</p><h3 id="界面的布局"><a href="#界面的布局" class="headerlink" title="界面的布局"></a>界面的布局</h3><p>首先从界面的布局开始说起,九宫格顾名思义有九个点,其次就是6条横线,6条竖线,4条向右下方倾斜的线以及4条向左下方倾斜的线。接下来我们用代码的方式创建这些视图。</p><p>很显然我们需要一个容器来容纳下这些子控件,因此我们自己取名的视图 <code>PicUnlockView</code> 需要继承 <code>ViewGruop</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><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></pre></td><td class="code"><pre><span class="line">class PicUnlockView : ViewGroup {</span><br><span class="line"> /**</span><br><span class="line"> * 有三个构造方法,其中第三个构造方法</span><br><span class="line"> * 的三个参数分别是:</span><br><span class="line"> * 1.上下文</span><br><span class="line"> * 2.布局参数,它的类型是集合,里面放的是各种布局参数</span><br><span class="line"> * 3.布局样式</span><br><span class="line"> */</span><br><span class="line"> constructor(context: Context) : super(context) {</span><br><span class="line"> //我们将所有视图的创建都放在initUi()里面</span><br><span class="line"> initUi()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {</span><br><span class="line"> initUi()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> constructor(context: Context, attrs: AttributeSet, style: Int) : super(context, attrs, style) {</span><br><span class="line"> initUi()</span><br><span class="line"> }</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="九个点的创建及显示"><a href="#九个点的创建及显示" class="headerlink" title="九个点的创建及显示"></a>九个点的创建及显示</h4><p>在创建9个点之前,我们需要先来计算一下必要的参数,由于我们待会会对创建好的点进行布局,因此会重复用到两个点之间的距离,所以我们先将它计算出来并声明成全局变量。</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></pre></td><td class="code"><pre><span class="line">//已测量父容器的宽高</span><br><span class="line">//由于父容器的宽高在这个时候才会被测量出来,所以我们space在这里计算</span><br><span class="line">override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {</span><br><span class="line"> //计算两个点之间的间距</span><br><span class="line"> space = (width - 3 * dotSize) / 2</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们明白在手指拖动或点击的过程中需要将符合条件的点和线条显示出来,那我们如何能做到呢?怎么才能让代码明白我们的意图呢?</p><p>这里我们就可以引入 <code>tag</code> 值,将每个点的 <code>tag</code> 值以此设置成 <code>1..9</code> ,接着就将两点之间的线段用这两个点的 <code>tag</code> 拼接起来,成为这条线段的 <code>tag</code> 值,比如第一条横线就是 <code>12</code> ,然后将这些 <code>tag</code> 存在一个集合中。</p><p>这样当我们手指在屏幕上滑动的时候,就可以算出手指经过两点的 <code>tag</code> 值,进而推出两点间的线段的 <code>tag</code> 值,最后判断这个 <code>tag</code> 值是否存在,若存在则显示 <code>tag</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><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></pre></td><td class="code"><pre><span class="line">//这个方法用来创建9个点</span><br><span class="line">private fun initNineDot() {</span><br><span class="line"> for (i in 1..9) {</span><br><span class="line"> ImageView(context).apply {</span><br><span class="line"> //给每张图片设置最初显示的图片</span><br><span class="line"> setImageResource(R.drawable.dot_normal)</span><br><span class="line"> //设置每个点的 tag 值</span><br><span class="line"> tag = "$i"</span><br><span class="line"> //将每个 ImageView 加到容器中去</span><br><span class="line"> addView(this)</span><br><span class="line"> //收集已经创建好的点</span><br><span class="line"> dotViews.add(this)</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">//这个方法用来指定这9个点如何布局和显示</span><br><span class="line">private fun layoutNineDot() {</span><br><span class="line"> for (row in 0..2) {</span><br><span class="line"> for (column in 0..2) {</span><br><span class="line"> //指定每个图片的 左 上 右 下 各个位置</span><br><span class="line"> val left = column * (dotSize + space)</span><br><span class="line"> val top = row * (dotSize + space)</span><br><span class="line"> val right = left + dotSize</span><br><span class="line"> val bottom = top + dotSize</span><br><span class="line"> /**</span><br><span class="line"> * 每个视图添加到容器之后,后一个视图会重叠在前一个视图上</span><br><span class="line"> * 第一个被添加上去的索引是0,第二个就是1...</span><br><span class="line"> * 因此可以用索引来拿到每个视图,并将其摆放在合适的位置</span><br><span class="line"> * 用 getChildAt(index) 来得到视图</span><br><span class="line"> */</span><br><span class="line"> val index = row * 3 + column</span><br><span class="line"> val dotView = getChildAt(index)</span><br><span class="line"> //将这个子控件进行布局,告诉它因该在父容器中如何显示</span><br><span class="line"> dotView.layout(left, top, right, bottom)</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><h4 id="六条横线的创建及显示"><a href="#六条横线的创建及显示" class="headerlink" title="六条横线的创建及显示"></a>六条横线的创建及显示</h4><p>由于六条横线和上面九个点的创建和显示大同小异,这里就省略了一些注释,其实道理都是一样的,计算 <code>tag</code> 值,计算对应 <code>view</code> 的 <code>index</code> 值,计算 <code>left</code> <code>top</code> <code>right</code> <code>bottom</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><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></pre></td><td class="code"><pre><span class="line">private fun initHorizontalLine() {</span><br><span class="line"> /**</span><br><span class="line"> * 这是六条横线对应的 tag 值</span><br><span class="line"> * 12 23</span><br><span class="line"> * 45 56</span><br><span class="line"> * 78 89</span><br><span class="line"> */</span><br><span class="line"> var tag = 0</span><br><span class="line"> for (i in 0..5) {</span><br><span class="line"> ImageView(context).apply {</span><br><span class="line"> setImageResource(R.drawable.line_horizontal)</span><br><span class="line"> if (i == 0) {</span><br><span class="line"> tag = 12</span><br><span class="line"> } else {</span><br><span class="line"> tag += i % 2 * 11 + (i + 1) % 2 * 22</span><br><span class="line"> }</span><br><span class="line"> this.tag = tag</span><br><span class="line"> Log.d(TAG, "tag: $tag")</span><br><span class="line"> Log.d(TAG, "initLandscapeLine: ")</span><br><span class="line"> addView(this)</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">private fun layoutHorizontalLine() {</span><br><span class="line"> for (row in 0..2) {</span><br><span class="line"> for (column in 0..1) {</span><br><span class="line"> val left = dotSize + column * (dotSize + space)</span><br><span class="line"> val top = dotSize / 2 + row * lineSize</span><br><span class="line"> val right = left + space</span><br><span class="line"> val bottom = top + dp2px(2)</span><br><span class="line"></span><br><span class="line"> //找到这根线在父容器中的索引</span><br><span class="line"> val index = 9 + row * 2 + column</span><br><span class="line"> val lineView = getChildAt(index)</span><br><span class="line"> lineView.layout(left, top, right, bottom)</span><br><span class="line"> lineView.visibility = INVISIBLE</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="六条竖线的创建与显示"><a href="#六条竖线的创建与显示" class="headerlink" title="六条竖线的创建与显示"></a>六条竖线的创建与显示</h4><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><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><br><span class="line">private fun initVerticalLine() {</span><br><span class="line"> //14 25 36</span><br><span class="line"> //47 58 69</span><br><span class="line"> var tag = 0</span><br><span class="line"> for (i in 0..5) {</span><br><span class="line"> ImageView(context).apply {</span><br><span class="line"> setImageResource(R.drawable.line_vertical)</span><br><span class="line"> if (i == 0) {</span><br><span class="line"> tag = 14</span><br><span class="line"> } else {</span><br><span class="line"> tag += 11</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> this.tag = tag</span><br><span class="line"> addView(this)</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><span class="line">private fun layoutVerticalLine() {</span><br><span class="line"> for (row in 0..1) {</span><br><span class="line"> for (column in 0..2) {</span><br><span class="line"> val left = dotSize / 2 + column * (dotSize + space)</span><br><span class="line"> val top = dotSize + row * (dotSize + space)</span><br><span class="line"> val right = left + dp2px(2)</span><br><span class="line"> val bottom = top + space</span><br><span class="line"></span><br><span class="line"> //找到这根线在父容器中的索引</span><br><span class="line"> val index = 15 + row * 3 + column</span><br><span class="line"> val lineView = getChildAt(index)</span><br><span class="line"> lineView.layout(left, top, right, bottom)</span><br><span class="line"> lineView.visibility = INVISIBLE</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="4条向右倾斜的直线"><a href="#4条向右倾斜的直线" class="headerlink" title="4条向右倾斜的直线"></a>4条向右倾斜的直线</h4><p>上同,只不过这里需要注意的是由于几条线都是对角线,而我们的点都是圆点,因此考虑线条<code>left</code> <code>top</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><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><br><span class="line">private fun initSlashLine() {</span><br><span class="line"> //15 26</span><br><span class="line"> //48 59</span><br><span class="line"> var tag = 0</span><br><span class="line"> for (i in 0..3) {</span><br><span class="line"> ImageView(context).apply {</span><br><span class="line"> setImageResource(R.drawable.line_right)</span><br><span class="line"> if (i == 0) {</span><br><span class="line"> tag = 15</span><br><span class="line"> } else {</span><br><span class="line"> tag += (i % 2) * 11 + (i + 1) % 2 * 22</span><br><span class="line"> }</span><br><span class="line"> this.tag = tag</span><br><span class="line"> Log.d(TAG, "initSlashLine: tag = $tag")</span><br><span class="line"> addView(this)</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><span class="line">private fun layoutSlashLine() {</span><br><span class="line"> for (row in 0..1) {</span><br><span class="line"> for (column in 0..1) {</span><br><span class="line"> val left = dotSize / 2 + sqrt(2.0) / 4 * dotSize + column * (dotSize + space)</span><br><span class="line"> val top = dotSize / 2 + sqrt(2.0) * dotSize / 4 + row * (dotSize + space)</span><br><span class="line"> val right = left + space + (dotSize / 2 - sqrt(2.0) * dotSize.toDouble() / 4) * 2</span><br><span class="line"> val bottom = top + space + (dotSize / 2 - sqrt(2.0) * dotSize.toDouble() / 4) * 2</span><br><span class="line"></span><br><span class="line"> //找到这根线在父容器中的索引</span><br><span class="line"> val index = 21 + row * 2 + column</span><br><span class="line"> val lineView = getChildAt(index)</span><br><span class="line"> lineView.layout(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())</span><br><span class="line"> lineView.visibility = INVISIBLE</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="4条向左倾斜的直线"><a href="#4条向左倾斜的直线" class="headerlink" title="4条向左倾斜的直线"></a>4条向左倾斜的直线</h4><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><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">private fun initLLine() {</span><br><span class="line"> //24 35</span><br><span class="line"> //57 68</span><br><span class="line"> var tag = 0</span><br><span class="line"> for (i in 0..3) {</span><br><span class="line"> ImageView(context).apply {</span><br><span class="line"> setImageResource(R.drawable.line_left)</span><br><span class="line"> if (i == 0) {</span><br><span class="line"> tag = 24</span><br><span class="line"> } else {</span><br><span class="line"> tag += (i % 2) * 11 + (i + 1) % 2 * 22</span><br><span class="line"> }</span><br><span class="line"> this.tag = tag</span><br><span class="line"> addView(this)</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">private fun layoutLLine() {</span><br><span class="line"> for (row in 0..1) {</span><br><span class="line"> for (column in 0..1) {</span><br><span class="line"> val left = dotSize / 2 + sqrt(2.0) / 4 * dotSize + column * (dotSize + space)</span><br><span class="line"> val top = dotSize / 2 + sqrt(2.0) * dotSize / 4 + row * (dotSize + space)</span><br><span class="line"> val right = left + space + (dotSize / 2 - sqrt(2.0) * dotSize.toDouble() / 4) * 2</span><br><span class="line"> val bottom = top + space + (dotSize / 2 - sqrt(2.0) * dotSize.toDouble() / 4) * 2</span><br><span class="line"></span><br><span class="line"> //找到这根线在父容器中的索引</span><br><span class="line"> val index = 25 + row * 2 + column</span><br><span class="line"> val lineView = getChildAt(index)</span><br><span class="line"> lineView.layout(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())</span><br><span class="line"> lineView.visibility = INVISIBLE</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="界面的逻辑"><a href="#界面的逻辑" class="headerlink" title="界面的逻辑"></a>界面的逻辑</h3><p>现在我们来思考一下这个界面的逻辑,当我们手指点击或者滑动或者抬起时,这个 <code>view</code> 应该作出怎样的回应,应该显示什么样的内容,最后还有怎么将密码传递给外部去。</p><h4 id="解锁图案的实现"><a href="#解锁图案的实现" class="headerlink" title="解锁图案的实现"></a>解锁图案的实现</h4><p>我们单独用一个方法 <code>dealWithTouchPoint()</code>,处理手指按下或滑动后视图相应的逻辑。通过给每一个小点加上 <code>矩形区域(Rect)</code>,然后使用里面的方法 <code>contains()</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><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></pre></td><td class="code"><pre><span class="line">private fun dealWithTouchPoint(x: Float, y: Float) {</span><br><span class="line"> dotViews.forEach {</span><br><span class="line"> //给每个点加上一个矩形区域</span><br><span class="line"> val rect = RectF(</span><br><span class="line"> it.x,</span><br><span class="line"> it.y,</span><br><span class="line"> it.x + it.width,</span><br><span class="line"> it.y + it.height</span><br><span class="line"> )</span><br><span class="line"> if (rect.contains(x, y)) {</span><br><span class="line"> //判断是不是第一个点</span><br><span class="line"> if (lastSelectedDotView == null) {</span><br><span class="line"> //直接点亮</span><br><span class="line"> changeSelectedDotViewStatus(it, ViewStatus.SELECTED)</span><br><span class="line"> } else {</span><br><span class="line"> val lastTag = (lastSelectedDotView!!.tag as String).toInt()</span><br><span class="line"> val currentTag = (it.tag as String).toInt()</span><br><span class="line"> //获取两点间线的tag</span><br><span class="line"> val lineTag = if (lastTag < currentTag)</span><br><span class="line"> lastTag * 10 + currentTag</span><br><span class="line"> else</span><br><span class="line"> currentTag * 10 + lastTag</span><br><span class="line"> //判断tags数组中是否有这个值</span><br><span class="line"> if (allLineTags.contains(lineTag)) {</span><br><span class="line"> //存在这条线,使用tag将它拿到</span><br><span class="line"> val lineView = findViewWithTag<ImageView>(lineTag)</span><br><span class="line"> if (!allSelectedDotViews.contains(it)) {</span><br><span class="line"> //点亮点</span><br><span class="line"> changeSelectedDotViewStatus(it, ViewStatus.SELECTED)</span><br><span class="line"> if (!allSelectedLineViews.contains(lineView)) {</span><br><span class="line"> //点亮线</span><br><span class="line"> changeSelectedLineStatus(lineView, ViewStatus.SELECTED)</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><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="向外部传递密码"><a href="#向外部传递密码" class="headerlink" title="向外部传递密码"></a>向外部传递密码</h4><p>我们使用高阶函数(callback)来实现外部传值的功能。通过外部来接收这个密码,再作相应的逻辑,比如判断密码是否正确。</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></pre></td><td class="code"><pre><span class="line">var callback: ((String) -> Unit)? = null</span><br><span class="line">private fun dealWithResult() {</span><br><span class="line"> callback?.let {</span><br><span class="line"> it(passwordBuilder.toString())</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>当前这个 <code>view</code> 复用性较高,完成了一套密码视图该有的功能,但不足的是线条没有通过绘制完成,而是依靠图片。</p><p>这里提供的仅为部分核心代码,完整代码请见:<a href="https://github.com/cofbro/android-unlockView">https://github.com/cofbro/android-unlockView</a></p>]]></content>
<categories>
<category> Android </category>
</categories>
<tags>
<tag> Kotlin </tag>
<tag> Demo </tag>
</tags>
</entry>
<entry>
<title>Android版计算器</title>
<link href="/2022/07/12/HomemadeCalculator/"/>
<url>/2022/07/12/HomemadeCalculator/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>计算器是每个手机自带的必备功能,生活中常用来计算比较大型复杂的数据,今天我准备自己写一个计算器APP出来,以巩固Android学习。</p><hr><h2 id="分析与设计"><a href="#分析与设计" class="headerlink" title="分析与设计"></a>分析与设计</h2><p>对于整个计算器软件的编写,我们分为两大部分,第一部分是UI设计,另一部分是逻辑实现。</p><h3 id="UI设计"><a href="#UI设计" class="headerlink" title="UI设计"></a>UI设计</h3><p>通常程序猿不需要懂太多的UI设计,但是简单的UI设计我们还是需要学会。计算器我们见得多了,最常见的就是顶部一个方形的显示屏,接着下面就是10多个按键,这些按键大多是圆形的,有了这些常识,我们倒不妨画个图。</p><p><img src="https://s1.328888.xyz/2022/07/12/Lk8qi.png"></p><p>这就是整个计算器大概的样子。</p><h4 id="确定布局"><a href="#确定布局" class="headerlink" title="确定布局"></a>确定布局</h4><p>那么接下来就是真正实现UI界面的时候了。<br>个人觉得现在constraintLayout(约束布局)最好用,因为动动手指就能实时为你展现UI界面,特别直观与方便,因此根标签就是constraintlayout了。</p><p>整个布局是这样的</p><p><img src="https://s1.328888.xyz/2022/07/13/LoxuF.png"></p><h4 id="确定视图种类和数目"><a href="#确定视图种类和数目" class="headerlink" title="确定视图种类和数目"></a>确定视图种类和数目</h4><p>显示屏就是一个TextView,而其余的按键都是Button,总的来说就是需要16个Button和3个icon图标以及一个TextView,为每个Button和icon以及TextView添加监听事件,实现相应的逻辑即可。</p><h4 id="实现圆形Button"><a href="#实现圆形Button" class="headerlink" title="实现圆形Button"></a>实现圆形Button</h4><p>为了能改变Button的颜色以及样式,我们需要在两个 <code>theme</code> 中将 style 中的 parent 改为</p><p><code><style name="Theme.xxx" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge"></style></code></p><p>在 <code>res -> drawable</code> 文件夹中新建一个xml文件,添加如下代码,即可实现圆形Button</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><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"><?xml version="1.0" encoding="utf-8"?></span><br><span class="line"><shape xmlns:android="http://schemas.android.com/apk/res/android"</span><br><span class="line"> android:shape="oval"></span><br><span class="line"> <corners android:radius="25dp" /> <!-- 设置圆角弧度 --></span><br><span class="line"> <solid android:color="#fafafa" /> <!-- 设置背景颜色 --></span><br><span class="line"> <size</span><br><span class="line"> android:width="40dp"</span><br><span class="line"> android:height="40dp" /> <!-- 设置大小 --></span><br><span class="line"> <stroke</span><br><span class="line"> android:width="0dp"</span><br><span class="line"> android:color="#fff" /> <!-- 设置描边大小与颜色 --></span><br><span class="line"></shape></span><br></pre></td></tr></table></figure><h4 id="设置渐变色"><a href="#设置渐变色" class="headerlink" title="设置渐变色"></a>设置渐变色</h4><p>同样也在 <code>res -> drawable</code> 文件夹中新建xml文件,添加如下代码</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"><?xml version="1.0" encoding="utf-8"?></span><br><span class="line"><shape xmlns:android="http://schemas.android.com/apk/res/android"></span><br><span class="line"> <gradient</span><br><span class="line"> android:startColor="#74b981" <!-- 起始色彩 --></span><br><span class="line"> android:endColor="#a1d47e" <!-- 结束色彩 --></span><br><span class="line"> android:angle="45"</span><br><span class="line"> /></span><br><span class="line"></shape></span><br></pre></td></tr></table></figure><h4 id="更改字体样式"><a href="#更改字体样式" class="headerlink" title="更改字体样式"></a>更改字体样式</h4><p>首先下载一个字体压缩包,将 <code>xxx.ttf</code> 解压到 <code>res -> font</code> (如果没有font文件夹就创建一个),然后在 <code>activity_main.xml</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"><Button</span><br><span class="line"> ...</span><br><span class="line"> android:fontFamily="@font/myfont"/></span><br></pre></td></tr></table></figure><h4 id="引入iconfont图标"><a href="#引入iconfont图标" class="headerlink" title="引入iconfont图标"></a>引入iconfont图标</h4><h5 id="在iconfont官网下载对应的图标并加到项目中"><a href="#在iconfont官网下载对应的图标并加到项目中" class="headerlink" title="在iconfont官网下载对应的图标并加到项目中"></a>在iconfont官网下载对应的图标并加到项目中</h5><p>进入<a href="https://www.iconfont.cn/">iconfont官网</a>,选择合适的图标添加至项目,然后下载压缩包到本地,最后将整个解压后的文件加到项目中的 <code>app -> src -> main -> assets</code>中(如果没有assets文件就新建一个)</p><h5 id="编写一个帮助类"><a href="#编写一个帮助类" class="headerlink" title="编写一个帮助类"></a>编写一个帮助类</h5><p>在将 <code>iconfont</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><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">package com.example.myapp;</span><br><span class="line"></span><br><span class="line">import android.content.Context;</span><br><span class="line">import android.graphics.Typeface;</span><br><span class="line">import android.view.View;</span><br><span class="line">import android.view.ViewGroup;</span><br><span class="line">import android.widget.TextView;</span><br><span class="line"></span><br><span class="line">public class FontManager {</span><br><span class="line"> //根据地址,找到iconfont中的iconfont.ttf文件</span><br><span class="line"> public static final String ROOT = "iconfonts/",ICONFONTS = ROOT + "iconfont.ttf";</span><br><span class="line"></span><br><span class="line"> public static Typeface getTypeface(Context context, String font) {</span><br><span class="line"> return Typeface.createFromAsset(context.getAssets(), font);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //图标一般都是包含在一个ViewGroup中,比如constraintlayout</span><br><span class="line"> //我们可以写一个方法,遍历指定xml parent 并且递归的覆盖每个TextView的字体</span><br><span class="line"> public static void markAsIconContainer(View v, Typeface typeface) {</span><br><span class="line"> if (v instanceof ViewGroup) {</span><br><span class="line"> ViewGroup vg = (ViewGroup) v;</span><br><span class="line"> for (int i = 0; i < vg.getChildCount(); i++) {</span><br><span class="line"> View child = vg.getChildAt(i);</span><br><span class="line"> markAsIconContainer(child, typeface);</span><br><span class="line"> }</span><br><span class="line"> } else if (v instanceof TextView) {</span><br><span class="line"> ((TextView) v).setTypeface(typeface);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="在xml文件中添加字段"><a href="#在xml文件中添加字段" class="headerlink" title="在xml文件中添加字段"></a>在xml文件中添加字段</h5><p>在 <code>values -> icons.xml</code>(如果没有就创建一个),将对应的iconfont图标的Unicode编码用字段名储存起来,以便在 <code>activity_main.xml</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><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><resources></span><br><span class="line"> <string name="return0">&#xe63b;</string></span><br><span class="line"> <string name="calender">&#xe8b4;</string></span><br><span class="line"> <string name="record">&#xe600;</string></span><br><span class="line"></resources></span><br></pre></td></tr></table></figure><p>而 <code>activity_main.xml</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"><TextView</span><br><span class="line"> // ...</span><br><span class="line"> android:text="@string/calender" /></span><br></pre></td></tr></table></figure><h5 id="使用help类显示图标"><a href="#使用help类显示图标" class="headerlink" title="使用help类显示图标"></a>使用help类显示图标</h5><p>所有准备工作都做完了,接下来就是调用help类了,在 <code>MainActivity</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">//helper帮助类, 加载iconfonts的必要操作</span><br><span class="line">Typeface iconFont = FontManager.getTypeface(getApplicationContext(), FontManager.ICONFONTS);</span><br><span class="line">FontManager.markAsIconContainer(findViewById(R.id.icons_container), iconFont);</span><br></pre></td></tr></table></figure><p>接下来就能看到正常的图标了</p><p><img src="https://s1.328888.xyz/2022/07/13/Ls240.jpg"></p><h3 id="逻辑部分实现"><a href="#逻辑部分实现" class="headerlink" title="逻辑部分实现"></a>逻辑部分实现</h3><p>我们先总体分析一下,对于这个计算器,有很多重复的功能,比如有11个数字键的功能都极其相似,因此我们可以专门写一个方法进行封装;而其余的4个运算键也是类似的功能,我们封装一个方法用于实现 <code>+ - x ÷</code> 功能,最后就是归零键与 <code>=</code> 键,只需要单独写两个方法就好了。因此我们首先写这5个方法(其中还包括显示小数点功能的方法),分别是 <code>bindNormalClickEvent</code> , <code>bindSpecialClickEvent</code> , <code>bindCalClickEvent</code> , <code>bindACClickEvent</code> , <code>bindGetResultEvent</code> 。</p><h4 id="bindNormalClickEvent方法"><a href="#bindNormalClickEvent方法" class="headerlink" title="bindNormalClickEvent方法"></a>bindNormalClickEvent方法</h4><p>为了能使绑定这个动作的 <code>btn</code> 完成相应数字(符号)在屏幕上的显示,我们设置 <code>num</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><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">//input是stringBuilder类型,保存着从键盘上输入的各个数字(符号)</span><br><span class="line">public void bindNormalClickEvent(Button btn, String num) {</span><br><span class="line"> btn.setOnClickListener(view -> {</span><br><span class="line"> input.append(num);</span><br><span class="line"> mTextView.setText(input);</span><br><span class="line"> frequency++; //记录数字(符号)的个数</span><br><span class="line"> });</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h4 id="bindSpecialClickEvent方法"><a href="#bindSpecialClickEvent方法" class="headerlink" title="bindSpecialClickEvent方法"></a>bindSpecialClickEvent方法</h4><p>这个方法和 <code>bindNormalClickEvent</code> 方法差不多,只是记录是否有小数点,方便后面操作转换成 <code>double</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><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">public void bindSpecialClickEvent(Button btn, String num) {</span><br><span class="line"> btn.setOnClickListener(view -> {</span><br><span class="line"> //special 限定了一次计算中只能按一次 '.' 键,special初始为true</span><br><span class="line"> if (special) {</span><br><span class="line"> input.append(num);</span><br><span class="line"> mTextView.setText(input);</span><br><span class="line"> frequency++;</span><br><span class="line"> special = false;</span><br><span class="line"> isDecimal = true; //数据中是否包含小数点</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h4 id="bindCalClickEvent"><a href="#bindCalClickEvent" class="headerlink" title="bindCalClickEvent"></a>bindCalClickEvent</h4><p>这个方法用来绑定 <code>+ - x ÷</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><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">public void bindCalClickEvent(Button btn, String num) {</span><br><span class="line"> btn.setOnClickListener(view -> {</span><br><span class="line"> //isOnlySymbol规定 运算按钮一次运算中只能点击一次</span><br><span class="line"> if (isOnlySymbol){</span><br><span class="line"> symbol = num;</span><br><span class="line"> for (int i = 0; i < frequency; i++) {</span><br><span class="line"> //isDecimal来判定运算数中是否有小数点</span><br><span class="line"> if (isDecimal && ((int) input.charAt(i) - 46) == 0) {</span><br><span class="line"> whereDecimal = frequency - i - 1;</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> //将stringBuilder中的字符串提取出来转化为 double 类型的数字</span><br><span class="line"> int temp = frequency;</span><br><span class="line"> for (int i = 0; i < temp; i++) {</span><br><span class="line"> if (isDecimal && ((int) input.charAt(i) - 46) == 0) {</span><br><span class="line"> frequency++;</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"> firstNum += ((int) input.charAt(i) - 48) * Math.pow(10, frequency - 1 - i);</span><br><span class="line"> }</span><br><span class="line"> //whereDecimal 标记了小数点的位置,为转为成double型做准备</span><br><span class="line"> if (isDecimal) whereDecimal++;</span><br><span class="line"> firstNum /= Math.pow(10, whereDecimal);</span><br><span class="line"> hasSymbol = true;</span><br><span class="line"> if (input.length() != 0) {</span><br><span class="line"> input.delete(0, temp);</span><br><span class="line"> input.append(num);</span><br><span class="line"> mTextView.setText(input);</span><br><span class="line"> //输入完第一个数将这些属性重置,以便第二个数的输入</span><br><span class="line"> frequency = 0;</span><br><span class="line"> isDecimal = false;</span><br><span class="line"> whereDecimal = 0;</span><br><span class="line"> special = true;</span><br><span class="line"> }</span><br><span class="line"> //确保运算按钮一次运算中只能点击一次</span><br><span class="line"> isOnlySymbol = false;</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><h4 id="bindACClickEvent方法"><a href="#bindACClickEvent方法" class="headerlink" title="bindACClickEvent方法"></a>bindACClickEvent方法</h4><p>这是计算器的 <code>AC</code> 归零键功能的实现,原理是将stringBuilder中的字符清空,将所有属性全部初始化。</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><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">public void bindACClickEvent(Button btn) {</span><br><span class="line"> btn.setOnClickListener(view -> {</span><br><span class="line"> input.setLength(0);</span><br><span class="line"> firstNum = 0;</span><br><span class="line"> secondNum = 0;</span><br><span class="line"> mTextView.setText(input);</span><br><span class="line"> special = true;</span><br><span class="line"> frequency = 0;</span><br><span class="line"> isDecimal = false;</span><br><span class="line"> hasSymbol = false;</span><br><span class="line"> hasFirstNum = false;</span><br><span class="line"> isOnlySymbol = true;</span><br><span class="line"> whereDecimal = 0;</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="bindGetResultEvent方法"><a href="#bindGetResultEvent方法" class="headerlink" title="bindGetResultEvent方法"></a>bindGetResultEvent方法</h4><p>这个方法又和 bindCalClickEvent 很相似,只是最后多了一段运算符判定的代码,分别对应着 <code>+ - x ÷</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><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">public void bindGetResultEvent(Button button) {</span><br><span class="line"> button.setOnClickListener(view -> {</span><br><span class="line"> if (hasSymbol) {</span><br><span class="line"> double result;</span><br><span class="line"> input.delete(0, 1);</span><br><span class="line"> for (int i = 0; i < frequency; i++) {</span><br><span class="line"> if (isDecimal && ((int) input.charAt(i) - 46) == 0) {</span><br><span class="line"> whereDecimal = frequency - i - 1;</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> int temp2 = frequency;</span><br><span class="line"> for (int i = 0; i < temp2; i++) {</span><br><span class="line"> if (isDecimal && ((int) input.charAt(i) - 46) == 0) {</span><br><span class="line"> frequency++;</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"> secondNum += ((int) input.charAt(i) - 48) * Math.pow(10, frequency - 1 - i);</span><br><span class="line"> }</span><br><span class="line"> if (isDecimal) whereDecimal++;</span><br><span class="line"> secondNum /= Math.pow(10, whereDecimal);</span><br><span class="line"></span><br><span class="line"> if (Objects.equals(symbol, "+")) {</span><br><span class="line"> result = firstNum + secondNum;</span><br><span class="line"> } else if (Objects.equals(symbol, "x")) {</span><br><span class="line"> result = firstNum * secondNum;</span><br><span class="line"> } else if (Objects.equals(symbol, "÷")) {</span><br><span class="line"> result = firstNum / secondNum;</span><br><span class="line"> } else {</span><br><span class="line"> result = firstNum - secondNum;</span><br><span class="line"> }</span><br><span class="line"> input.setLength(0);</span><br><span class="line"> input.append(result);</span><br><span class="line"> mTextView.setText(input);</span><br><span class="line"> hasSymbol = false;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="其余特殊按键"><a href="#其余特殊按键" class="headerlink" title="其余特殊按键"></a>其余特殊按键</h4><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><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><br><span class="line">//这是结束当前activity按钮</span><br><span class="line">public void toLastPage(View view) {</span><br><span class="line"> TextView textView = (TextView) view;</span><br><span class="line"> textView.setOnClickListener(view1 -> finish());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">//这是对于某些功能还未开放的提示按钮</span><br><span class="line">public void onTipping(View view, Context context) {</span><br><span class="line"> view.setOnClickListener(view1 -> Toast.makeText(context, "功能暂未开放", Toast.LENGTH_SHORT).show());</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><img src="https://s1.328888.xyz/2022/07/14/LgNwX.jpg"></p><p>源代码以及素材在 github 上:<a href="https://github.com/cofbro/Calculator-java-and-kotlin-">https://github.com/cofbro/Calculator-java-and-kotlin-</a></p>]]></content>
<categories>
<category> Android </category>
</categories>
<tags>
<tag> Java </tag>
<tag> Demo </tag>
</tags>
</entry>
<entry>
<title>FlyingBirds</title>
<link href="/2022/07/06/FlyingBirds/"/>
<url>/2022/07/06/FlyingBirds/</url>
<content type="html"><![CDATA[<h2 id="一-前言"><a href="#一-前言" class="headerlink" title="一.前言"></a>一.前言</h2><p>学校安排的学年设计,需要交一份基于Java或c++编写的小游戏,我自然是选择Java了,我准备交一份《飞翔的小鸟》游戏上去。在这里记录一下整个项目的过程。</p><p>飞翔的小鸟想必大家小时候都玩过,我记得我当时偷偷玩手机的时候还下载过这个游戏,没想到有一天我会亲手写这个游戏。</p><hr><h2 id="二-分析"><a href="#二-分析" class="headerlink" title="二.分析"></a>二.分析</h2><p>在写代码量较多,结构稍复杂的程序中,我们首先要建立UML类图。首先要明确有哪些对象,从以前我们玩这个游戏的经验来说,第一个必须是要有会飞的小鸟,那么它怎么会飞呢?我们只需要多张小鸟扇动翅膀的图片,然后按顺序在屏幕前闪过,这个小鸟就像实在飞行中的状态了。第二,需要天空类和地面类以及障碍物类。最后根据各自需要实现的功能来编写其类中的方法。</p><p>我们来看看具体的UML类图:<br><img src="https://s1.328888.xyz/2022/07/05/5V3P.png"></p><p>我们照着UML类图,一步一步剖析。</p><h4 id="1-Column类"><a href="#1-Column类" class="headerlink" title="1.Column类"></a>1.Column类</h4><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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">class Column {</span><br><span class="line"> int column_x, column_y; //钢管的中心坐标</span><br><span class="line"> int width, height; //宽度高度</span><br><span class="line"> int gap = 140; //钢管的空隙</span><br><span class="line"> Random random = new Random(); //随机坐标</span><br><span class="line"> BufferedImage coBufferImage; //钢管图片</span><br><span class="line"></span><br><span class="line"> //构造方法,指定障碍物的宽度,并且随机生成障碍物的高度</span><br><span class="line"> public Column(int x) {</span><br><span class="line"> super();</span><br><span class="line"> File coImage = new File("images/column.png");</span><br><span class="line"> try {</span><br><span class="line"> coBufferImage = ImageIO.read(coImage);</span><br><span class="line"> } catch (IOException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> column_x = x;</span><br><span class="line"> column_y = random.nextInt(180) + 150;</span><br><span class="line"> width = coBufferImage.getWidth();</span><br><span class="line"> height = coBufferImage.getHeight();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 钢管动画方法</span><br><span class="line"> //每次障碍物中心点坐标减1,如果障碍物离开屏</span><br><span class="line"> //幕右端则在屏幕左端重新出现,并且障碍物高度随机生成</span><br><span class="line"> public void move() {</span><br><span class="line"> column_x--;</span><br><span class="line"> if (column_x < -width / 2) {</span><br><span class="line"> column_y = random.nextInt(180) + 150;</span><br><span class="line"> column_x = 432 + width / 2;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="2-Ground类"><a href="#2-Ground类" class="headerlink" title="2.Ground类"></a>2.Ground类</h4><p>地面我们只需要关注地面的坐标就行了,它和上面的Column类非常相似。</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><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">class Ground {</span><br><span class="line"> int ground_x, ground_y; // 地面的坐标</span><br><span class="line"> BufferedImage grBufferImage; // 地面图片</span><br><span class="line"></span><br><span class="line"> public Ground() {</span><br><span class="line"> super();</span><br><span class="line"> File grImage = new File("images/ground.png");</span><br><span class="line"> try {</span><br><span class="line"> grBufferImage = ImageIO.read(grImage);</span><br><span class="line"> } catch (IOException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> //设置地面图片的位置</span><br><span class="line"> ground_y = 500;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 地面动画方法</span><br><span class="line"> //每次调用这个方法地面的图片向左移动1,</span><br><span class="line"> //如果地面图片到达了尽头就从新使图片回到最初位置继续向左移动,如此反复。</span><br><span class="line"> public void move() {</span><br><span class="line"> ground_x--;</span><br><span class="line"> if (ground_x < -110) {</span><br><span class="line"> ground_x = 0;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="3-Bird类"><a href="#3-Bird类" class="headerlink" title="3.Bird类"></a>3.Bird类</h4><p>到了Bird类这里就稍稍有点复杂了,我们需要考虑的属性也变得多了起来。首先最容易想到的就是图片的宽高和中心坐标了,其次我们需要考虑小鸟在x轴和y轴的移动速度</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line">class Bird {</span><br><span class="line"> int bird_x = 60, bird_y = 300; // 鸟的中心点坐标</span><br><span class="line"> int bird_width, bird_height; // 鸟的宽度,高度</span><br><span class="line"> double speed = 20; // 速度</span><br><span class="line"> double g = 4; // 加速度</span><br><span class="line"> double s; // 运动距离</span><br><span class="line"> double t = 0.3; // 运动时间</span><br><span class="line"> BufferedImage biBufferImage; // 鸟图片</span><br><span class="line"> BufferedImage[] images = new BufferedImage[8];</span><br><span class="line"></span><br><span class="line"> public Bird() {</span><br><span class="line"> super();</span><br><span class="line"> //反复按顺序绘制图片以达到动画的效果</span><br><span class="line"> for (int i = 0; i < images.length; i++) {</span><br><span class="line"> File biImage = new File("images/" + i + ".png");</span><br><span class="line"> try {</span><br><span class="line"> images[i] = ImageIO.read(biImage);</span><br><span class="line"> } catch (IOException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> biBufferImage = images[0];</span><br><span class="line"> bird_width = biBufferImage.getWidth();</span><br><span class="line"> bird_height = biBufferImage.getHeight();</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"> int index = 0;</span><br><span class="line"> public void change() {</span><br><span class="line"> index++;</span><br><span class="line"> biBufferImage = images[index / 3 % 8];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //小鸟移动的方法</span><br><span class="line"> double ratation; //倾斜角度</span><br><span class="line"> public void move_go() {</span><br><span class="line"> double v0 = speed;</span><br><span class="line"> s = v0 * t - 0.5 * g * t * t;</span><br><span class="line"> double vt = v0 - g * t;</span><br><span class="line"> speed = vt;</span><br><span class="line"> bird_y = bird_y - (int) s;//小鸟下落后的在y轴方向的位置</span><br><span class="line"> ratation = s / 16;</span><br><span class="line"> if (bird_y <= bird_height / 2) {//这里限制小鸟最高只能飞到了屏幕的最上方</span><br><span class="line"> bird_y = bird_height / 2;</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"> public void refly() {</span><br><span class="line"> speed = 20;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //撞击地面</span><br><span class="line"> //撞到了地面就返false</span><br><span class="line"> public boolean hit(Ground ground) {</span><br><span class="line"> return bird_y + bird_height / 2 >= ground.ground_y;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //撞击钢管</span><br><span class="line"> //如果碰到了钢管的左侧、右侧、上方和下方都返回false,否则返回true</span><br><span class="line"> public boolean hit(Column column) {</span><br><span class="line"> int left_x = column.column_x - column.width / 2 - bird_width / 2;</span><br><span class="line"> int right_x = column.column_x + column.width / 2 + bird_width / 2;</span><br><span class="line"> int top_y = column.column_y - column.gap / 2 + bird_height / 2 - 5;</span><br><span class="line"> int down_y = column.column_y + column.gap / 2 - bird_height / 2 + 5;</span><br><span class="line"> if (bird_x > left_x && bird_x < right_x) {</span><br><span class="line"> if (bird_y > top_y && bird_y < down_y) {</span><br><span class="line"> return false;</span><br><span class="line"> } else {</span><br><span class="line"> return true;</span><br><span class="line"> }</span><br><span class="line"> } else {</span><br><span class="line"> return false;</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h4 id="4-Sky类"><a href="#4-Sky类" class="headerlink" title="4.Sky类"></a>4.Sky类</h4><p>Sky类是本程序中最复杂的一类,因为它是其他三类的组合,并且它自身要包含绘制图片以及游戏逻辑的等功能。对于游戏逻辑这一部分,我们的想法是程序每经历很小的一段时间就循环进行图形的绘制,并且要执行Column,Ground两类中的move方法且判断一次游戏是否结束,整个过程中都要监听键盘和鼠标点击事件来相应Bird中的move_go方法。</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br></pre></td><td class="code"><pre><span class="line">class Sky extends JPanel {</span><br><span class="line"> private static final long serialVersionUID = 1L;//游戏版本号</span><br><span class="line"> BufferedImage bgBufferImage; //背景图片</span><br><span class="line"> Ground ground = new Ground(); //地面</span><br><span class="line"> Column column = new Column(350); //钢管</span><br><span class="line"> Column column2 = new Column(600); //钢管</span><br><span class="line"> static Bird bird = new Bird(); //小鸟</span><br><span class="line"> int score = 0; //游戏得分</span><br><span class="line"> BufferedImage startBufferImage; //开始准备界面</span><br><span class="line"> boolean isStart; //是否开始游戏</span><br><span class="line"> BufferedImage overBufferImage; //游戏结束界面</span><br><span class="line"> boolean isOver; //游戏是否结束</span><br><span class="line"></span><br><span class="line"> public Sky() {</span><br><span class="line"> super();</span><br><span class="line"> //读取图片</span><br><span class="line"> File bgImage = new File("images/bg.png");</span><br><span class="line"> File starImage = new File("images/start.png");</span><br><span class="line"> File overImage = new File("images/gameover.png");</span><br><span class="line"> try {</span><br><span class="line"> bgBufferImage = ImageIO.read(bgImage);</span><br><span class="line"> startBufferImage = ImageIO.read(starImage);</span><br><span class="line"> overBufferImage = ImageIO.read(overImage);</span><br><span class="line"> } catch (IOException e) {</span><br><span class="line"> e.printStackTrace();</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"> @Override</span><br><span class="line"> public void paint(Graphics graphics) {</span><br><span class="line"> //画背景</span><br><span class="line"> graphics.drawImage(bgBufferImage, 0, 0, null);</span><br><span class="line"> //获取新的画笔对象</span><br><span class="line"> Graphics2D gg = (Graphics2D) graphics;</span><br><span class="line"> gg.rotate(-bird.ratation, bird.bird_x, bird.bird_y);</span><br><span class="line"> //画小鸟</span><br><span class="line"> graphics.drawImage(bird.biBufferImage, bird.bird_x - bird.bird_width</span><br><span class="line"> / 2, bird.bird_y - bird.bird_height / 2, null);</span><br><span class="line"> gg.rotate(bird.ratation, bird.bird_x, bird.bird_y);</span><br><span class="line"> //画钢管</span><br><span class="line"> graphics.drawImage(column.coBufferImage, column.column_x - column.width</span><br><span class="line"> / 2, column.column_y - column.height / 2, null);</span><br><span class="line"> graphics.drawImage(column2.coBufferImage, column2.column_x</span><br><span class="line"> - column2.width / 2, column2.column_y - column2.height / 2,</span><br><span class="line"> null);</span><br><span class="line"> //画地面</span><br><span class="line"> graphics.drawImage(ground.grBufferImage, ground.ground_x,</span><br><span class="line"> ground.ground_y, null);</span><br><span class="line"> //画文字</span><br><span class="line"> graphics.setColor(Color.BLUE);</span><br><span class="line"> graphics.setFont(new Font("楷体", Font.ITALIC, 30));</span><br><span class="line"> graphics.drawString("分数:" + score, 100, 600);</span><br><span class="line"> //画开始准备图片</span><br><span class="line"> if (!isStart && !isOver) {</span><br><span class="line"> graphics.drawImage(startBufferImage, 0, 0, null);</span><br><span class="line"> }</span><br><span class="line"> //画结束界面</span><br><span class="line"> if (isOver) {</span><br><span class="line"> graphics.drawImage(overBufferImage, 0, 0, null);</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"> public void action() {</span><br><span class="line"> //定义鼠标监听器</span><br><span class="line"> //用鼠标可以控制小鸟在y轴上的位置</span><br><span class="line"> MouseAdapter adapter = new MouseAdapter() {</span><br><span class="line"> @Override</span><br><span class="line"> public void mousePressed(MouseEvent e) {</span><br><span class="line"> // System.out.println("点击了鼠标");</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"> if (isOver) {</span><br><span class="line"> bird = new Bird();</span><br><span class="line"> ground = new Ground();</span><br><span class="line"> column = new Column(350);</span><br><span class="line"> column2 = new Column(600);</span><br><span class="line"> score = 0;</span><br><span class="line"> isOver = false;</span><br><span class="line"> isStart = false;</span><br><span class="line"> } else {</span><br><span class="line"> bird.refly();</span><br><span class="line"> isStart = true;</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"> this.addMouseListener(adapter);</span><br><span class="line"> //定义键盘监听器</span><br><span class="line"> //这里用键盘可以控制小鸟在x轴和y轴上的位置</span><br><span class="line"> KeyAdapter keyAdapter = new KeyAdapter() {</span><br><span class="line"> @Override</span><br><span class="line"> public void keyPressed(KeyEvent e) {</span><br><span class="line"> char charA = e.getKeyChar();</span><br><span class="line"> if (charA == 'w') {</span><br><span class="line"> if (bird.bird_y > 20) {</span><br><span class="line"> bird.bird_y -= 20;</span><br><span class="line"> }</span><br><span class="line"> } else if (charA == 's') {</span><br><span class="line"> if (bird.bird_y < 465) {</span><br><span class="line"> bird.bird_y += 20;</span><br><span class="line"> }</span><br><span class="line"> } else if (charA == 'a') {</span><br><span class="line"> if (bird.bird_x > 20) {</span><br><span class="line"> bird.bird_x -= 20;</span><br><span class="line"> }</span><br><span class="line"> } else if (charA == 'd') {</span><br><span class="line"> if (bird.bird_x < 395) {</span><br><span class="line"> bird.bird_x += 20;</span><br><span class="line"> }</span><br><span class="line"> } else if (charA == ' ') {</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"> if (isOver) {</span><br><span class="line"> bird = new Bird();</span><br><span class="line"> ground = new Ground();</span><br><span class="line"> column = new Column(350);</span><br><span class="line"> column2 = new Column(600);</span><br><span class="line"> score = 0;</span><br><span class="line"> isOver = false;</span><br><span class="line"> isStart = false;</span><br><span class="line"> } else {</span><br><span class="line"> bird.refly();</span><br><span class="line"> isStart = true;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> super.keyPressed(e);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> //将上面的键盘监听事件加入到程序中</span><br><span class="line"> this.addKeyListener(keyAdapter);</span><br><span class="line"> this.requestFocus();</span><br><span class="line"></span><br><span class="line"> while (true) {</span><br><span class="line"> //循环执行下面方法</span><br><span class="line"> //判断游戏是否开始</span><br><span class="line"> if (isStart && !isOver) {</span><br><span class="line"> ground.move();</span><br><span class="line"> column.move();</span><br><span class="line"> column2.move();</span><br><span class="line"> bird.change();</span><br><span class="line"> bird.move_go();</span><br><span class="line"> }</span><br><span class="line"> //判断撞击障碍</span><br><span class="line"> if (bird.bird_x - bird.bird_width / 2 == column.column_x</span><br><span class="line"> + column.width / 2</span><br><span class="line"> || bird.bird_x - bird.bird_width / 2 == column2.column_x</span><br><span class="line"> + column2.width / 2) {</span><br><span class="line"> score++;</span><br><span class="line"> }</span><br><span class="line"> if (bird.hit(ground) || bird.hit(column) || bird.hit(column2)) {</span><br><span class="line"> isStart = false;</span><br><span class="line"> isOver = true;</span><br><span class="line"> }</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(20);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> //再次绘制一次图形</span><br><span class="line"> repaint();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="5-main函数"><a href="#5-main函数" class="headerlink" title="5.main函数"></a>5.main函数</h4><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><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">public static void main(String[] args) {</span><br><span class="line"> // 定义画框</span><br><span class="line"> JFrame jf = new JFrame("bird_game");</span><br><span class="line"> jf.setSize(432, 674);</span><br><span class="line"> jf.setAlwaysOnTop(false);</span><br><span class="line"> jf.setLocationRelativeTo(null);</span><br><span class="line"> jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);</span><br><span class="line"> jf.setResizable(false);</span><br><span class="line"> Sky sky = new Sky();</span><br><span class="line"> jf.add(sky);</span><br><span class="line"> // 显示画框</span><br><span class="line"> jf.setVisible(true);</span><br><span class="line"> //执行程序逻辑</span><br><span class="line"> sky.action();</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><hr><h2 id="三-结果演示"><a href="#三-结果演示" class="headerlink" title="三.结果演示"></a>三.结果演示</h2><h4 id="1-游戏开始界面"><a href="#1-游戏开始界面" class="headerlink" title="1.游戏开始界面"></a>1.游戏开始界面</h4><p><img src="https://s1.328888.xyz/2022/07/06/I4S5.png"></p><h4 id="2-游戏进行界面"><a href="#2-游戏进行界面" class="headerlink" title="2.游戏进行界面"></a>2.游戏进行界面</h4><p><img src="https://s1.328888.xyz/2022/07/06/ITlN.png"></p><h4 id="3-游戏结束界面"><a href="#3-游戏结束界面" class="headerlink" title="3.游戏结束界面"></a>3.游戏结束界面</h4><p><img src="https://s1.328888.xyz/2022/07/06/Ihay.png"></p><p>整个飞翔的小鸟到这里就结束了,我这个学年设计也差不多了(#^.^#)。最后附上源代码和素材,github仓库地址:<a href="https://github.com/cofbro/FlyingBird/tree/main">https://github.com/cofbro/FlyingBird/tree/main</a></p>]]></content>
<categories>
<category> Diary </category>
</categories>
<tags>
<tag> Java </tag>
</tags>
</entry>
<entry>
<title>随便说说</title>
<link href="/2022/07/05/suibiOne/"/>
<url>/2022/07/05/suibiOne/</url>
<content type="html"><![CDATA[<h1 id="随便说说"><a href="#随便说说" class="headerlink" title="随便说说"></a>随便说说</h1><p>自从期末复习周开始我就已经很久没有写过博客了,全都是为了应付学校里的考试,真的是太多了-_-||,这学期考试大概有10几门,不过终于熬过考试月了,还是不错了。</p><hr><p>现在我又有了新任务,第一个是学校安排的学年设计,我打算就模仿一下经典的飞翔的小鸟((<em>^▽^</em>)),还有一个就是互联网+我所负责的网页设计,最后就是还要学习安卓课程,确实挺累的…今年暑假也就没有了。</p>]]></content>
<categories>
<category> 杂文 </category>
</categories>
<tags>
<tag> 随笔 </tag>
</tags>
</entry>
<entry>
<title>有关Java多线程</title>
<link href="/2022/06/17/multithreading/"/>
<url>/2022/06/17/multithreading/</url>
<content type="html"><![CDATA[<h2 id="一-关于多线程"><a href="#一-关于多线程" class="headerlink" title="一.关于多线程"></a>一.关于多线程</h2><p>如果要处理多个业务时,而这些业务之间没有很强的依赖,如A,B,C,此时不按顺序执行这三个业务。当A出现阻塞时,此时CPU是空闲的,我们可以先让B加载进CPU进行执行,当A没有被阻塞时再将A让CPU执行,这样效率明显要高于顺序执行。多线程解决的是并发的问题,目的是使任务执行效率更高,实现前提是“阻塞”。它们看上去时同时在执行的,但实际上只是分时间片使用CPU而已。</p><hr><h2 id="二-进程与线程"><a href="#二-进程与线程" class="headerlink" title="二.进程与线程"></a>二.进程与线程</h2><p>线程是比进程更小一级的执行单位,一个进程可以包含多个线程。多进程操作系统能同时运行多个进程(程序),由于 CPU 具备分时机制,所以每个进程都能循环获得自己的CPU 时间片。由于 CPU 执行速度非常快,使得所有程序好像是在同时运行一样。我们最常见的就是main方法可以看做一个进程或者是一个主线程,而在main方法里面开启的线程就是其子线程,他们同时存在,也可以同时运行。</p><hr><h2 id="三-开启线程的方式"><a href="#三-开启线程的方式" class="headerlink" title="三.开启线程的方式"></a>三.开启线程的方式</h2><h4 id="1-继承Thread类"><a href="#1-继承Thread类" class="headerlink" title="1.继承Thread类"></a>1.继承Thread类</h4><p>定义一个类,继承Thread类,并重写Thread类的run()方法。run()方法是线程要完成的功能。创建一个继承了Thread类的对象,产生一个线程。并使用该对象的start方法,启动线程。</p><p><em>注意:如果只是调用run()方法,只是相当于普通调用了次方法,JVM不会启动线程。必须使用start()方法,JVM自动调用此线程的run()方法</em></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><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></pre></td><td class="code"><pre><span class="line">public class myThread extends Thread{</span><br><span class="line">private String name;</span><br><span class="line">public ThreadOne() {}</span><br><span class="line">public ThreadOne(String name) {</span><br><span class="line">super();</span><br><span class="line">this.name = name;</span><br><span class="line">}</span><br><span class="line">public void run() {</span><br><span class="line">for(int i=0;i<10;i++) {</span><br><span class="line">System.out.println(name + 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><br><span class="line"></span><br><span class="line">public class Test {</span><br><span class="line">public static void main(String[] args) {</span><br><span class="line"> myThread t1 = new myThread("第一个线程:");</span><br><span class="line"> myThread t2 = new myThread("第二个线程:");</span><br><span class="line"> t1.start();</span><br><span class="line"> t2.start();</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="2-实现runnable接口"><a href="#2-实现runnable接口" class="headerlink" title="2.实现runnable接口"></a>2.实现runnable接口</h4><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><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">public class myThread implements Runnable {</span><br><span class="line">private String name;</span><br><span class="line">public ThreadOne() {</span><br><span class="line">super();</span><br><span class="line">}</span><br><span class="line">public ThreadOne(String name) {</span><br><span class="line">super();</span><br><span class="line">this.name = name;</span><br><span class="line">}</span><br><span class="line">@Override</span><br><span class="line">public void run() {</span><br><span class="line">for(int i = 0; i < 10; i++) {</span><br><span class="line">System.out.println(name + 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><br><span class="line"></span><br><span class="line">public class Test {</span><br><span class="line">public static void main(String[] args) {</span><br><span class="line"> myThread threadOne = new myThread("第一个线程:");</span><br><span class="line"> myThread threadTwo = new myThread("第二个线程:");</span><br><span class="line"> Thread t1 = new Thread(threadOne);</span><br><span class="line"> Thread t2 = new Thread(threadTwo);</span><br><span class="line"> t1.start();</span><br><span class="line"> t2.start();</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这两种开启线程的运行结果都是两个子线程(t1,t2)相互争夺CPU资源,可能 t1线程执行了一小半,t2线程开始执行了,而后t1又将CPU资源抢占过去,这些都是随机的,那么有没有能控制线程的执行顺序的方法呢?</p><ul><li>public void final setPriority(int newPriority)</li><li>public void final getPriority()</li></ul><p>Java为我们提供了两个方法,很显然一个是设置优先级,一个是获取优先级。其中newPriority是优先指数,它越大优先级越高。但是用此方法可能会有异常。</p><h4 id="3-异常"><a href="#3-异常" class="headerlink" title="3.异常"></a>3.异常</h4><p>a.IllegalArgumentException 如果优先级不在MIN_PRIORITY和MAX_PRIORITY之间就会出此异常<br><em>注:MAX_PRIORITY = 10,MIN_PRIORITY = 1,NORM_PRIORITY = 5,线程默认优先级为5</em></p><p>b.SecurityException 如果当前线程不能修改此线程就会出此异常</p><p>因此我们给 <code>t1</code> 设置优先级时,会优先将 <code>t1</code> 执行完毕(若优先级相等则随机执行)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">t1.setPriority(10)//此时优先执行优先级高的,也就是t1</span><br></pre></td></tr></table></figure><hr><h2 id="四-线程控制"><a href="#四-线程控制" class="headerlink" title="四.线程控制"></a>四.线程控制</h2><p>关于线程控制有三个方法,</p><ul><li>static void sleep(long millis) 暂停millis毫秒执行此线程</li><li>void join() 等待这个线程死亡</li><li>void setDaemon(boolean on) 标记此为守护线程,若所有线程都是守护线程,JVM将退出,但不是立即退出</li></ul><h4 id="1-sleep-方法"><a href="#1-sleep-方法" class="headerlink" title="1.sleep()方法"></a>1.sleep()方法</h4><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><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></pre></td><td class="code"><pre><span class="line">public class myThread extends Thread{</span><br><span class="line">public void run() {</span><br><span class="line">for(int i = 0; i < 10; i++) {</span><br><span class="line"> System.out.println(getName + ":" + i);</span><br><span class="line"> try{</span><br><span class="line"> Thread.sleep(100);</span><br><span class="line"> } catch(InterruptException e){</span><br><span class="line"> e.printStackTrace();</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><span class="line">public class Test {</span><br><span class="line">public static void main(String[] args) {</span><br><span class="line"> myThread t1 = new myThread();</span><br><span class="line"> myThread t2 = new myThread();</span><br><span class="line"> t1.setName("cc")</span><br><span class="line"> t2.setName("aa")</span><br><span class="line"> t1.start();</span><br><span class="line"> t2.start();</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="2-join-方法"><a href="#2-join-方法" class="headerlink" title="2.join()方法"></a>2.join()方法</h4><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><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></pre></td><td class="code"><pre><span class="line">public class myThread extends Thread{</span><br><span class="line">public void run() {</span><br><span class="line">for(int i=0;i<10;i++) {</span><br><span class="line">System.out.println(getName + ":" + 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><br><span class="line">public class Test {</span><br><span class="line">public static void main(String[] args) {</span><br><span class="line"> myThread t1 = new myThread();</span><br><span class="line"> myThread t2 = new myThread();</span><br><span class="line"> myThread t3 = new myThread();</span><br><span class="line"> t1.setName("aa");</span><br><span class="line"> t2.setName("bb");</span><br><span class="line"> t3.setName("cc");</span><br><span class="line"> try{</span><br><span class="line"> t1.join();</span><br><span class="line"> } catch(InterruptException e){</span><br><span class="line"> e.printStackTrace();</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"> //只有将t1执行完后才会开启t2和t3线程</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="3-setDaemon-方法"><a href="#3-setDaemon-方法" class="headerlink" title="3.setDaemon()方法"></a>3.setDaemon()方法</h4><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><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">public class myThread extends Thread{</span><br><span class="line">public void run() {</span><br><span class="line">for(int i=0;i<10;i++) {</span><br><span class="line">System.out.println(getName + ":" + 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><br><span class="line">public class Test {</span><br><span class="line">public static void main(String[] args) {</span><br><span class="line"> myThread t1 = new myThread();</span><br><span class="line"> myThread t2 = new myThread();</span><br><span class="line"> t1.setName("aa");</span><br><span class="line"> t2.setName("bb");</span><br><span class="line"> Thread.currentThread().setName("c");//设置主线程名字为c</span><br><span class="line">t1.setDaemon(true);</span><br><span class="line">t2.setDaemon(true);</span><br><span class="line"> t1.start();</span><br><span class="line"> t2.start();</span><br><span class="line"> for(int i = 0; i < 10; i++){</span><br><span class="line"> System.out.println(Thead.currentThread.getName + ":" + i);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"> //若主线程执行完后,JVM将关闭,程序结束</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="五-线程的生命周期"><a href="#五-线程的生命周期" class="headerlink" title="五.线程的生命周期"></a>五.线程的生命周期</h2><h4 id="1-java线程周期"><a href="#1-java线程周期" class="headerlink" title="1.java线程周期"></a>1.java线程周期</h4><p><img src="https://s1.328888.xyz/2022/06/17/0HNyq.jpg"></p><h4 id="2-线程的五个状态"><a href="#2-线程的五个状态" class="headerlink" title="2.线程的五个状态"></a>2.线程的五个状态</h4><ul><li><p>新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();</p></li><li><p>就绪状态(Runnable):当调用线程对象的start()方法,线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了start()方法后,此线程立即就会执行;</p></li><li><p>运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;</p></li><li><p>阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:</p></li><li><p>死亡状态(Dead):线程执行完了或者因异常退出了run()方法</p></li></ul><p>(1)等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;</p><p>(2)同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;</p><p>(3)其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。</p><hr><h2 id="六-线程安全"><a href="#六-线程安全" class="headerlink" title="六.线程安全"></a>六.线程安全</h2><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><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></pre></td><td class="code"><pre><span class="line">public class Ticket implements Runnable {</span><br><span class="line"> final Object obj = new Object();</span><br><span class="line"> int ticket = 100;</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> while (true) {</span><br><span class="line"> if (ticket > 0) {</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(100);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + ":" + ticket);</span><br><span class="line"> ticket--;</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><span class="line">public class Test {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> Ticket tt = new Ticket();</span><br><span class="line"> Thread t1 = new Thread(tt);</span><br><span class="line"> Thread t2 = new Thread(tt);</span><br><span class="line"> t1.setName("售票一");</span><br><span class="line"> t2.setName("售票二");</span><br><span class="line"> t1.start();</span><br><span class="line"> t2.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>t1和t2共用一个资源,他们都会相互争夺CPU资源,当t1被CPU加载后,因为调用了sleep()方法就会进入阻塞状态,这时t2就会被CPU加载,这就会出现如图所示售票一和售票二交替出现的效果。</p><p><img src="https://s1.328888.xyz/2022/06/18/0WmcW.png"></p><p>那么如何解决这种问题呢?</p><h4 id="1-使用同步代码块"><a href="#1-使用同步代码块" class="headerlink" title="1.使用同步代码块"></a>1.使用同步代码块</h4><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><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">public class Ticket implements Runnable {</span><br><span class="line"> final Object obj = new Object();</span><br><span class="line"> int ticket = 100;</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> while (true) {</span><br><span class="line"> synchronized (obj) {</span><br><span class="line"> if (ticket > 0) {</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(100);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + ":" + ticket);</span><br><span class="line"> ticket--;</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><span class="line">//测试类</span><br><span class="line">public class Test {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> Ticket tt = new Ticket();</span><br><span class="line"> Thread t1 = new Thread(tt);</span><br><span class="line"> Thread t2 = new Thread(tt);</span><br><span class="line"> t1.setName("售票一");</span><br><span class="line"> t2.setName("售票二");</span><br><span class="line"> t1.start();</span><br><span class="line"> t2.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用synchronized即可,当t1率先抢占到CPU资源时,它就为这个代码块上了锁,当他调用sleep()方法进去阻塞阶段时,t2进入CPU加载,但由于被上了锁,因此t2只能等待t1执行结束,释放了CPU资源时才能被CPU加载,这就保证了数据的安全。</p><h4 id="2-使用同步方法"><a href="#2-使用同步方法" class="headerlink" title="2.使用同步方法"></a>2.使用同步方法</h4><p>在方法名前面添加关键字synchronized</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><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">public class Ticket implements Runnable {</span><br><span class="line"> int ticket = 100;</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> while (ticket >= 0){</span><br><span class="line"> sellTicket();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public synchronized void sellTicket() {</span><br><span class="line"> if (ticket > 0) {</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(100);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + ":" + ticket);</span><br><span class="line"> ticket--;</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">public class Test {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> Ticket tt = new Ticket();</span><br><span class="line"> Thread t1 = new Thread(tt);</span><br><span class="line"> Thread t2 = new Thread(tt);</span><br><span class="line"> t1.setName("售票一");</span><br><span class="line"> t2.setName("售票二");</span><br><span class="line"> t1.start();</span><br><span class="line"> t2.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="3-Lock锁"><a href="#3-Lock锁" class="headerlink" title="3.Lock锁"></a>3.Lock锁</h4><p>使用同步代码块不能直观的看到锁与解锁,而Lock锁能更清晰的表达如何加锁和释放锁,比synchronized方法可以更加获得广泛的锁定操作。</p><p>Lock中的两个方法</p><ul><li>void lock():获得锁</li><li>void unlock():释放锁</li></ul><p>Lock是接口不能直接实例化,只能采用它的实现类ReentrantLock来实例化。</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><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">import java.util.concurrent.locks.Lock;</span><br><span class="line">import java.util.concurrent.locks.ReentrantLock;</span><br><span class="line"></span><br><span class="line">public class Ticket implements Runnable {</span><br><span class="line"> Lock lock = new ReentrantLock();</span><br><span class="line"> int ticket = 100;</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> while (true) {</span><br><span class="line">使用try{}catch{}语句防止线程中方法出错导致不能成功释放锁而一直占用CPU资源的情况</span><br><span class="line"> try {</span><br><span class="line"> lock.lock();</span><br><span class="line"> if (ticket > 0) {</span><br><span class="line"> try {</span><br><span class="line"> Thread.sleep(10);</span><br><span class="line"> } catch (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + ":" + ticket);</span><br><span class="line"> ticket--;</span><br><span class="line"> }</span><br><span class="line"> }finally {</span><br><span class="line"> lock.unlock();</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><span class="line">public class Test {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> Ticket tt = new Ticket();</span><br><span class="line"> Thread t1 = new Thread(tt);</span><br><span class="line"> Thread t2 = new Thread(tt);</span><br><span class="line"> t1.setName("售票一");</span><br><span class="line"> t2.setName("售票二");</span><br><span class="line"> t1.start();</span><br><span class="line"> t2.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> Java多线程 </category>
</categories>
<tags>
<tag> Java </tag>
</tags>
</entry>
<entry>
<title>算法练习:不同的二叉搜索树(动态规划)</title>
<link href="/2022/06/03/unique-binary-search-trees/"/>
<url>/2022/06/03/unique-binary-search-trees/</url>
<content type="html"><![CDATA[<h2 id="一-前言"><a href="#一-前言" class="headerlink" title="一.前言"></a>一.前言</h2><p>LeetCode 题目:<a href="https://leetcode.cn/problems/unique-binary-search-trees/">96. 不同的二叉搜索树</a><br>给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。</p><p><code>示例 1:</code></p><blockquote><p>输入:n = 3<br>输出:5</p></blockquote><p><code>示例2:</code></p><blockquote><p>输入:n = 1<br>输出:1</p></blockquote><p>题目很简洁,但是分析起来却有点复杂,首先搞清楚什么是二叉搜索树。二叉搜索树分为左子树和右子树,左子树还能再分为左子树和右子树,就像这样:<br><img src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic4.zhimg.com%2Fv2-06ce569cea6d80d3a649ff69b4de1cc4_1440w.jpg%3Fsource%3D172ae18b&refer=http%3A%2F%2Fpic4.zhimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1656745281&t=9da45f912e7d67e118a778a2b40aa5f7"></p><p>二叉搜索树有三个条件:</p><ol><li>若它的左子树不空,则左子树上所有结点的值均小于它的<a href="https://baike.baidu.com/item/%E6%A0%B9%E7%BB%93%E7%82%B9/9795570">根结点</a>的值</li><li>若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值</li><li>它的左、右子树也分别为二叉搜索树</li></ol><h2 id="二-分析思考"><a href="#二-分析思考" class="headerlink" title="二.分析思考"></a>二.分析思考</h2><p>我们将 dp 数组定义为 1 到 n 互不相同的二叉搜索树的个数,先来尝试写写 <code>n = 1</code> 和 <code>n = 2</code>的情况<br><img src="https://s1.328888.xyz/2022/06/02/WeHL2.png"></p><p>这两个其实还挺简单的,再来看看 <code>n = 3</code> 的时候<br><img src="https://s1.328888.xyz/2022/06/02/We884.png"><br>这个时候不同的二叉搜索树就有 5 个了,我们来看看这五个是怎么根据前面的结果推导出来的。此图片中前两棵树是根据 dp[2]中的第一个二叉搜索树得出来的,他们结构都是一样的,都是没有左子树,右子树有两个结点。而 dp[3]中的第三和第四棵树同理和 dp[2]中的第二棵树结构相同。</p><p>那么 dp[3]中最后一棵树是怎么来的呢,我们将眼光放宽一点,这时候会发现它和 dp[1]中那棵树结构很相似!</p><p>继续探索一下这个规律:</p><ul><li>二叉搜索树当以 <code>1</code> 为起点时,它的个数为右子树的个数 dp[2];</li><li>二叉搜索树当以 <code>2</code> 为起点时,它的个数为右子树的个数 dp[1] * 左子树的个数 dp[1];</li><li>二叉搜索树当以 <code>3</code> 为起点时,它的个数为左子树的个数 dp[2];<br>则总共的二叉搜索树为:<code>dp[2] + dp[1] * dp[1] + dp[2]</code><br><em>注:定义 dp[0] = 1,使之符合下面通式的规律</em></li></ul><p>我们将这个规律扩展到 <code>n</code>,我们需要计算出从 1 到 n 为起点的每一个二叉搜索树的个数,因此需要循环来遍历,则总共的二叉搜索树为:<code>dp[i] += dp[i - j] * dp[j -1]</code>,其中<code>i</code> 为外层循环且<code>i <= n</code>,<code>j</code> 为内层循环且 <code>j <= i</code>。<br>为了验证一下这个规律,我们继续往下写一组二叉搜索树,当<code>n = 4</code>时<br><img src="https://s1.328888.xyz/2022/06/02/We53C.png"></p><p>其中二叉搜索树为个数为:<code>dp[0] * dp[3] + dp[1] * dp[2] + dp[2] * dp[1] + dp[3] * dp[0] = 14</code>,确实符合上述规律…</p><h2 id="三-代码"><a href="#三-代码" class="headerlink" title="三.代码"></a>三.代码</h2><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><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">class Solution {</span><br><span class="line"> public int numTrees(int n) {</span><br><span class="line"> int[] dp = new int[n + 1];</span><br><span class="line"> dp[0] = 1;</span><br><span class="line"> dp[1] = 1;</span><br><span class="line"> for(int i = 2; i <= n; i++){</span><br><span class="line"> for(int j = 1; j <= i; j++){</span><br><span class="line"> dp[i] += dp[i - j] * dp[j -1];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return dp[n];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>ps.虽然代码只有这么点,但是分析和思考过程却远远不止这点,要想掌握动态规划还得多加练习和总结啊。</p>]]></content>
<categories>
<category> LeetCode </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 算法练习 </tag>
<tag> 动态规划 </tag>
</tags>
</entry>
<entry>
<title>Kotlin之lambda 表达式与高阶函数</title>
<link href="/2022/06/02/lambda/"/>
<url>/2022/06/02/lambda/</url>
<content type="html"><![CDATA[<h2 id="一-lambda-表达式"><a href="#一-lambda-表达式" class="headerlink" title="一.lambda 表达式"></a>一.lambda 表达式</h2><p>lambda 在 Java 里面也有,不过 Kotlin 中的 lambda 使用起来更方便、更灵活。<br>lambda 其实就是匿名函数,能使代码变得更加灵活简洁,其表达式为: <code>{变量定义 -> 代码块}</code> , 如:<code>{ a, b -> a + b }</code></p><blockquote><p>lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的函数</p></blockquote><p>1、lambda 函数比较轻便,即用即仍,很适合需要完成一项功能,但是此功能只在此一处使用,连名字都很随意的情况下;<br>2、匿名函数,一般用来给 filter, map 这样的函数式编程服务;<br>3、作为回调函数,传递给某些应用,比如消息处理</p><ul><li>完整的 <code>lambda</code> 表达式</li></ul><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">val method: (num1: Int, num2: Int) -> Boolean = { a: Int,b: Int -></span><br><span class="line"> a < b</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>完整语法形式的参数声明放在圆括号内,其中 <code>num1</code> 和 <code>num2</code>只是变量的声明,可以不写,但是后面的函数体必须定义相应的变量(a,b),并有可选的类型标注, 函数体跟在一个 <code>-></code> 符号之后。如果推断出的该 <code>lambda</code> 的返回类型不是 <code>Unit</code>,那么该 lambda 主体中的最后一个(或可能是单个) 表达式会视为返回值。</p><p>关于 <code>-></code>:<br>a.其参数(如果有的话)在 -> 之前声明<br>b.函数体(如果存在的话)在 -> 后面</p><ul><li>因此 <code>lambda</code> 表达式可以简化成下面这种</li></ul><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">val method: (Int, Int) -> Boolean = { a, b -></span><br><span class="line"> a < b //函数体中最后一行表达式即为 lambda 的返回值</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><ul><li>还能省略函数的声明,只留下函数体</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">val method= { a: Int, b: Int -> a < b }//Kotlin可以自动推断返回类型,也可省略返回值类型的声明</span><br></pre></td></tr></table></figure><ul><li>当 <code>lambda</code> 中有未用到的参数时可以用 <code>_</code> 表示</li></ul><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">val method: (num1: Int, num2: Int) -> Boolean = { _, _ -></span><br><span class="line"> true</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h2 id="二-高阶函数"><a href="#二-高阶函数" class="headerlink" title="二.高阶函数"></a>二.高阶函数</h2><p>高阶函数就是以函数为参数的一种特殊的函数,通常与 <code>lambda</code> 配合起来一起使用,即 <code>lambda</code> 作为参数。</p><ul><li>高阶函数的声明实现与调用</li></ul><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">val sum = { x: Int -> x}</span><br><span class="line"></span><br><span class="line">fun method(age: Int, lambda: (a: Int) -> Int): Int {</span><br><span class="line"> return lambda(10 * age)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">method(10, sum)</span><br></pre></td></tr></table></figure><ul><li>当然高阶函数调用参数也可以直接放 <code>lambda</code></li></ul><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">fun method(age: Int, lambda: (a: Int) -> Int): Int {</span><br><span class="line"> return lambda(10 * age)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">method(10, lambda = { a -> a.and(20) })</span><br></pre></td></tr></table></figure><ul><li>it 隐式参数<br>当 <code>lambda</code> 中只有一个参数时,你不必再去单独定义一个变量来接收它,Kotlin 提供了 <code>it</code> 参数,代表的是你声明 <code>lambda</code> 时定义的那个唯一参数。</li></ul><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></pre></td><td class="code"><pre><span class="line">fun method(age: Int, lambda: (a: Int) -> Int): Int {</span><br><span class="line"> return lambda(10 * age)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">method(10, lambda = {it.and(20)})//这里的 it 就是前面声明中的 a</span><br></pre></td></tr></table></figure><ul><li>当高阶函数中的最后一个参数是 <code>lambda</code> 表达式时,<code>lambda</code>可以放在圆括号外面</li></ul><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><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">fun method(age: Int, lambda: (a: Int) -> Int): Int {</span><br><span class="line"> return lambda(10 * age)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">method(10){</span><br><span class="line"> it.and(20)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">//当高阶函数只有 lambda 作为参数时,圆括号也能省略</span><br><span class="line">fun myMethod(lambda: (a: Int) -> Int): Int {</span><br><span class="line"> return lambda(10 * age)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">myMethod {</span><br><span class="line"> it.and(20)</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h2 id="三-拓展函数与高阶函数"><a href="#三-拓展函数与高阶函数" class="headerlink" title="三.拓展函数与高阶函数"></a>三.拓展函数与高阶函数</h2><p>扩展函数是指在一个类上增加一种新的函数(方法),通常在某个类后面加 <code>.</code> 和 <code> 方法名</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">fun String.myPrint(){</span><br><span class="line"> println(this)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在熟悉普通的扩展函数之后,我们往里面加一个 <code>泛型</code> 和 <code>lambda</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><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">//Standard.kt是Kotlin库的一部分,它定义了一些基本inline函数。</span><br><span class="line">//在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间的问题</span><br><span class="line">//特别的引入了inline修饰符,表示为内联函数</span><br><span class="line">inline fun <T> T.myAlso(block: (T) -> Unit): Unit {</span><br><span class="line"> //泛型表示任何类型,只有在调用的时候才会判断你写的属于哪个类型</span><br><span class="line"> block(this)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">//Person是个类,里面有个属性 age</span><br><span class="line">val p = Person(10)//age = 10</span><br><span class="line"></span><br><span class="line">p.myAlso {</span><br><span class="line"> it.age = 30//将 p 的 age 修改为30</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><ul><li>当 <code>lambda</code> 也是一个扩展函数时</li></ul><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><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">inline fun <T> T.myApply(block: T.() -> T): T {</span><br><span class="line"> return this.block()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">//Person是个类,里面有个属性 age</span><br><span class="line">val p = Person(10)</span><br><span class="line"> p.myApply {</span><br><span class="line"> this.age = 20//将 p 的 age 修改为20</span><br><span class="line"> this//lambda 返回值</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> Android </category>
</categories>
<tags>
<tag> Kotlin </tag>
</tags>
</entry>
<entry>
<title>算法练习:整数拆分(动态规划)</title>
<link href="/2022/05/30/integer-break/"/>
<url>/2022/05/30/integer-break/</url>
<content type="html"><![CDATA[<h2 id="一-前言"><a href="#一-前言" class="headerlink" title="一.前言"></a>一.前言</h2><p>最近一直在了解动态规划,这是 LeetCode 上面的一道动规的题。</p><p><a href="https://leetcode.cn/problems/integer-break/">343. 整数拆分</a></p><p>给定一个正整数 <code>n</code> ,将其拆分为 <code>k</code> 个 <strong>正整数</strong> 的和( <code>k >= 2</code> ),并使这些整数的乘积最大化。<br>返回 <em>你可以获得的最大乘积</em> 。</p><p><code>示例1:</code></p><blockquote><p>输入: n = 2<br>输出: 1<br>解释: 2 = 1 + 1, 1 × 1 = 1。</p></blockquote><p><code>示例2:</code></p><blockquote><p>输入: n = 10<br>输出: 36</p></blockquote><p>解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。</p><hr><h2 id="二-思路"><a href="#二-思路" class="headerlink" title="二.思路"></a>二.思路</h2><p>说到动态规划,我认为最重要的就是确定自己的 <code>dp数组</code> 的含义,其次就是 <code>递推公式</code> 了。</p><ul><li>确定 <code>dp[i]</code> 的含义</li></ul><p>我们重新浏览一遍题,给定一个正整数 <code>n</code> ,需要将它分成若干个整数,返回最大的乘积。因此我们可以定义 <code>dp[i]</code> 表示每个正整数拆分为若干个正整数所对应的最大乘积,若要确定 <code>dp[i]</code> 的值,我们可以根据 <code>dp[i]</code> 以前的元素进行运算从而得到最大的<br><code>dp[i]</code> 的值。</p><ul><li>确定 <code>dp[i]</code> 的值</li></ul><p><code>dp[i]</code> 的值是由两种方式来共同确定的。<br>第一,<code>dp[i] = dp[i - j] * j</code> 其中 <code>i</code> 代表外层循环, <code>j</code> 代表内层循环,<code>j</code> 从 <code>1</code> 开始逐个求出 <code>dp[i]</code> ,最后取最大值。<br>第二,<code>dp[i]</code> = <code>(i - j) * j</code>,同上,也是取最大值。上面那种方式是将 <code>i</code> 分成了 <code>n</code> 个 <code>(n > 2)</code>。而这种方式是将 <code>i</code> 分成了<code>n</code> 个 <code>(n = 2)</code>。</p><ul><li>确定递推公式</li></ul><p>其实到这里,递推公式大致样式也就出来了:<br><code>dp[i] = Math.max(dp[i], Math.max(dp[i - j] * j, (i - j) * j))</code> ,那么可能会有人问为什么还要比较一次 <code>dp[i]</code> 呢?因为我们内层循环中一周后,会算出很多 <code>dp[i]</code> ,我们只需要保存最大的 <code>dp[i]</code>。</p><hr><h2 id="三-代码"><a href="#三-代码" class="headerlink" title="三.代码"></a>三.代码</h2><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><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">class Solution {</span><br><span class="line"> public int integerBreak(int n) {</span><br><span class="line"> int[] dp = new int[n + 1];</span><br><span class="line"> //dp[2]对应的值应该是1,而dp[2]之前的元素在此问题中无实际意义,因此无需初始化</span><br><span class="line"> dp[2] = 1;</span><br><span class="line"> for(int i = 3; i <= n; i++){</span><br><span class="line"> for(int j = 1; j <= i - j; j++){</span><br><span class="line"> dp[i] = Math.max(dp[i], Math.max(dp[i - j] * j, j * (i - j)));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return dp[n];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>时间复杂度 O(n^2)</p><ul><li>题外话<br>这道题用动态规划的话时间复杂度似乎有点高,其实这道题可以用数学方法来写的,这里用到一个结论:当整数 <code>n</code> 尽可能地等分为 <code>3</code>时乘积最大。如果感兴趣的同学可以去证明一下。</li></ul><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><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">class Solution {</span><br><span class="line"> public int integerBreak(int n) {</span><br><span class="line"> if(n <= 3) return n - 1;</span><br><span class="line"> int a = n / 3, b = n % 3;</span><br><span class="line"> //当 n 分刚好能分成若干个 3 时</span><br><span class="line"> if(b == 0) return (int)Math.pow(3, a);</span><br><span class="line"> //当 n 尽可能分成 3 时,余出一个 1</span><br><span class="line"> if(b == 1) return (int)Math.pow(3, a - 1) * 4;</span><br><span class="line"> //当 n 尽可能分成 3 时,余出一个 2</span><br><span class="line"> return (int)Math.pow(3, a) * 2;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>时间复杂度:O(1)</p>]]></content>
<categories>
<category> LeetCode </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 算法练习 </tag>
<tag> 动态规划 </tag>
</tags>
</entry>
<entry>
<title>Hexo更换主题之Butterfly</title>
<link href="/2022/05/27/ChengeToButterfly/"/>
<url>/2022/05/27/ChengeToButterfly/</url>
<content type="html"><![CDATA[<h2 id="一-前言"><a href="#一-前言" class="headerlink" title="一.前言"></a>一.前言</h2><p>前段时间搭建好了 Hexo 框架并用上了 Fluid 主题,后来不知道怎么回事,我的 Fluid 主题出了点问题,正好对我来说 Fluid 主题有点太简约了,索性换了一个 Butterfly 主题。下面说的一些配置的效果可以参考我的博客:<a href="https://cofbro.github.io/">cofbro 的博客 - Hello I’m cofbro</a><br>如果还没有安装 Hexo,可以看看这篇文章——<a href="https://www.jianshu.com/p/265ff281e159">Hello Hexo!一款高效的个人博客框架 </a></p><h2 id="二-Butterfly-的下载"><a href="#二-Butterfly-的下载" class="headerlink" title="二.Butterfly 的下载"></a>二.Butterfly 的下载</h2><p>打开你的 Hexo 根目录,点击鼠标右键,进入 <code>Git Bash Here</code>,输入以下命令:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i hexo-theme-butterfly</span><br></pre></td></tr></table></figure><p>随后咱们依次进入 <code>Hexo -> themes -> butterfly</code> 找到 <code>_config.yml</code> 文件,将里面的内容复制到 Hexo 根目录中的 <code>_config.butterfly.yml(如果没有就创建)</code>。</p><h2 id="三-关于-Butterfly-配置"><a href="#三-关于-Butterfly-配置" class="headerlink" title="三.关于 Butterfly 配置"></a>三.关于 Butterfly 配置</h2><blockquote><p>注意:本文仅展示部分配置,如需更多请参考<a href="https://butterfly.js.org/">Butterfly - A Simple and Card UI Design theme for Hexo</a></p></blockquote><h3 id="1-config-yml-中的配置"><a href="#1-config-yml-中的配置" class="headerlink" title="1._config.yml 中的配置"></a>1._config.yml 中的配置</h3><ul><li><p>打开根目录中的 <code>_config.yml</code> 文件,找到 <code>theme</code> 将其修改为 <code>Butterfly</code> ,以后配置更改就在这里面。</p></li><li><p>看板娘功能:需要先安装</p></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install --save hexo-helper-live2d</span><br></pre></td></tr></table></figure><p>再进入 <code>_config.yml</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><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">live2d:</span><br><span class="line"> enable: true #是否显示看板娘</span><br><span class="line"> scriptFrom: local</span><br><span class="line"> model:</span><br><span class="line"> use: live2d-widget-model-tororo #需要与后面的命令相同,直接更换名字即可</span><br><span class="line"> display:</span><br><span class="line"> position: left #宽高以及位置自己看着调</span><br><span class="line"> width: 200</span><br><span class="line"> height: 300</span><br><span class="line"> mobile:</span><br><span class="line"> show: true #是否在手机进行显示</span><br></pre></td></tr></table></figure><p>在通过 npm 下载对应的模型</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install live2d-widget-model-tororo #对应的模型命令,这是只小白猫</span><br></pre></td></tr></table></figure><p>其他的模型见<a href="https://blog.csdn.net/wang_123_zy/article/details/87181892"> Hexo 添加 Live2D 看板娘+模型预览</a></p><h3 id="2-config-butterfly-yml-中的配置"><a href="#2-config-butterfly-yml-中的配置" class="headerlink" title="2._config.butterfly.yml 中的配置"></a>2._config.butterfly.yml 中的配置</h3><ul><li>创建页:<br>刚换主题后,除了主页好像都是需要你自己去创建的,下面这些命令就是创建页的:</li></ul><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><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">hexo new page about #创建关于页</span><br><span class="line"></span><br><span class="line">hexo new page tags #创建标签页</span><br><span class="line"></span><br><span class="line">hexo new page categories #创建分类页</span><br><span class="line"></span><br><span class="line">hexo new page categories #创建分类页</span><br><span class="line"></span><br><span class="line">hexo new page link #创建友链页</span><br></pre></td></tr></table></figure><ul><li>其他基础配置</li></ul><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><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">menu:</span><br><span class="line"> 首页: / || fas fa-home</span><br><span class="line"> 归档: /archives/ || fas fa-archive</span><br><span class="line"> 标签: /tags/ || fas fa-tags</span><br><span class="line"> 分类: /categories/ || fas fa-folder-open</span><br><span class="line"> #菜单||fas fa-list:</span><br><span class="line"> #音乐: /music/ || fas fa-music</span><br><span class="line"> #电影: /movies/ || fas fa-video</span><br><span class="line"> 友链: /link/ || fas fa-link</span><br><span class="line"> 关于: /about/ || fas fa-heart</span><br><span class="line"></span><br><span class="line">上面是一些右上角的标签,可以自定义选择</span><br></pre></td></tr></table></figure><ul><li>下面这个是局部搜索,不过需要通过 npm 安装</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-generator-search --save</span><br></pre></td></tr></table></figure><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">local_search:</span><br><span class="line"> enable: true</span><br><span class="line"> preload: false</span><br><span class="line"> CDN:</span><br><span class="line"> labels:</span><br><span class="line"> input_placeholder: Search for Posts</span><br><span class="line"> hits_empty: "We didn't find any results for the search: ${query}"</span><br><span class="line"># 如果没有查到内容相关内容显示</span><br></pre></td></tr></table></figure><ul><li>打字机效果</li></ul><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><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">subtitle:</span><br><span class="line"> enable: true</span><br><span class="line"> # Typewriter Effect (打字效果)</span><br><span class="line"> effect: true</span><br><span class="line"> # loop (循環打字)</span><br><span class="line"> loop: true</span><br><span class="line"> # source 調用第三方服務</span><br><span class="line"> # source: true</span><br><span class="line"> # source: 1 https://hitokoto.cn/</span><br><span class="line"> # source: 2 http://yijuzhan.com/</span><br><span class="line"> # source: 3 https://www.jinrishici.com/</span><br><span class="line"> # subtitle 會先顯示 source , 再顯示 sub 的內容</span><br><span class="line"> source: 4</span><br><span class="line"> # 如果關閉打字效果,subtitle 只會顯示 sub 的第一行文字</span><br><span class="line"> sub:</span><br><span class="line"> - 梦想这东西和经典一样 永远不会因为时间而褪色</span><br><span class="line"> - 何其荣幸 何德何能 #注意这里的格式 空格和 '-'</span><br></pre></td></tr></table></figure><ul><li>鼠标点击特效</li></ul><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><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">activate_power_mode:</span><br><span class="line"> enable: false</span><br><span class="line"> colorful: true # open particle animation (冒光特效)</span><br><span class="line"> shake: true # open shake (抖動特效)</span><br><span class="line"> mobile: false</span><br><span class="line"></span><br><span class="line"># Mouse click effects: fireworks (鼠標點擊效果: 煙火特效)</span><br><span class="line">fireworks:</span><br><span class="line"> enable: true</span><br><span class="line"> zIndex: 9999 # -1 or 9999</span><br><span class="line"> mobile: false</span><br><span class="line"></span><br><span class="line"># Mouse click effects: Heart symbol (鼠標點擊效果: 愛心)</span><br><span class="line">click_heart:</span><br><span class="line"> enable: false</span><br><span class="line"> mobile: false</span><br><span class="line"></span><br><span class="line"># Mouse click effects: words (鼠標點擊效果: 文字)</span><br><span class="line">ClickShowText:</span><br><span class="line"> enable: false</span><br><span class="line"> text:</span><br><span class="line"> # - I</span><br><span class="line"> # - LOVE</span><br><span class="line"> # - YOU</span><br><span class="line"> fontSize: 15px</span><br><span class="line"> random: false</span><br><span class="line"> mobile: false</span><br></pre></td></tr></table></figure><p><em>提示:主题中很多 <code>js</code> 是通过 CDN 服务下载的,Butterfly 用的 <code>jsdeliver</code> 在国内已经 g 了,如果想要正常显示特效请在 <code>_config.butterfly.yml</code> 找到 <code>CDN</code> 。</em></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><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">CDN:</span><br><span class="line"> # The CDN provider of internal scripts (主題內部 js 的 cdn 配置)</span><br><span class="line"> # option: local/jsdelivr</span><br><span class="line"> # Dev version cannot choose jsdelivr (dev版的主題不能設置為 jsdelivr)</span><br><span class="line"> internal_provider: local</span><br><span class="line"> # The CDN provider of third party scripts (第三方 js 的 cdn 配置)</span><br><span class="line"> # option: local/jsdelivr</span><br><span class="line"> # when set it to local, you need to install hexo-butterfly-extjs</span><br><span class="line"> third_party_provider: local #在这里改为local</span><br><span class="line"> option:</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></pre></td><td class="code"><pre><span class="line">npm install hexo-butterfly-extjs</span><br></pre></td></tr></table></figure><p>如果报错:npm WARN deprecated <a href="mailto:formidable@1.2.6">formidable@1.2.6</a>: Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: <a href="https://bit.ly/2ZEqIau">https://bit.ly/2ZEqIau</a></p><p>则先键入以下命令,就可以在本地加载 js 文件了:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install formidable@v3</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> Hexo </category>
</categories>
<tags>
<tag> Hexo更换主题 </tag>
</tags>
</entry>
<entry>
<title>算法练习:只有两个键的键盘(数学法,动态规划)</title>
<link href="/2022/05/26/zhi-you-liang-ge-jian-de-jian-pan-by-lee-ussa/"/>
<url>/2022/05/26/zhi-you-liang-ge-jian-de-jian-pan-by-lee-ussa/</url>
<content type="html"><![CDATA[<h2 id="一-前言"><a href="#一-前言" class="headerlink" title="一.前言"></a>一.前言</h2><p>又到了记录代码的时候了,这道题来自 LeetCode,<a href="https://leetcode.cn/problems/2-keys-keyboard/">只有两个键的键盘</a>:</p><p>最初记事本上只有一个字符 ‘A’ 。你每次可以对这个记事本进行两种操作:<br>Copy All(复制全部):复制这个记事本中的所有字符(不允许仅复制部分字符)。<br>Paste(粘贴):粘贴 上一次 复制的字符。<br>给你一个数字 n ,你需要使用最少的操作次数,在记事本上输出 恰好 n 个 ‘A’ 。返回能够打印出 n 个 ‘A’ 的最少操作次数。</p><p>下面给出两个例子:<br><code>示例1:</code></p><blockquote><p>输入:3<br>输出:3<br>解释:<br>最初, 只有一个字符 ‘A’。<br>第 1 步, 使用 Copy All 操作。<br>第 2 步, 使用 Paste 操作来获得 ‘AA’。<br>第 3 步, 使用 Paste 操作来获得 ‘AAA’。</p></blockquote><p><code>示例2:</code></p><blockquote><p>输入:n = 1<br>输出:0</p></blockquote><h2 id="二-题解"><a href="#二-题解" class="headerlink" title="二.题解"></a>二.题解</h2><p>首先说说笔者写这道题的经历吧,不怕大家笑话,这道题我足足写了 3 小时…最开始看到这道题的时候我想的是我可以将 n 以前的操作 <code>A</code> 的次数以次数为键用 <code>Hashmap</code> 存起来,然后遇到 <code>n</code> 的时候直接去 <code>Hashmap</code> 里面拿,当我去提交的时候,发现有些测试用例是过不了的,比若说 741。然后我重新思考,发现可以用递归,比如 <code>n = 24</code>,但是由于思考深度不够,又有一些情况没考虑到,去提交的又数次失败…</p><p>由于递归从最开始就没考虑到那种情况,因此改起来特别麻烦,而这不得不使我重新选择一条路出发。所以由此看来啊,做题之前真要静下心来思考,思考范围要广一点。</p><p>接下来就说第一种方法,我称之为数学规律法。</p><h3 id="1-数学规律法"><a href="#1-数学规律法" class="headerlink" title="1.数学规律法"></a>1.数学规律法</h3><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><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">当输入不同的 n 时,对应输出的值:</span><br><span class="line"></span><br><span class="line">1 = 0</span><br><span class="line">2 = 1 2 --> 2次//因为需要复制一次,粘贴一次,因此是2</span><br><span class="line">7 = 1 2 3 4 5 6 7 --> 7次// 1 + 1 + 1 + 1 + 1 + 1 + 1 = 7</span><br><span class="line">12 = 1 2 3 6 12 --> 6次 //2 + 1 + 2 + 2 = 7</span><br><span class="line">18 = 1 2 3 6 9 18 --> 8次 //2 + 1 + 2 + 1 +2 = 8</span><br><span class="line">25 = 1 2 3 4 5 10 15 20 25 --> 10次 //2 + 1 + 1 + 2 +1 + 1 + 1 + 1 = 10</span><br><span class="line">741 = 35次 --> 这个待会讲</span><br></pre></td></tr></table></figure><p>既然是数学方法,当然要运用强大的数学规律,我们仔细观察一番会发现:如果是质数那么它的最小操作次数就是它本身,对于非质数,比如说 <code>12</code> ,上面得出 <code>6</code> 次答案的过程我们倒着看分成 3 个片段,12 <- 6 || 6 <- 3 || 3 <- 2 <- 1。</p><p>首先来看 12 <- 6 这一个片段,<code>6</code> 如果要成为 <code>12</code> 必须先全部复制再粘贴,也就是说这里会有 2 次操作;再看 6 <- 3,<code>3</code> 如果要成为 <code>6</code> 也必须先全部复制再粘贴,也会有 2 次操作;最后看 3 <- 2 <- 1 这个片段,<code>1</code> 如果要成为 <code>3</code> 需要全部复制、粘贴、粘贴,也就是要 3 次。我们再来看看这样一个东西:<code>12</code> 除以 <code>2</code> 等于 <code>6</code>,这里操作次数+2,<code>6</code> 除以 <code>2</code> 等于 <code>3</code>,这里操作次数+2,<code>3</code> 除以 <code>3</code> 等于 <code>1</code>,这里操作次数+3。2 + 2 + 3 = 6。你会惊奇的发现:只需要找到能整除 n 的除数,之后全部加起来就等于最后答案了。</p><p>不相信?再来试一个!<code>25</code> 除以 <code>5</code> 等于 <code>5</code>,操作次数+5;<code>5</code> 除以 <code>5</code> 等于 <code>1</code>,操作次数+5,最终答案:5 + 5 = 10。是不是感觉有点神奇?其实这就是质因数的分解,<code>25</code> 能分成 <code> 5</code> 和 <code>5</code>。<code>12</code> 能分成 <code>2</code> 和 <code>2</code> 和 <code>3</code> ,因此最小操作数为 7。因此最上面的 <code>741</code> 分解质因数为 <code>19</code> 和 <code>13</code> 和 <code>3</code>,所以其最小操作数为 <code>35</code>。</p><p>这种方法接近双百,附图:<br><img src="https://s3.bmp.ovh/imgs/2022/05/26/226699885901c278.webp"></p><h4 id="a-数学规律法代码"><a href="#a-数学规律法代码" class="headerlink" title="a).数学规律法代码"></a>a).数学规律法代码</h4><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><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">class Solution {</span><br><span class="line"> public int minSteps(int n) {</span><br><span class="line"> int result = 0;</span><br><span class="line"> int i;</span><br><span class="line"> //当 n 被分解到 1 时,退出循环</span><br><span class="line"> while(n != 1){</span><br><span class="line"> for( i = 2; i <= n; i++){</span><br><span class="line"> //遍历找出能整除 n 的 i</span><br><span class="line"> if(n % i == 0){</span><br><span class="line"> n /= i;</span><br><span class="line"> //result加上能整除 n 的 i</span><br><span class="line"> result += i;</span><br><span class="line"> i = 2;//成功整除 n 后,进行新的 i 循环,找出下一个能整除 n 的i</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return result;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h3 id="2-动态规划-LeetCode-官方题解"><a href="#2-动态规划-LeetCode-官方题解" class="headerlink" title="2.动态规划(LeetCode 官方题解)"></a><a href="https://leetcode.cn/problems/2-keys-keyboard/">2.动态规划(LeetCode 官方题解)</a></h3><h5 id="1-动态规划技巧"><a href="#1-动态规划技巧" class="headerlink" title="1.动态规划技巧"></a>1.动态规划技巧</h5><p>先简单说说动态规划的一些技巧吧。</p><ul><li>确定好 dp 数组的含义,一定要理解 dp[i]所表示的是什么</li><li>动态规划的数据通常是由前面一个数据推导演变而来的,因此需要得出推导公式</li><li>最后就是 dp 数组的初始化了,这个需要额外注意,因为初始化一旦有问题,就会导致最终答案出错</li></ul><h5 id="2-思路"><a href="#2-思路" class="headerlink" title="2.思路"></a>2.思路</h5><p>举个例子,还是拿 <code>12</code> 来说吧,<code>12 = 1 -> 2 -> 3 -> 6 -> 12</code>,我们会发现要得出 <code>12</code> 的最小操作次数,需要知道 <code>6</code> 的最小操作次数再加上复制粘贴的次数(2 次),进而需要知道 <code>3</code> 的最小操作次数再加上复制粘贴的次数(2 次)…总的来说就是如果要找出 <code>i</code> 个 <code>A</code> ,则必定要先找到 <code>i</code> 的因数 <code>j</code> ,而后通过 <code>i / j</code> 次复制粘贴得到 <code>i</code> 个 <code>A</code> 因此会发现这个递推规律:<br><img src="https://s3.bmp.ovh/imgs/2022/05/26/a0cf0451b9b0ab5f.webp" alt="递推公式"><br>其中, <code>j | i</code> 表示 <code>j</code> 能整除 <code>i</code> ,<code>i / j</code> 表示复制粘贴的次数。对于每一个 <code>dp[i]</code> 只需要等于最小的 <code>f[j] + i / j</code>。</p><hr><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><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">class Solution {</span><br><span class="line"> public int minSteps(int n) {</span><br><span class="line"> int[] f = new int[n + 1];</span><br><span class="line"> for (int i = 2; i <= n; ++i) {</span><br><span class="line"> f[i] = Integer.MAX_VALUE;</span><br><span class="line"> for (int j = 1; j <= i; ++j) {</span><br><span class="line"> if (i % j == 0) {</span><br><span class="line"> f[i] = Math.min(f[i], f[j] + i / j);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return f[n];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> LeetCode </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 算法练习 </tag>
<tag> 动态规划 </tag>
</tags>
</entry>
<entry>
<title>算法练习:回文子字符串的个数(多种方法)</title>
<link href="/2022/05/26/huiwenZiChuan/"/>
<url>/2022/05/26/huiwenZiChuan/</url>
<content type="html"><![CDATA[<h2 id="一-前言"><a href="#一-前言" class="headerlink" title="一.前言"></a>一.前言</h2><p>今天介绍一道相对简单的练习题,同样是来自 LeetCode。</p><p><a href="https://leetcode.cn/problems/a7VOhD/">回文子字符串的个数,给定一个字符串 s ,请计算这个字符串中有多少个回文子字符串。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。</a></p><p><code>示例1:</code></p><blockquote><p>输入:s = “abc”<br>输出:3<br>解释:三个回文子串: “a”, “b”, “c”</p></blockquote><p><code>示例2:</code></p><blockquote><p>输入:s = “aaa”<br>输出:6<br>解释:6 个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”</p></blockquote><p>题目来源:<a href="https://leetcode.cn/problems/a7VOhD/">LeetCode</a></p><h2 id="二-题解"><a href="#二-题解" class="headerlink" title="二.题解"></a>二.题解</h2><p>其实关于回文的题有很多种,而解法更是多种多样,今天介绍两种方法,<code>递归</code>和<code>中心拓展</code>。</p><h4 id="1-递归"><a href="#1-递归" class="headerlink" title="1.递归"></a>1.递归</h4><p>简单来说递归就是一个函数调用自己本身的行为就叫递归。递归具有代码简洁,易读的特点,但是缺点也不容忽视:</p><p>a).运行效率较低。<br>b).可能会导致栈溢出,如果递归次数太多,需要在内存栈中分配空间以保存参数以及临时变量就会过多,当超出栈的容量,就会导致栈溢出。</p><p>我们思路是这样的:首先通过遍历找到所有的子串,然后再判断子串是否是回文。那么怎么判断是否是回文呢?比如这样的一个字符串<code>abcba</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><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">class Solution {</span><br><span class="line"> public int countSubstrings(String s) {</span><br><span class="line"> int result = 0;//最终结果</span><br><span class="line"> //两层循环,从字符串首部开始遍历处所有子字符串</span><br><span class="line"> for (int i = 0; i < s.length(); i++) {</span><br><span class="line"> for (int j = i; j < s.length(); j++) {</span><br><span class="line"> //每个子字符串调用isHuiwen,若返回 true 则 result + 1</span><br><span class="line"> if (isHuiwen(s, i, j)) {</span><br><span class="line"> result++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return result;</span><br><span class="line"> }</span><br><span class="line"> //isHuiwen需要三个参数,s 是字符串,i 是子字符串第一个字符,j 是子字符串的最后一个字符</span><br><span class="line"> public static boolean isHuiwen(String s, int i, int j) {</span><br><span class="line"> //两种情况返回true(1,2)</span><br><span class="line"> //1.当子字符串的长度是奇数时,此时回文中心只有一个,当递归到 i == j时,说明全部首尾字符判断完毕,全部相等,返回true</span><br><span class="line"> if (i == j) return true;</span><br><span class="line"> else {</span><br><span class="line"> if (s.charAt(i) != s.charAt(j)) {</span><br><span class="line"> //如果二者不相等,直接返回false,退出递归</span><br><span class="line"> return false;</span><br><span class="line"> } else {</span><br><span class="line"> //2.当子字符串的长度为偶数,此时回文中心是两个字符,由于上面的if中已经判断 i 和 j是相同的</span><br><span class="line"> //因此到这里也将首尾字符全部判断完毕,全部相等,返回true</span><br><span class="line"> if (i + 1 == j) {</span><br><span class="line"> return true;</span><br><span class="line"> } else return isHuiwen(s, ++i, --j);</span><br><span class="line"> //如果程序走到这儿,说明不满足上面任意一种情况,因此在调用函数进一步判断 ++i 和 --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><p>我们上面这种方法找出子字符串需要两层循环,时间复杂度为 O(n^2) 而判断它是否为回文又是 O(n),因此总的复杂度为 O(n^3)。显然效率不高,那我们再来看看<a href="https://leetcode.cn/problems/a7VOhD/solution/hui-wen-zi-zi-fu-chuan-de-ge-shu-by-leet-ejfv/">LeetCode 官方题解吧</a>!——中心拓展</p><h4 id="2-中心拓展"><a href="#2-中心拓展" class="headerlink" title="2.中心拓展"></a>2.中心拓展</h4><p>大致思路:我们从每一个可能成为回文中心的点开始,若前后字符相等就拓展,反之,则停止拓展。</p><p>当子字符串是奇数时,回文中心是一个;当子字符串是偶数个时,回文中心是两个。有没有一种方法可以统一二者呢?答案是有的。通过规律我们可以发现:无论子字符串长度是偶数还是奇数,我们都需要遍历 <code>2n - 1</code> 次字符串,换句话说就是有 <code>2n - 1</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><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">class Solution {</span><br><span class="line"> public int countSubstrings(String s) {</span><br><span class="line"> int n = s.length(), ans = 0;</span><br><span class="line"> for (int i = 0; i < 2 * n - 1; ++i) {</span><br><span class="line"> //向前拓展从i开始,向后拓展从r开始</span><br><span class="line"> int l = i / 2, r = i / 2 + i % 2;</span><br><span class="line"> //当子字符串个数为偶数时,i 和 r 相等</span><br><span class="line"> while (l >= 0 && r < n && s.charAt(l) == s.charAt(r)) {</span><br><span class="line"> //当前后字符相等且 i 和 r 未越界时</span><br><span class="line"> //开始向两边拓展</span><br><span class="line"> --l;</span><br><span class="line"> ++r;</span><br><span class="line"> ++ans;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return ans;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> LeetCode </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 算法练习 </tag>
</tags>
</entry>
<entry>
<title>乘积小于 K 的子数组(滑动窗口)</title>
<link href="/2022/05/25/%E4%B9%98%E7%A7%AF%E5%B0%8F%E4%BA%8E-K-%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%EF%BC%88%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%EF%BC%89/"/>
<url>/2022/05/25/%E4%B9%98%E7%A7%AF%E5%B0%8F%E4%BA%8E-K-%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%EF%BC%88%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%EF%BC%89/</url>
<content type="html"><![CDATA[<h2 id="一-前言"><a href="#一-前言" class="headerlink" title="一.前言"></a>一.前言</h2><p>今天奉上的题是来自<a href="https://leetcode.cn/problems/subarray-product-less-than-k/">LeetCode</a>中的一道中等难度的题,但是如果了解滑动窗口的思想,其实这道题也是比较简单的,题目如下:</p><p>给你一个整数数组 <code>nums</code> 和一个整数 <code>k</code> ,请你返回子数组内所有元素的乘积严格小于<code>k</code> 的连续子数组的数目。</p><div class="note success simple"><p><code>示例一:</code></p><p>输入:nums = [10,5,2,6], k = 100<br>输出:8<br>解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2],、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。<br>需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。</p><p><code>示例二:</code></p><p>输入:nums = [1,2,3], k = 0<br>输出:0</p></div><hr><h2 id="二-思考"><a href="#二-思考" class="headerlink" title="二.思考"></a>二.思考</h2><p>像这种从一个数组里面找一些子数组或者子字符串的问题大都可往这方面靠,一般来说都是可以解决的。因此我们大致思路是这样的,同样定义两个指针(一个叫 left,一个叫 i),它们都指向数组的第一个元素。我们用 i 指针来遍历数组,每当 i 指向新的元素时我们都计算 i 指针和 left 指针之间(窗口之间)元素的积并判断一下是否小于 k,若成立则当前子数组就符合条件,用 n 来记录一次。反之,则 i 继续移动下去。</p><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><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">class Solution {</span><br><span class="line"> public int numSubarrayProductLessThanK(int[] nums, int k) {</span><br><span class="line"> int left = 0,i = 0;//定义左指针和右指针</span><br><span class="line"> int n = 0;//用来记录子数组的个数</span><br><span class="line"> int addtion = 1;//表示子数组中元素的积</span><br><span class="line"> while(left < nums.length){//首先判断循环条件,当left指针指向nums的最后一个元素时,循环结束</span><br><span class="line"> //循环进来后,计算i和left之间各元素的积</span><br><span class="line"> addtion *= nums[i];</span><br><span class="line"> i++;//i指针向后移动</span><br><span class="line"> if (addtion >= k){//若子数组中的各元素积大于k</span><br><span class="line"> left++;//说明子数组中元素超出条件范围,将left指针向右移动</span><br><span class="line"> //让i重新和left指向同一元素,开始新一轮的查找,因此addtion也要归为原始状态</span><br><span class="line"> i = left;</span><br><span class="line"> addtion = 1;</span><br><span class="line"> }else n++;//若符合条件,则记录一下,使n+1</span><br><span class="line"> //但是还有一种情况,i到left之间的子数组都符合条件,这时i指向了数组的最后一个元素</span><br><span class="line"> if (i == nums.length){</span><br><span class="line"> //不能使i越界,因此将两个指针重新合到一块,重新开始新的查找,同理依然重置addtion</span><br><span class="line"> i = left + 1;</span><br><span class="line"> left++;</span><br><span class="line"> addtion = 1;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return n;//返回最后结果</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="三-再次思考"><a href="#三-再次思考" class="headerlink" title="三.再次思考"></a>三.再次思考</h2><p>如果理解了上面这个做法的话,那让我们想想是否还有更优解呢?<br>在上面这个做法中,我们每开始一次新的查找,都需要将 i 指针和 left 指针重新指向同一个新的元素,并且 addtion 也需要重置为 1,这样看来这其中确实有不妥之处,那让我们来看看官方的的做法吧。<br><code>LeetCode官方题解:</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><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">class Solution {</span><br><span class="line"> public int numSubarrayProductLessThanK(int[] nums, int k) {</span><br><span class="line"> int n = nums.length, ret = 0;</span><br><span class="line"> int prod = 1, i = 0;//prod相当于我们之前的addtion</span><br><span class="line"> for (int j = 0; j < n; j++) {</span><br><span class="line"> prod *= nums[j];</span><br><span class="line"> //官方给出的代码中以下和我们之前的方法有一点差异</span><br><span class="line"> while (i <= j && prod >= k) {</span><br><span class="line"> prod /= nums[i];</span><br><span class="line"> i++;</span><br><span class="line"> }</span><br><span class="line"> ret += j - i + 1;//这是i和j指针之间的元素个数,它在数值上等于新增符合条件的子数组个数</span><br><span class="line"> }</span><br><span class="line"> return ret;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><p>思路与分析:整体思想和我们最开始讲的是一样的,只不过在记录子数组个数和重置各元素积这两部进行了优化。</p><p>我们着重讲述 while 中有差异的这段,我们可以这么理解:i 指针相当于第一种方法的 left,j 指针相当于之前的 i。这里 while 中有一个 <code>i <= j</code> 条件,这是用来控制 i 指针的范围的,也就是说 i 指针是不能超过 j 指针的,这样就会有一个好处:因为 <code>j < n</code>,所以 i 就不会超出数组的长度了,也就能避免我们第一种方法还要专门写个<code>if</code>来判断 i 是否越界,简化了代码。代码进入循环后,也就意味着 <code>prod > k</code> 了,这时我们为了符合条件,需要把 i 指针向后移动,而 prod 值只需要除以<code>nums[i]</code>就等于此时窗口中元素的值。这和我们第一种方法相比就显得非常优秀了,我们之前的方法需要每次把 prod 值归 1,然后由要经历 prod 等于原来那个值的过程,这就显得有点多余了。</p>]]></content>
<categories>
<category> LeetCode </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 算法练习 </tag>
</tags>
</entry>
<entry>
<title>算法练习:统计好数字的数目(快速幂)</title>
<link href="/2022/05/13/fastMi/"/>
<url>/2022/05/13/fastMi/</url>
<content type="html"><![CDATA[<h2 id="一-前言"><a href="#一-前言" class="headerlink" title="一.前言"></a>一.前言</h2><p>又是在力扣被题折磨的一次,反反复复地提交,反反复复的不通过,不过还好了解到了快速幂的思想,这种方式能大幅提高程序的运行速度。</p><blockquote><p>题目:我们称一个数字字符串是 好数字 当它满足(下标从 0 开始)偶数 下标处的数字为 偶数 且 奇数 下标处的数字为 质数 (2,3,5 或 7)。</p></blockquote><ul><li>比方说,”2582” 是好数字,因为偶数下标处的数字(2 和 8)是偶数且奇数下标处的数字(5 和 2)为质数。但 “3245” 不是 好数字,因为 3 在偶数下标处但不是偶数。<br>给你一个整数 n ,请你返回长度为 n 且为好数字的数字字符串 总数 。由于答案可能会很大,请你将它对 109 + 7 取余后返回 。</li></ul><p>一个 数字字符串 是每一位都由 0 到 9 组成的字符串,且可能包含前导 0.<a href="https://leetcode.cn/problems/count-good-numbers/">(题目来源:LeetCode)</a></p><h2 id="二-分析"><a href="#二-分析" class="headerlink" title="二.分析"></a>二.分析</h2><p>这道题乍一看似乎挺简单,由于是让我们返回好数字的个数,对于偶数下标来说符合条件的无非就 0,2,4,6,8 这几个数字,而奇数下标只有 1,3,5,7 这四个数字。我们只需要计算每一位上符合条件的数字的个数之积,顶多再模上 1000000007 就是最终答案了。但是真的是这样的吗?</p><ul><li>敲完代码直接提交(自信加蔑视)</li></ul><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><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">class Solution {</span><br><span class="line"> public int countGoodNumbers(long n) {</span><br><span class="line"> double result = 0;</span><br><span class="line"> if (n % 2 == 0){</span><br><span class="line"> result = (Math.pow(4,n/2) * Math.pow(5,n/2)) % 1000000007;</span><br><span class="line"> }else {</span><br><span class="line"> result = (Math.pow(4,n/2) * Math.pow(5,n/2 + 1)) % 1000000007;</span><br><span class="line"> }</span><br><span class="line"> return (int) result;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>心想这不就完了吗,很难吗?但是最终结果就是它死活不让我过,卡在了 50:<br><img src="https://s3.bmp.ovh/imgs/2022/05/12/9cda7a82442d9a34.webp" alt="迷茫啊..."><br>我***,这哪有什么问题,这都不让过?离谱啊…… 思考一番无果我只能去翻翻源码,这一下又让我有了新的收获:<br><img src="https://s3.bmp.ovh/imgs/2022/05/12/7c450cf14f75d653.webp"><br>它是一个本地方法,也就是由 c/c++编写的方法,java 能拿来用,而这个 pow 方法返回四舍五入到最接近的 int 值的参数值,而 int 最多只能表示 2147483647(2 的 31 次方-1),但是代码里面的 5 的 50 次方早就超过了其最大值,因此这里已经溢出,错误也就随之产生了。</p><p>行,那我就换个方法,不用这个 pow 方法,我自己写一个能得到指数值的方法不就好了吗?</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><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></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><span class="line"></span><br><span class="line">public class Test {</span><br><span class="line"> public static long normalCal(int power, int base) {</span><br><span class="line"> long result = 1;</span><br><span class="line"> for (int i = 0; i < power; i++) {</span><br><span class="line"> result = result * base;</span><br><span class="line"> }</span><br><span class="line"> return result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> long result = normalCal(30,5 );</span><br><span class="line"></span><br><span class="line"> System.out.println(result);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里我们求的是 5 的 30 次方,那么就会执行 30 次循环。不难发现其时间复杂度为 O(N),其中 N 为指数。在我们这段代码中还好,要是我换成 5 的 100 次方呢?那么会有以下两个问题:</p><ul><li>1.假如真的是 5 的 200 次方,即使我们用 long 接收,结果也会溢出的,这又会回到前面的问题上去。</li><li>2.就算我们想尽办法正确得到了这个结果,但这会花费大量的时间,这样提交上去肯定会超时,最终也会不成功。<br>那么是否有一种二者问题都解决掉的方案呢?答案肯定是有的,这就是之前我们将要说到的快速幂思想。</li></ul><h2 id="三-快速幂思想"><a href="#三-快速幂思想" class="headerlink" title="三.快速幂思想"></a>三.快速幂思想</h2><p><em>我们先来初步认识下快速幂,给定一个 2 的 12 次方式子让你计算,你拿到这个题后会怎么想呢?</em></p><ul><li>为了计算的简便,我们通常可以将 2 的 12 次方转换为 4 的 6 次方进而转成 16 的 4 次方…进行计算。</li></ul><hr><p>1.快速幂正好是这种思想,在计算机中 2 的 12 次方要循环 12 次,而我们通过将指数减半,底数平方的思想可以减少循环的次数,从而提高性能。就比如上述的 2 的 12 次方:我们通过快速幂的思想思考它是这样的:2 的 12 次方 -> 4 的 6 次方 -> 16 的 3 次方 -> 256 的 1 次方 乘以 16。前者循环了 12 次,而后者只需要循环 4 次,要是有更高指数计算,则能节省循环的次数也就不言而喻了。</p><p>那我们怎么解决因为计算出的值过大而溢出导致出错的问题呢?我们先来了解一下模的运算规律吧:<br><code>a). (a+b)%p=(a%p+b%p)%p</code><br><code>b). (a-b)%p=(a%p-b%p)%p</code><br><code>c). (a*b)%p=(a%p * b%p)%p</code> 2.在这里只需要用到第三条结论,我们可以在计算过程中就开始不断取模,而不是等到最终结果出来之后再模运算,这样就能避免最终答案太大导致出错的问题了。</p><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><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">public long myAns(lon base, long index) {</span><br><span class="line"> int k = 1000000007;</span><br><span class="line"> long result = 1;</span><br><span class="line"> while (index > 0) {//当指数减到1时跳出循环</span><br><span class="line"> if (index % 2 == 1) {</span><br><span class="line"> //奇数往这儿走,由于这里是奇数,指数减半会有余数,因此我们只需多乘以一个原来的result就行了</span><br><span class="line"> index = index - 1;</span><br><span class="line"> result = result * base % k;//乘上原来的底数</span><br><span class="line"> base = base * base % k;//底数平方</span><br><span class="line"> }else {</span><br><span class="line"> //偶数来这里,这里就是正常的指数减半,底数平方</span><br><span class="line"> index = index / 2;</span><br><span class="line"> base = (base * base) % k;</span><br><span class="line"> }</span><br><span class="line"> //我们可以看到这种方式节约的循环次数是指数级的</span><br><span class="line"> return result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其实上面这个代码还能更精简一些,我们还能对它进行优化。<br>我们来看看整道题的全部代码:</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><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">class Solution {</span><br><span class="line"> //优化后的代码,这个方法就是进行幂运算</span><br><span class="line"> public long findMyResult(long base, long index) {</span><br><span class="line"> long result = 1;</span><br><span class="line"> while(index> 0){</span><br><span class="line"> if(power % 2 == 1){</span><br><span class="line"> result = result * base % 1000000007;</span><br><span class="line"> }</span><br><span class="line"> index = index / 2;//奇数除以2会舍去小数点后面的数,能自动实现减1操作</span><br><span class="line"> base = base * base % 1000000007;</span><br><span class="line"> }</span><br><span class="line"> return result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> public int countGoodNumbers(long n) {</span><br><span class="line"> if(n % 2 == 0){</span><br><span class="line"> long result = findMyResult(4,n/2) % 1000000007;</span><br><span class="line"> result = (result * findMyResult(5,n/2)) % 1000000007;</span><br><span class="line"> return (int) result;</span><br><span class="line"> }else{</span><br><span class="line"> long result = (findMyResult(4,n/2) % 1000000007);</span><br><span class="line"> result = (result * findMyResult(5,n/2 + 1)) % 1000000007;</span><br><span class="line"> return (int) result;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>完 ~</p>]]></content>
<categories>
<category> Diary </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 算法练习 </tag>
</tags>
</entry>
<entry>
<title>Hello Hexo!一款高效的个人博客框架</title>
<link href="/2022/05/12/mypost/"/>
<url>/2022/05/12/mypost/</url>
<content type="html"><![CDATA[<h2 id="一-前言"><a href="#一-前言" class="headerlink" title="一.前言"></a>一.前言</h2><h4 id="什么是-Hexo?"><a href="#什么是-Hexo?" class="headerlink" title="什么是 Hexo?"></a>什么是 Hexo?</h4><blockquote><p>Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 <a href="http://daringfireball.net/projects/markdown/">Markdown</a>(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。</p></blockquote><p>现在网上大多数的博客系统其实都是别人的框架,比较流行的框架有 WordPress, halo , hugo 还有我们今天要讲的 Hexo。</p><h2 id="二-安装准备"><a href="#二-安装准备" class="headerlink" title="二.安装准备"></a>二.安装准备</h2><ul><li>本文部分代码可能失效,具体以<a href="https://hexo.io/zh-cn/docs/">官网 | Hexo</a>为准<br>由于 Hexo 是基于 Node.js 的,因此我们需要先下载 Node.js,另外只需要安装 git 工具就 ok 了。</li></ul><h4 id="1-安装Node-js"><a href="#1-安装Node-js" class="headerlink" title="1.安装Node.js"></a>1.安装<a href="https://nodejs.org/en/">Node.js</a></h4><p>傻瓜式安装,一直 next 即可。如有疑惑请自行上网搜索,网上安装教程很全,相信大家不会在这耽搁太多时间。安装完成后我们任意位置单击鼠标右键会有一个 Git Bash Here,点击后就会弹出 git 命令框了。<br><em>注意:若想要后面下载速度更快,请打开 git 输入以下命令</em></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm config set registry "https://registry.npm.taobao.org"</span><br></pre></td></tr></table></figure><h4 id="2-安装Git"><a href="#2-安装Git" class="headerlink" title="2.安装Git "></a>2.安装<a href="https://git-scm.com/">Git </a></h4><p>上同。</p><h2 id="三-Hexo-安装"><a href="#三-Hexo-安装" class="headerlink" title="三.Hexo 安装"></a>三.Hexo 安装</h2><p>完成上述操作后即可开始安装 Hexo。我们通过 npm 来安装 hexo。桌面鼠标单机右键,打开 Git Bash Here。</p><ul><li>键入以下命令:</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install -g hexo-cli</span><br></pre></td></tr></table></figure><ul><li>之后在指定路径新建文件夹(英文名),右键打开 git 命令框,输入指令:</li></ul><blockquote><p>踩坑点:千万要在你刚建好的文件夹中打开 git 命令框!</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo init</span><br></pre></td></tr></table></figure><p>到这里 Hexo 就彻底安装完毕了。我们可以看到刚刚的文件夹里面新增了很多文件以及文件夹:<br><img src="https://s3.bmp.ovh/imgs/2022/05/12/d1f38c45ad2cbd15.jpg" alt="文件夹概览"></p><h2 id="四-Hexo-使用"><a href="#四-Hexo-使用" class="headerlink" title="四.Hexo 使用"></a>四.Hexo 使用</h2><h4 id="1-自定义配置"><a href="#1-自定义配置" class="headerlink" title="1.自定义配置"></a>1.自定义配置</h4><p>打开_config.yml 进行基础配置,比如修改你的博客的名字之类的,详情见<a href="https://hexo.io/zh-cn/docs/">官网 | Hexo</a><br><img src="https://s3.bmp.ovh/imgs/2022/05/12/3696058c67c12c00.webp"></p><h4 id="2-创建文章"><a href="#2-创建文章" class="headerlink" title="2.创建文章"></a>2.创建文章</h4><p>a).先整体说一下各个文件是干嘛的</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">├── _config.yml //这个是配置文件,用于修改文章数据的,点进去之后里面会有详细注解</span><br><span class="line">├── package.json //应用程序信息,依赖的模块列表</span><br><span class="line">├── scaffolds //包含三个文章模板,分别是draft,page,post。博客的定制修改会对模板进行修改。</span><br><span class="line">├── source //我们写的文章文件夹就存在这里,包括我们的 Markdown 文稿,这里面一般也可以新建一个文件夹用来放一些我们博客中需要用到的图片</span><br><span class="line">| ├── _drafts //就是草稿</span><br><span class="line">| └── _posts //这就是存文章的文件夹</span><br><span class="line">└── themes //用于修改Hexo主题的</span><br></pre></td></tr></table></figure><p>b).我们进入根目录,就是你刚刚新建的文件夹的名字<br><img src="https://s3.bmp.ovh/imgs/2022/05/12/7fb240132cd720cd.webp"></p><p>输入以下命令:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new [layout] <title></span><br></pre></td></tr></table></figure><p>也可以不设置 <code>layout</code> ,默认使用 <a href="https://hexo.io/zh-cn/docs/configuration">_config.yml</a> 中的 <code>default_layout</code> 参数代替(默认是 post)。如果标题包含空格的话,请使用引号括起来。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new "My First Post"</span><br></pre></td></tr></table></figure><p>之后我们从根目录依次点击 source –> _posts –> My First Post.md 编辑文件。<br>进入之后他是长这样的:<br><img src="https://s3.bmp.ovh/imgs/2022/05/12/d1f38c45ad2cbd15.jpg"><br><code>---</code> 之间是 front-matter,是我们每篇文章的一些变量,我们可以在这里单独进行设置,也可以在_config.yml 中统一配置。而在<code>---</code>之外的地方就是我们写自己博客内容的位置了。需要注意要用 MarkDown 语法哦。<br>c).写完保存之后,在根目录再次打开 git 命令框输入</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo clean</span><br></pre></td></tr></table></figure><ul><li>每次运行前建议 clean 一下缓存,免得出现一些奇奇怪怪的问题</li></ul><p>d).输入以下命令,生成静态网页文件</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo g</span><br></pre></td></tr></table></figure><p>e).最后,输入下列代码</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo s</span><br></pre></td></tr></table></figure><p>敲完回车后就在本地服务器上运行了,你的博客就可以在本地浏览了,方法:在浏览器 url 框中输入 <a href="http://localhost:4000/">http://localhost:4000</a> 即可查看。</p>]]></content>
<categories>
<category> Hexo </category>
</categories>
<tags>
<tag> Hexo安装 </tag>
</tags>
</entry>
<entry>
<title>轻松理解JDBC并使用</title>
<link href="/2022/05/08/%E8%BD%BB%E6%9D%BE%E7%90%86%E8%A7%A3JDBC%E5%B9%B6%E4%BD%BF%E7%94%A8/"/>
<url>/2022/05/08/%E8%BD%BB%E6%9D%BE%E7%90%86%E8%A7%A3JDBC%E5%B9%B6%E4%BD%BF%E7%94%A8/</url>
<content type="html"><![CDATA[<h2 id="一-前言"><a href="#一-前言" class="headerlink" title="一.前言"></a>一.前言</h2><p>最近学习了 JDBC,觉得非常重要,因此连忙熬夜记录下来。</p><h2 id="二-什么是-JDBC?"><a href="#二-什么是-JDBC?" class="headerlink" title="二.什么是 JDBC?"></a>二.什么是 JDBC?</h2><blockquote><p>JDBC(Java Data Base Connectivity,java 数据库连接)是一种用于执行<a href="https://so.csdn.net/so/search?q=SQL%E8%AF%AD%E5%8F%A5&spm=1001.2101.3001.7020">SQL 语句</a>的 Java API,可以为多种关系数据库提供统一访问,它由一组用 Java 语言编写的类和接口组成。</p></blockquote><p>具体来说就是 Java 为了能够让用户操作不同的数据库而编写出来的一套标准接口,我们需要调用它从而能够操作不同的数据库。而像 MySQL,SQLite,SQLService 不同的厂商为了能让 Java 操作自己家的数据库也需要实现 Java 提供的接口,这些实现了 JDBC 接口的类打成一个 jar 包,也就是我待会提到的数据库驱动。</p><h2 id="三-JDBC-的五个步骤"><a href="#三-JDBC-的五个步骤" class="headerlink" title="三.JDBC 的五个步骤"></a>三.JDBC 的五个步骤</h2><p><em>1.注册驱动</em><br><em>2.建立 Java 与数据库的联系</em><br><em>3.获得可操作的数据库对象</em><br><em>4.执行 SQL 语句</em><br><em>5.查看结果</em></p><h2 id="四-补充:"><a href="#四-补充:" class="headerlink" title="四.补充:"></a>四.补充:</h2><h4 id="1-connection"><a href="#1-connection" class="headerlink" title="1.connection"></a>1.connection</h4><h5 id="a-解释:"><a href="#a-解释:" class="headerlink" title="a).解释:"></a>a).解释:</h5><p>Connection 与特定数据库的连接(会话),在连接上下文中执行 sql 语句并返回结果。</p><h5 id="b-常用方法:"><a href="#b-常用方法:" class="headerlink" title="b).常用方法:"></a>b).常用方法:</h5><ul><li>createStatement():创建向数据库发送 sql 的 statement 对象。</li><li>prepareStatement(sql) :创建向数据库发送预编译 sql 的 PrepareSatement 对象。</li><li>prepareCall(sql):创建执行存储过程 callableStatement 对象。</li><li>setAutoCommit(boolean autoCommit):设置事务是否自动提交。</li><li>commit() :在链接上提交事务。</li><li>rollback() :在此链接上回滚事务。</li></ul><h4 id="2-statement"><a href="#2-statement" class="headerlink" title="2.statement"></a>2.statement</h4><h5 id="a-解释:-1"><a href="#a-解释:-1" class="headerlink" title="a).解释:"></a>a).解释:</h5><p>用于执行静态 SQL 语句并返回它所生成结果的对象。三种 Statement 类:</p><ul><li>Statement:由 createStatement 创建,用于发送简单的 SQL 语句(不带参数)。</li><li>PreparedStatement :继承自 Statement 接口,由 preparedStatement 创建,用于发送含有一个或多个参数的 SQL 语句。PreparedStatement 对象比 Statement 对象的效率更高,并且可以防止 SQL 注入,所以我们一般都使用 PreparedStatement。</li><li>CallableStatement:继承自 PreparedStatement 接口,由方法 prepareCall 创建,用于调用存储过程。</li></ul><h5 id="b-常用方法:-1"><a href="#b-常用方法:-1" class="headerlink" title="b).常用方法:"></a>b).常用方法:</h5><ul><li>execute(String sql):运行语句,返回是否有结果集</li><li>executeQuery(String sql):运行 select 语句,返回 ResultSet 结果集。</li><li>executeUpdate(String sql):运行 insert/update/delete 操作,返回更新的行数。</li><li>addBatch(String sql) :把多条 sql 语句放到一个批处理中。</li><li>executeBatch():向数据库发送一批 sql 语句执行。</li></ul><h2 id="五-Java-操作数据库实战"><a href="#五-Java-操作数据库实战" class="headerlink" title="五.Java 操作数据库实战"></a>五.Java 操作数据库实战</h2><h4 id="1-注册驱动"><a href="#1-注册驱动" class="headerlink" title="1.注册驱动"></a>1.注册驱动</h4><p>a).普通方式注册驱动</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DriverManager.registerDriver(new com.mysql.jdbc.Driver);</span><br></pre></td></tr></table></figure><p>b).利用反射,用类加载器的方式注册驱动</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Class.forName("com.mysql.jdbc.Driver");</span><br></pre></td></tr></table></figure><h4 id="2-建立-Java-与数据库的联系"><a href="#2-建立-Java-与数据库的联系" class="headerlink" title="2.建立 Java 与数据库的联系"></a>2.建立 Java 与数据库的联系</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/database", "root", "24694");</span><br></pre></td></tr></table></figure><p>第一个参数是固定写法,代表了你连接的是哪个数据库,这里是 MySQL<br><em>说明:jdbc:mysql 类似于网络协议 https,这是固定写法。而 port 代表端口号</em><br>第二个参数是数据库用户名<br>第三个参数是数据库密码</p><h4 id="3-获得可操作的数据库对象"><a href="#3-获得可操作的数据库对象" class="headerlink" title="3.获得可操作的数据库对象"></a>3.获得可操作的数据库对象</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">stmt = conn.createStatement();</span><br></pre></td></tr></table></figure><h4 id="4-执行-SQL-语句"><a href="#4-执行-SQL-语句" class="headerlink" title="4.执行 SQL 语句"></a>4.执行 SQL 语句</h4><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">//执行SQL语句,会返回修改语句的条数,因为只涉及到一条操作,所以会返回1</span><br><span class="line">String sql = "insert into test(age,name,local) values(50,'cofbro','China')";</span><br><span class="line">int num = stmt.execute(sql);</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><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">public class DatabaseDemo {</span><br><span class="line"> Connection conn = null;</span><br><span class="line"> Statement stmt = null;</span><br><span class="line"></span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> try {</span><br><span class="line"> //注册驱动</span><br><span class="line"> DriverManager.registerDriver(new com.mysql.jdbc.Driver);</span><br><span class="line"> //建立JVM与数据库的连接</span><br><span class="line"> conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/database", "root", "24694");</span><br><span class="line"> //获取数据库操作对象</span><br><span class="line"> stmt = conn.createStatement();</span><br><span class="line"> //执行SQL语句,会返回修改语句的条数,因为只涉及到一条操作,所以会返回1</span><br><span class="line"> String sql = "insert into test(age,name,local) values(50,'cofbro','China')";</span><br><span class="line"> int num = stmt.execute(sql);</span><br><span class="line"> //查看是否修改成功</span><br><span class="line"> System.out.println(num == 1 ? "保存成功" : "保存失败");</span><br><span class="line"> } catch (SQLException e) {</span><br><span class="line"> //若有异常,打印出异常信息</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } finally {</span><br><span class="line"> //释放资源</span><br><span class="line"> if (stnt != null) {</span><br><span class="line"> try {</span><br><span class="line"> //先关小的,才能关上大的</span><br><span class="line"> stmt.close();</span><br><span class="line"> } catch (SQLException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> try {</span><br><span class="line"> if (conn != null){</span><br><span class="line"> conn.close();</span><br><span class="line"> }</span><br><span class="line"> }catch (SQLException e){</span><br><span class="line"> e.printStackTrace();</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>]]></content>
<categories>
<category> Diary </category>
</categories>
<tags>
<tag> Java </tag>
</tags>
</entry>
<entry>
<title>Android动态广播(android8.0)</title>
<link href="/2022/05/07/Android%E5%8A%A8%E6%80%81%E5%B9%BF%E6%92%AD-android8-0/"/>
<url>/2022/05/07/Android%E5%8A%A8%E6%80%81%E5%B9%BF%E6%92%AD-android8-0/</url>
<content type="html"><![CDATA[<h2 id="Android-广播事件"><a href="#Android-广播事件" class="headerlink" title="Android 广播事件"></a>Android 广播事件</h2><h3 id="1-广播注册分类"><a href="#1-广播注册分类" class="headerlink" title="(1).广播注册分类"></a>(1).广播注册分类</h3><h4 id="a-动态注册-在-UI-中注册的广播,例如:"><a href="#a-动态注册-在-UI-中注册的广播,例如:" class="headerlink" title="(a).动态注册 在 UI 中注册的广播,例如:"></a>(a).动态注册 在 UI 中注册的广播,例如:</h4><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">IntentFilter intentFilter = new IntentFilter();</span><br><span class="line">//实现了Parcelable接口,可用于过滤广播</span><br><span class="line">intentFilter.addAction(Constants.ACTION_SEND_MSG);</span><br><span class="line">//设置过滤器,规定能接受的广播信号</span><br><span class="line">MessageReceiver messageReceiver = new MessageReceiver();</span><br><span class="line">this.registerReceiver(messageReceiver,intentFilter);</span><br><span class="line">//注册广播</span><br></pre></td></tr></table></figure><h4 id="b-静态注册"><a href="#b-静态注册" class="headerlink" title="(b).静态注册"></a>(b).静态注册</h4><p>需要在 manifest 中进行注册(在安卓 8.0 后系统废除了大部分静态广播,最好使用动态注册)。</p><h3 id="2-广播类型分类"><a href="#2-广播类型分类" class="headerlink" title="(2).广播类型分类"></a>(2).广播类型分类</h3><h4 id="a-系统广播"><a href="#a-系统广播" class="headerlink" title="(a).系统广播"></a>(a).系统广播</h4><p>系统中已经定义的广播,此类广播只能由系统发出,并且需要在 intent-filter 中加上系统已经写的 action。</p><h4 id="b-自定义广播"><a href="#b-自定义广播" class="headerlink" title="(b).自定义广播"></a>(b).自定义广播</h4><pre><code>顾名思义,是用户自己定义的广播。</code></pre><h3 id="3-动态实现广播"><a href="#3-动态实现广播" class="headerlink" title="(3).动态实现广播"></a>(3).动态实现广播</h3><h4 id="a-我们首先需要一个广播接收类"><a href="#a-我们首先需要一个广播接收类" class="headerlink" title="(a)我们首先需要一个广播接收类"></a>(a)我们首先需要一个广播接收类</h4><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><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"> public class MessageReceiver extends BroadcastReceiver{</span><br><span class="line"> //新建一个自己的广播接受类继承自BroadcastReceiver</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void onReceive(Context context, Intent intent) {</span><br><span class="line"> //重写onReceive方法,此方法写自己需要实现的逻辑</span><br><span class="line"> String detail = intent.getStringExtra(Constants.KEY_CONTENT);</span><br><span class="line"> Toast.makeText(SecondActivity.this, detail, Toast.LENGTH_SHORT).show();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="a-其次注册动态广播"><a href="#a-其次注册动态广播" class="headerlink" title="(a)其次注册动态广播"></a>(a)其次注册动态广播</h4><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">MessageReceiver messageReceiver = new MessageReceiver();</span><br><span class="line">this.registerReceiver(messageReceiver,intentFilter);</span><br></pre></td></tr></table></figure><h4 id="c-最后需要通过-send-方法发送一个广播供广播接收者接受"><a href="#c-最后需要通过-send-方法发送一个广播供广播接收者接受" class="headerlink" title="(c)最后需要通过 send 方法发送一个广播供广播接收者接受"></a>(c)最后需要通过 send 方法发送一个广播供广播接收者接受</h4><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><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">public void send(View v){</span><br><span class="line"> String content = mInputBox.getText().toString();</span><br><span class="line"> //获取输入框中的值</span><br><span class="line"> Intent intent = new Intent();</span><br><span class="line"> intent.setAction(Constants.ACTION_SEND_MSG);</span><br><span class="line"> //设置action发送哪个广播,Constant类中书写的是常量</span><br><span class="line"> intent.putExtra(Constants.KEY_CONTENT,content);</span><br><span class="line"> //通过intent携带额外数据</span><br><span class="line"> sendBroadcast(intent);</span><br><span class="line"> //发送广播</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>整个过程完毕</p>]]></content>
<categories>
<category> Android </category>
</categories>
<tags>
<tag> Android </tag>
<tag> Java </tag>
</tags>
</entry>
<entry>
<title>算法练习:无重复字符的最长子串(滑动窗口)</title>
<link href="/2022/05/07/%E7%AE%97%E6%B3%95%E7%BB%83%E4%B9%A0%EF%BC%9A%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2%EF%BC%88%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%EF%BC%89/"/>
<url>/2022/05/07/%E7%AE%97%E6%B3%95%E7%BB%83%E4%B9%A0%EF%BC%9A%E6%97%A0%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2%EF%BC%88%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%EF%BC%89/</url>
<content type="html"><![CDATA[<h2 id="一-前言"><a href="#一-前言" class="headerlink" title="一.前言"></a>一.前言</h2><p>今天遇到了这种找出无重复字符的最长子串的算法题,小编思来想去依旧没有想到有什么好方法,直到看到了大佬的算法,极为震撼,想写篇博客,记录下来。</p><p>题目如下:给出一组字符串,如 s = “ adsjkhf ”,返回值为 7,s = “abb”,返回值为 2,s = “aab”,返回值为 2。请判断此字符串中无重复字符的最长子串。</p><h2 id="二-补充"><a href="#二-补充" class="headerlink" title="二.补充"></a>二.补充</h2><p>Java HashMap 中的方法:</p><p>get() 方法获取指定 key 对应对 value:map.get(key)</p><p>put() 方法将指定的键/值对插入到 HashMap 中:map.put(key,value)</p><p>containsKey()方法如果 map 中存在指定的 key 对应的映射关系返回 true,否则返回 false。</p><h2 id="三-题解"><a href="#三-题解" class="headerlink" title="三.题解"></a>三.题解</h2><p>本题主要通过滑动窗口的思想解决。那么什么是滑动窗口呢?其实就是类似于两个前后指针,两个指针不断向后移,我们只关注指针中间的数,最后返回指针中间的元素的个数。</p><h4 id="1-分析"><a href="#1-分析" class="headerlink" title="(1) 分析"></a>(1) 分析</h4><p>我们先定义一个 map 容器用于存放字符串中的每个字符,然后从字符串的第一个字符 a 开始( i = 0 )开始找,此时 map 里面是没有这个元素的,因此我们把 a 存放进 map 中,继续执行此操作直到出现 map 中有相同的字符,此时只需把 left 指针指向相同元素的后一位,两个指针中间的部分就是不重复字符的子串,此时这个子串的长度就为 i - left + 1。我们只需要用这个方法往后看去(随着 i 增加,即后指针向后指去),寻找是否有比当前子串长度更大的,若遇到则将现在的长度替换即可。</p><p>注意:字符串可能是这样的 –> “ abbac”。按照我们的方法,会先将 ab 存入到我们的 map 当中,此时 left = 0,当 i=2 时,由于 map 中已经有 b,因此 left 将等于 get(b) + 1 = 2。随后 i 指针向后指去又遇到 map 已有元素 a,按照前面的算法,此刻 left = get(a) + 1 = 1,但实际上 left 不会变,仍然等于 2,因此在求 left 值时总应该取最大的那个。</p><h4 id="2-代码详解"><a href="#2-代码详解" class="headerlink" title="(2) 代码详解"></a>(2) 代码详解</h4><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><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><br><span class="line">public int lengthOfLongestSubstring(String s) {</span><br><span class="line"></span><br><span class="line"> int maxLen = 0;//记录最长的的无重复字符串的长度</span><br><span class="line"></span><br><span class="line"> int left = 0;//前指针</span><br><span class="line"></span><br><span class="line"> HashMap<Character,Integer> map = new HashMap<>();</span><br><span class="line"></span><br><span class="line"> for (int i=0;i<s.length();i++){</span><br><span class="line"></span><br><span class="line"> if (map.containsKey(s.charAt(i))){ //若map中有此元素则执行下面代码</span><br><span class="line"></span><br><span class="line"> left = Math.max(left,map.get(s.charAt(i))+1); //上面已经解释,此处应一直取较大的left</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> map.put(s.charAt(i),i); //将s中的第i个元素存入map中</span><br><span class="line"></span><br><span class="line"> maxLen = Math.max(maxLen,i -left + 1); //maxLen即为两个指针中间的元素个数</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return maxLen;</span><br><span class="line"></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h4 id="3-图解"><a href="#3-图解" class="headerlink" title="(3) 图解"></a>(3) 图解</h4><p>最初 left=0,i=0,map 中无任何元素,此时 map 中将添加 a 字符</p><p>此时 i=1,map 中将添加 b 字符</p><p>此时 i=2,map 中将添加 c 字符</p><p>此时 i=3,检测到 map 中已有 a,left 将指向到 map 中的 a 的后一位 b<br>以此类推遍历完字符串直到 i = s.length(),返回最后的 maxLen 即为最终答案。</p>]]></content>
<categories>
<category> LeetCode </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 算法练习 </tag>
</tags>
</entry>
<entry>
<title>扑克比大小(Java&Kotlin)</title>
<link href="/2022/05/06/My-New-Post/"/>
<url>/2022/05/06/My-New-Post/</url>
<content type="html"><![CDATA[<h2 id="一-前言"><a href="#一-前言" class="headerlink" title="一.前言"></a>一.前言</h2><p>想要具备面向对象的思维,多写代码必不可少,今天记录的是一个能迅速使大家建立起面向对象思维的一个小 Demo<span id="more"></span>:从一副扑克中抽出 2 张牌来进行比较,具体如下:</p><div class="note success flat"><p><em>1.此副牌大小顺序为 2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < J < Q < K < A.</em><br><em>2.若点数相同则比较花色 ♠ < ♥ < ♣ < ♦ .</em><br><em>注意:一副扑克中没有相同的两张牌.</em></p></div><h2 id="二-思考"><a href="#二-思考" class="headerlink" title="二.思考"></a>二.思考</h2><p>既然讲到面向对象的思维,那么在敲代码前一定要好好思考下比较两张扑克所包含的动作和事物有哪些?首先可以确定的是需要一副扑克,那么这固然是一类(Poker 类),只有有了扑克我们才能比大小,然而扑克就只是扑克吗,扑克中包含了哪些东西 ?点数和花色?那么这又是两类(PokerNumber 类和 PokerSuit 类),由于扑克牌是由点数和花色组成的,那么也就是说 PokerNumber 类和 PokerSuiit 类与 Poker 类是聚合关系。我们明白了扑克的组成,那么又是怎样将牌发出去从而比较大小的呢?这里就需要一个 PokerManager 来初始化扑克(我们的目的是实例化对象后就能将牌初始化,正好可以用构造方法)并将其发出去。</p><p>整个过程的 UML 图如下:<br><img src="https://upload-images.jianshu.io/upload_images/28033194-5c1753dbcb4edc77.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="整个UML概览"></p><ul><li>简单介绍一下过程:我们是通过 PokerNumber 和 PokerSuit 来组成单一的扑克牌的,而这一张扑克是要被收到一起组成一副扑克,换句话说就是需要被 PokerManager 集中起来统一创建管理。在 PokerManager 中定义两个数组分别储存点数和花色。我们知道 PokerManager 中有很多 Poker,那 Poker 又是怎么来的呢,这就需要剩下的两个类组成它了:PokerNumber 和 PokerSuit。分别代表扑克点数和花色,这样一张扑克就诞生了。</li></ul><h4 id="1-创建-PokerNumber-类与-PokerSuit-类"><a href="#1-创建-PokerNumber-类与-PokerSuit-类" class="headerlink" title="1.创建 PokerNumber 类与 PokerSuit 类"></a>1.创建 PokerNumber 类与 PokerSuit 类</h4><p>为什么要创建这两类呢?是因为我们需要在创建花色和点数的时候就要为之后比大小做准备。我们一旦将点数和花色确定为字符串类型的时候,就无法通过 String 来比大小,只能再引入一个变量 tag(tag 为花色数组和点数数组的索引),通过 tag 来比较,因此要把这两个属性分装起来成为一个类。</p><h5 id="a-PokerNumber-类"><a href="#a-PokerNumber-类" class="headerlink" title="a).PokerNumber 类"></a>a).PokerNumber 类</h5><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><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></pre></td><td class="code"><pre><span class="line">public class PokerNumber {</span><br><span class="line"> private String number;</span><br><span class="line"> private int tag;</span><br><span class="line"></span><br><span class="line"> public PokerNumber(String number, int tag) {</span><br><span class="line"> this.number = number;</span><br><span class="line"> this.tag = tag;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public String getNumber() {</span><br><span class="line"> return number;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void setNumber(String number) {</span><br><span class="line"> this.number = number;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public int getTag() {</span><br><span class="line"> return tag;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void setTag(int tag) {</span><br><span class="line"> this.tag = tag;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="b-PokerSuit-类"><a href="#b-PokerSuit-类" class="headerlink" title="b).PokerSuit 类"></a>b).PokerSuit 类</h5><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><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></pre></td><td class="code"><pre><span class="line">public class PokerSuit {</span><br><span class="line"> private String suit;</span><br><span class="line"> private int tag;</span><br><span class="line"></span><br><span class="line"> public PokerSuit(String suit, int tag) {</span><br><span class="line"> this.suit = suit;</span><br><span class="line"> this.tag = tag;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public String getSuit() {</span><br><span class="line"> return suit;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void setSuit(String suit) {</span><br><span class="line"> this.suit = suit;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public int getTag() {</span><br><span class="line"> return tag;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void setTag(int tag) {</span><br><span class="line"> this.tag = tag;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="2-创建-Poker-类"><a href="#2-创建-Poker-类" class="headerlink" title="2.创建 Poker 类"></a>2.创建 Poker 类</h4><p>Poker 类中包含 PokerNumber 和 PokerSuit 属性。此外还应在此类写出比较扑克大小的方法并且重写 toString()方法修改 poker 输出格式。</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><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">public class Poker {</span><br><span class="line"> private PokerNumber pokerNumber;</span><br><span class="line"> private PokerSuit pokerSuit;</span><br><span class="line"> //初始化poker的两个属性 -> 点数和花色</span><br><span class="line"> public Poker(PokerNumber pokerNumber, PokerSuit pokerSuit) {</span><br><span class="line"> this.pokerNumber = pokerNumber;</span><br><span class="line"> this.pokerSuit = pokerSuit;</span><br><span class="line"> }</span><br><span class="line"> //比较扑克牌大小</span><br><span class="line"> public boolean compareTo(Poker poker){</span><br><span class="line"> if (this.pokerNumber.getTag() == poker.pokerNumber.getTag()){</span><br><span class="line"> //点数相同的情况下,若前者花色大与后者花色则返回true</span><br><span class="line"> return this.pokerSuit.getTag() > poker.pokerSuit.getTag();</span><br><span class="line"> //点数大与后者,则返回true</span><br><span class="line"> }else return this.pokerNumber.getTag() > poker.pokerNumber.getTag();</span><br><span class="line"> }</span><br><span class="line"> //重写toString() 格式化poker的输出格式</span><br><span class="line"> @Override</span><br><span class="line"> public String toString() {</span><br><span class="line"> return</span><br><span class="line"> pokerNumber.getNumber() +</span><br><span class="line"> pokerSuit.getSuit();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="3-创建-PokerManager-类"><a href="#3-创建-PokerManager-类" class="headerlink" title="3.创建 PokerManager 类"></a>3.创建 PokerManager 类</h4><p>PokerManager 类用于集中创建和管理扑克。并且我们要通过此类 getPoker 方法得到一张具体扑克。</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">public class PokerManager {</span><br><span class="line"> ArrayList<Poker> pokers = new ArrayList<>();//创建Poker类集合存放每一张扑克</span><br><span class="line"> public PokerManager(){</span><br><span class="line"> String[] numbers = {"2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"};</span><br><span class="line"> String[] suits = {"♦", "♣", "♥", "♠"};</span><br><span class="line"> for (int i = 0; i < numbers.length; i++){</span><br><span class="line"> for (int j = 0; j< suits.length; j++){</span><br><span class="line"> //通过两层for循环创建扑克,外层循环确定点数,内层循环确定花色</span><br><span class="line"> pokers.add(new Poker(new PokerNumber(numbers[i],i),new PokerSuit(suits[j],j)));</span><br><span class="line"> //将创建出来的扑克存进pokers集合里</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"> public Poker getPoker(){</span><br><span class="line"> Random r = new Random();</span><br><span class="line"> int index = r.nextInt(pokers.size());</span><br><span class="line"> //得到[0,pokers集合长度]大小的随机数</span><br><span class="line"> Poker poker = pokers.get(index);//拿到对应索引处的扑克</span><br><span class="line"> pokers.remove(index);//去重,已经拿到过的扑克牌应从这副牌中删去</span><br><span class="line"> return poker;//返回扑克对象</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="4-创建-Test-类,测试代码"><a href="#4-创建-Test-类,测试代码" class="headerlink" title="4.创建 Test 类,测试代码"></a>4.创建 Test 类,测试代码</h4><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><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">public class Test {</span><br><span class="line"></span><br><span class="line"> private static Poker poker2;</span><br><span class="line"> private static Poker poker1;</span><br><span class="line"></span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> PokerManager pokerManager = new PokerManager();</span><br><span class="line"> //实例化对象,得到两张扑克牌</span><br><span class="line"> poker1 = pokerManager.getPoker();</span><br><span class="line"> poker2 = pokerManager.getPoker();</span><br><span class="line"> //调用compareTo方法判断大小,并输出判定结果</span><br><span class="line"> if (poker1.compareTo(poker2)){</span><br><span class="line"> System.out.println(poker1 + ">" + poker2);</span><br><span class="line"> }else</span><br><span class="line"> System.out.println(poker1 + "<" + poker2);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="5-运行结果"><a href="#5-运行结果" class="headerlink" title="5.运行结果"></a>5.运行结果</h4><p><img src="https://upload-images.jianshu.io/upload_images/28033194-2822d9331ccc07e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"><br><img src="https://upload-images.jianshu.io/upload_images/28033194-b8bd69732e47bcdd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"><br><img src="https://upload-images.jianshu.io/upload_images/28033194-cd7f119357feecb3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p><p>先记录到 Java…</p>]]></content>
<categories>
<category> Diary </category>
</categories>
<tags>
<tag> Java </tag>
<tag> Kotlin </tag>
</tags>
</entry>
</search>