-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
494 lines (247 loc) · 92.8 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Yz's Blog</title>
<link href="http://yoursite.com/atom.xml" rel="self"/>
<link href="http://yoursite.com/"/>
<updated>2021-11-10T16:06:00.068Z</updated>
<id>http://yoursite.com/</id>
<author>
<name>Yz</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>操作系统并发之常见问题</title>
<link href="http://yoursite.com/2021/11/10/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%B9%B6%E5%8F%91%E4%B9%8B%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/"/>
<id>http://yoursite.com/2021/11/10/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%B9%B6%E5%8F%91%E4%B9%8B%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/</id>
<published>2021-11-10T13:38:55.000Z</published>
<updated>2021-11-10T16:06:00.068Z</updated>
<content type="html"><![CDATA[<h3 id="并发程序中缺陷的类型"><a href="#并发程序中缺陷的类型" class="headerlink" title="并发程序中缺陷的类型"></a>并发程序中缺陷的类型</h3><ul><li>非死锁缺陷</li><li>死锁缺陷</li></ul><p>在 2008 年发表的 <a href="https://www.cs.columbia.edu/~junfeng/09fa-e6998/papers/concurrency-bugs.pdf" target="_blank" rel="noopener">Learning from Mistakes — A Comprehensive Study on Real<br>World Concurrency Bug Characteristics</a> 论文中,进行了详细的说明<br><img src="/2021/11/10/操作系统并发之常见问题/img.png" alt="img.png"><br>通过知名的开源软件已知的并发问题统计,发现出错最多的是非死锁缺陷</p><h4 id="非死锁缺陷"><a href="#非死锁缺陷" class="headerlink" title="非死锁缺陷"></a>非死锁缺陷</h4><p> 主要有两种常见的:违反原子性、错误顺序缺陷</p><p> <strong>违反原子性缺陷</strong><br> <img src="/2021/11/10/操作系统并发之常见问题/img_1.png" alt="img_1.png"><br> 第一个线程检查 proc_info 非空后打印,第二个线程将 proc_info 指针置为 NULL,现在当第一个线程进入非空判断后(line:2),发生中断,时间片轮转到第二个线程将 proc_info 指针置为NUll,<br> 显然这样会照成引用空指针,最后程序崩溃,显然这不是我们像要的。</p><p> 什么是原子性?要么一致,要么不一致。</p><p> 定义引出:由上面的问题,我们可以定义这类问题,即 if 判断应该和代码块内部保持一致。即代码段本意是原子的(整块执行),但在执行中并没有强制实现原子性。</p><p> 如何修复?我们只需要给共享变量的访问加上锁,确保每个线程获取 proc_info 时,只有一个,就像下面这样<br> <img src="/2021/11/10/操作系统并发之常见问题/img_2.png" alt="img_2.png"><br> <img src="/2021/11/10/操作系统并发之常见问题/img_3.png" alt="img_3.png"></p><p> <strong>违反顺序缺陷</strong><br> <img src="/2021/11/10/操作系统并发之常见问题/img_4.png" alt="img_4.png"><br> 观察上面的代码,如果线程 1 并没有首先执行,线程 2 就会引用空指针异常</p><p> 我们希望的是线程 1 创建完毕后,线程 2 读取到 mThread 的 State 值</p><p> 因此违反顺序缺陷的定义是 “ <strong>两个内存访问的预期顺序</strong> 被打破了(即 A 应该在 B 之前执行,但是实际运行中却不是这个顺序)”</p><p> 如何修复呢?我们可以通过 <strong>条件变量</strong> 来强制保持顺序访问<br> <img src="/2021/11/10/操作系统并发之常见问题/img_5.png" alt="img_5.png"><br> <img src="/2021/11/10/操作系统并发之常见问题/img_12.png" alt="img_12.png"></p><p> 在这段代码中,我们添加的 mtLock 锁来控制访问 mtInit 变量(line:11)以及条件变量 mtCond ,通过 mtInit 来判断是否需要放入(line:23) <strong>条件变量队列中</strong>,<br> 关于条件变量(这里你可以简单的理解为一个队列),wait 是等待并入队,signal 则是唤醒并出队。<br> 这样我们的代码也就保证的顺序性</p><p> 在论文的研究中发现,97% 非死锁问题 是违反原子性与违反顺序,因此我们应该着重关注这些问题。</p><h4 id="死锁缺陷"><a href="#死锁缺陷" class="headerlink" title="死锁缺陷"></a>死锁缺陷</h4><p>一个经典的死锁案例</p><p><img src="/2021/11/10/操作系统并发之常见问题/img_6.png" alt="img_6.png"></p><p>线程 1 Lock(L1) Lock(L2),线程 2 Lock(L2) Lock(L1), 当线程 1 占有锁 L1,发生上下文切换到 线程 2,<br>线程 2 锁住了 L2,当试图锁住 L1时,发现 线程 1 已经持有 L1,而线程 1 并没有释放锁 L1,所以发生了死锁</p><p>为什么会发生死锁?<br>我们发现上述情况是互<strong>相依赖对方的锁</strong>,当对方互相占有锁时,双方都没办法释放,很重要的原因是<strong>锁的顺序</strong></p><p>另外一种情况是封装,例如在Java Vector类中的 AddAll() 方法<br><figure class="highlight plain"><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">Vector v1, v2;</span><br><span class="line">v1.AddAll(v2);</span><br></pre></td></tr></table></figure></p><p>因为在内部需要保证线程安全,v1 v2的锁都需要获取,假设 AddAll 方法,先给v1 上锁,再给 v2 上锁,同时此刻有另外一个线程在调用 v2.AddAll(v1),<br>那么此时就有可能照成死锁,所以<strong>封装会屏蔽一些信息</strong>,我们要确保小心的使用封装的方法,并且要考虑会出现的情况。</p><p><strong>产生死锁的 4 个条件</strong>(如果这 4 个条件的任何一个没有满足,死锁就不会产生)</p><ul><li>互斥:线程对于需要的资源进行互斥的访问(例如一个线程抢到锁)。</li><li>持有并等待:线程持有了资源(例如已将持有的锁),同时又在等待其他资源(例如,需要获得的锁)。</li><li>非抢占:线程获得的资源(例如锁),不能被抢占</li><li>循环等待:线程之间存在一个环路,环路上每个线程都额外持有一个资源,而这<br>个资源又是下一个线程要申请的。</li></ul><p><strong>循环等待</strong></p><p>预防此问题,最实用的方法则是获取锁时是全序(total ordering)的,假设有两个锁 L1、L2,线程 1 获取 L1、L2 锁,线程 2 获取 L1、L2 锁,<br>按照<strong>顺序的获取锁</strong>,就不会在产生此问题了</p><p>当然顺序获取在复杂应用中(很多锁的情况),就不在这么适用了,另外一种则是偏序(partial ordering),偏序也是全序只不过用了<strong>比较大小</strong>的方式进行先后顺序的获取</p><p><img src="/2021/11/10/操作系统并发之常见问题/img_7.png" alt="img_7.png"><br>这里使用到了地址高低位的技巧来保证锁的顺序,从而避免了死锁,Linux 中的内存映射就是一个偏序锁的好例子</p><p><strong>持有并等待</strong></p><p>死锁的持有并等待条件,可以通过原子地抢锁来避免。 </p><p><img src="/2021/11/10/操作系统并发之常见问题/img_8.png" alt="img_8.png"></p><p>由于在拿到 prevention 锁之后,线程不会产生切换,从而避免的死锁,<br>任何其他的线程想要访问 L1、L2 都必须通过 拿到 prevention(全局锁)后才能执行。<br>其实就是在获取锁时,保证只有一个线程进入,但也降低了并发</p><p><strong>非抢占</strong></p><p><img src="/2021/11/10/操作系统并发之常见问题/img_9.png" alt="img_9.png"></p><p>在调用 unlock 之前,锁是被占有的,在获取 L1 锁后,我们尝试获取 L2,如果获取失败我们就会释放 L1 锁,<br>并重新执行,确保一次性拿到 L1 和 L2锁。其实也是避免依赖,在获取 L2 锁时,失败就重来。</p><p>这种情况下会导致活锁,两个线程有可能一直重复,又同时都抢锁失败,这种情况下,<strong>系统一直在运行这段代码</strong></p><p><strong>互斥</strong></p><p>最后的方法是,避免互斥的存在,如果没有互斥,依赖也就不复存在了,那么怎么样才能保证程序的正常,又避免互斥呢?</p><p>想法很简单:通过强大的硬件指令,我们可以构造出不需要锁的数据结构,例如比较并交换(compare-and-swap)CAS(网上很多资料不在赘述),<br>虽然避免了死锁,但有可能产生活锁</p><p><strong>通过调度避免死锁</strong></p><p>除了死锁预防,某些场景更适合死锁避免(avoidance)。在使用之前我们需要了解全局的信息,包<br>括不同线程在运行中对锁的需求情况,从而使得后续的调度能够避免产生死锁</p><p><img src="/2021/11/10/操作系统并发之常见问题/img_10.png" alt="img_10.png"></p><p>只要避免 T1 T2 重叠就能避免死锁</p><p><strong>检查和恢复</strong></p><p>最后一种也就是<strong>重启大法</strong>,如果一个操作系统一年死机一次,你会重启系统,然后继续愉快的使用。</p><p>提示:不要总是完美( TOM WEST 定律),不是所有值得做的事情都值得做好,如果坏事很少发生,并且造成的影响很小,那么我们不应该去花费大<br>量的精力去预防它</p><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>并发程序中缺陷的类型,大致分为两类:<strong>非死锁缺陷、死锁缺陷</strong></p><p>产生死锁的 4 个条件:互斥(CAS避免)、持有并等待(全局锁保证原子执行)、非抢占(trylock)、循环等待(顺序的获取锁)<br>通过在实际项目中统计总结出并发的缺陷,进行分类,以及产生的原因,进而避免这类问题的发生。</p><p>通过高质量的书籍是获取计算机论文中压缩的信息很好的途径,作者既保持了高浓度,也毫无保留的给出了<strong>信息源</strong>,这对我的学习方式有很大改变。</p><p>为什么读论文?因为一切都有起因,一切都是演化而来,问题不是凭空产生的。比起某些不负责任的博客、书籍,我觉得论文是真正的研究了问题,搞清楚了前因后果,真正去实践、验证过的,这样留下的才是有价值的。</p>]]></content>
<summary type="html"><h3 id="并发程序中缺陷的类型"><a href="#并发程序中缺陷的类型" class="headerlink" title="并发程序中缺陷的类型"></a>并发程序中缺陷的类型</h3><ul>
<li>非死锁缺陷</li>
<li>死锁缺陷</li>
</ul>
<</summary>
<category term="操作系统" scheme="http://yoursite.com/tags/操作系统/"/>
</entry>
<entry>
<title>操作系统并发之信号量</title>
<link href="http://yoursite.com/2021/11/07/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%B9%B6%E5%8F%91%E4%B9%8B%E4%BF%A1%E5%8F%B7%E9%87%8F/"/>
<id>http://yoursite.com/2021/11/07/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%B9%B6%E5%8F%91%E4%B9%8B%E4%BF%A1%E5%8F%B7%E9%87%8F/</id>
<published>2021-11-07T08:44:49.000Z</published>
<updated>2021-11-07T10:01:38.516Z</updated>
<content type="html"><![CDATA[<h3 id="0x01-信号量"><a href="#0x01-信号量" class="headerlink" title="0x01 信号量"></a>0x01 信号量</h3><p>定义:<br>在计算机科学中,信号量是一种变量或抽象数据类型,用于控制多线程对公共资源的访问,避免并发系统(如多任务操作系统)中的临界区问题。一个平凡的信号量是一个普通的变量,它根据程序员定义的条件而改变(例如,增加或减少,或切换)。</p><h3 id="0x02-初始化"><a href="#0x02-初始化" class="headerlink" title="0x02 初始化"></a>0x02 初始化</h3><p>在操作之前我们需要初始化信号量<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">#include <semaphore.h></span><br><span class="line"> sem_t s;</span><br><span class="line"> sem_init(&s, 0, 1);</span><br></pre></td></tr></table></figure></p><p>sem_init 有三个参数,第一个是变量地址,第二个为 0 代表同一进程,多个线程共享,第三个是初始值</p><h3 id="0x03-操作"><a href="#0x03-操作" class="headerlink" title="0x03 操作"></a>0x03 操作</h3><p>在 POSIX 标准,由两组 API 界定操作:sem_wait()和 sem_post()</p><p>sem_wait() 代表线程挂起等待,要么立刻返回(大于等于 1 时),直到调用 post,但也会有多个线程调用 wait,对于多个不满足条件的线程,都将会进入<strong>等待队列</strong></p><p>sem_post() 代表如果<strong>等待队列</strong>有等待线程,则唤醒其中一个,如果信号量为负数,代表有多少个线程在等待中</p><h3 id="0x04-应用"><a href="#0x04-应用" class="headerlink" title="0x04 应用"></a>0x04 应用</h3><p>信号量的应用很广泛,这里例举两个经典场景,<strong>锁</strong> 和 <strong>条件变量</strong></p><ul><li><p>锁</p><ul><li>我们知道锁有两个状态,已占有,非占有,则我们使用信号量来表示,为 0/1 界定,sem_wait(-1)、sem_post(+1) 环绕的就是受保护的临界区<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sem_t m; </span><br><span class="line">sem_init(&m, 0, 1); </span><br><span class="line">sem_wait(&m);</span><br><span class="line">// xxx code</span><br><span class="line">sem_post(&m);</span><br></pre></td></tr></table></figure></li></ul><p>信号量作为锁,初始值要设定为 1<br>信号量作为锁,要注意作用域,作用域不适当会照成死锁</p></li><li><p>条件变量</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">sem_t s;</span><br><span class="line"> void *</span><br><span class="line"> child(void *arg) {</span><br><span class="line"> printf("child\n");</span><br><span class="line"> sem_post(&s); // signal here: child is done</span><br><span class="line"> return NULL;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> int</span><br><span class="line"> main(int argc, char *argv[]) {</span><br><span class="line"> sem_init(&s, 0, X); // what should X be?</span><br><span class="line"> printf("parent: begin\n");</span><br><span class="line"> pthread_t c;</span><br><span class="line"> Pthread_create(c, NULL, child, NULL);</span><br><span class="line"> sem_wait(&s); // wait here for child</span><br><span class="line"> printf("parent: end\n");</span><br><span class="line"> return 0;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></li><li><p>如果我们想输出成这样的效果,初始值应该为多少呢 ?</p><p> // parent: begin</p><p> // child</p><p> // parent: end</p></li></ul><p>如果要满足这样的条件,必须满足父线程等待子线程执行完成情况,<br>我们需要考虑两种情况</p><ul><li>第一种,父线程创建了子线程,但子线程并没有运行,如果初始值是 1,wait 之后,<br>父线程继续执行,子线程可能运行在父线程 wait 之前,也可能是之后,如果初始值为 0,<br>则此时 父线程 wait 只能等待, 子线程 post 才会唤醒父线程</li></ul><ul><li>第二种,子线程在父线程调用 sem_wait()之前就运行结束,由于这种情况都满足输出结果,故不用考虑</li></ul><h3 id="0x05-总结与抽象"><a href="#0x05-总结与抽象" class="headerlink" title="0x05 总结与抽象"></a>0x05 总结与抽象</h3><ul><li>信号量可以实现锁和条件变量,反过来锁和条件变量也可实现信号量</li></ul><ul><li>信号量思想:<br> 如果抽象出信号量思想,我会与数轴相联系,都是对<strong>数</strong>的<strong>界定划分</strong>,使得数的定义划分开来,从而达到我们想要的效果</li></ul><ul><li>处理并发时,有时候简单原则更好,需要有锁的时候用锁,信号量虽然能实现效果,但需要从信号量 -> 锁,多了一个步骤,不如直接锁来的语义明确 </li></ul><ul><li>数据库最大连接数,通过信号量也可以实现。</li></ul><h3 id="0x06-引用"><a href="#0x06-引用" class="headerlink" title="0x06 引用"></a>0x06 引用</h3><ul><li>WIKI</li><li>操作系统导论</li></ul>]]></content>
<summary type="html"><h3 id="0x01-信号量"><a href="#0x01-信号量" class="headerlink" title="0x01 信号量"></a>0x01 信号量</h3><p>定义:<br>在计算机科学中,信号量是一种变量或抽象数据类型,用于控制多线程对公共资源的访问</summary>
<category term="操作系统" scheme="http://yoursite.com/tags/操作系统/"/>
</entry>
<entry>
<title>Java-SPI原理解析</title>
<link href="http://yoursite.com/2021/09/26/Java-SPI%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/"/>
<id>http://yoursite.com/2021/09/26/Java-SPI%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/</id>
<published>2021-09-26T14:36:23.000Z</published>
<updated>2021-09-26T15:49:36.224Z</updated>
<content type="html"><![CDATA[<h3 id="What-is-SPI"><a href="#What-is-SPI" class="headerlink" title="What is SPI?"></a>What is SPI?</h3><p>Wiki:Service provider interface (SPI) is an API intended to be implemented or extended by a third party. It can be used to enable framework extension and replaceable components.</p><p>服务提供者接口( SPI ) 是一种旨在由第三方实现或扩展的API。它可用于启用框架扩展和可替换组件。</p><h3 id="Ex"><a href="#Ex" class="headerlink" title="Ex"></a>Ex</h3><ol><li>创建一个接口/抽象类</li><li>实现该接口/继承抽象类</li><li>resources 目录下创建 META-INF/services (后面会说为什么是这个目录)</li><li>创建文件(该接口/抽象类的全限定类名),文件内容为该接口/抽象类下的 实现/子类</li><li>使用 ServiceLoad.load(interface/abstract class) 加载</li></ol><p>示例代码:<a href="https://github.com/hjj5258/spi-demo" target="_blank" rel="noopener">Ex repo</a></p><h3 id="原理解析"><a href="#原理解析" class="headerlink" title="原理解析"></a>原理解析</h3><ul><li>SPI关键在于 ServiceLoad<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">private static final String PREFIX = "META-INF/services/";</span><br></pre></td></tr></table></figure></li></ul><p>我们可以看到 PREFIX 变量指定了该路径,也就是为什么要在 resource 下面创建该目录</p><ul><li>load 方法</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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></pre></td><td class="code"><pre><span class="line">public static <S> ServiceLoader<S> load(Class<S> service) {</span><br><span class="line"> ClassLoader cl = Thread.currentThread().getContextClassLoader();</span><br><span class="line"> return ServiceLoader.load(service, cl);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> 最后会调用 ServiceLoader 的构造方法</span><br><span class="line"> private ServiceLoader(Class<S> svc, ClassLoader cl) {</span><br><span class="line"> service = Objects.requireNonNull(svc, "Service interface cannot be null");</span><br><span class="line"> loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;</span><br><span class="line"> acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;</span><br><span class="line"> reload();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void reload() {</span><br><span class="line"> providers.clear();</span><br><span class="line"> lookupIterator = new LazyIterator(service, loader);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private S nextService() {</span><br><span class="line"> if (!hasNextService())</span><br><span class="line"> throw new NoSuchElementException();</span><br><span class="line"> String cn = nextName;</span><br><span class="line"> nextName = null;</span><br><span class="line"> Class<?> c = null;</span><br><span class="line"> try {</span><br><span class="line"> c = Class.forName(cn, false, loader);</span><br><span class="line"> } catch (ClassNotFoundException x) {</span><br><span class="line"> fail(service,</span><br><span class="line"> "Provider " + cn + " not found");</span><br><span class="line"> }</span><br><span class="line"> if (!service.isAssignableFrom(c)) {</span><br><span class="line"> fail(service,</span><br><span class="line"> "Provider " + cn + " not a subtype");</span><br><span class="line"> }</span><br><span class="line"> try {</span><br><span class="line"> S p = service.cast(c.newInstance());</span><br><span class="line"> providers.put(cn, p);</span><br><span class="line"> return p;</span><br><span class="line"> } catch (Throwable x) {</span><br><span class="line"> fail(service,</span><br><span class="line"> "Provider " + cn + " could not be instantiated",</span><br><span class="line"> x);</span><br><span class="line"> }</span><br><span class="line"> throw new Error(); // This cannot happen</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>最后可以发现使用的是 ClassLoader 进行加载,使用当前线程的 ClassLoader,如果等于 null,则使用 SystemClassLoader,<br>这里不再说明 ClassLoader双亲委派机制</p><p>观察 nextService方法,我们可以看到通过Class.forName() 进行加载类,因为我们之前在 resources 目录下定义了实现/继承类的全限定类名,<br>最后通过 c.newInstance() 进行实例化,至此我们完成了类的加载,以及类实例的创建</p><h3 id="Why-use-SPI"><a href="#Why-use-SPI" class="headerlink" title="Why use SPI?"></a>Why use SPI?</h3><p>通过观察,我们发现SPI可以通过外部加载来提供扩展,比较灵活。</p><p>也因为外部加载使得依赖解耦,让类的实现/继承分离,在设计原则上这是一种实现扩展性的手段</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>SPI 的原理其实就是类加载机制原理,通过类加载器加载类,然后在实例化类</p><p>通过 SPI PREFIX 变量我们也可以推论出,我们可以自己实现一套规则,加载自定义目录下的类</p><p>Dubbo RPC 也用了 SPI 机制,当然现在还没有深入研究去看实现原理,不过大致的思路也应该能猜到了</p><h3 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h3><p><a href="https://en.wikipedia.org/wiki/Service_provider_interface" target="_blank" rel="noopener">https://en.wikipedia.org/wiki/Service_provider_interface</a><br><a href="https://www.baeldung.com/java-spi" target="_blank" rel="noopener">https://www.baeldung.com/java-spi</a></p>]]></content>
<summary type="html"><h3 id="What-is-SPI"><a href="#What-is-SPI" class="headerlink" title="What is SPI?"></a>What is SPI?</h3><p>Wiki:Service provider interface </summary>
<category term="Java" scheme="http://yoursite.com/tags/Java/"/>
</entry>
<entry>
<title>jodd-props设计浅析</title>
<link href="http://yoursite.com/2021/09/25/jodd-props%E8%AE%BE%E8%AE%A1%E6%B5%85%E6%9E%90/"/>
<id>http://yoursite.com/2021/09/25/jodd-props%E8%AE%BE%E8%AE%A1%E6%B5%85%E6%9E%90/</id>
<published>2021-09-25T14:29:05.000Z</published>
<updated>2021-09-25T14:54:19.029Z</updated>
<content type="html"><![CDATA[<h3 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h3><p>最近在做信创改造的项目中接触到 jodd 框架,在原有项目中的配置文件第一次见到还可以这样写配置文件的…,<br>所以思来想去这个问题一直想知道答案,今天就来研究研究</p><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>文档如下:<a href="https://props.jodd.org" target="_blank" rel="noopener">doc</a></p><p>至于为什么使用 jodd 的props?他对原生JDK有兼容性,但对JDK做了增强。<br>你可以理解为包装了一层,加入了一些方便他人使用的特性</p><h3 id="原理浅析"><a href="#原理浅析" class="headerlink" title="原理浅析"></a>原理浅析</h3><p>其实也就是加载特定的文件 -> 解析 -> 存储MataData(元信息),然后还是使用的容器去读写</p><p>我们存储了MataData,那么我们就可以与 Web 项目中的做集成,如转换成 Bean 加载到 Spring IoC 中做进一步的增强,注入DataSource等等</p><h3 id="设计浅析"><a href="#设计浅析" class="headerlink" title="设计浅析"></a>设计浅析</h3><p>对于配置文件,我们思考一下会遇到什么场景问题?</p><p>在 jodd 里,我们看到作者的设计,考虑了以下场景</p><ol><li>多个环境</li><li>我想引用已经定义的 Key (可以参考maven中定义变量)</li><li>多种编码</li><li>域(如yz.xxx1、yz.xxx2、yz.xxx3)这里我把框架中的Sections理解为 [域]</li><li>默认选择值(当同一个Key 有多个 Value的时候)</li></ol>]]></content>
<summary type="html"><h3 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h3><p>最近在做信创改造的项目中接触到 jodd 框架,在原有项目中的配置文件第一次见到还可以这样写配置文件的…,<br>所以思来想去这个问题一直想</summary>
<category term="jodd框架" scheme="http://yoursite.com/tags/jodd框架/"/>
</entry>
<entry>
<title>谈痛感</title>
<link href="http://yoursite.com/2021/07/29/%E8%B0%88%E7%97%9B%E6%84%9F/"/>
<id>http://yoursite.com/2021/07/29/%E8%B0%88%E7%97%9B%E6%84%9F/</id>
<published>2021-07-29T13:44:22.000Z</published>
<updated>2021-07-29T14:30:58.583Z</updated>
<content type="html"><![CDATA[<h3 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h3><p> 为什么我开始思考“痛感”?起因源于一则更新的播客—— <a href="https://www.pythonhunter.org/episodes/ep31" target="_blank" rel="noopener">Ep 31. 架构设计与 12fallacy(下)</a>。</p><p> 播客中谈论到,在我们讨论方案的时候,往往几个小时都讨论不出结果,问题在于每人对于好的方案的定义是不明确的。</p><p> 引子:我们先要思考什么是好的方案,好在什么地方,没有明确定义的讨论是没有意义的。</p><h3 id="为什么是“痛感”?"><a href="#为什么是“痛感”?" class="headerlink" title="为什么是“痛感”?"></a>为什么是“痛感”?</h3><p> 因为在记忆中,我最先想到的东西就是它,可以说我一切的自驱力都来源于它。</p><p> 我常常陷入一种想要却又无力的一种状态。在我的反思中,一直是痛感才能使我够自驱。如果不能够自驱,说明痛感还不够,即使你把痛感提前了。</p><h3 id="如何将“痛感”提前?"><a href="#如何将“痛感”提前?" class="headerlink" title="如何将“痛感”提前?"></a>如何将“痛感”提前?</h3><p> 保持危机意识,时常想想自己要被淘汰了吗?</p><p> 保持信息同步,坚持阅读习惯,你如果观察过一些人就会发现他们都会阅读大量的信息。</p><h3 id="如何加深“痛感”?"><a href="#如何加深“痛感”?" class="headerlink" title="如何加深“痛感”?"></a>如何加深“痛感”?</h3><p> 我们来回想一下,如果有一个问题,你不得不解决,而你又解决不了。此刻你是不是会感受到“痛感“强烈。</p><p> 为什么此刻你的“痛感“会强烈?<br> 因为这是人的本能,人是会趋利避害的。而你没办法避免的时候,你会发现一些后果你无法接受,所以此刻你会感受强烈。</p><p> 我们再来观察一个事例,一个人刚开始接触到一个领域,而随着时间的积累,这个人逐渐熟练的掌握这份工作的日常。随着越来越熟练,每天重复的做自己已经掌握的工作,感觉不到增长,会开始产生厌恶心理,从而产生”痛感“。</p><p> 至此,我们已经了解到人是会趋利避害的,那怎样利用它,从而达到我们的目的呢?<br> 我们可以告知大脑”痛感“是有利的。比如我又感受到”痛感“了,说明我在向前迈步,让大脑相信”痛感“是有利的。</p><p> 如果一项技能量化后是10小时掌握,那我们可以缩短掌握的时间,让剩余的时间”熟练“重复,从而产生”痛感“。<br> 所以我们就可以利用大脑,我们需要常常保持一直”熟练“重复的状态(不同技能),就可以获得”痛感“,从而达到自驱的目的。</p>]]></content>
<summary type="html"><h3 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h3><p> 为什么我开始思考“痛感”?起因源于一则更新的播客—— <a href="https://www.pythonhunter.org/epis</summary>
<category term="感悟" scheme="http://yoursite.com/tags/感悟/"/>
</entry>
<entry>
<title>原则</title>
<link href="http://yoursite.com/2021/07/24/%E5%8E%9F%E5%88%99/"/>
<id>http://yoursite.com/2021/07/24/%E5%8E%9F%E5%88%99/</id>
<published>2021-07-24T14:45:37.000Z</published>
<updated>2021-07-24T14:52:38.006Z</updated>
<content type="html"><![CDATA[<h3 id="本文是思考后总结的原则,不限定类型,不限定时间新增,遇到问题即可打开"><a href="#本文是思考后总结的原则,不限定类型,不限定时间新增,遇到问题即可打开" class="headerlink" title="本文是思考后总结的原则,不限定类型,不限定时间新增,遇到问题即可打开"></a>本文是思考后总结的原则,不限定类型,不限定时间新增,遇到问题即可打开</h3><ul><li><p>处理问题原则:</p><ul><li>有没有问过自己如何解决?</li><li>有没有参考类似问题的解决方案?</li><li>有没有尝试让身边的人有经验的人帮忙?</li><li>问题无法解决,有没有反馈?</li><li>解决问题,而不是逃避问题,自己在憋屁!</li><li>不要因为他人问题而导致自己问题的产生!</li></ul></li><li><p>提问的原则</p><ul><li>是否避免了xy问题?</li><li>描述是否简介,原问题是什么?我的解决方案是什么?我尝试做的时候遇到什么?(问题的一生)</li><li>提问之前,能不能自己多考虑一下,可以把几个问题合并一起问?</li></ul></li><li><p>沟通原则</p><ul><li>能否简洁的描述清楚?询问过对方理解吗?保证准确无误的传递信息</li><li>多用“我们”、“我”之间的口吻描述,拉近距离,拒绝使用“你们”、“你”等口吻描述</li><li>尊重对方有不同意见,尽量让双方达成意见一致</li></ul></li></ul>]]></content>
<summary type="html"><h3 id="本文是思考后总结的原则,不限定类型,不限定时间新增,遇到问题即可打开"><a href="#本文是思考后总结的原则,不限定类型,不限定时间新增,遇到问题即可打开" class="headerlink" title="本文是思考后总结的原则,不限定类型,不限定时间新</summary>
<category term="原则" scheme="http://yoursite.com/tags/原则/"/>
</entry>
<entry>
<title>JVM系列:基本原理</title>
<link href="http://yoursite.com/2021/06/14/JVM%E7%B3%BB%E5%88%97-%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/"/>
<id>http://yoursite.com/2021/06/14/JVM%E7%B3%BB%E5%88%97-%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/</id>
<published>2021-06-14T10:39:46.000Z</published>
<updated>2021-06-14T12:33:09.152Z</updated>
<content type="html"><![CDATA[<ul><li>为什么我们要学习JVM?<ul><li>学习JVM,能更好的了解程序为什么是这样运作的</li><li>能够自定义参数,优化JVM,使得我们的程序在JVM中获得更好的性能 </li></ul></li><li>先来看下大致的流程图<br><img src="/2021/06/14/JVM系列-基本原理/01.png" alt="image"></li></ul><p>首先我们来创建几个用于测试的类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">A</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">A</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"Init A class"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">B</span> <span class="keyword">extends</span> <span class="title">A</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> B b = <span class="keyword">new</span> B();</span><br><span class="line"> C c = <span class="keyword">new</span> C();</span><br><span class="line"> c.test();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">C</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"test method"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li><p>字节码是什么?为什么有字节码<br>我们都知道Java是跨平台的,那它为什么能够跨平台,答案就是字节码,<br> 它在操作系统上层封装了JVM,使用的class字节码,对操作系统解耦了。</p></li><li><p>JVM如何使用字节码的<br>首先我们编写的x.java文件通过打包或者命令的方式编译成了.class文件,<br>JVM可以通过这些.class文件获取到相关的信息,然后用于程序的执行,默认会找到main方法。</p></li><li><p>JVM加载字节码的过程</p><ul><li>加载<br> 加载是类加载中的一个过程,不是类加载<br> 通过全限定类名获得二进制流<br> 转化成方法区的运行时数据结构<br> 生成一个代表这个类的java Class对象,作为方法区这个类的各种数据入口</li><li>连接<ul><li>验证<br>校验.class文件是否符合JVM规范,至少它要能被JVM识别才行</li><li>准备<br>为.class文件分配所需的内存,其程序中的变量都会被初始化为 <strong>初始值</strong></li><li>解析<br>符号引用替换成直接引用</li><li>初始化<br>如果有父类,会先初始化父类<br>会执行static块代码<br>将程序中的变量,需要真正的赋予值</li></ul></li><li>使用</li><li>卸载</li></ul></li><li><p>JVM类加载</p><ul><li>什么时候加载(这个类被用到的时候)</li><li>类加载器<ul><li>启动类加载器(BootStrap ClassLoader)</li><li>扩展类加载器(Extension ClassLoader)</li><li>应用类加载器(Applicaition ClassLoader)</li><li>自定义加载器</li></ul></li><li>双亲委派机制<ul><li>作用(为了保证核心不会被串改,保证同一个文件不会被加载多次)</li><li>加载时会向上加载</li></ul></li></ul></li><li><p>JVM运行时数据区域</p><ul><li>方法区(线程共享)<br> 用于存储类、常量等信息</li><li>程序计数器(线程私有)<br> 用于存储当前执行指令位置,线程直接切换之后要知道上次执行到哪里了吧?</li><li>虚拟机栈(线程私有)<br> 存储线程中的局部变量,以及虚拟机方法调用情况,为虚拟机服务</li><li>本地方法栈<br> 虚拟机使用的到的Native方法,HotShot将虚拟机栈和本地方法栈合二为一</li><li>堆内存<br> 对象实例开辟的内存空间,对象的实际地址</li></ul></li></ul>]]></content>
<summary type="html"><ul>
<li>为什么我们要学习JVM?<ul>
<li>学习JVM,能更好的了解程序为什么是这样运作的</li>
<li>能够自定义参数,优化JVM,使得我们的程序在JVM中获得更好的性能 </li>
</ul>
</li>
<li>先来看下大致的流程图<br><img sr</summary>
<category term="JVM" scheme="http://yoursite.com/tags/JVM/"/>
</entry>
<entry>
<title>面试复盘总结</title>
<link href="http://yoursite.com/2021/06/13/%E9%9D%A2%E8%AF%95%E5%A4%8D%E7%9B%98%E6%80%BB%E7%BB%93/"/>
<id>http://yoursite.com/2021/06/13/%E9%9D%A2%E8%AF%95%E5%A4%8D%E7%9B%98%E6%80%BB%E7%BB%93/</id>
<published>2021-06-13T11:21:46.000Z</published>
<updated>2021-06-13T13:16:42.854Z</updated>
<content type="html"><![CDATA[<p>来到“资本国”也有一个多月了,最近调休加放假时间相对充裕,<br>所以想对之前的找工作的心路历程做一个复盘与总结</p><p>2021春节后提桶跑路,休息了两个月,找工作花了一个星期。</p><p>先来说说我休息时,做了些什么。</p><p>首先是信息收集,对于一线城市工作要求,然后在结合自己的实际情况制定方案实施。</p><p>当时觉得自己的基础还是不够扎实,所以看的是Java编程思想,<br>对于Spring一些特性也不是特别清晰。所以着重的还是对这两块进行学习,下图是我这两个月的学习情况</p><p><img src="/2021/06/13/面试复盘总结/01.png" alt="image"></p><ul><li><p>简历编写需要符合以下原则</p><ul><li>清晰明了,突出重点(该加粗的加粗)</li><li>把重要的放在最前面,职业经历 按照时间、担任的职位、达成的成就等等</li><li>项目经历,按照项目简介、技术栈、负责什么、使用了什么技术解决了什么问题、结果是什么(量化)</li></ul></li><li><p>面试题参考 </p><ul><li><a href="https://github.com/Snailclimb/JavaGuide" target="_blank" rel="noopener">javaGuide</a></li><li><a href="https://github.com/CyC2018/CS-Notes" target="_blank" rel="noopener">cycNote</a></li></ul></li></ul><p>当初想法是学完在面试,但在跟朋友沟通时发现,<br>可以边面试边学习,这样能有一个比较好的反馈。</p><p>掌握这些之后 13k 我觉得在一线是没有问题的,我已经亲身经历过。</p><ul><li><p>工作的选择</p><ul><li>自研 > 外包 (一般自研都需要线下面试)</li><li>用的是哪些技术栈</li><li>做的是什么行业的项目(金融项目 > 政府项目,根据你的职业规划发展来选择)</li><li>询问上班时间,加班情况</li><li>加班费计算、社保挡位、公积金、年终奖、其他福利等等,不要太相信HR</li><li>可以各家HR谈判,比如说你拿到了15k的offer但不是你理想的公司,<br> 那就可以跟你理想的公司HR进行沟通,看看能不能调整薪资之类的,一般HR招人是有提成的(所以说他的利益是跟你一致的)</li><li>项目是否稳定(是否是持续迭代的)</li><li>人员是否稳定(流动)。</li></ul></li><li><p>入职前的Tips</p><ul><li>入职前先在网上预定公司附近住酒店,在确定上班地点后开始看房租房</li><li>带齐相关证件信息,离职证明</li></ul></li></ul><p>写在最后</p><p>如果心中有跳槽想法,需要提前计划布局,这样能尽最大程度的无缝切换。</p><p>现在Java相关的岗位,都会问你微服务,大部分项目也是微服务。在进入公司后不懂就问,<br>总会有一个过程,只要你是态度是积极的,同事还是很愿意帮助你的,ps我进来之后1天半看完项目后上手微服务就开始搬砖了…还是搬砖啊!</p><p>不得不说一线城市的整个迭代过程还是挺震撼的,比如发版需要经过2轮测试环境:sit,uat,有自动构建、监控等工具,闲下来的时候有人给你code review(这个看得负责人),有专门的需求文档、设计图(蓝湖)、沟通起来也很轻松。</p><p>最后推荐几本书吧</p><ul><li>Java核心技术第10版 卷1、2 图形化不看</li><li>Java编程思想 挑选章节看</li><li>Java并发编程之美</li><li>Java并发编程的艺术</li><li>Java NIO</li><li>Spring 实战</li><li>MySQL 必知必会(工具书)</li><li>高性能MySQL 看索引章节</li></ul><p>视频方面,看B站尚硅谷的就好了 2.5倍速</p>]]></content>
<summary type="html"><p>来到“资本国”也有一个多月了,最近调休加放假时间相对充裕,<br>所以想对之前的找工作的心路历程做一个复盘与总结</p>
<p>2021春节后提桶跑路,休息了两个月,找工作花了一个星期。</p>
<p>先来说说我休息时,做了些什么。</p>
<p>首先是信息收集,对于一线城</summary>
<category term="面试相关" scheme="http://yoursite.com/tags/面试相关/"/>
</entry>
<entry>
<title>论年轻人的“精神支柱”在哪里?</title>
<link href="http://yoursite.com/2021/06/13/%E8%AE%BA%E5%B9%B4%E8%BD%BB%E4%BA%BA%E7%9A%84%E2%80%9C%E7%B2%BE%E7%A5%9E%E6%94%AF%E6%9F%B1%E2%80%9D%E5%9C%A8%E5%93%AA%E9%87%8C%EF%BC%9F/"/>
<id>http://yoursite.com/2021/06/13/%E8%AE%BA%E5%B9%B4%E8%BD%BB%E4%BA%BA%E7%9A%84%E2%80%9C%E7%B2%BE%E7%A5%9E%E6%94%AF%E6%9F%B1%E2%80%9D%E5%9C%A8%E5%93%AA%E9%87%8C%EF%BC%9F/</id>
<published>2021-06-13T11:05:16.000Z</published>
<updated>2021-06-13T11:09:34.516Z</updated>
<content type="html"><![CDATA[<p>年轻人一定要有自己的精神世界,不管是何种形式,它都会成为你依旧热爱生活的支柱。</p><p>回想我的整个过往,前期游戏成为我的支柱,我可以很仔细研究打法攻略,思考每次操作,复盘团战,总结原因,只要你对一个东西有目标想去追求它,那么它一定会成为你的精神支柱,至少你的精神是丰富的,每天都有自己想去做的事情,也可以说,就是要找到你感兴趣的事情。当然这个途中你可能会遇到很多问题,也或者在途中会觉得没有意思,但这是一件很正常事情,人生就是这样,认识世界,改造世界,你觉得没有意思之后,继续寻找下一个,你依旧可以活着很充实。这里要说明一点,就是物质基础才能决定你有权利选择!</p><p>在我对游戏没有多大兴趣时候,我尝试阅读书籍,我发现原来在学校厌恶的读书和我理解的读书完全不一样,一切变得有趣起来。比如叔本华的人生的智慧,人类简史等等,每一次阅读都是跟作者的一次沟通,对世界的一次认识。你可以发现,有些问题不止你一个人遇到过,你所有的焦虑、困惑,前人也都碰到过,并且都进行了深度剖析,让你从最底层明白你为什么会产生这样的现象,很多问题就这样有了答案。当我体验到这样一种感觉,我瞬间就被深深的吸引了,开始了痴迷式的阅读,想弄明白很多问题的底层逻辑,我把这种感觉称之为求知欲,这更像是人的一种本能。</p><p>阅读不仅仅是寻找答案,更是打开大脑思考的大门。当你看到一本书中出现的专有名词,或者出现你不了解的东西的时候,往往这种求知欲会使得你继续阅读更多书籍,而在这个过程中会产生很多思考,因为多个信息涌入你的大脑,你需要做验证、关联、联系,转化成自己思维能够理解的事物,在根据这些事物进行抽象,从而得出本质。</p><p>阅读不仅仅是进行思考,更是一种经验的方法论。比如哪个场景下使用数据结构更为高效,往往我们可以转化成现实世界中的实际问题来看,图书馆摆放书籍根据分类,可以很高效的进行搜索。如果你理解树形结构,其实就很好的联系到这个现实中的这个例子,再比如计算机中的并发,你可以观察医院是怎么并发的,医生如何解决并发的,他会预约排号,来一个看一个,看完一个叫你去检查,而不是傻傻的一直等待。所以你会发现所谓的数据结构其实就是对生活场景的总结抽象,这些抽象则是凝结的方法论。我们再看的广一些,在遍历树形结构时候,通常有两种做法,广度优先和深度优先,在职业发展的道路上,前期我们可以广度优先,比如都接触一下前端、后端、运维、dba、美工等等,然后找到一个你喜欢的进行深度优先。我们可以发现这种方法论其实就是一种思维,你可以任意的推论到任何场景中进行验证,也可以以此,继续形成新的方法论。</p><p>阅读不仅仅是学习方法论,更是眺望世界的望远镜。当你阅读越多,认识到的世界也全面,看待问题的角度也开始变多,会采用论证的方法去看待信息。例如某个新闻出现,你不会马上认为这是真的,你会进行考证。现代媒体总想着吸引眼球,而信息的质量实在堪忧,这让我感到厌恶,从而使得我不得不寻找高质量的信息源,我认为这是一种发展倒退的现象。看看抖音、快手、微博里的信息,今天哪个娱乐明星(不包括真正的艺术家)怎么样,明天谁又上了热搜,可见年轻人的精神匮乏程度,这些资本控制着人的精神获取源,不断的对你灌输质量堪忧的信息,相比阅读书籍,作者通过大量的考证、编排、整理,信息质量就高很多,那么阅读时的产生的精神养料也会很充足。对于现代快节奏的生活,高品质的慢下来就显得尤为稀缺,而这也是我们应该追求的,而不是快餐式的被资本消费。</p><p>对于工程类工作不仅仅是要阅读技术书籍,更多的也要阅读社科、金融、文学等书籍,因为技术只是小小的一部分,提升认知也是很重要的,认知更像一种加速度,能够帮助你快速的学习,让你获得指数级的成长。</p><p>在工地搬砖的原子</p><p>2021年6月11日 01.53</p>]]></content>
<summary type="html"><p>年轻人一定要有自己的精神世界,不管是何种形式,它都会成为你依旧热爱生活的支柱。</p>
<p>回想我的整个过往,前期游戏成为我的支柱,我可以很仔细研究打法攻略,思考每次操作,复盘团战,总结原因,只要你对一个东西有目标想去追求它,那么它一定会成为你的精神支柱,至少你的精神是丰</summary>
<category term="感悟" scheme="http://yoursite.com/tags/感悟/"/>
</entry>
<entry>
<title>本质是什么?</title>
<link href="http://yoursite.com/2021/01/10/%E6%9C%AC%E8%B4%A8%E6%98%AF%E4%BB%80%E4%B9%88/"/>
<id>http://yoursite.com/2021/01/10/%E6%9C%AC%E8%B4%A8%E6%98%AF%E4%BB%80%E4%B9%88/</id>
<published>2021-01-10T15:07:05.000Z</published>
<updated>2021-06-13T11:03:04.773Z</updated>
<content type="html"><![CDATA[<h2 id="本质到底是什么?"><a href="#本质到底是什么?" class="headerlink" title="本质到底是什么?"></a>本质到底是什么?</h2><p>本质,通过表面的,在骨子里的,有价值内涵的东西?</p><p>不得不说,在学习数学上,你或许也有这样的启发,例如:高斯算法,在计算1+2+3….+100=?<br>你会怎样做?你会使用一个一个数的想加吗?还是像高斯一样使用倒序排列上述然后想加,最终每个列想加的数都是一样的,在乘以多少个列,在除以2得到最终结果。<br>那么重点来了?高斯是怎么知道的呢?难度这就是天才吗?</p><p>下面就具体问题具体分析,在我们所经历的教育里,真正你学会了什么?公式的运用?数学到底是什么呢?我觉得它是一种思维的抽象,是思维的升级,<br>在解题的时候我们往往有多种解题思路,有的人10步,有的人5步,有的人2步,这难道是智商的不同?<br>我觉得不竟然是全部,往往高斯这类人更能想到问题的本质,驾驭数学,让它随意的变成自己的工具,这就是长期锻炼大脑所带来的能力,只是在表面看来是解题快,所以这正是思考,思考能让大脑得到进化,也就是所谓的聪明。<br>在现代社会里,人很容易丧失独立的思考能力,也使人的大脑不但没有得到进化,反而使人拒绝思考,让大脑变得懒惰,反正有高斯的公式,我带入就可以得到结果了,开开心心交卷,反正我分数拿到手了,我不用去想他是怎么推论得来的公式但这也扼杀了你的创造性,从自己推到公式到得到结论,再到验证结论,这个过程也是你的思维在进化,这将会变成你自己的东西。</p><p>不断的提高你的境界水平,这才是本质,如何才能知道,你是否理解到本质了,首先你要明白发明人到底为什么这样做,做的目的是什么?和原有的又有什么升级?是否具有通用条件,答案就是多几个深层次的为什么,别小看这几个为什么,这能给你打开一扇往下深挖的大门,当你越是往下挖,你就会知道的越多,知道的越多,就会越来越觉得自己渺小,保持对事物的渴望,你就会成几何倍速的疯狂吸收,<br>并且新的问题也又会引发你的思考,直到循环结束,条件为你已经很轻松的理解,并且自己也能独立的做出来(推论),直到你觉得这件事情对你没有任何挑战,你会渐渐的感到厌烦厌恶这个层次的东西。<br>所以总结一下,本质就是不断的追求,追求你的当下,因为人在不断的追求中会慢慢对自己人生越来越清晰。</p>]]></content>
<summary type="html"><h2 id="本质到底是什么?"><a href="#本质到底是什么?" class="headerlink" title="本质到底是什么?"></a>本质到底是什么?</h2><p>本质,通过表面的,在骨子里的,有价值内涵的东西?</p>
<p>不得不说,在学习数学上,你或</summary>
<category term="感悟" scheme="http://yoursite.com/tags/感悟/"/>
</entry>
<entry>
<title>csapp:week2总结</title>
<link href="http://yoursite.com/2020/10/25/csapp-week2%E6%80%BB%E7%BB%93/"/>
<id>http://yoursite.com/2020/10/25/csapp-week2%E6%80%BB%E7%BB%93/</id>
<published>2020-10-25T09:52:27.000Z</published>
<updated>2020-10-27T11:41:54.374Z</updated>
<content type="html"><![CDATA[<h3 id="csapp-week-2-总结"><a href="#csapp-week-2-总结" class="headerlink" title="csapp week 2 总结"></a>csapp week 2 总结</h3><ul><li><p>字节:8个位</p></li><li><p>计算机通过2进制码,进行执行,大部分计算机系统使用ASCII码表示文本字符</p></li><li><p>程序的生命周期</p><ul><li>预处理阶段:告诉预处理读取所需的系统头文件,并把它插入到程序文本中,得到完整的程序并生成 .i文件</li><li>编译阶段:编译器将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序</li><li>汇编阶段:汇编器将hello.s翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在文件hello.o文件中,它是一个二进制文件</li><li>链接阶段:程序中调用的其他的函数指令,例如printf函数,它会把预编译好的printf.o以某种方式合并到hello.o程序中去,链接器就负责处理这种合并,结果就得到hello文件,他是一个可以执行文件,可以被加载到内存中,由系统执行<br><img src="/2020/10/25/csapp-week2总结/image-20201025171329299.png" alt="image-20201025171329299"></li><li>把import所需的类插入到程序 -> 编译成汇编代码 -> 汇编代码 -> 二进制代码 -> 链接所需库的预编译文件 ->可执行文件</li></ul></li><li><p>系统的硬件组成</p><ul><li>总线:贯穿整个系统,它携带信息字节并负责在各个部件间传递,总线被设计成传送定长的字节块,也就是字,32位4字节,64位8字节</li><li>I/O设备:输入输出设备是系统与外部的通道,每个I/O设备都通过一个控制器或适配器与I/O总线连接</li><li>主存:是一个临时存储设备,用来存放程序和程序处理的数据</li><li>处理器:cpu<ul><li>加载:从主存复制一个字节或者一个字到寄存器,覆盖寄存器原来的内容</li><li>存储:从寄存器复制一个字节或一个字到主存的某个位子,覆盖原来的内容</li><li>操作:把两个寄存器的内容复制的ALU(算术逻辑单元),ALU对这两个做算术运算,并将结果存放到一个寄存器中,覆盖该寄存器原来的内容</li><li>跳转:从指令本身抽取一个字,并将这个字复制到程序计数器(下一条指令)中,已覆盖原来的值</li></ul></li></ul></li><li><p>运行hello程序</p><p> <img src="/2020/10/25/csapp-week2总结/image-20201025171439942.png" alt="image-20201025171439942"></p><ul><li><p>加载可执行文件hello,hello中的代码从磁盘复制到主存中</p></li><li><p>利用直接存储器存取(DMA)</p><p><img src="/2020/10/25/csapp-week2总结/image-20201025171803588.png" alt="image-20201025171803588"></p></li><li><p>一但hello的代码和数据被加载到主存中,处理器就开始执行hello程序的main程序中的机器语言指令,将字符串的字节从主存复制到寄存器,在从寄存器文件复制到显示设备</p><p><img src="/2020/10/25/csapp-week2总结/image-20201025172026065.png" alt="image-20201025172026065"></p></li></ul></li><li><p>高速缓存</p><ul><li>程序运行时候,从磁盘复制到主存,主存复制到寄存器,系统设计主要目的就是使这些复制操作尽可能快的完成,针对处理器与与主存之间的差异,采用的更小更快的设备称为高速缓存存储器,作为暂时的集结区域,存放处理器近期可能会需要的信息。</li><li>利用高速缓存将程序的性能提高一个数量级,原理就是预先读取数据,用到的时候直接从缓存取。</li></ul></li><li><p>操作系统管理硬件</p><ul><li><p>操作系统有两个基本功能:1.防止硬件失控的应用程序滥用,2.控制复杂的硬件设备,通过几个抽象概念(进程、虚拟内存、文件)</p><ul><li><p>进程:进程是操作系统对一个正在运行的程序的一种抽象,在一个系统上可以运行多个进程,一个进程的指令和另一个进程的指令是交错执行的称为上下文切换</p></li><li><p>保持跟踪进程运行所需的所有状态信息:上下文,用于恢复进程时候使用(要知道进程执行到哪里了)</p><p><img src="/2020/10/25/csapp-week2总结/image-20201025173249842.png" alt="image-20201025173249842"></p></li><li><p>线程:一个进程中由多个线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据</p></li><li><p>虚拟内存:它是一个抽象的概念,它为每个进程提供了一个假象,即每个进程都在独占的使用主存。</p></li><li><p>虚拟地址空间:每个进程看到的内存都是一致的。</p><p><img src="/2020/10/25/csapp-week2总结/image-20201025173652830.png" alt="image-20201025173652830"></p><p>地址空间最上面的区域是保留给操作系统中的代码和数据</p><p>地址底部是存放用户进程定义的代码和数据 </p><ul><li>程序代码和数据:对于所有进程来说,都是从同一固定位置开始的</li><li>堆:运行时候堆,可以动态伸缩</li><li>共享库:存放标准库代码和数据的区域</li><li>栈:编译器用它来实现函数调用,可伸缩</li><li>内核虚拟内存:为内核保留的区域,不允许程序读写这个区域的内容</li></ul></li><li><p>文件:字节序列</p></li></ul></li></ul></li><li><p>系统之间利用网络通信</p><p><img src="/2020/10/25/csapp-week2总结/image-20201025174426948.png" alt="image-20201025174426948"></p><p><img src="/2020/10/25/csapp-week2总结/image-20201025174437314.png" alt="image-20201025174437314"></p></li><li><p>并发:指一个同时具有多个活动,两个人抢一个厕所</p></li><li><p>并行:一次同时运行多个,两个人同时上厕所,一人一个厕所</p></li><li><p>指令级并行:流水线</p></li><li><p>抽象概念</p><p><img src="/2020/10/25/csapp-week2总结/image-20201025174746950.png" alt="image-20201025174746950"></p></li></ul>]]></content>
<summary type="html"><h3 id="csapp-week-2-总结"><a href="#csapp-week-2-总结" class="headerlink" title="csapp week 2 总结"></a>csapp week 2 总结</h3><ul>
<li><p>字节:8个位</p</summary>
<category term="深入理解计算机系统" scheme="http://yoursite.com/tags/深入理解计算机系统/"/>
</entry>
<entry>
<title>操作系统导论:week1总结</title>
<link href="http://yoursite.com/2020/10/17/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%AF%BC%E8%AE%BA-week1%E6%80%BB%E7%BB%93/"/>
<id>http://yoursite.com/2020/10/17/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%AF%BC%E8%AE%BA-week1%E6%80%BB%E7%BB%93/</id>
<published>2020-10-17T07:53:35.000Z</published>
<updated>2020-10-25T09:54:37.862Z</updated>
<content type="html"><![CDATA[<h3 id="操作系统导论week1总结"><a href="#操作系统导论week1总结" class="headerlink" title="操作系统导论week1总结"></a>操作系统导论week1总结</h3><ul><li><p>为什么有操作系统?</p><ul><li><p>我们来看看在没有操作系统的时候,冯·诺依曼(Von Neumann)体系,这里举一个程序运行的例子:处理器从内存中获取指令,然后进行解码(弄清楚是哪条指令),然后执行它,如两个数进行相加、访问内存、检查条件、跳转函数等,为了简化,人们在上层添加了操作系统层,使得程序与设备或者资源交互更加容易,让编写程序的人不在担心如何与设备进行交互,更加的关心程序想要达到什么目的,操作系统对外暴露了很多接口,我们只需要关心接口如何使用,而不在担心如何接口如何实现。</p></li><li><p>它取得CPU、内存或磁盘等物理资源(resources),并对它们进行虚拟化(virtualize)。它处理与并发(concurrency)有关的麻烦且棘手的问题。它持久地(persistently)存储文件,从而使它们长期安全。鉴于我们希望建立这样一个系统,所以要有一些目标,以帮助我们集中设计和实现,并在必要时进行折中。找到合适的折中是建立系统的关键。</p></li></ul></li><li><p>什么是操作系统?</p><ul><li>可以容易的让程序运行变得容易,允许程序共享内存,能够让设备与程序进行交互。</li></ul></li><li><p>虚拟化</p><ul><li><p>虚拟化CPU</p><ul><li><p>操作系统负责提供这种假象(illusion),即系统拥有非常多的虚拟CPU的假象。将单个CPU(或其中一小部分)转换为看似无限数量的CPU,从而让许多程序看似同时运行,这就是所谓的虚拟化CPU(virtualizingthe CPU)。</p></li><li><p>它是如何制造虚拟化CPU?</p><ul><li>我们知道在还没有操作系统的时候(条件为单核CPU),如果运行一个程序那么只能等这个程序执行完毕之后才能执行下一个程序,为了可以执行多个程序,操作系统将CPU的资源分割成时间片,让执行变成以时间片为单位,例如我运行多个程序,程序1获得了时间片那么程序1就得以执行,程序2此时获得了时间片程序2得以执行,然后进行来回的切换,在多个程序之间切换我们称为,上下文切换,只是切换的速度很快,对于用户来说感知不到,这就是时分共享(time sharing),是操作系统共享资源所使用的最基本的技术之一。通过允许资源由一个实体使用一小段时间,然后由另一个实体使用一小段时间,如此下去,所谓的资源(例如,CPU或网络链接)可以被许多人共享。</li></ul></li></ul></li><li><p>虚拟化内存</p><ul><li><p>我们知道访问内存必须知道地址,知道地址才能读入,更新内存。</p></li><li><p>每个进程都有自己的私有虚拟地址空间,操作系统以某种方式映射到机器的物理内存上。</p></li></ul></li></ul></li><li><p>并发</p><ul><li>例如运行一个不断的累加的程序,有很多并发执行的线程,在操作同一个内存空间,如果代码不谨慎,此时有可能存在不正确的结果,程序将计数器的值从内存加载到寄存器,然后递增,再然后保存回内存。但指令并不是以原子方式(atomically)执行(所有的指令一次性执行)的,所以奇怪的事情可能会发生</li></ul></li><li><p>持久性</p><ul><li><p>在操作系统中存储数据,而管理这些数据通常被称为文件系统(file system),因此它负责以可靠和高效的方式,将用户创建的任何文件(file)存储在系统的磁盘上。</p></li><li><p>在操作系统上操作文件,分为三步:</p><ul><li><p>第一步open()打开文件。</p></li><li><p>第二个是write(),将一些数据写入文件。</p></li><li><p>第三个是close(),只是简单地关闭文件,从而表明程序不会再向它写入更多的数据</p></li></ul></li></ul></li><li><p>进程</p><ul><li><p>进程就是运行中的程序,一个进程只是一个正在运行的程序</p></li><li><p>机器状态(machine state):程序在运行时可以读取或更新的内容</p><ul><li><p>内存:正在运行的程序读取和写入的数据也在内存中。因此进程可以访问的内存(称为地址空间,address space)</p></li><li><p>寄存器:许多指令明确地读取或更新寄存器,因此显然,它们对于执行该进程很重要</p></li><li><p>程序计数器(ProgramCounter,PC):告诉我们程序当前正在执行哪个指令</p></li><li><p>栈指针(stackpointer):管理函数参数栈、局部变量</p></li><li><p>帧指针(frame pointer):返回地址</p></li></ul></li><li><p>API</p><ul><li><p>创建(create):操作系统必须包含一些创建新进程的方法。</p></li><li><p>销毁(destroy):由于存在创建进程的接口,因此系统还提供了一个强制销毁进程的接口。</p></li><li><p>等待(wait):有时等待进程停止运行是有用的,因此经常提供某种等待接口。</p></li><li><p>其他控制(miscellaneous control):除了杀死或等待进程外,有时还可能有其他控制。</p></li><li><p>状态(statu):通常也有一些接口可以获得有关进程的状态信息,例如运行了多长时间,或者处于什么状态。<img src="/2020/10/17/操作系统导论-week1总结/01.jpg" alt="img"></p></li></ul></li><li><p>数据结构</p><ul><li>操作系统追踪进程的一些重要信息。对于停止的进程,寄存器上下文将保存其寄存器的内容。当一个进程停止时,它的寄存器将被保存到这个内存位置。通过恢复这些寄存器(将它们的值放回实际的物理寄存器中),操作系统可以恢复运行该进程。<img src="/2020/10/17/操作系统导论-week1总结/02.jpg" alt="img"></li></ul></li><li><p>进程API的使用</p><ul><li><p>fork()系统调用</p><ul><li>会从本进程copy一个进程,此时子进程会进行初始化,它拥有自己的地址空间(即拥有自己的私有内存)、寄存器、程序计数器,通过返回值可以判断父子进程,返回值等于0则是子进程,至于什么时候子进程执行,我们不能掌控,有可能在父进程调用的fork()之后或者之前,CPU调度程序(scheduler)决定了某个时刻哪个进程被执行。</li></ul></li><li><p>wait()系统调用</p><ul><li>父进程需要等待子进程执行完毕,可以用wait()来控制进程执行顺序</li></ul></li><li><p>exec()系统调用</p><ul><li>可以创建一个进程运行其他程序,fork()是copy,exec()则是让你自由选择。</li></ul></li></ul></li></ul></li></ul>]]></content>
<summary type="html"><h3 id="操作系统导论week1总结"><a href="#操作系统导论week1总结" class="headerlink" title="操作系统导论week1总结"></a>操作系统导论week1总结</h3><ul>
<li><p>为什么有操作系统?</p>
<ul</summary>
<category term="操作系统" scheme="http://yoursite.com/tags/操作系统/"/>
</entry>
<entry>
<title>操作系统:开篇</title>
<link href="http://yoursite.com/2020/10/13/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%BC%80%E7%AF%87/"/>
<id>http://yoursite.com/2020/10/13/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%BC%80%E7%AF%87/</id>
<published>2020-10-13T14:22:58.000Z</published>
<updated>2020-10-14T11:10:05.630Z</updated>
<content type="html"><![CDATA[<ul><li><p>如何学习操作系统?<br> 在有了一定的组成原理的知识之后,开始了操作系统的学习,<br> 咨询了相关大佬后,推荐了我操作系统导论这本书 + xv6(MIT 6.828)。</p><p> 此书围绕三个主题开展对操作系统的介绍:</p><ul><li>虚拟化(virtualization)</li><li>并发(concurrency)</li><li>持久性(persistence)</li></ul></li><li><p>为什么要学习操作系统?</p><ul><li>了解操作系统如何做到运行多个程序?</li><li>了解操作系统里的基本概念:什么是进程、线程、以及为什么线程之间会有许多问题?</li><li>了解操作系统是怎么进行调度的?</li><li>了解操作系统如何进行文件管理?</li></ul></li><li>学了之后有什么用<ul><li>在学习完之后,会对程序在操作系统后如何运行和管理的,有一个全貌的了解<br>对组成原理的知识能够串联起来,对于多线程编程会更加清晰,学习JVM也会变得更加简单…</li></ul></li></ul>]]></content>
<summary type="html"><ul>
<li><p>如何学习操作系统?<br> 在有了一定的组成原理的知识之后,开始了操作系统的学习,<br> 咨询了相关大佬后,推荐了我操作系统导论这本书 + xv6(MIT 6.828)。</p>
<p> 此书围绕三个主题开展对操作系统的介绍:</p>
<ul>
<</summary>
<category term="操作系统" scheme="http://yoursite.com/tags/操作系统/"/>
</entry>
<entry>
<title>数据结构:线索二叉树</title>
<link href="http://yoursite.com/2020/10/13/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E7%BA%BF%E7%B4%A2%E4%BA%8C%E5%8F%89%E6%A0%91/"/>
<id>http://yoursite.com/2020/10/13/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E7%BA%BF%E7%B4%A2%E4%BA%8C%E5%8F%89%E6%A0%91/</id>
<published>2020-10-13T13:31:04.000Z</published>
<updated>2020-10-13T14:17:54.965Z</updated>
<content type="html"><![CDATA[<ul><li>我们知道在二叉树中有三种遍历序列:前序遍历、中序遍历、后序遍历,可以很方便的找到某个节点的左右孩子(二叉链表实现),但是我们要获取某种遍历序列中的前驱节点或后继节点就会变得很困难。</li><li><p>解决办法:</p><ul><li>通过遍历寻找,需要耗费时间</li><li>通过空间换时间(把孩子节点为NULL的节点,设置序列中的前驱和后继节点),<br>这样就可以快速的访问到序列中的前驱后后继了<br><img src="/2020/10/13/数据结构-线索二叉树/01.png" alt="image"></li></ul></li><li><p>结构实现:<br> <img src="/2020/10/13/数据结构-线索二叉树/02.png" alt="image"></p></li><li><p>初始化<br> <img src="/2020/10/13/数据结构-线索二叉树/03.png" alt="image"></p></li><li><p>遍历<br><img src="/2020/10/13/数据结构-线索二叉树/04.png" alt="image"></p><ul><li>visit(p)为访问该节点</li></ul></li></ul>]]></content>
<summary type="html"><ul>
<li>我们知道在二叉树中有三种遍历序列:前序遍历、中序遍历、后序遍历,可以很方便的找到某个节点的左右孩子(二叉链表实现),但是我们要获取某种遍历序列中的前驱节点或后继节点就会变得很困难。</li>
<li><p>解决办法:</p>
<ul>
<li>通过遍历寻找,需要</summary>
<category term="数据结构" scheme="http://yoursite.com/tags/数据结构/"/>
</entry>
<entry>
<title>数据结构-基础铺垫</title>
<link href="http://yoursite.com/2019/12/01/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E5%9F%BA%E7%A1%80%E9%93%BA%E5%9E%AB/"/>
<id>http://yoursite.com/2019/12/01/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E5%9F%BA%E7%A1%80%E9%93%BA%E5%9E%AB/</id>
<published>2019-12-01T10:26:46.000Z</published>
<updated>2020-10-13T16:07:03.705Z</updated>
<content type="html"><![CDATA[<ol><li>数据</li></ol><ul><li>数据:是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。数据不仅仅包括整型,实类等数据类型,还包括最重要的数字和字符等文字数据。这里所说的数据,就是符号,要满足以下两个条件:<ul><li>可以输入到计算机中</li><li>能被计算机程序处理</li></ul></li></ul><ol start="2"><li>数据元素</li></ol><ul><li>数据元素:数据元素是组成数据的基本单位,在计算机中通常作为整体处理,也被称为记录。比如,在人类中,什么是数据元素?当然是人啦。<br>畜类呢?牛、马、羊等动物,当人就是畜类的数据元素。</li></ul><ol start="3"><li>数据项</li></ol><ul><li>数据项:一个数据元素可以由若干个数据项组成。比如人是一个数据元素,可以有眼、耳、鼻、嘴、手、脚这些数据项,也可以是姓名、年龄、出生地址等这些数据项,具体有哪些数据项目,是根据你系统所决定的。<strong>数据项</strong>是<strong>数据</strong>不可分割的<strong>最小单位</strong></li></ul><ol start="4"><li>数据对象</li></ol><ul><li>数据对象:是性质相同的数据元素的集合,是数据的子集。性质相同是指具有相同的数量和类型的数据项,比如,人都有姓名、生日、性别等相同的数据项。在实际应用中,处理的数据元素通常具有相同性质,在不产生混淆的情况下,我们都将数据对象简称为数据。</li></ul><ol start="5"><li>数据结构</li></ol><ul><li>结构:所谓结构简单理解就是关系,比如分子结构,就是组成分子的原子之间的排列方式。不同的数据元素之间不是独立的,而是存在特定关系,我们将这些关系称为结构。</li><li>数据结构:是相互之间存在一种或多种特定关系的数据元素的集合。</li></ul><ol start="6"><li>定义中的一种或多种特定关系,具体是什么样的关系?<br>按照视点不同,我们把<strong>数据结构分为逻辑结构与物理结构</strong>。</li><li>逻辑结构</li></ol><ul><li><p>逻辑结构:是指数据对象中数据元素之间的相互关系。</p><ul><li>集合结构:集合结构中的数据元素除了同属于一个集合外,它们之间没有任何关系,可以理解为数学中的集合。<br><img src="/2019/12/01/数据结构-基础铺垫/01.png" alt="image"></li><li>线性结构:线性结构中的数据元素之间是一对一的关系。<br><img src="/2019/12/01/数据结构-基础铺垫/02.png" alt="image"></li><li>树形结构:树形结构中的数据元素之间存在一种一对多的层次关系。例如,计算机的文件系统就是树形的。<br><img src="/2019/12/01/数据结构-基础铺垫/03.png" alt="image"></li><li>图形结构:图形结构的数据元素是多对多的关系。<br><img src="/2019/12/01/数据结构-基础铺垫/04.png" alt="image"></li></ul><p>逻辑结构是针对具体问题的,是为解决某个问题,在对问题理解的基础上,选择一个合适的数据结构表示数据元素之间的逻辑关系。</p></li></ul><ol start="8"><li><p>物理结构</p><ul><li><p>物理结构:是指数据的逻辑结构在计算机中的存储形式(也叫存储结构)。</p><p>数据是数据元素的集合,那么根据物理结构的定义,我们可以理解为如何把数据元素存储到计算机的存储器中,存储器主要针对内存而言,对于外部存储器数据通常用文件结构去描述。</p><p><strong>数据的存储结构应正确的反映数据元素之间的逻辑关系,这才是最为关键的,如何存储数据元素的逻辑关系,是实现物理结构(在计算机中)的难点与重点</strong></p><p>数据元素的存储结构形式有两种:顺序存储和链式存储</p></li><li><p>顺序存储结构<br>顺序存储结构:是把数据元素放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。说白了就是排队占位,谁也别插谁的队。在计算机语言中往往表现为数组。<br><img src="/2019/12/01/数据结构-基础铺垫/05.png" alt="image"></p></li><li><p>链式存储结构<br>链式存储结构:是把数据元素放在任意的存储单元里,这组存储单元是可以连续的,也可以是不连续的。<br>比如现在的银行、医院,都设置了排队系统,每个人去了都是先领一个号,等叫到时候去办理业务或看病。而这里的数据元素总在变化,整个结构都处于变化之中。</p><p>数据元素的存储关系不能反映其逻辑关系,因此需要用一个指针存放数据元素的地址,这样就能通过地址找到相关联数据元素的位置</p><p><img src="/2019/12/01/数据结构-基础铺垫/06.png" alt="image"></p><p>显然,链式存储结构非常灵活,数据存在哪里并不重要,只需要有一个指针存放了相应的地址就能找到它了。</p><p>逻辑结构是面向问题的,而物理结构是面向计算机的,其基本的目标就是将数据及其逻辑关系存储到计算机内存中。 </p></li></ul></li><li><p>数据类型</p><ul><li><p>数据类型:是指一组性质相同的值的集合及定义在此集合中的一些操作的总称。</p><p>在现实世界中,大家都需要住房子,也都希望房子越大越好。但显然,没有钱。于是就出现了各种各样的房型,有别墅,有错层的,有单间的……,这样就满足了不同人的需要。</p><p>在计算机里也是如此,比如你要计算计算1+1=2、3+5=8,可以使用整型数字进行加减运算,显然不需要开辟很大的适合小数甚至字符运算的内存空间,于是就有了各种各样的数据类型。在c语言中,按照取值不同,数据类型可以分为两类:</p></li><li>原子类型:是不可以在分解的基本类型,包括整型、实型、字符型等。</li><li><p>结构类型:由若干个类型组合而成,是可以再次分解的。例如,整型数组是由若干个整型数据组成的。</p><p>对于编程者来说,运算出结果才是他所关心的,根本不在乎cpu实现1+2进行了几次开关操作,所以我们就会考虑无论什么计算机,什么计算机语言,都会用到整数运算、实数运算、字符运算等操作,我们可以考虑把他们抽象出来。</p><p>抽象是指抽取出事物具有的普遍性的本质,抽象是一种思考问题的方式,他隐藏了繁杂的细节,只保留了实现目标所必须的信息,如计算机当中的接口、API。</p></li></ul></li><li><p>抽象数据类型<br>抽象数据类型(Abstract Data Type,ADT):是指一个数学模型定义在该模型上的一组操作。</p><p>比如刚才的例子,不管是什么计算机上,都拥有“整型”类型,也需要整数间的运算,那么整型就是一个抽象数据类型。因此,“抽象”的意义在于数据类型的数学抽象特性。</p><p>比如表示一个点,可以定义为point类型,2D的一个点可以用坐标(x,y)需要2个整型变量,3D中用(x,y,z)需要3个整型变量,这样就可以非常方便的操作一个point数据变量就能知道这一点的坐标了。</p></li></ol><h3 id="用图来总结一下吧:"><a href="#用图来总结一下吧:" class="headerlink" title="用图来总结一下吧:"></a>用图来总结一下吧:</h3><p><img src="/2019/12/01/数据结构-基础铺垫/07.png" alt="image"><br><img src="/2019/12/01/数据结构-基础铺垫/08.png" alt="image"></p>]]></content>
<summary type="html"><ol>
<li>数据</li>
</ol>
<ul>
<li>数据:是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。数据不仅仅包括整型,实类等数据类型,还包括最重要的数字和字符等文字数据。这里所说的数据,就是符号,要满足以下两个条</summary>
<category term="数据结构" scheme="http://yoursite.com/tags/数据结构/"/>
</entry>
<entry>
<title>英语二-句子的构成</title>
<link href="http://yoursite.com/2019/04/21/%E8%8B%B1%E8%AF%AD%E4%BA%8C-%E5%8F%A5%E5%AD%90%E7%9A%84%E6%9E%84%E6%88%90/"/>
<id>http://yoursite.com/2019/04/21/%E8%8B%B1%E8%AF%AD%E4%BA%8C-%E5%8F%A5%E5%AD%90%E7%9A%84%E6%9E%84%E6%88%90/</id>
<published>2019-04-21T15:15:29.000Z</published>
<updated>2020-10-13T16:17:07.320Z</updated>
<content type="html"><![CDATA[<h4 id="句子的构成"><a href="#句子的构成" class="headerlink" title="句子的构成"></a>句子的构成</h4><p><img src="/2019/04/21/英语二-句子的构成/01.png" alt="image"></p><h4 id="复合主语和复合动词"><a href="#复合主语和复合动词" class="headerlink" title="复合主语和复合动词"></a>复合主语和复合动词</h4><p><img src="/2019/04/21/英语二-句子的构成/02.png" alt="image"></p><h4 id="句子种类"><a href="#句子种类" class="headerlink" title="句子种类"></a>句子种类</h4><p><img src="/2019/04/21/英语二-句子的构成/03.png" alt="image"></p><h4 id="主语位置"><a href="#主语位置" class="headerlink" title="主语位置"></a>主语位置</h4><p><img src="/2019/04/21/英语二-句子的构成/04.png" alt="image"></p><h4 id="助动词"><a href="#助动词" class="headerlink" title="助动词"></a>助动词</h4><p><img src="/2019/04/21/英语二-句子的构成/05.png" alt="image"></p><h4 id="宾语"><a href="#宾语" class="headerlink" title="宾语"></a>宾语</h4><p><img src="/2019/04/21/英语二-句子的构成/06.png" alt="image"></p><h4 id="补语"><a href="#补语" class="headerlink" title="补语"></a>补语</h4><p><img src="/2019/04/21/英语二-句子的构成/07.png" alt="image"></p><h4 id="常见的不完整句子"><a href="#常见的不完整句子" class="headerlink" title="常见的不完整句子"></a>常见的不完整句子</h4><p><img src="/2019/04/21/英语二-句子的构成/08.png" alt="image"></p><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p><img src="/2019/04/21/英语二-句子的构成/09.png" alt="image"></p>]]></content>
<summary type="html"><h4 id="句子的构成"><a href="#句子的构成" class="headerlink" title="句子的构成"></a>句子的构成</h4><p><img src="/2019/04/21/英语二-句子的构成/01.png" alt="image"></p>
<</summary>
<category term="英语二" scheme="http://yoursite.com/tags/英语二/"/>
</entry>
<entry>
<title>高等数学工专-实数1</title>
<link href="http://yoursite.com/2019/04/21/%E9%AB%98%E7%AD%89%E6%95%B0%E5%AD%A6%E5%B7%A5%E4%B8%93-%E5%AE%9E%E6%95%B01/"/>
<id>http://yoursite.com/2019/04/21/%E9%AB%98%E7%AD%89%E6%95%B0%E5%AD%A6%E5%B7%A5%E4%B8%93-%E5%AE%9E%E6%95%B01/</id>
<published>2019-04-21T05:18:40.000Z</published>
<updated>2020-10-13T16:17:07.296Z</updated>
<content type="html"><![CDATA[<h3 id="实数"><a href="#实数" class="headerlink" title="实数"></a>实数</h3><ul><li><h4 id="符号表示"><a href="#符号表示" class="headerlink" title="符号表示"></a>符号表示</h4><ul><li><p><strong>实数集 R</strong></p></li><li><p><strong>有理数 Q</strong></p></li><li><p><strong>整数 Z</strong></p></li><li><p><strong>自然数集 N</strong></p></li></ul></li><li><h4 id="集合表示方法"><a href="#集合表示方法" class="headerlink" title="集合表示方法"></a>集合表示方法</h4><ul><li>列举法:A = {1,2,3,4}</li><li>属性法:B = {x|1<x<2}</li></ul></li><li><h4 id="区间"><a href="#区间" class="headerlink" title="区间"></a>区间</h4><ul><li>开区间:(a,b) 表示不包含两个端点a,b。</li><li>闭区间:[a,b] 表示包含两个端点a,b。</li><li>半开半闭区间:(a,b] , [a,b)。</li></ul></li><li><h4 id="邻域"><a href="#邻域" class="headerlink" title="邻域"></a>邻域</h4> 特殊区间 (α-δ,α+δ) 记为 U(α,δ) 即 U(α,δ)=(α-δ,α+δ)。<br> <img src="/2019/04/21/高等数学工专-实数1/01.png" alt="image"></li><li><h4 id="去心领域"><a href="#去心领域" class="headerlink" title="去心领域"></a>去心领域</h4> 只考虑点a邻近的点,不考虑点a,即 {x|x-δ<x<a且a<x<a+δ} ,我们称这个点集为a的 去心邻域,记作U°(a,δ)={x|x-δ<x<a且a<x<a+δ}。<br> <img src="/2019/04/21/高等数学工专-实数1/02.png" alt="image"></li></ul>]]></content>
<summary type="html"><h3 id="实数"><a href="#实数" class="headerlink" title="实数"></a>实数</h3><ul>
<li><h4 id="符号表示"><a href="#符号表示" class="headerlink" title="符号表示"></</summary>
<category term="高等数学工专" scheme="http://yoursite.com/tags/高等数学工专/"/>
</entry>
<entry>
<title>高等数学工专-导学</title>
<link href="http://yoursite.com/2019/04/21/%E9%AB%98%E7%AD%89%E6%95%B0%E5%AD%A6%E5%B7%A5%E4%B8%93-%E5%AF%BC%E5%AD%A6/"/>
<id>http://yoursite.com/2019/04/21/%E9%AB%98%E7%AD%89%E6%95%B0%E5%AD%A6%E5%B7%A5%E4%B8%93-%E5%AF%BC%E5%AD%A6/</id>
<published>2019-04-21T05:15:30.000Z</published>
<updated>2020-10-13T16:17:07.333Z</updated>
<content type="html"><![CDATA[<p><img src="/2019/04/21/高等数学工专-导学/01.png" alt="image"></p>]]></content>
<summary type="html"><p><img src="/2019/04/21/高等数学工专-导学/01.png" alt="image"></p>
</summary>
<category term="高等数学工专" scheme="http://yoursite.com/tags/高等数学工专/"/>
</entry>
<entry>
<title>概率前置知识</title>
<link href="http://yoursite.com/2019/04/21/%E6%A6%82%E7%8E%87%E5%89%8D%E7%BD%AE%E7%9F%A5%E8%AF%86/"/>
<id>http://yoursite.com/2019/04/21/%E6%A6%82%E7%8E%87%E5%89%8D%E7%BD%AE%E7%9F%A5%E8%AF%86/</id>
<published>2019-04-21T05:00:09.000Z</published>
<updated>2020-10-13T16:17:51.025Z</updated>
<content type="html"><![CDATA[<h3 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h3><h4 id="两个基本原理"><a href="#两个基本原理" class="headerlink" title="两个基本原理"></a>两个基本原理</h4><ul><li>乘法<ul><li>定义:完成某事要k个步骤,第一步有m1种方法,第二步有m2种方法…..,第k步有mk种方法,则完成此事共有m1<em>m2……</em>mk种方法。</li><li>例:甲城到乙城有3条旅游路线,乙城到丙城有2条旅游路线,则甲城到丙城共有<br>3 * 2 = 6 条旅游路线。</li></ul></li><li>加法<ul><li>定义:完成某事要k类途径,第一类有m1种方法,第二类有m2种方法……第k类有mk种方法,则完成此事共有m1+m2……+mk种方法。</li><li>例: 甲城到乙城有3类交通工具:汽车、火车、飞机,分别有5,3,2个班次,则甲城到乙城共有5 + 3 + 2 = 10个班次可选择。<h4 id="排列"><a href="#排列" class="headerlink" title="排列"></a>排列</h4></li></ul></li><li>排列<ul><li>定义:从n个不同球种取m(0<m≤n)个,按一定顺序排成一列,称此为一个排列,其排列(或取法)的总数记为<img src="/2019/04/21/概率前置知识/01.png" alt="image"></li><li>例:用1,2,3,4,5这5个数字可以组成多少个无重复的三位数?<img src="/2019/04/21/概率前置知识/02.png" alt="image"></li></ul></li><li>可重复排列<ul><li>定义:从n个不同球中每次取出一个,放回后再取一个,连续取r次得到的排列称为可重复排列,此排列总数(或取法有)n^r 种。</li><li>例:用1,2,3,4,5这5个数字可以组成多少个三位数?5 ^ 3 = 125 </li></ul></li><li>组合<ul><li>定义:从n个不同球中任取m(0<m≤n)个,不计顺序拼成一组,称此为一个组合,其组合(或取法)的总数记为<img src="/2019/04/21/概率前置知识/03.png" alt="image"></li><li>规定 <img src="/2019/04/21/概率前置知识/04.png" alt="image"></li><li>性质 <img src="/2019/04/21/概率前置知识/05.png" alt="image"></li><li>例:某批产品合格品100件、次品5件,从中任取3件,其中恰有1件次品,有多少种不同的取法?<img src="/2019/04/21/概率前置知识/06.png" alt="image"></li></ul></li></ul>]]></content>
<summary type="html"><h3 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h3><h4 id="两个基本原理"><a href="#两个基本原理" class="headerlink" title="两个基本原</summary>
<category term="概率" scheme="http://yoursite.com/tags/概率/"/>
</entry>
<entry>
<title>构建JavaScript知识体系</title>
<link href="http://yoursite.com/2018/11/25/%E6%9E%84%E5%BB%BAJavaScript%E7%9F%A5%E8%AF%86%E4%BD%93%E7%B3%BB/"/>
<id>http://yoursite.com/2018/11/25/%E6%9E%84%E5%BB%BAJavaScript%E7%9F%A5%E8%AF%86%E4%BD%93%E7%B3%BB/</id>
<published>2018-11-25T12:04:05.000Z</published>
<updated>2020-10-13T16:07:03.672Z</updated>
<content type="html"><![CDATA[<h2 id="README"><a href="#README" class="headerlink" title="README"></a>README</h2><p>如何构建自己的知识体系,首先我参考了以下几个关于JavaScript的网页</p><ul><li><a href="https://zh.javascript.info/" target="_blank" rel="noopener">现代Javascript教程</a></li><li><a href="https://wangdoc.com/javascript/" target="_blank" rel="noopener">阮一峰标准参考教程(alpha)</a></li><li><a href="http://es6.ruanyifeng.com/" target="_blank" rel="noopener">ECMAScript 6 入门</a></li></ul><p>关于为什么ES6是该知识体系的一部分?</p><ul><li>ES6对于现代Web技术是非常重要的!</li><li>它添加了许多特性</li><li>重新定义的JavaScript的严谨性</li></ul><p>关于为什么要构建知识体系,构建知识体系是对知识的梳理,以便于重新审视自己的遗漏的知识点</p><h2 id="路线"><a href="#路线" class="headerlink" title="路线"></a>路线</h2><ul><li>JavaScript的基本知识<ul><li>1.1 <a href="">Hello, world!</a></li></ul></li><li>代码质量<ul><li>…</li></ul></li><li>Objects(对象):基础知识</li><li>数据类型</li><li>高级函数</li><li>对象、类和继承</li></ul>]]></content>
<summary type="html"><h2 id="README"><a href="#README" class="headerlink" title="README"></a>README</h2><p>如何构建自己的知识体系,首先我参考了以下几个关于JavaScript的网页</p>
<ul>
<li><a </summary>
<category term="JavaScript" scheme="http://yoursite.com/tags/JavaScript/"/>
</entry>
</feed>