-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
487 lines (238 loc) · 180 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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>hyz's blog</title>
<link href="https://hyzgh.github.io/atom.xml" rel="self"/>
<link href="https://hyzgh.github.io/"/>
<updated>2023-09-27T13:03:51.970Z</updated>
<id>https://hyzgh.github.io/</id>
<author>
<name>hyz</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>elastic/go-elasticseach仓库学习笔记</title>
<link href="https://hyzgh.github.io/2023/09/27/elastic-go-elasticseach/"/>
<id>https://hyzgh.github.io/2023/09/27/elastic-go-elasticseach/</id>
<published>2023-09-27T12:58:13.000Z</published>
<updated>2023-09-27T13:03:51.970Z</updated>
<content type="html"><![CDATA[<p>仓库地址:<a href="https://github.com/elastic/go-elasticsearch">https://github.com/elastic/go-elasticsearch</a></p><p>仓库简介:The official Go client for Elasticsearch</p><p>分支:v7.11.0</p><h1 id="README-MakeFile"><a href="#README-MakeFile" class="headerlink" title="README & MakeFile"></a>README & MakeFile</h1><p>使用了<a href="https://github.com/gojp/goreportcard">goreportcard</a>对项目的质量进行了分析。</p><p>使用了codecov对代码覆盖率进行测试,网站界面用起来还挺舒服的。这个项目的覆盖率有80%+,挺高的。</p><p>使用了Github Action对项目进行自动构建编译。</p><p>使用MakeFile封装了一系列构建命令,其中有Unit-test和integration-test。它使用了go的条件编译,来区分两种测试。</p><p>比如<code>/esutil</code>下的测试代码属于unit-test。它不需要依赖es组件。</p><p>比如<code>/estransport</code>下的测试代码属于integration-test。它依赖了es组件,需要在本地先起好es。</p><p>发现Makefile还挺强大的,可以节省很多的重复性工作。比如其中的release命令,甚至可以将代码发布到Github的流程自动化。为了方便测试,Makefile也集成了用docker起ES的命令。通过Makefile,也可以很好的实践项目到API文档的输出。</p><h1 id="Client"><a href="#Client" class="headerlink" title="Client"></a>Client</h1><p>项目的入口在于<code>elasticsearch.go</code>,它提供了创建Client的函数。另外也有对应的一些_test.go文件,包括example, benchmark, test等。</p><p><code>Client</code>包含了两个成员,一个是<code>esapi.API</code>,另一个是<code>estransport.Interface</code>。</p><p><code>API</code>: </p><ul><li><p><code>/esapi/doc.go</code>的文档提供了比较充分的说明。</p></li><li><p>API是自动根据<a href="https://github.com/elastic/elasticsearch/tree/master/rest-api-spec/src/main/resources/rest-api-spec/api">ES官方client标准</a>生成的,生成代码在/internal/cmd/generate。更牛的是,ES官方还提供了client test,以保证不同语言实现的client都能复用。</p></li><li><p>API本身是个结构体,成员是函数。API的调用设计,提供了struct和function两种方式。为了实现struct方式的调用,Request定义了Do的method,感觉用起来还挺奇怪的。更倾向于使用function的模式,更紧凑一些。</p></li></ul><p><code>estransport.Interface</code>:</p><ul><li><p><code>/estransport/doc.go</code>有文档说明。estransport感觉蛮可配置化的,包括重试策略、节点选择、连接池、Logger等。</p></li><li><p><code>estransport.go</code>: 核心文件。主要实现函数是Perform。</p></li><li><p><code>logger.go</code>: 实现了日志的采集,其实不仅是日志,其他metrics的埋点也可以在这里做。</p></li><li><p><code>metrics.go</code>: 实现了metrics的采集。</p></li><li><p><code>connection.go</code>: 实现了连接池。只有在输入多个URL的时候有效。假如是使用云厂商的,一般会提供一个VIP,云厂商做了负载均衡。</p></li></ul><p><code>NewClient</code>源码阅读收获:</p><ul><li>默认是使用http.DefaultTransport,该transport没有设置读取header的超时时间,所以假如是客户端返回response慢,那么可能会导致hang住。</li></ul><p><code>Perform</code>源码阅读收获:</p><ul><li>为了实现重试,会将request body拷贝一份,因为http请求发送出去后就没了。http内置了GetBody函数可实现多次读取body。</li><li>默认超时是不会自动重试的,需要配置enableRetryOnTimeout为true才会。为什么这么设计?</li><li>为了实现并发访问,很多地方使用了Lock来实现的,包括对pool, metrics等字段的访问。</li><li>使用sleep来实现退避重试。</li><li>还是使用transport来发起请求,并在发起请求后使用logger来记录请求情况,用户可以用该logger记录日志、打点等。</li></ul><p><code>Connection</code>源码阅读收获:</p><ul><li>创建连接池只需要少量参数(host, username, password)。当真正发起请求前,需要将这些参数的绑定到Request中,以便发起http请求。</li><li>Next用于获取连接,OnSuccess用于归还成功的连接,OnFailure用于归还失败的连接。</li><li>实现了两种连接池,一种是单URL地址的连接池,另一种是多URL地址的连接池。后者相比于前者多了一个负载均衡策略,代码中定义了Selector来抽象。</li><li>singleConnectionPool其实只会返回自己的URL,没有建立连接的概念。这里复用连接是依赖底层的http client实现的。</li></ul>]]></content>
<summary type="html"><p>仓库地址:<a href="https://github.com/elastic/go-elasticsearch">https://github.com/elastic/go-elasticsearch</a></p>
<p>仓库简介:The official Go cl</summary>
<category term="编程" scheme="https://hyzgh.github.io/tags/%E7%BC%96%E7%A8%8B/"/>
<category term="仓库" scheme="https://hyzgh.github.io/tags/%E4%BB%93%E5%BA%93/"/>
</entry>
<entry>
<title>《代码整洁之道》阅读笔记</title>
<link href="https://hyzgh.github.io/2021/06/14/clean-code-reading-note/"/>
<id>https://hyzgh.github.io/2021/06/14/clean-code-reading-note/</id>
<published>2021-06-13T20:54:36.000Z</published>
<updated>2023-08-28T15:38:29.603Z</updated>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><blockquote><p>习艺之要有二:知和行。你应当习得有关原则、模式和实践的知识,穷尽应知之事,并且要对其了如指掌,通过刻苦实践掌握它。</p></blockquote><p>假如只有知,没有行。很多时候只是在阅读的时候感觉良好,但是在实际编程的时候却又写出糟糕的代码。因此,为了真正掌握编程的良好习惯,需要跟着作者的思路,进行完整的拆解。第一部分和第三部分是理论,读完只会感觉良好,而第二部分是拆解,是核心所在,只有好好阅读这部分才能习得精湛技艺。</p><h1 id="第1章-整洁代码"><a href="#第1章-整洁代码" class="headerlink" title="第1章 整洁代码"></a>第1章 整洁代码</h1><p>代码永存。</p><p>好代码很重要。</p><p>稍后等于永不。</p><p>写整洁代码,需要遵循大量的小技巧。我们称之为代码感。要想拥有良好的代码感,需要阅读代码、写代码,琢磨为什么这么写,该怎么写。</p><p>本书前传:《敏捷软件开发》。本书中涉及的一些概念在前传中有提到,假如不懂可以找这本书看看。</p><h1 id="第2章-命名"><a href="#第2章-命名" class="headerlink" title="第2章 命名"></a>第2章 命名</h1><p>要让命名名副其实、有意义。因为这样可以让代码更容易理解和修改。</p><p>避免误导:防止使用某些专有名词,防止使用List来命名map,防止使用相似的名称等。</p><p>做有意义的区分:不要随便使用错误的拼写或数字来绕过重复的命名,而应该使用有意义的区分。避免废话,废话就是冗余,应该消除冗余。为了做有意义的区分,可以添加形容词、场景等。</p><p>使用读得出来的名称,因为人类擅长记忆和使用单词。不要使用缩写等不易读的、需要解决的命名。</p><p>使用可搜索的命名,单字母名称仅用于短方法中的本地变量,名称长短应与其作用域大小相对应。若某个变量或常量使用得较多,应赋予其便于搜索的名称。</p><p>尽量避免使用编码,现代的编辑环境已经可以侦测到类型错误,不需要使用这种落后的标记方式。强行使用编码,反而会让代码不易读,成为冗余。所谓编码就是在变量上添加int, string等标志,我一般会在需要类型转化时这么命名。</p><p>类名应该是名词或名词短语。不要使用比较泛的命名,比如Data, Info, Core等。</p><p>方法名应该是动词或动词短语。属性访问器、修改器和断言应该根据其值命名,并加上get, set, is前缀。重载构造器时,使用描述了参数的静态工厂方法名,比如Complex.FromRealNumber(23.0)而不是new Complex(23.0)。</p><p>别扮可爱,即不要使用梗来命名,这样不易被所有人理解。</p><p>每个概念对应一个词。举个例子,controller和manager表示的是同个意思,就只用一个就好了,假如混用两者容易让人困惑,以为它们的作用不同。</p><p>别用双关语。举个例子,假如Add用来表示两个数相加,那么就不要用来表示将一个数插入到集合中,而应该用Append或Insert来区分它。</p><p>添加有意义的语境。常用的方法是将相关的变量一起放到一个类中,赋予这些变量一个语境。</p><h1 id="第3章-函数"><a href="#第3章-函数" class="headerlink" title="第3章 函数"></a>第3章 函数</h1><blockquote><p>函数的第一条规则是要短小,第二条规则还是要更短小。</p></blockquote><blockquote><p>函数应该做一件事。做好这件事。只做这一件事。</p></blockquote><p>只做一件事的函数的一个标志是不可以再被切分为多个函数区段。</p><p>每个函数一个抽象层级。向下规则,一系列To起头段落。</p><p>switch语句:应该将它埋在较低的抽象层,并且用多态来让它符合SRP、OCP原则。</p><p>函数命名:使用描述性的名称,别害怕长名称,别害怕花时间取名字,命名方式要保持一致。</p><p>函数参数:</p><ul><li>最理想的参数数量是零,其次是一,再次是二,应尽量避免三。</li><li>过多的参数会影响可读性、测试的复杂度</li><li>入参将布尔变量作为标识参数,是非常丑陋的,因为它说明了这个函数不只做了一件事情,应该尽快将其分成两个函数</li><li>当参数过多时,应该将一些参数封装成类,进行抽象</li><li>函数名称一般以动词或动宾结构为佳</li></ul><p>无副作用:函数中不应该做其他被隐藏起来的事。若有副作用,可能会导致古怪的时序性耦合或出乎预期的结果。</p><p>尽量避免使用输出参数,所谓输出参数,是指用入参中的参数用来输出。应该使用面向对象的思想,封装起来会更易读些。在实际工程中,确实有这么写的,确实难读,<strong>需要掌握封装对象的方法。</strong></p><p>分隔指令与询问:在具有歧义性的多个入参的函数时,解决方案是把指令与询问分隔开来,防止混淆的发生。<strong>(指令和询问混合的例子是什么?)</strong></p><p>别重复自己:整个模块的可读性会因为重复的消除而得到了提升。</p><p>结构化编程:每个函数、函数中的每个代码块都应该只有一个入口、一个出口。遵循这些规则,意味着在每个函数中只该有一个return语句,循环中不能有break或continue语句,而且永远不能有任何goto语句。对于小函数,这些规则助益不大,只有在大函数中才会有明显的好处。</p><h1 id="第4章-注释"><a href="#第4章-注释" class="headerlink" title="第4章 注释"></a>第4章 注释</h1><p>作者认为无需写注释是最完美的。这一点对于业务来说可能是合适的,但是对于开源库来说,还是需要注释来简明扼要地说明函数的行为。</p><p>只有代码不会骗人。注释会骗人,文档也会骗人,因为随着时间的推移会过时。</p><p>注释不能美化糟糕的代码。假如想要写注释,那么该问问自己代码是不是写得太烂了,需要重构。</p><p>用代码来阐述,能用函数或变量时就别用注释。比如一个很长的if表达式,与其用注释来说明,不如将它提取成一个函数,并起个合适的函数名。</p><p>好注释:</p><ul><li>法律信息</li><li>提供有用信息的注释</li><li>对意图的解释</li><li>阐释,和解释的区别在于它是描述性的,便于阅读</li><li>警示</li><li>Todo注释</li></ul><p>坏注释:</p><ul><li>喃喃自语</li><li>循规式、多余的注释</li><li>误导性、过时的注释</li><li>日志式注释</li><li>注释掉的代码,现在的代码工具能很快找回来,简洁更重要</li><li>不明显的联系。假如注释本身还不够清晰,还需要注释来说明注释,显然这是个坏注释</li></ul><h1 id="第5章-格式"><a href="#第5章-格式" class="headerlink" title="第5章 格式"></a>第5章 格式</h1><p>好的格式能提高代码的可读性。这里的格式不仅是括号换行之类的问题,还包括文件的代码行数等问题。Go中有gofmt,可以帮助干一些格式的工作,但并没有囊括所有。</p><p>垂直格式:</p><ul><li>短文件通常比长文件易于理解。</li><li>源文件名称应当简单且一目了然。</li><li>源文件最顶部应该给出最高层次的概念和算法,细节应该往下逐次展开。</li><li>源文件中的不同概念,应该用空白行隔开。比如封包声明、导入声明和每个函数之间要有空白行。</li><li>源文件中的有联系的概念,应该相互靠近。比如有关系的几个变量。</li><li>关系密切相关的概念,应该放在同一个文件中。避免在源文件之间跳转。</li><li>注重垂直距离。<ul><li>变量声明:变量声明应该尽可能地靠近其使用位置。类或结构体的变量应该统一放在类的顶部或底部,关键是位置要统一。</li><li>相关函数:如果某个函数调用到了另外一个,就应该把他们放在一起,而且调用者应该尽可能放在调用者上面。</li></ul></li></ul><p>横向格式:</p><ul><li>一行的字符数不要超出120字符。</li></ul><p>团队规则:</p><ul><li>一个团队应该约定一套编码规范。好的代码需要拥有一致和顺畅的风格,这样能减少阅读的复杂度。</li></ul><h1 id="第6章-对象和数据结构"><a href="#第6章-对象和数据结构" class="headerlink" title="第6章 对象和数据结构"></a>第6章 对象和数据结构</h1><blockquote><p>对象和数据结构的区别:对象把数据隐藏于抽象之后,暴露操作数据的函数。数据结构暴露其数据,没有提供有意义的函数。</p></blockquote><p>书中提到的对数据结构的这种说法我是不赞同的,因为数据结构通常也提供有意义的函数,提供抽象。比如很多学过的数据结构,队列、栈、二叉树、红黑树,实际上都是暴露操作数据的函数。作者这里想表达的内容是,应该进行有意义的、更高一层的抽象,将接口定义和底层实现分开。</p><p>但是,书中也提到了,暴露操作和暴露数据之间,也各有适用的场景。我们称暴露数据的代码称为过程式代码,暴露操作的代码称为面向对象代码。</p><blockquote><p>过程式代码便于在不改动既有数据结构的前提下添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类。</p><p>过程式代码难以添加数据结构,因为必须修改所有函数。面向对象代码难以添加新函数,因为必须修改所有类。</p></blockquote><p>其实,我对这里的难点没有特别体会到,感觉两种方式都OK。借助于现在智能的IDE,跳转起来都还挺方便的,修改的工作量似乎也差不多。但是这里有一个点是,假如自己发现用某种做法比较难时,可以想想有没有其他更容易的做法。</p><p>什么是德墨忒尔定律,解决了什么问题?</p><p>用于解决多个模块过于耦合的问题,是一个为了让模块之间松耦合的有效方法。该定律内容简单来说,是一个模块不应该了解其他模块的底层细节。举个例子,有A, B, C三个类,对应三个实例是objA, objB, objC,假如objA依赖了objB, objB依赖了objC,那么objA应该只关心objB的方法和属性,不应该关心objC的细节,objB在和objC交互后应该将结果封装给objA。其实,在微服务中,也存在这种思想,假设有A, B, C三个服务,A依赖了B,B依赖了C,现在A需要处理下游服务的状态码,它应该只关心B服务返回的状态码,而不需要关心服务C返回的状态码。假如不这么做,假设B有很多个底层服务,那么A就需要关心很多个底层服务了,这会很耦合。</p><p>假如是一个结构体有多个层级,访问底层的成员时会用类似<code>a.b.c.d</code>这种方式,这并不被认为违反德墨忒尔定律,因为它很简单清晰。</p><h1 id="第7章-错误处理"><a href="#第7章-错误处理" class="headerlink" title="第7章 错误处理"></a>第7章 错误处理</h1><p>主要介绍了Java使用Exception处理错误的方法。在Go中,处理错误一般是逐级传递error。Exception相比于error传递的好处在于,可以不需要逐级传递,在遇到错误时throw exception,在最外层进行try catch处理exception。但是这样做也有坏处,throw的地方会散布于各处。</p><p>不管是抛出错误还是异常,多应该给出明确的异常发生的环境说明,方便定位问题。</p><p>对错误进行分类、抽象、封装,可以让错误更加清晰。</p><p>该章节的一些其他建议和Java比较耦合,比如不返回null。作为一个Gopher,对其缺少共鸣,在Go中是经常会返回nil的,对nil的检查也很常见。</p><p>总的来说,错误处理的方式是多样的,不同语言有不同的流派。写过Java的人可能更喜欢Exception的处理方式,之前在一个课程上遇见过在Go中使用Exception来处理异常的,挺hack的。</p><p>错误处理的目标应该是尽可能清晰、统一,我认为Go的error处理是符合这一个目标的。虽然逐级处理error有点麻烦,但是这样做可以保证可读性,足够清晰。</p><h1 id="第8章-边界"><a href="#第8章-边界" class="headerlink" title="第8章 边界"></a>第8章 边界</h1><p>边界是指自身代码和第三方程序、开放源代码的界限。本章节主要介绍保持软件边界整洁的实践手段和技巧。</p><ul><li>封装第三方代码。当第三方代码的接口是比较通用的时候,当接入自身系统时就需要一定的转换。为了避免在每一处代码都进行转换,可以对其进行一层封装,屏蔽转换的细节。泛型是避免转换的一种方式,自己写个数据结构封一层也是一种方式。</li><li>使用adapter模式使用尚不存在的代码。假如第三方的API的具体定义还不确定,可以先定义一个interface,写上我们预期的行为,此后的开放基于该interface进行开发。当第三方API确定后,定义一个adapter,使用它们的API接口实现我们定义的interface。这样,我们就可以在不被它们API阻塞的情况下完成开发。</li></ul><h1 id="第9章-单元测试"><a href="#第9章-单元测试" class="headerlink" title="第9章 单元测试"></a>第9章 单元测试</h1><p>保持测试的整洁,测试代码也应该像正式代码一样可维护。</p><p>FIRST原则:</p><ul><li>Fast: 可以快速执行完</li><li>Independent: 可以并发执行,无相互依赖</li><li>Repeatable: 可以重复执行,无副作用</li><li>Self-Validating: 可以自我验证,有意义的校验可以发现问题</li><li>Timely: 及时编写,在写正式代码的时候就产生测试代码</li></ul><h1 id="第10章-类"><a href="#第10章-类" class="headerlink" title="第10章 类"></a>第10章 类</h1><p>单一职责原则:类应该只有一个修改的原则。</p><p>为了保证上面这个原则,类应该尽可能小。</p><p>内聚:类中的变量尽可能都被每个方法用到。</p><p>可以将类拆小,将可能发生联系的方法和变量放到一个类,从而让这个类更内聚。</p><p>问题:10.3中举的SQL拆分例子,拆分后的类方法<code>generate()</code>,是怎么work的啊?</p><h1 id="第11章-系统"><a href="#第11章-系统" class="headerlink" title="第11章 系统"></a>第11章 系统</h1><p>要系统层级上面的整洁。使用抽象。</p><p>将系统的构造与使用分开。假如不分开,测试会有点难写,因为缺少抽象,难以mock。</p><p>分开方法:</p><ul><li>集中在main函数进行构造</li><li>工厂:可以在运行的时候调用,但是构造还是由工厂方法自己负责</li><li>依赖注入</li></ul><p>感觉这一章较多知识和Java相关,比如所谓的POJO, Java代理, EJB, Java AOP, AspectJ等。</p><p>其中,Java AOP将特定领域的东西横切成面,统一处理的思想。是有点感触的,但是不深。</p><p>DSL:领域特定语言,用于描述系统层级的各个领域。感觉和银平大佬推的什么宣言有点类似。</p><p>打造一个系统,也可以用保守主义的思想,先打造出一个大致可行的简单方案,不要一上来就进行恢弘的设计。根据后面的需求,有更多输入的情况下,再对系统进行演进。不要过度设计,不要提前恢弘设计。</p><h1 id="第12章-迭进"><a href="#第12章-迭进" class="headerlink" title="第12章 迭进"></a>第12章 迭进</h1><p>这一章提到了改进系统设计的实用技巧:</p><ol><li>运行所有测试</li><li>不可重复</li><li>表达力</li><li>减少重复</li></ol><p>第一条是改进的基础,第二到第四条是重构中要注意的点。</p><p>实际工作中,测试都没有跑起来,后面的几点也无从谈起啊!</p><h1 id="第13章-并发编程"><a href="#第13章-并发编程" class="headerlink" title="第13章 并发编程"></a>第13章 并发编程</h1><p>servlet标准模式,是Java的一种web框架,类似于Go的gin框架。提供并发处理http请求的解决方法,让工程师集中集中精力写业务逻辑,不需要关心底层的TCP连接、HTTP协议解析、复用线程、IO异常处理等。</p><p>许多Java相关的部分。总的来看,有以下写好并发编程代码的方法:</p><ul><li>学习并发问题的可能原因:data race;使用了公共资源池,比如线程池。</li><li>学习并发技术及类库:自旋锁;mutex;atomic</li><li>掌握并发的基本常识<ul><li>缩小临界区,减少锁的影响范围</li><li>减少共享对象,使用通信的方式(为什么?可能是让读和写更明显了)</li></ul></li></ul><h1 id="第14章-逐步改进"><a href="#第14章-逐步改进" class="headerlink" title="第14章 逐步改进"></a>第14章 逐步改进</h1><p>作者说了,案例才是这本书最精华的部分,所以要好好研读,琢磨消化。</p><p>派生类:基于基类的类,拥有基类的方法。解决代码重用的问题,DRY。</p><p>看了两三次,对于一开始介绍的最终版本没有看懂,有点受打击。先跳过吧,因为这部分内容和Java也关系有点多。</p><h1 id="第15章-JUnit内部"><a href="#第15章-JUnit内部" class="headerlink" title="第15章 JUnit内部"></a>第15章 JUnit内部</h1><p>看标题就是和Java相关比较大,可以先抱着粗略的态度看看。</p><p>实际看下来,和Java关系不大,能看懂,有收获感。重点看看作者做出某种重构的考量。</p><p>但是感觉实际工作中,假如抱着这样的态度去改态度,会累死= =、除非有契机去推动这样的事情发生。</p><h1 id="第16章-重构SerialDate"><a href="#第16章-重构SerialDate" class="headerlink" title="第16章 重构SerialDate"></a>第16章 重构SerialDate</h1><p>作者通过单元测试,发现这个库存在的许多问题。这说明单元测试是挺有用的,可以发现算法问题。对于业务代码,当然也是有用的,但是业务中的测试,会面临许多外部调用的mock。有什么方便的方法去mock业务中的外部调用吗,让测试更容易。</p><p>重点看看作者做出某种重构的考量,比如命名、函数拆分、细小改变等。感觉作者这种组织模式很不适合阅读,看起来费劲,所以我只粗略看看。</p><h1 id="第17章-味道与启发"><a href="#第17章-味道与启发" class="headerlink" title="第17章 味道与启发"></a>第17章 味道与启发</h1><p>作者在读了一些源代码后,在实践中提出了他积累的一个CheckList。不妨从中看看对自己有感触的部分。</p><p>服务编译应该追求一个脚本就可以构建。假如有仓库依赖,建议在README做好相关说明,或者在编译脚本中给出指南。</p><p>同理,单元测试也应该追求一个脚本就可以跑完所有单元测试。可以将必要的环境变量等信息封装到脚本中。</p><p>DRY原则我们都懂,大佬们也认为这是最重要的一个原则之一。努力践行这个原则,可以提升自己的设计抽象能力。</p><p>问题:如何践行DRY原则,有哪些具体的编程方法?</p><p>有OO、结构化编程、数据库范式等。</p><p>我发现测试不可读的一个原因,是其中存在许多隐式的约定、魔数等坏味道的代码。假如我们对待测试也像业务代码那样认真,就可以提高可读性。</p><p>在较高层级放置默认值,这一点比较好。我看了ares有些库,会将默认值放在底层,就不太可读,常常需要跳转好几次才能找到默认值。或者注释也是一种方法。</p><p>名称应该说明副作用。这一条比较有感触,自己在工作看到<code>getXXX()</code>的函数,并不一定会想到他会有写操作。用<code>createOrReturnXXX()</code>就表明可能有写操作。</p><h1 id="第一遍读完回顾"><a href="#第一遍读完回顾" class="headerlink" title="第一遍读完回顾"></a>第一遍读完回顾</h1><p>时间:2023年2月19日晚上</p><p>断断续续读了挺久的这本书,今晚终于读完了。</p><p>问题:如何发挥这本书的价值?</p><ul><li>最重要的是保持匠心,追求代码的整洁</li><li>其次,建立这本书的思维导图,心中有个全景图,做一个checkLIst</li><li>最后,常常回顾一下这本书,常读常新</li></ul>]]></content>
<summary type="html"><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><blockquote>
<p>习艺之要有二:知和行。你应当习得有关原则、模式和实践的知识,穷尽应知之事,并且要对其了如指掌,通过刻苦实践掌握它。</summary>
<category term="编程" scheme="https://hyzgh.github.io/tags/%E7%BC%96%E7%A8%8B/"/>
</entry>
<entry>
<title>《自控力》阅读笔记</title>
<link href="https://hyzgh.github.io/2021/06/05/The-Willpower-Instinct-readingNote/"/>
<id>https://hyzgh.github.io/2021/06/05/The-Willpower-Instinct-readingNote/</id>
<published>2021-06-05T08:25:01.000Z</published>
<updated>2021-06-16T16:17:22.310Z</updated>
<content type="html"><![CDATA[<p>最近阅读完了《自控力》这本书,书里讲的一些内容挺有道理和依据的,能解释生活中的一些行为。读完本书可以明白自己为何失控、如何失控,以及如何更好地避免失控,更好地掌控自己的生活。本博文以总结技巧为主,知识点为辅,说明该如何提高自己的自控力。</p><p>当我们觉得自己的自控力很差时,其实其他大多数人也存在和我们一样的问题。这本书教给我一个很重要的道理,即任何人都是普通人,都会受生理的限制,会产生普通人面对诱惑时该有的表现。我们首先应该有自知之明,明白人类的自控力系统很孱弱,需要我们好好地运用技巧来“提高”自控力。</p><h1 id="第一章-意志力是什么"><a href="#第一章-意志力是什么" class="headerlink" title="第一章 意志力是什么"></a>第一章 意志力是什么</h1><p>我理解文中提到的意志力和自控力是同义词,都表示对自己生活的掌控能力。</p><p>生活中的事情,可以归为三类:我要做,我不要,我想要。意志力就是驾驭这三种事情,在这三者中寻得平衡。</p><p>“我想要”是自己的目标和欲望,它越强大,越能让自己做该做的事情,并让自己不做不应该做的事情。</p><p>每个人都会有积极的欲望和消极的欲望。积极欲望通常是更长期的目标,比如拥有规律的作息、健壮的身体、良好的学习习惯、向上的职业规划。而消极欲望,通常是短期的放纵,比如玩游戏、刷短视频、刷朋友圈。<strong>这里有个核心问题是怎么让自己的积极欲望更强大, 而消极欲望更渺小?</strong></p><p>书中提到,每个会有两个自我,一个会控制自己,另一个则充满冲动。前者是多做积极事情的推动者,而后者则比较本能。</p><p>技巧:给两个自我起个名字,以更好地认清两个自我的存在。比如可以将控制自我称为mage,而将冲动的自我称为monster。通过分别赋予前者积极的语义,后者消极的语义,以利于在做决策时,更好地唤醒明智的自己。</p><p>通常来说,我们做决定时,通常都像开了自动挡,或者说,我们大多数时候会被过去的习惯所驱动。</p><p><strong>这里有个核心问题,如何在做决定时唤醒自己?</strong></p><p>技巧:集中注意力。比如在做决定时,关闭电子设备,闭上眼睛,做几个深呼吸。</p><p>技巧:回忆做过的决定。比如可以在睡前,回忆一下今天没有去健身房健身的场景,受到了什么因素的干扰,下次应该怎么集中注意力。</p><p>前额皮质:大脑中用于控制行为的区域。分成三部分,分别对应三类事情。</p><p>技巧:练习冥想。冥想可以让更多的血液流进前额皮质,帮助自己做正确的事情。</p><h1 id="第二章-意志力本能"><a href="#第二章-意志力本能" class="headerlink" title="第二章 意志力本能"></a>第二章 意志力本能</h1><p>本能是指人类先天携带的应对外界刺激时的反应。有些本能是先天携带的,比如生存、进食、应对危险等。</p><p>本能、习惯其实都是在描述人类应对某一类事情如何表现。通常我们会说如何培养习惯,而不会说如何培养本能,因为本能是不可更改的。我们可以通过更改自己的习惯,来改变自己。</p><p>多巴胺:一种让自己觉得做得某件事情会很快乐的化学物质。</p><p>比如,在面对食物诱惑时,大脑会分泌一些多巴胺,让自己产生进食的冲动。这个应该因人而异,笔者面对食物诱惑时,多巴胺似乎分泌不足,因为自己总是不太想吃东西。另外,还有个例子是让很多人深受其恼的熬夜刷短视频,在我们想再刷几分钟就睡觉时,大脑总会分泌多巴胺让这个行为继续下去。</p><p>本能相对应的自控表现,称之为“三思而后行”反应,即让自己慢下来,先想想,再做行动。在这个时代,我们应该做的是多做“三思而后行”反应,少依本能行事。</p><p><strong>这里有个核心问题,如何让自己三思而后行?</strong></p><p>技巧:将时间分块,强制事情执行前先思考。假如处于诱惑中,则应先强制中断当前的事情,先远离诱惑。</p><p>心率变异度:反映意志力情况。越高,越能抵抗诱惑。</p><p><strong>问题:怎么提高心意变异率?</strong></p><ul><li>快速有效的方法,将呼吸频率降低到每分钟4~6次,也就是每分钟呼吸用10~15秒时间。</li><li>长期坚持的方法,每周锻炼三次。这里的锻炼包括健身房、游泳、伸展运动、公园散步、打扫房间等活动。只要让自己离开床和沙发的活动,就有所帮助。</li><li>充足、规律的晚上睡眠,以及适当的午休、小憩。</li></ul><p>素材:在网上搜索了一个问题,跳转到了知乎,看完了回答,刷起了短视频,本来打算查完问题就午休一会的,结果浪费了两个小时在刷视频上。睡了一觉后, 把手机扔在一边,才得以开始干活。</p><p>素材:晚上,特别是周五的晚上,就会感觉很兴奋,想玩手机,打游戏、刷短视频、刷消息等。不知不觉就熬夜了。</p><p>应对方法?</p><ul><li>属于“我不要”的挑战。</li><li>不是强迫自己去睡觉,而是远离那些让自己没法睡觉的东西。并且在这个过程增强自己的控制力,比如用深呼吸大法。</li></ul><h1 id="第三章-意志力极限"><a href="#第三章-意志力极限" class="headerlink" title="第三章 意志力极限"></a>第三章 意志力极限</h1><p>意志力像肌肉一样,是有极限的。</p><p>例子:当自己学习了一段时间后,不应该做的事情是玩手机,因为很有可能这会触发自己陷入一段很长的无意义行为中。正确的做法是脱离工作区域,随便做点和成瘾无关的事情,让自己的身心放松下来,以便迎接下一阶段的事情。</p><p>例子:遇到不爽的事情应该马上解决,不然会一直消耗自己的意志力,是一个debuff。</p><p>持续使用意志力会让它消耗,应该让它恢复。</p><p>问题:如何补充意志力?</p><ul><li>放松</li><li>补充能量</li></ul><p>虽然短期吃点东西能补充能量,提高意志力,但是这长远来看,血糖的突然波动会影响身体使用糖分的能力,即身体中的含糖量很高却没有多少能量可用。一种更好的方法是保证身体有足够的食物供应,以实现更持久稳定的能量供应。</p><p>实践:尝试低血糖饮食,让自己的血糖更稳定。低血糖食物包括瘦肉蛋白、坚果、豆类、粗纤维谷类、麦片、水果、蔬菜等。</p><p>在一些小事上持续自控会提高整体的意志力。</p><p>实践:以前不太记录时间的话,可以尝试增强自我监控能力,可以记录每天的时间花在哪里上了。做好这件小事,可以帮助自己认识自己到底浪费了多少时间。</p><p>问题:当自己觉得意志力告急,控制不了自己时,可以怎么挽救一下?</p><p>很多时候意志力告急可能就像跑步跑不下去一样,可以通过逼自己一把来实现突破。</p><p>假如是“我想要”的事情,可以假想一下挑战成功后获得的奖励是什么,我会有什么回报,是更健康、更靠谱、更幸福、更自由、更有钱,还是更成功?另外,假如挑战成功,还有谁会获益?假如是自己的榜样,他会建议自己怎么做?假如是自己讨厌的人,他会放弃吗,自己能做得比他更好吗?</p><p>假如是“我不要”的事情,可以假想一下持续做了这些事情带来的可怕后果,越形象越好。假如自己喜欢的人发现自己做了不该做的事情,会怎么看自己?自己讨厌的人也陷入了这些事情,自己是和他一样的吗?</p><h1 id="第四章-道德许可"><a href="#第四章-道德许可" class="headerlink" title="第四章 道德许可"></a>第四章 道德许可</h1><p>道德许可:在做了好事或者以为自己会做好事后,纵容自己做坏事的现象。</p><p>例子:</p><ul><li>在晚上进行健身后,更容易导致晚上不学习</li><li>在学习一段时间后,更可能纵容自己打很久的游戏,导致游戏时间远远大于学习时间</li><li>当为复习考试花了很多时间后,自我感觉相当良好,更可能花整晚时间和朋友打游戏“放松”</li><li>假想了一个很完美的计划后,很容易感觉良好,本想让自己短暂“放松”,但最终导致计划一点也没有执行</li><li>今天犯错了,信誓旦旦地保证明天好好补救,实际上明天还是这样</li></ul><p>道德许可意味着在前进一小步后,很可能也会退一小步,假如能克服这一点,那么就能前进一大步。</p><p>问题:如何克服道德许可?</p><ul><li>牢记目标,而不是行动。即关注为什么,而不是做了什么。</li><li>将自己做过的好事用于证明自己对目标有多坚定,而不盘点自己取得了多少进步。</li><li>告诉自己明天和今天毫无区别。假想一下以后每天都像今天这样放纵,会有什么可怕后果。</li></ul><h1 id="第五章-渴望不是幸福"><a href="#第五章-渴望不是幸福" class="headerlink" title="第五章 渴望不是幸福"></a>第五章 渴望不是幸福</h1>]]></content>
<summary type="html"><p>最近阅读完了《自控力》这本书,书里讲的一些内容挺有道理和依据的,能解释生活中的一些行为。读完本书可以明白自己为何失控、如何失控,以及如何更好地避免失控,更好地掌控自己的生活。本博文以总结技巧为主,知识点为辅,说明该如何提高自己的自控力。</p>
<p>当我们觉得自己的自控力</summary>
<category term="读书笔记" scheme="https://hyzgh.github.io/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
</entry>
<entry>
<title>Docker 代理</title>
<link href="https://hyzgh.github.io/2021/05/15/docker-proxy/"/>
<id>https://hyzgh.github.io/2021/05/15/docker-proxy/</id>
<published>2021-05-15T14:30:14.000Z</published>
<updated>2021-05-15T14:57:15.693Z</updated>
<content type="html"><![CDATA[<p>最近在玩docker,由于众所周知的原因,在国内访问海外的网站速度会很慢,在docker内的表现是git clone和apt install缓慢。这个问题的常见解决方法是换源、挂代理。这里我使用了挂代理的方式解决,主要是考虑比较通用,不需要给每个工具配置一遍源。</p><p>在container里面挂代理,需要确定container是如何和host的网络通信的。实际上,在host有一个docker0的虚拟网卡,用于container和host通信,可以这么查看:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># host</span></span><br><span class="line">$ ip addr show docker0</span><br><span class="line">4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default</span><br><span class="line"> <span class="built_in">link</span>/ether 02:42:64:51:fb:47 brd ff:ff:ff:ff:ff:ff</span><br><span class="line"> inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0</span><br><span class="line"> valid_lft forever preferred_lft forever</span><br><span class="line"> inet6 fe80::42:64ff:fe51:fb47/64 scope <span class="built_in">link</span></span><br><span class="line"> valid_lft forever preferred_lft forever</span><br></pre></td></tr></table></figure><p>在container内,我们同样观察一下网络的路由配置:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># container</span></span><br><span class="line">$ ip route show</span><br><span class="line">default via 172.17.0.1 dev eth0 </span><br><span class="line">172.17.0.0/16 dev eth0 src 172.17.0.4 </span><br></pre></td></tr></table></figure><p>可以发现container的ip包默认会转发到172.17.0.1,这正是host的docker0网卡配置的ip。至此,我们确定了container和host通信的ip地址。</p><p>为了让host允许container访问所有port,需要修改host的ip路由表:</p><figure class="highlight bash"><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"><span class="comment"># host</span></span><br><span class="line">iptables -A INPUT -i docker0 -j ACCEPT</span><br></pre></td></tr></table></figure><p>假设在host已经配置了一个可用的代理地址127.0.0.1:7890,那么container访问这个代理就需要使用172.17.0.1:7890。</p><p>在container可以如此配置代理:</p><figure class="highlight bash"><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"><span class="built_in">export</span> http_proxy=http://172.17.0.1:7890</span><br><span class="line"><span class="built_in">export</span> https_proxy=http://172.17.0.1:7890</span><br></pre></td></tr></table></figure><p>这样设置后,运行<code>curl www.google.com</code>成功,就说明代理生效了。通过设置这两个变量后,apt install和git clone也成功挂上了代理。</p><p>参考:</p><ul><li><a href="https://docs.docker.com/network/proxy/#configure-the-docker-client">Configure Docker to use a proxy server</a></li><li><a href="https://stackoverflow.com/questions/31324981/how-to-access-host-port-from-docker-container"><a href="https://stackoverflow.com/questions/31324981/how-to-access-host-port-from-docker-container">How to access host port from docker container</a></a></li></ul>]]></content>
<summary type="html"><p>最近在玩docker,由于众所周知的原因,在国内访问海外的网站速度会很慢,在docker内的表现是git clone和apt install缓慢。这个问题的常见解决方法是换源、挂代理。这里我使用了挂代理的方式解决,主要是考虑比较通用,不需要给每个工具配置一遍源。</p>
<</summary>
<category term="docker" scheme="https://hyzgh.github.io/tags/docker/"/>
</entry>
<entry>
<title>MySQL学习笔记1 事务并发</title>
<link href="https://hyzgh.github.io/2021/05/03/MySQL-Learning-note-1-lock/"/>
<id>https://hyzgh.github.io/2021/05/03/MySQL-Learning-note-1-lock/</id>
<published>2021-05-03T09:35:37.000Z</published>
<updated>2021-06-26T07:56:22.650Z</updated>
<content type="html"><![CDATA[<p>最近一段时间学习了MySQL的锁,各种各样的锁,全局锁、表锁、MDL、行锁、间隙锁等等。</p><p>每种锁都有其存在的意义,都是为了解决某一种问题。锁是解决事务并发问题的基本手段之一,除了锁,还有多版本控制等无锁手段。</p><p>假如不使用一些手段,那么并发事务会存在脏读、不可重复读、幻读等问题。为了学习MySQL的并发事务隔离手段,我们首先需要了解事务存在的这几类并发问题。</p><h1 id="事务并发问题"><a href="#事务并发问题" class="headerlink" title="事务并发问题"></a>事务并发问题</h1><h2 id="脏读"><a href="#脏读" class="headerlink" title="脏读"></a>脏读</h2><p>什么是脏读?脏读就是读到了其他未提交事务的数据。为了让这一过程更易理解,我们使用MySQL来复现。MySQL 5.7的事务默认隔离级别是可重复读,该级别下不存在问题。所以需要将MySQL的隔离级别设置为READ UNCOMMITTED(读未提交),该级别下会存在脏读。具体过程如下:</p><p>session A,读取记录:</p><blockquote><p>MySQL root@(none):my_db> SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED<br>MySQL root@(none):my_db> begin;</p><p>MySQL root@(none):my_db> select balance from account where name = ‘A’;<br>+———+<br>| balance |<br>+———+<br>| 1000 |<br>+———+</p></blockquote><p>session B,修改记录:</p><blockquote><p>MySQL root@(none):my_db> begin;<br>MySQL root@(none):my_db> update account set balance = balance + 100 where name = ‘A’;</p></blockquote><p>session A,读取记录:</p><blockquote><p>MySQL root@(none):my_db> select balance from account where name = ‘A’;<br>+———+<br>| balance |<br>+———+<br>| 1100 |<br>+———+</p></blockquote><p>可以发现,session A读到了尚未提交的session B的更新,属于脏读。</p><p>脏读存在什么问题呢?假如session B回滚,那么这个数据就是脏数据,这会影响逻辑的正确性。举一个具体例子,客户A正在ATM机提款,这时候客户B想给他转了一笔钱,业务逻辑是先扣除B的余额,再给A加上钱。在给A加完钱后的一时刻,银行后台读取了A的账户余额,客户B的操作由于某些原因回滚了,这时候读取到的就是脏数据,造成的后果是多给A钱,而客户B没有损失。</p><p>为了解决这个问题,MySQL引入了一致性视图的概念,在事务开启时给数据库的状态“拍”了个快照。</p><p>具体来说,给每个事务设置了trx id,单调递增,是事务的唯一标志。当发生查询时,会查找小于等于自己trx id版本的记录。假设在session A开始时,trx id是1。在session B开启时,trx id是2。那么在session B更新记录时,这条记录的版本号是2。而在session A查询时,它查不到这条版本号为2的记录,会找到未更新前的那条记录。这就解决了上面提到的脏读问题。</p><p>我们在将事务的隔离级别设置为可重复读,再实践一下:</p><p>session A,读取记录:</p><blockquote><p>MySQL root@(none):my_db> begin<br>Query OK, 0 rows affected<br>Time: 0.000s<br>MySQL root@(none):my_db> select balance from account where name = ‘A’;<br>+———+<br>| balance |<br>+———+<br>| 1000 |<br>+———+</p></blockquote><p>session B,修改记录:</p><blockquote><p>MySQL root@(none):my_db> begin;</p><p>MySQL root@(none):my_db> update account set balance = balance - 100 where name = ‘A’;</p></blockquote><p>session A,读取记录:</p><blockquote><p>MySQL root@(none):my_db> select balance from account where name = ‘A’;<br>+———+<br>| balance |<br>+———+<br>| 1000 |<br>+———+</p></blockquote><p>可以发现session A读不到session B的更改了,解决了脏读问题。</p><p>总结:为了解决脏读问题,MySQL没有用到锁,而是使用了多版本控制机制,通过为每个事务开启一个视图来保持一致性。</p><h2 id="不可重复读"><a href="#不可重复读" class="headerlink" title="不可重复读"></a>不可重复读</h2><p>什么是不可重复读?不可重复读就是一个事务在没有修改的情况下,同样的普通查询在不同时刻得到了不一样的结果。</p><p>上面的实验看起来也是种不可重复读,但是实际上它不是。不可重复读,指的是其他已提交事务产生的影响,而不是未提交事务的影响。这就是脏读和不可重复读的区别点。</p><p>让我们做个实验复现一下,首先需要将session A的事务隔离级别设置为READ COMMITTED(读已提交)</p><p>session A,设置隔离级别并读取记录:</p><blockquote><p>MySQL root@(none):my_db> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED</p><p>MySQL root@(none):my_db> select balance from account where name = ‘A’;<br>+———+<br>| balance |<br>+———+<br>| 900 |<br>+———+</p></blockquote><p>session B,修改记录:</p><blockquote><p>MySQL root@(none):my_db> update account set balance = balance - 100 where name = ‘A’;</p></blockquote><p>session A,读取记录:</p><blockquote><p>MySQL root@(none):my_db> select balance from account where name = ‘A’;<br>+———+<br>| balance |<br>+———+<br>| 800 |<br>+———+</p></blockquote><p>可以发现两次session A读取的结果并不一致。注意这里session B和脏读的实验不同,没有使用<code>begin</code>开启事务。假如没有使用<code>begin</code>开启事务,那么默认情况下事务会马上提交。</p><p>让我们将隔离级别设置回可重复读,再实验一下:</p><p>session A,读取记录:</p><blockquote><p>MySQL root@(none):my_db> select balance from account where name = ‘A’;<br>+———+<br>| balance |<br>+———+<br>| 600 |<br>+———+</p><p>MySQL root@(none):my_db> begin<br>MySQL root@(none):my_db> select balance from account where name = ‘A’;<br>+———+<br>| balance |<br>+———+<br>| 600 |<br>+———+</p></blockquote><p>session B,修改记录:</p><blockquote><p>MySQL root@(none):my_db> update account set balance = balance - 100 where name = ‘A’;</p></blockquote><p>session A,读取记录:</p><blockquote><p>MySQL root@(none):my_db> select balance from account where name = ‘A’;<br>+———+<br>| balance |<br>+———+<br>| 600 |<br>+———+</p></blockquote><p>可以发现在可重复读的隔离级别下,可以重复读。原理同样是MVCC。</p><p>问题:更新的时候会读到最新值,算不可重复读吗?</p><p>不算,根据不可重复读的定义,只有在不修改只有读取的情况下才算不可重复读。实际上,在可重复读的级别下,更新的时候,读取到其他事务的值是符合预期的。</p><p>问题:这样会存在问题吗?</p><p>假设我们有这么一个业务场景,在扣除某个人的余额前,需要先判断它是否足额(保证非负),然后再执行更新操作。</p><p>经过实验发现存在这样的情况:</p><p>session A:</p><blockquote><p>MySQL root@(none):my_db> begin;<br>MySQL root@(none):my_db> select balance from account where name = ‘A’;<br>+———+<br>| balance |<br>+———+<br>| 100 |<br>+———+</p></blockquote><p>session B:</p><blockquote><p>MySQL root@(none):my_db> update account set balance = 0 where name = ‘A’;</p></blockquote><p>session A:</p><blockquote><p>MySQL root@(none):my_db> select balance from account where name = ‘A’;<br>+———+<br>| balance |<br>+———+<br>| 100 |<br>+———+<br>MySQL root@(none):my_db> update account set balance = balance - 100 where name = ‘A’;<br>MySQL root@(none):my_db> select balance from account where name = ‘A’;<br>+———+<br>| balance |<br>+———+<br>| -100 |<br>+———+</p></blockquote><p>可以发现出现了负值了,这是我们不允许出现的。</p><p>问题:怎么解决这个问题呢?</p><p>分析:使用记录锁。假如session A依赖于第一次查询的结果,那么就应该对它加锁,防止在自己完成事务前被修改。在MySQL中,可以使用<code>for udpate</code>对查询加记录所。在session A对记录加锁后,其他事务不再允许修改相应的记录,直到session A的事务结束(commit或rollback)。在执行语句加锁,在事务结束释放锁,被称为两阶段锁协议。</p><p>问题:为什么要在结束的时候释放,提前释放会有问题么?</p><p>假如提前释放其实可以提高并发度,但是MySQL似乎不支持提前释放。有一种特殊的自增锁(auto-inc),会自动提前释放。它是用于给自增字段生成id,加锁是为了保证递增。</p><p>问题:两阶段锁协议解决了什么问题?</p><p>问题:如何验证加了什么锁?</p><p>可以使用Innodb Lock Monitor,具体可以查看下面的教程。</p><blockquote><p>记录锁的事务数据在SHOW ENGINE INNODB STATUS和InnoDB监视器输出中看起来类似于以下内容:</p><p>RECORD LOCKS space id 58 page no 3 n bits 72 index PRIMARY of table test.t trx id 10078 <strong>lock_mode X locks rec but not gap</strong></p></blockquote><p>我们分析下Monitor的输出信息,先执行一个简单的select … for update操作:</p><blockquote><p>MySQL root@(none):my_db> begin<br>MySQL root@(none):my_db> select balance from account where name = ‘A’ for update;<br>+———+<br>| balance |<br>+———+<br>| 0 |<br>+———+</p></blockquote><p>查看输出信息:</p><blockquote><p>TRANSACTIONS<br>-———–<br>Trx id counter 2821<br>Purge done for trx’s n:o < 0 undo n:o < 0 state: running but idle<br>History list length 0<br>LIST OF TRANSACTIONS FOR EACH SESSION:<br>—TRANSACTION 421563048392544, not started<br>0 lock struct(s), heap size 1136, 0 row lock(s)<br>—TRANSACTION 2820, ACTIVE 6 sec<br>4 lock struct(s), heap size 1136, 3 row lock(s)<br>MySQL thread id 25, OS thread handle 140087744558848, query id 74 localhost root<br><strong>TABLE LOCK</strong> table <code>my_db</code>.<code>account</code> trx id 2820 <strong>lock mode IX</strong><br><strong>RECORD LOCKS</strong> space id 24 page no 4 n bits 72 index uniq_index_name of table <code>my_db</code>.<code>account</code> trx id 2820 <strong>lock_mode X</strong><br><strong>Record lock</strong>, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0<br> 0: len 1; hex 41; asc A;;<br> 1: len 8; hex 8000000000000001; asc ;;</p><p><strong>RECORD LOCKS</strong> space id 24 page no 3 n bits 72 <strong>index PRIMARY of table</strong> <code>my_db</code>.<code>account</code> trx id 2820 <strong>lock_mode X locks rec but not gap</strong><br>Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0<br> 0: len 8; hex 8000000000000001; asc ;;<br> 1: len 6; hex 000000000921; asc !;;<br> 2: len 7; hex 350000014f0110; asc 5 O ;;<br> 3: len 1; hex 41; asc A;;<br> 4: len 4; hex 80000000; asc ;;</p><p><strong>RECORD LOCKS</strong> space id 24 page no 4 n bits 72 <strong>index uniq_index_name</strong> of table <code>my_db</code>.<code>account</code> trx id 2820 <strong>lock_mode X locks gap before rec</strong><br>Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0<br> 0: len 1; hex 42; asc B;;<br> 1: len 8; hex 8000000000000002; asc ;;</p></blockquote><p>我将其中关键信息加粗了一下,可以发现该事务加了4个锁。包括1个TABLE LOCK和3个RECORD LOCKS:</p><ul><li>table lock<ul><li>lock mode IX 意向锁,在添加行锁之前添加,不会和行级的X, S锁发生冲突,只会和表级的X, S锁发生冲突。</li></ul></li><li>record locks<ul><li>lock_mode X 对name=A的这行上写锁</li><li>index PRIMARY of table, lock_mode X locks rec but not gap 是对主键索引加锁,对name=A这行加锁</li><li>index uniq_index_name, lock_mode X locks gap before rec 是对二级索引uniq_index_name加锁,在记录前面加了gap锁</li></ul></li></ul><p>问题:lock_mode X和index PRIMARY of table, lock_mode X locks rec but not gap有什么区别?</p><p>问题:行锁有哪些?</p><p>记录所、gap锁。。。</p><h2 id="幻读"><a href="#幻读" class="headerlink" title="幻读"></a>幻读</h2><p>幻读是什么?幻读是一个事务在没有修改的情况,在不同时刻的进行两次修改读,读取到了不同的结果。幻读是write query,而不可重复读是read query。</p><p>Todo</p><h1 id="锁"><a href="#锁" class="headerlink" title="锁"></a>锁</h1><p>Todo</p><h2 id="Innodb-Lock-Monitor"><a href="#Innodb-Lock-Monitor" class="headerlink" title="Innodb Lock Monitor"></a>Innodb Lock Monitor</h2><p>为了观察SQL是被哪种锁锁住了,可以使用Innodb Lock Monitor。</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></pre></td><td class="code"><pre><span class="line">SET GLOBAL innodb_status_output=ON;</span><br><span class="line">SET GLOBAL innodb_status_output_locks=ON;</span><br></pre></td></tr></table></figure><p>可以认为innodb_status_output是monitor的总开关,innodb_status_output_locks是lock monitor的开关,两者需要都打开才算启用成功。这两条命令的生命周期为server本次运行的周期,当server关闭后,下次启动会恢复成关闭状态。</p><p>获取Monitor输出:</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">SHOW ENGINE INNODB STATUS\G</span><br></pre></td></tr></table></figure><p>在终端上输出时,加上<code>\G</code>可以让输出的可读性更佳。输出的字段有许多,我们主要关注和锁相关的字段。主要是<code>LATEST DETECTED DEADLOCK</code>和<code>TRANSACTIONS</code>字段。关于这两个字段的官方解释:</p><blockquote><p><code>LATEST DETECTED DEADLOCK</code></p><p>This section provides information about the most recent deadlock. It is not present if no deadlock has occurred. The contents show which transactions are involved, the statement each was attempting to execute, the locks they have and need, and which transaction <code>InnoDB</code> decided to roll back to break the deadlock. The lock modes reported in this section are explained in <a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html">Section 14.7.1, “InnoDB Locking”</a>.</p><p><code>TRANSACTIONS</code></p><p>If this section reports lock waits, your applications might have lock contention. The output can also help to trace the reasons for transaction deadlocks.</p></blockquote><p>注意点:</p><blockquote><p>When <code>InnoDB</code> monitors are enabled for periodic output, <code>InnoDB</code> writes the output to <a href="https://dev.mysql.com/doc/refman/5.7/en/mysqld.html"><strong>mysqld</strong></a> server standard error output (<code>stderr</code>) every 15 seconds, approximately.</p></blockquote><p>monitor的输出是周期性,大概15s会输出一次。</p><h1 id="分布式锁"><a href="#分布式锁" class="headerlink" title="分布式锁"></a>分布式锁</h1><p>可以利用mysql的唯一索引实现分布式锁。原理存在唯一索引的情况,插入重复的key会进行加锁。下面介绍会加什么锁,以及可能的问题。</p><p>首先创建一个的带有唯一索引的表distributed_lock:</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">CREATE TABLE `distributed_lock` (</span><br><span class="line"> `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',</span><br><span class="line"> `c` int,</span><br><span class="line"> PRIMARY KEY (`id`),</span><br><span class="line"> UNIQUE KEY (`c`)</span><br><span class="line">) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general</span><br></pre></td></tr></table></figure><p>使用两个事务,分别执行insert语句</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">begin;</span><br><span class="line">insert into distributed_lock (c) values(1);</span><br></pre></td></tr></table></figure><p>时序图如下:</p><img src="../../../Documents/notebook/img/image-20210626153812741.png" alt="image-20210626153812741" style="zoom:50%;" /><p>注意到session A成功获取到了锁,那么这里到底获取的哪些锁呢?</p><p>我们可以使用上面介绍的Lock Monitor一探究竟。</p><p>下面是session B阻塞那一时刻的完整日志:</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">TRANSACTIONS</span><br><span class="line">------------</span><br><span class="line">Trx id counter 7973</span><br><span class="line">Purge done for trx's n:o < 7972 undo n:o < 0 state: running but idle</span><br><span class="line">History list length 2</span><br><span class="line">LIST OF TRANSACTIONS FOR EACH SESSION:</span><br><span class="line">---TRANSACTION 421898576025440, not started</span><br><span class="line">0 lock struct(s), heap size 1136, 0 row lock(s)</span><br><span class="line">---TRANSACTION 7972, ACTIVE 1 sec inserting</span><br><span class="line">mysql tables in use 1, locked 1</span><br><span class="line">LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1</span><br><span class="line">MySQL thread id 13, OS thread handle 140423219771136, query id 126 localhost root update</span><br><span class="line">insert into distributed_lock (c) values(5)</span><br><span class="line">------- TRX HAS BEEN WAITING 1 SEC FOR THIS LOCK TO BE GRANTED:</span><br><span class="line">RECORD LOCKS space id 38 page no 4 n bits 72 index c of table `my_db`.`distributed_lock` trx id 7972 lock</span><br><span class="line"> mode S waiting</span><br><span class="line">Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0</span><br><span class="line"> 0: len 4; hex 80000005; asc ;;</span><br><span class="line"> 1: len 8; hex 800000000000000a; asc ;;</span><br><span class="line"></span><br><span class="line">------------------</span><br><span class="line">TABLE LOCK table `my_db`.`distributed_lock` trx id 7972 lock mode IX</span><br><span class="line">RECORD LOCKS space id 38 page no 4 n bits 72 index c of table `my_db`.`distributed_lock` trx id 7972 lock</span><br><span class="line"> mode S waiting</span><br><span class="line">Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0</span><br><span class="line"> 0: len 4; hex 80000005; asc ;;</span><br><span class="line"> 1: len 8; hex 800000000000000a; asc ;;</span><br><span class="line"></span><br><span class="line">---TRANSACTION 7967, ACTIVE 6 sec</span><br><span class="line">2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1</span><br><span class="line">MySQL thread id 17, OS thread handle 140423553578752, query id 125 localhost root</span><br><span class="line">TABLE LOCK table `my_db`.`distributed_lock` trx id 7967 lock mode IX</span><br><span class="line">RECORD LOCKS space id 38 page no 4 n bits 72 index c of table `my_db`.`distributed_lock` trx id 7967 lock</span><br><span class="line">_mode X locks rec but not gap</span><br><span class="line">Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0</span><br><span class="line"> 0: len 4; hex 80000005; asc ;;</span><br><span class="line"> 1: len 8; hex 800000000000000a; asc ;;</span><br></pre></td></tr></table></figure><p>其中,transaction 7972被阻塞了,它应该就是session B。日志显示它被Record Lock阻塞了,该Record Lock应该就是c = 5的那一行。另外可以注意到表已经被加上了IX锁,这个锁表示插入意向锁。</p><p>transaction 7967表示session A。它持有两个锁,一个是表的IX锁,另一个是Record Lock。日志显示not gap,表示这个Record Lock不是间隙锁,因此是c = 5那一行的锁。</p><p>问题:表的IX锁有什么用?</p><p>问题:什么时候会加间隙锁?</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ol><li><a href="https://dev.mysql.com/doc/refman/5.6/en/set-transaction.html">SET TRANSACTION Statement</a></li><li><a href="https://bytedance.feishu.cn/wiki/wikcnbPsHVuvGZF0YaakT6YC2lf">InnoDB的事务和锁</a></li><li>《MySQL实战45讲》——极客时间</li><li><a href="https://www.percona.com/blog/2012/03/27/innodbs-gap-locks/">InnoDB Gap Locks</a></li><li><a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-enabling-monitors.html">Enabling InnoDB Monitors</a></li><li><a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-standard-monitor.html">InnoDB Standard Monitor and Lock Monitor Output</a></li><li><a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html">innodb-locking</a></li><li></li></ol>]]></content>
<summary type="html"><p>最近一段时间学习了MySQL的锁,各种各样的锁,全局锁、表锁、MDL、行锁、间隙锁等等。</p>
<p>每种锁都有其存在的意义,都是为了解决某一种问题。锁是解决事务并发问题的基本手段之一,除了锁,还有多版本控制等无锁手段。</p>
<p>假如不使用一些手段,那么并发事务会存</summary>
<category term="MySQL" scheme="https://hyzgh.github.io/tags/MySQL/"/>
</entry>
<entry>
<title>Golang源码学习1 源码编译</title>
<link href="https://hyzgh.github.io/2020/11/25/golang-source-code-learning-1-compile-source-code/"/>
<id>https://hyzgh.github.io/2020/11/25/golang-source-code-learning-1-compile-source-code/</id>
<published>2020-11-25T00:40:12.000Z</published>
<updated>2020-11-28T07:42:43.102Z</updated>
<content type="html"><![CDATA[<p>前些天在工作中用Go写出了一个内存泄露的bug,原因是没有认真阅读文档,使用姿势不对。根据官方文档纠正使用姿势后,解决掉了这个bug。但是,对于为什么会内存泄露我还不是很明白,于是打算对源码研究一番,就有了这篇博文。</p><p>我的电脑操作系统是Linux,之前安装Go的方式是根据<a href="https://golang.org/dl/">官网教程</a>下载的二进制,在<code>$GOROOT</code>有编译好的二进制文件,也有源代码。通常在写项目代码时,会跳转标准库的函数说明,就是跳转到这里的源代码。这里的源代码用来参考还好,但是不适合折腾。我选择了从github拷贝最新的源代码来阅读。一是不会影响正常项目的编译,二是能拉取到最新的源代码,三是可以看到整个项目各个大佬的commit。</p><p>下载源代码,直接<code>git clone</code>即可。</p><p>下载了源代码后,开始尝试阅读源码。发现一个问题,IDE会对一些Go标准库的函数标红,提示没有这个函数的引用。经过查看commit,发现这个函数是新增加的,而自己IDE使用的sdk还是老旧的、之前下载的二进制文件。为了让GoLand认识这些新函数,需要使用这份新的源码编译。</p><p>对源码的编译。我参考了<a href="https://golang.org/doc/install/source">Installing Go from source</a>,这里不再赘述。</p><p>学习Golang的源码,就是跟顶尖程序员学习编程,是一件蛮有意义的事情。后面会写一些Golang源码学习相关的博文。</p><p>(全文完)</p>]]></content>
<summary type="html"><p>前些天在工作中用Go写出了一个内存泄露的bug,原因是没有认真阅读文档,使用姿势不对。根据官方文档纠正使用姿势后,解决掉了这个bug。但是,对于为什么会内存泄露我还不是很明白,于是打算对源码研究一番,就有了这篇博文。</p>
<p>我的电脑操作系统是Linux,之前安装Go</summary>
<category term="Go" scheme="https://hyzgh.github.io/tags/Go/"/>
</entry>
<entry>
<title>生活记录-拔智齿</title>
<link href="https://hyzgh.github.io/2020/09/13/life-record-wisdom-tooth/"/>
<id>https://hyzgh.github.io/2020/09/13/life-record-wisdom-tooth/</id>
<published>2020-09-13T09:20:30.000Z</published>
<updated>2020-09-13T14:56:32.644Z</updated>
<content type="html"><![CDATA[<p>前段时间去体检的时候,医生建议我把左边的下智齿给拔了,原因是长得不好,牙齿间有较大空隙,会藏食物,不好清理。其实之前去洁牙的时候有拍片,也建议我把它拔了,但是由于某些原因没有拔。一是因为除了清理比较麻烦外,暂时没有造成实际的影响(比如疼痛)。二是因为在深圳广州拔牙很贵,那时不确定学校的医保给不给报销,就拖到了现在。</p><p>前些天深圳的社保卡刚办理好,于是打算去把这颗智齿拔了,防止它藏食物所造成的潜在口腔疾病风险。</p><p>在出发前,查了一些资料,以及从同事那里获取了一些信息。得知拔阻生齿是可以走医保统筹报销以及商业保险的,但是实际上并不是所有拔牙都可以这样。事后推测同事应该是因为是手术才可以走医保统筹报销。正常的话,拔牙只能走医保个人账户以及商业保险。</p><p>在深圳,医保根据缴纳比例的不同,分成一二三档。对于不同档的参保者,有不同的医保待遇。通过查询公司的文档,我得知公司给我缴纳的是一档医保。</p><p>通过查询资料,学习了一下针对深圳一档参保者的知识点,摘抄如下:</p><ul><li>需要到定点医疗机构就医才能享受医疗保险待遇。</li><li>走统筹报销的,由市社会保险机构与定点医疗机构或定点零售药店按协议约定结算。</li><li>走个人账户支付的,由本市定点医疗机构或定点零售药店从参保人的个人账户中划扣,个人账户不足支付的,应当由参保人现金支付。</li><li>在非本市定点医疗机构发生的医疗费用,由参保人先行支付后向市社会保险机构申请报销,市社会保险机构按本办法的规定予以审核,符合条件的予以支付。参保人先行支付医疗费用的,应在费用发生或出院之日起十二个月内申请报销,逾期不予报销。</li><li>参保单位、参保人中断缴交医疗保险费的,自中断缴交的次月1日起,停止享受医疗保险统筹基金支付的医疗保险待遇,但其个人账户余额可继续使用。<strong>注意不要断保。</strong></li><li>基本医疗保险药品、诊疗项目、服务设施标准的目录按照国家及广东省公布的目录执行。可参考:<a href="http://www.nhsa.gov.cn/art/2019/8/20/art_37_1666.html">《国家基本医疗保险、工伤保险和生育保险药品目录》</a>。<strong>一般也不需要特别关注这个目录,假如能走统筹报销的话,支持的时候自然也会走报销。假如想要省钱的话,可以在就医时,告诉医生尽量用目录里的药物。另外在去药店买药时,也可以查一查这个目录,在这个目录再去买,否则网上买可能实惠些。</strong></li><li>基本医疗保险一档参保人个人账户用于支付参保人门诊基本医疗费用、地方补充医疗费用、在定点零售药店凭本市市内定点医疗机构医生开具的处方购买医疗保险目录范围内药品的费用,个人账户不足支付部分由个人自付。基本医疗保险一档参保人连续参保满一年,在同一医疗保险年度内个人自付的门诊基本医疗费用和地方补充医疗费用超过本市上年度在岗职工平均工资5%的,超过部分由基本医疗保险大病统筹基金或地方补充医疗保险基金按规定支付70%,参保人年满70周岁以上的支付80%。</li><li>基本医疗保险一档参保人个人账户积累额超过本市上年度在岗职工平均工资5%的,超过部分可以用于买药、给家人用等。</li><li>基本医疗保险一档参保人在本市定点社康中心发生的基本医疗费用和地方补充医疗费用,70%由其个人账户支付,30%由基本医疗保险大病统筹基金、地方补充医疗保险基金按规定支付。但不包括<strong>口腔科治疗费用</strong>、康复理疗费用、大型医疗设备检查治疗费用等。</li><li>基本医疗保险一档参保人在本市定点医疗机构门诊做大型医疗设备检查和治疗所发生的基本医疗费用、地方补充医疗费用,80%由基本医疗保险大病统筹基金、地方补充医疗保险基金按规定支付。</li><li>统筹基金、地方补充医疗保险基金存在支付限额,连续参保时间越久,限额越高。连续三个月断保需要重新计算,<strong>因此注意不要断保。</strong></li></ul><p>我这次选择了去北京大学深圳医院的口腔科拔牙,在深圳,这家医院的实力是最顶尖的之一。口腔科下也细分了许多科室,需要注意不要挂错科室了。拔牙需要挂牙槽外科。因为新冠病毒的原因,需要在医护人员的引导下填写一张表单。中途等号的时候有点担心需要做完新冠病毒检测才能拔牙,时间可能不够。实际上并不需要做检测就可以拔牙了。</p><p>就诊医生有点迟到,但是我来得比较早,很快就排到我了。医生肉眼检查了一下我的智齿,发现看得不是很清楚,并且我也无法提供拍片的照片,因此建议我先去拍片。于是我听从医生的吩咐,排了较长的队拍了个片。回来后医生很快就根据拍片单做出判断,跟我说明下面的左右各一颗智齿都需要拔掉,因为继续长的话会顶到其他牙齿,建议这次只拔一颗。我选择了拔左下角的那颗智齿。医生很专业,给我的左下角口腔打上麻醉,然后上电钻、钳子等各种工具,行云流水地一顿操作,感觉不到十分钟就拔完了。结束时,医生还让我看了眼自己的牙齿,挺大的完整两瓣,含着血丝。不知道能不能带走纪念,本来想问问的,但是口腔上了麻醉说话有点困难,就没问了。最后,医生问我有没有布洛芬、头孢这些药,那时候我不知道这是啥药,当然回答没有。他给我开了一些药,然后我记了下注意事项后就去付款了。感觉自己对于常见的药物并不熟悉,家里也没有备药的习惯,以后有空可以了解一下常用的药。另外做得不好的一点是,忘了询问医生药要吃多久了,以后就医的时候要问清楚用药持续时间。</p><p>接下来到了手术付款时间,花了大概1300元,不能走统筹,全部走了个人医保账户。第一次用医保,当时也不是很清楚个人账户和统筹账户的区别,后来查资料发现,统筹才是医保报销,个人医保是个人支付的。</p><p>虽然是走的个人支付,但是好在公司有商业保险,能报销90%,最后相当于自己花了100多拔了一颗智齿。感觉商业保险还是很有用的,有空可以给家人看看有什么保险可以购买,提高家庭的抗风险能力。</p><p>接下来还有另外一颗智齿要拔,由于医保账户的余额不是很够,假如现在拔的话需要走现金支付,考虑到医保一般用不完,所以有点亏。于是打算医保余额足够了,再去拔另外一颗智齿吧。</p><p>北京大学深圳医院虽然离自己住的地方有点远,并且挂号困难,但是还是值得的,因为它拥有很专业的医生。专业度这点是非常珍贵的,假如经验不足,那么可能需要花费更多的时间、做出不合适的判断、甚至在手术过程造成失误等。这一点我深有感触,由于拔完牙后还需要再去拆线,这次给我拆线的是一个实习生,给我的感觉是拆得有点艰难,耗费的时间比拔牙还要久= =、</p><p>这次拔牙,还学到了一个生活经验,即有疑问可以找有关部门咨询,这样获取信息的效率很高。比如我对于拔牙不能走统筹保险这一点比较疑惑,通过网上一顿搜索都没有找到合理的解释,最后在网上发现可以打社保局的电话(12333)咨询,于是就打电话问问。最终得到了满意的答复,解答了心中的疑惑,发现去医院做门诊一般是需要个人账户支付的,除非门诊一年累计支付超过了一定额度,才能走70%的统筹报销。其实生活中是有许多部门很有用的,包括社保局、监管部门、工信部、公安局等,有空可以多了解一下他们的职责,以便在需要的时候用上。</p><p>参考:</p><ul><li><a href="http://www.gd.gov.cn/zwgk/wjk/zcfgk/content/post_2530804.html">深圳市社会医疗保险办法</a></li></ul>]]></content>
<summary type="html"><p>前段时间去体检的时候,医生建议我把左边的下智齿给拔了,原因是长得不好,牙齿间有较大空隙,会藏食物,不好清理。其实之前去洁牙的时候有拍片,也建议我把它拔了,但是由于某些原因没有拔。一是因为除了清理比较麻烦外,暂时没有造成实际的影响(比如疼痛)。二是因为在深圳广州拔牙很贵,那时</summary>
<category term="life" scheme="https://hyzgh.github.io/tags/life/"/>
</entry>
<entry>
<title>CSAPP 学习笔记 7 虚拟内存</title>
<link href="https://hyzgh.github.io/2020/08/29/CSAPP-learning-note-7/"/>
<id>https://hyzgh.github.io/2020/08/29/CSAPP-learning-note-7/</id>
<published>2020-08-29T09:13:37.000Z</published>
<updated>2020-12-05T03:45:01.077Z</updated>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>好久没看CSAPP这本书了。最近在学习redis的AOF重写过程,发现对于涉及的一些概念有些模糊,比如fork、页表、虚拟内存、物理内存等。而且,最近在工作中也经常遇到和内存相关的问题,在利用top命令来排查内存问题的过程中,发现手册里提到的一些内存的概念,自己都不甚清晰。</p><p>于是很有必要重新捡起这本书看看,梳理一下书中第9章虚拟内存的相关知识,巩固一下基础。</p><h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><p>操作系统的内存是由许多进程共享的,假如进程之间内存没有独立开来,那么很有可能导致进程崩溃。</p><p>虚拟内存是现代系统提供的一种对主存的抽象概念,用于将进程间的内存空间相互独立开来。</p><blockquote><p> 现代操作系统都使用的是按需页面调度的方式。</p></blockquote><p>从top的manual中可以查看Linux的多种内存类型的定义。</p><blockquote><p>physical memory: a limited resource where code and data must reside when executed or referenced.</p><p>optional swap file: where modified (dirty) memory can be saved and later retrieved if too many demands are made on physical memory.</p><p>virtual memory: a nearly unlimited resource.</p></blockquote><p>其中,虚拟内存是近似无限大的,它的意义在于:</p><blockquote><ol><li>abstraction, free from physical memory addresses/limits</li><li>isolation, every process in a separate address space</li><li>sharing, a single mapping can serve multiple needs</li><li>flexibility, assign a virtual address to a file</li></ol></blockquote><p>对于第一点和第二点,就是CSAPP中提到的进程内存之间抽象和隔离。</p><p>第三点和第四点是什么意思呢?</p><p>另外,手册中也提到了不管是哪种内存类型,都是以页面的方式进行管理,通常是4KB。当然,在某些场景可以将页面设置得大一些,称为big pgae.</p><p>对于每一个page,在Linux中,都可以划分到某一类中。具体类别如下:</p><p><img src="/../../../../Documents/notebook/img/image-20201129171849191.png" alt="image-20201129171849191"></p><p>对于物理内存和虚拟内存,可以划分到这四类中任意一个。而对于swap file的内存,只能划分到#1和#3.</p><p>Anonymous和File-backed有什么区别:</p><blockquote><p><strong>File</strong>-<strong>backed</strong> mapping maps an area of the process’s virtual memory to <strong>files</strong>; i.e. reading those areas of memory causes the <strong>file</strong> to be read. It is the default mapping type. <strong>Anonymous</strong> mapping maps an area of the process’s virtual memory not <strong>backed</strong> by any <strong>file</strong>. The contents are initialized to zero.</p></blockquote><p>top可以显示的关于内存的各种指标,参考手册如下:</p><pre><code> %MEM - simply RES divided by total physical memory CODE - the `pgms' portion of quadrant 3 DATA - the entire quadrant 1 portion of VIRT plus all explicit mmap file-backed pages of quadrant 3 RES - anything occupying physical memory which, beginning with Linux-4.5, is the sum of the following three fields: RSan - quadrant 1 pages, which include any former quadrant 3 pages if modified RSfd - quadrant 3 and quadrant 4 pages RSsh - quadrant 2 pages RSlk - subset of RES which cannot be swapped out (any quadrant) SHR - subset of RES (excludes 1, includes all 2 & 4, some 3) SWAP - potentially any quadrant except 4 USED - simply the sum of RES and SWAP VIRT - everything in-use and/or reserved (all quadrants)</code></pre><p>使用Go写一个简单的hello, world程序:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"time"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"hello, world"</span>)</span><br><span class="line"> time.Sleep(<span class="number">1</span> * time.Hour)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行后,通过top的mem视图可以观察该进程的内存情况:</p><p><img src="/../../../../Documents/notebook/img/image-20201205114259481.png" alt="image-20201205114259481"></p><p>这里的VIRT, RES, CODE, DATA, SHR的单位是KiB。</p><p>VIRT:虚拟内存占用大概是700MiB。(感觉有点反直觉)</p><p>RES:实际的物理内存占用大概是1.8MiB。</p><p>CODE:代码占用的内存是584KiB。</p><p>DATA:运行栈和堆的数据大概是102MiB。</p><p>SHR:共享内存大概是1MiB。</p><p>我感觉实际的内存占用都有点大,能更细粒度地观察内存花在哪里的吗?分析一下。</p><p>怎么分析该进程的内存情况呢?</p><p>怎么理解这四种内存页呢?mmap这个函数的定义是什么?</p><p>top如何保存视图?</p><p>(未完待续)</p>]]></content>
<summary type="html"><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>好久没看CSAPP这本书了。最近在学习redis的AOF重写过程,发现对于涉及的一些概念有些模糊,比如fork、页表、虚拟内存、物理内存等。</summary>
<category term="计算机系统" scheme="https://hyzgh.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F/"/>
<category term="CSAPP" scheme="https://hyzgh.github.io/tags/CSAPP/"/>
</entry>
<entry>
<title>《刻意练习》读书笔记</title>
<link href="https://hyzgh.github.io/2020/06/19/PEAK-Secrets-from-the-New-Science-of-Expertise-reding-note/"/>
<id>https://hyzgh.github.io/2020/06/19/PEAK-Secrets-from-the-New-Science-of-Expertise-reding-note/</id>
<published>2020-06-19T03:47:33.000Z</published>
<updated>2020-06-19T03:48:56.991Z</updated>
<content type="html"><![CDATA[<p><a href="https://book.douban.com/subject/26895993/">《刻意练习》</a>这本书的前半部分讲述了刻意练习是什么,为什么管用,以及杰出人物如何运用它来发展杰出的能力,后半部分讲述了如何运用刻意练习。</p><h1 id="文前页"><a href="#文前页" class="headerlink" title="文前页"></a>文前页</h1><p>提高生活技能、工作能力,都离不开大量的练习。</p><p>练习不是不断的重复,正确的练习需要好导师、有目标、有反馈等。</p><p>一万小时定律并不严谨,它存在以下几个问题:</p><ul><li>不同专业领域的技能习得时间与练习时间并不存在一个一万小时的最低阈值。</li><li>成功与练习时间并不完全成正比,天赋虽然在其中不起决定性作用,但也会是一大影响因子。</li><li>练习的成果并不与时间呈正相关,它也取决于练习方法。</li></ul><p>刻意练习的核心观点是,那些处于中上水平的人们,拥有一种较强的记忆能力:长时记忆。长时记忆正是区分卓越者与一般人的一个重要能力,它才是刻意练习的指向与本质。</p><p>通过怎样刻意练习才能够获得优秀的长时记忆呢?刻意练习的任务难度要适中,能收到反馈,有足够的次数重复练习,学习者能够纠正自己的错误。</p><p>长时记忆的培养要点:</p><ul><li>赋予意义,精细编码:能非常快地明白自己领域的单词与术语,在存储信息的时候,可以有意识地采取元认知的各项加工策略。</li><li>提取结构或模式:往往需要将专业领域的知识、提取结构或者模式以更好的方式存储。</li><li>加快速度、增加连接:在编码和提取过程方面很快,增加长时记忆与工作记忆之间的各种通路。</li></ul><h1 id="引言-天才存在吗"><a href="#引言-天才存在吗" class="headerlink" title="引言 天才存在吗"></a>引言 天才存在吗</h1><p>基因确实会在很多方面影响我们,但是,要想成为人们口中的天才,通常需要通过大量的、正确的练习。</p><h1 id="第一章-有目的的练习"><a href="#第一章-有目的的练习" class="headerlink" title="第一章 有目的的练习"></a>第一章 有目的的练习</h1><p>一个工作20年的老师并不一定比工作5年的老师有经验,通过有目的的练习,后者可以超越前者。</p><p>有目的的练习,有以下几个特点:</p><ul><li>具有定义明确的特定目标</li><li>保持专注</li><li>包含反馈</li><li>走出舒适区</li></ul><p>当遇到瓶颈时,应该鼓励自己尽力克服它,并尝试各种不同的办法,不达目的不罢休。</p><p>当实在坚持不下去的时候,可以加强自己的动机,或者降低要求(降低意志力损耗)。</p><p>在练习的过程中,要学会总结出背后的模式,在更高的层次上去掌握它。</p><h1 id="第二章-大脑的适应能力"><a href="#第二章-大脑的适应能力" class="headerlink" title="第二章 大脑的适应能力"></a>第二章 大脑的适应能力</h1><p>大脑的适应能力很强,不要给自己设限。实际上,人类身体各部位适应能力都很强。据称,最多次数的俯卧撑是46001个,最多次数的引体向上是4654个。</p><p>人类的身体有一种偏爱稳定性的倾向。因此,改变是缓慢痛苦的,但身体会适应这个过程,最终重新达成平衡,变得稳定。</p><p>经常性的训练会让大脑受到训练挑战的区域发生改变,大脑通过自身重新布线的方式来适应这些挑战。</p><p>三个重要细节:</p><ul><li>年轻的大脑适应能力更强</li><li>训练发展大脑中的某些部位,可能会对其他部位产生负面影响</li><li>由训练引起的认知和生理变化需要继续保持训练</li></ul><h1 id="第三章-心理表征"><a href="#第三章-心理表征" class="headerlink" title="第三章 心理表征"></a>第三章 心理表征</h1><p>研究表明,国际象棋大师在记忆有意义的棋盘时表现得新手好得多,但是在记忆无意义的棋盘时并不比新手好多少。同理,人们记忆有意义句子的能力,要比记忆无意义句子的能力强得多。之所以有这种现象,是因为心理表征在起作用。</p><p>国际大师心理表征的强大之处,在于他可以对棋盘进行有效地编码,记住棋子的位置和它们之间的相互关系,记住在这种局面下有效的招法,另外,还能将注意力集中在单个棋子上,并且在心理上“移动”它们,以观察对棋局的影响。简单地说,心理表征使得大师即可看到一片森林,也可以观察一棵树。</p><p>心理表征是一种与我们大脑正在思考的某个物体、某个观点、某些信息或其他任何事物相对应的心理结构,或具体,或抽象。</p><p>刻意练习包括创建心理表征。由于各个行业或领域之间心理表征的细节具有极大差异,我们难以给出一个十分清晰的顶层定义,但基本上,这些表征是信息预先存在的模式(比如事实、图片、规则、关系等等),这些模式保存在长时记忆中,可以用于有效且快速地顺应某些类型的局面。</p><p>心理表征的作用:</p><ul><li>有助于找出规律,进而预测未来,进行无意识决策等。比如攀援运动员可以快速选择石头。</li><li>有助于解释信息。比如一名资深足球球迷在阅读一篇足球赛的文章时,会理解得更好并记住得更多。</li><li>有助于组织信息。比如专业医生能够考虑大量的事实,根据心理表征做出诊断。</li><li>有助于制订计划。比如外科医生在第一次拿起手术刀之前,就会想象整个手术该怎么样进行,以及如何应对手术过程中出现某些意料之外的事情和潜在的风险时,应该怎么办。</li><li>有助于高效学习。比如专业音乐家在练习某件新作品时,对好的演奏有清晰的认识,进而指导自己的练习。并且,它们可以更好地认识自己犯下的错误。</li></ul><p>刻意练习的主要目的在于创建有效的心理表征。</p><h1 id="第四章-黄金标准"><a href="#第四章-黄金标准" class="headerlink" title="第四章 黄金标准"></a>第四章 黄金标准</h1><p>可以进行刻意练习的行业或领域的特点:</p><ol><li>对于绩效的衡量,总是存在客观的方面。比如象棋比赛的输赢,唱歌比赛的专家评价等。</li><li>这些行业或领域往往具有足够的竞争性,以至于从业人员有强烈的动机来训练和提高。</li><li>这些行业或领域通常都是已经形成规模,相关的技能已得到数十年甚至数世纪的培养。</li><li>这些行业或领域中,有一些从业人员还担任导师和教练,发展出一整套训练方法。</li></ol><p>刻意练习与有目的的练习,在两个重要方面上存在差别:</p><ol><li>它需要一个已经得到合理发展的行业或领域,也就是说,最杰出的从业者已达到一定程度的表现水平,使他们与其他刚刚进入的从业者明显地区分开来。</li><li>它需要一位能够布置练习作业的导师,以帮助学生提高他的水平。</li></ol><p>刻意练习的特点:</p><ul><li>刻意练习发展的技能,是已经拥有一整套行之有效的训练方法的技能。</li><li>刻意练习发生在舒适圈之外。</li><li>刻意练习包含得到良好定义的特定目标。</li><li>刻意练习是有目的的,需要人们完全的关注和有意识的行动。</li><li>刻意练习包含反馈,以及为应对那些反馈而就行调整的努力。反馈可以是来自他人或自己。</li><li>刻意练习既产生有效的心理表征,又依靠有效的心理表征。</li></ul><p>如何运用刻意练习:</p><ul><li>寻找行业中最优秀的人,推测他们为何如此优秀,向他们学习训练方法。</li><li>最佳方法是寻找一位优秀的导师,让他帮忙制定训练计划,给予自己指导和反馈。</li></ul><h1 id="第五章-在工作中运用刻意练习原则"><a href="#第五章-在工作中运用刻意练习原则" class="headerlink" title="第五章 在工作中运用刻意练习原则"></a>第五章 在工作中运用刻意练习原则</h1><p>美国的王牌飞行员计划告诉我们,可以想办法来辩认某个领域或行业中的杰出人物,然后训练其他那些表现不太出色的人,并且尽可能使后者达到前者的水平。</p><p>辨认并拒绝两种错误思想:</p><ul><li>认为某人的能力通常受到基因特征的限制。正确的做法是摆正心态。</li><li>如果足够长时间地做某件事情,一定会更擅长。正确的做法是找到不足,挖掘进步的点,寻找正确的训练方法。</li></ul><p>知识和技能是存在区别的。传统的教学方法总是强调传授更多的知识,而不是培养技能。</p><p>包含某些互动因子的方法,比如费曼学习法,有更好的效果。</p><h1 id="第六章-在生活中运用刻意练习原则"><a href="#第六章-在生活中运用刻意练习原则" class="headerlink" title="第六章 在生活中运用刻意练习原则"></a>第六章 在生活中运用刻意练习原则</h1><ul><li>找位好导师,导师的水平比自己高,并且最好是一对一指导。好的导师,他的教学和辅导应该足够高效,可以帮助自己更快地建立心理表征。</li><li>如果练习的时候在走神,或者很放松,或者只是为了好玩,那么可能不会进步。不专注,练习是没效果的,专注和投入至关重要。好的练习,应该在练习后会让自己感到大脑被掏空。</li><li>假如没有导师,自己也可以帮助自己更好地练习。在练习过程,应该思考优秀的人会怎么做,优秀的标准是怎么样的,自己与优秀的差距在哪里,最好是能通过每次练习得到反馈,看到自己的进步。</li><li>用三个F建立有效的心理表征:focus, feedback, fix.</li><li>当觉得自己停滞不前时,可以尝试不同的方式挑战自己、攻克特定的弱点等。</li><li>保持动机也许是每个投入到有目的的训练或者刻意练习中的人最终要面对的最大问题。保持动机可以从两个角度入手。<ul><li>弱化停下脚步的理由。最有效的方式是留出固定的时间来练习,不受所有其他事情所干扰。</li><li>增强继续前行的倾向。鼓励自己,精心设置目标,看到自己的进步。</li></ul></li></ul><h1 id="第七章-成为杰出人物的路线图"><a href="#第七章-成为杰出人物的路线图" class="headerlink" title="第七章 成为杰出人物的路线图"></a>第七章 成为杰出人物的路线图</h1><p>成为杰出人物通常有四个阶段:</p><ol><li>产生兴趣</li><li>变得认真。一开始,需要父母和导师以多种方式鼓励孩子,让他充满动力。在最后,孩子开始体会刻意练习带来的回报,变得越来越能够自我激励。</li><li>全力投入。学生通常会寻找最好的导师来指导自己的练习。在这个阶段,动机完全靠学生自己来保持,但家人依然能够发挥重要的支持作用。为了得到优秀的训练资源,通常要求家庭有一定的经济条件。</li><li>开拓创新。这些人所在的贡献,彻底改变了他们所在的领域或行业。他们是引领整个时代的人进入全新世界的开拓者。创新,离不开刻意练习,它需要大量的刻意练习作为基础。</li></ol><h1 id="第八章-怎样解释天生才华"><a href="#第八章-怎样解释天生才华" class="headerlink" title="第八章 怎样解释天生才华"></a>第八章 怎样解释天生才华</h1><p>杰出人物通过年复一年的刻意练习,在漫长而艰苦的过程中一步一步改进,终于练就了他们杰出的能力,没有捷径可走。</p><p>作者通过帕格尼尼、莫扎特等人的例子,表达了天才也需要大量的刻意练习。</p><p>莫扎特的父亲是一个不得志的音乐家,希望将莫扎特培养成杰出的音乐家。他在莫扎特很小的时候,就让他练习各种乐器,投入了很大的精力。莫扎特也付出了大量时间去练习,才得以成为“神童”,成为杰出的音乐家。在现代,得益于日益进步的训练资源,网上存在不少“神童”的视频,比成年人还要强。</p><p>“自闭症奇才”的能力通常在非常特定的领域或行业中出现。他们更有可能比不具备奇才的普通自闭症孩子更加注重细节,而且更倾向于反复的行为。当某件事情引起他的注意时,他们会将注意力全部集中在那件事上面,因此,他们很可能是通过大量的刻意练习培养出来的才能。</p><p>有些人会认为自己缺乏某方面的才华,比如五音不全、不擅长数学等。研究表明,这是可以通常刻意练习改进的。</p><p>智商高的人确实在一开始可能会有更好的表现,但是想变得真正杰出,训练时间比智商更重要。研究表明,国际象棋大师的智商并不比普通人高。也就是说,真正起决定性因素的是训练,而不是智商。</p><p>人的基因确实会对我们有各种影响,这目前还不明确。而且,基因我们也无法改变。唯一能做的,只是通过后天的努力来达成自己的目标。</p><h1 id="第九章-用刻意练习创造全新的世界"><a href="#第九章-用刻意练习创造全新的世界" class="headerlink" title="第九章 用刻意练习创造全新的世界"></a>第九章 用刻意练习创造全新的世界</h1><p>作者主要阐述了刻意练习的作用和影响。</p><p>作者还提到了要给各行各业的人建立心理表征,以帮助他们更好地训练。以我的理解,这里的心理表征,是一种大局观,知道杰出的人物和自己的差距在哪里,知道自己的技能树是怎么样的,知道有哪些方式可以有效提高自己的技能。当存在有效的心理表征后,就应该不断通过自身的努力来提高、完善和改进自己。</p>]]></content>
<summary type="html"><p><a href="https://book.douban.com/subject/26895993/">《刻意练习》</a>这本书的前半部分讲述了刻意练习是什么,为什么管用,以及杰出人物如何运用它来发展杰出的能力,后半部分讲述了如何运用刻意练习。</p>
<h1 id="文</summary>
<category term="读书笔记" scheme="https://hyzgh.github.io/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
</entry>
<entry>
<title>《微习惯》读书笔记</title>
<link href="https://hyzgh.github.io/2020/05/10/Mini-Habits-reading-note/"/>
<id>https://hyzgh.github.io/2020/05/10/Mini-Habits-reading-note/</id>
<published>2020-05-10T08:23:52.000Z</published>
<updated>2020-05-10T09:57:50.832Z</updated>
<content type="html"><![CDATA[<p><a href="https://book.douban.com/subject/26877306/">豆瓣链接</a></p><p>本书主要写了微习惯是什么,以及通过微习惯来培养好习惯的依据和规则。</p><h1 id="第一章-微习惯是什么"><a href="#第一章-微习惯是什么" class="headerlink" title="第一章 微习惯是什么"></a>第一章 微习惯是什么</h1><p>小决心优于大决心。许多人拥有大决心,但是却不去执行,效果是零。有小决心的人,更容易执行某件事情,虽然单次的收益比较小,但远大于不执行。日积月累,小决心产生的收益会很大。</p><p>拥有大决心而不执行,原因是大脑觉得这次行动很难,因此会选择逃避。拥有小决心,就不太会存在这个问题。小决心是为了让大脑接受这次行动。通常来讲,在开始后,会想花更多的时间在上面。</p><p>微习惯就是将较大的行动拆分成容易执行的小行动。比如,每天写3000字可以改成每天写50字,每天锻炼1小时可以改成每天锻炼5分钟。</p><p>养成一个习惯,需要的天数会受各种因素影响。我们假如想要养成一个习惯,不必在意需要的天数,因为重要的是坚持。当连续做某件事情时,大脑的抵触情绪会越来越低,会自然地养成习惯。当有某天没有坚持习惯时,不用气馁,一天的堕落从整个过程上看并不会影响习惯的养成,我们需要做的是克服心理上的自暴自弃。</p><h1 id="第二章-大脑的工作原理"><a href="#第二章-大脑的工作原理" class="headerlink" title="第二章 大脑的工作原理"></a>第二章 大脑的工作原理</h1><p>可以将大脑粗分为潜意识部分和意识部分。从大脑的角度看,改变习惯的两个关键点是重复和回报。如果有回报,大脑会更愿意重复一件事情。重复是潜意识大脑使用的语言,潜意识大脑喜欢效率,这是我们能养成习惯的原因。</p><p>大脑是变化缓慢、状态稳定的,因此习惯的养成过程是缓慢的,习惯的保持是稳定的。</p><p>习惯改变涉及到大脑的两个部分:前额皮层和基底神经节。前额皮层是大脑的意识部分,是管理者,负责让大脑其他部分喜欢上自己想要的东西,用于习惯的执行。基底神经节是大脑的潜意识部分,没有思想,只会一味重复,能够探测都模式,用于新习惯的重复和养成。</p><h1 id="第三章-动力VS意志力"><a href="#第三章-动力VS意志力" class="headerlink" title="第三章 动力VS意志力"></a>第三章 动力VS意志力</h1><p>动力越高,做某件事情所需的意志力就越少。假如动力是满分,那么几乎不需要意志力就会去做某件事情。而假如动力为零,那么就需要很强的意志力才能去做某件事情。</p><p>有些人会使用“激发动力”策略来养成习惯。虽然动力确实有利于执行某件事情,但是它存在诸多问题。第一,动力难以永久保持高昂,容易导致习惯在养成的过程中断。第二,动力的影响因素多,不稳定。第三,不会每次都愿意激发动力。第四,根据热情递减原则,动力会减少,假如只依靠动力,那么会动力不足。</p><p>当连续执行某个行动后,我们会注意到心中不再充满动力。这可能是更稳定和自动的基底神经节正在夺取控制权,我们应该做的是利用微习惯继续执行下去,渡过这个艰难期,迎来新习惯。</p><p>动力并不是行动的唯一基础,我们还可以依靠意志力、习惯等。动力是好东西,只是不可靠。假如我们可以借助意志力,那么动力会变得更加可靠。如果先采取行动,动力会被迅速地激发。</p><p>使用意志力来采取行动远比努力激发动力靠谱。第一,意志力很可靠。第二,意志力可以被强化。第三,意志力策略可以通过计划执行。</p><p>意志力的工作原理是什么?意志力是有阈值的,并不是取之不尽用之不竭的。引起意志力损耗的五大主要因素有努力程度、感知程度、消极情绪、主观疲劳和血糖水平。抵抗诱惑,做决定等行为都会引起意志力的损耗。</p><p>我们需要克服这五大障碍,合理使用意志力,养成习惯。</p><h1 id="第四章-微习惯策略"><a href="#第四章-微习惯策略" class="headerlink" title="第四章 微习惯策略"></a>第四章 微习惯策略</h1><p>微习惯策略就是强迫自己每天实施1到4个“小得到不可思议”的计划好的行动。这些运动小得到不会失败,小到不会因为特殊情况就被自己轻易放弃。它们有双重作用——激励自己继续做下去,并会成为微习惯。</p><p>以微习惯方式运用意志力,可以有效地减少意志力的损耗。因为微习惯的努力程度要求小、感知难度小、消极情绪低、主观疲劳小、血糖水平降低少。</p><p>微习惯能更科学地拓宽我们的舒适区。因为大脑是抗拒改变的,因此大幅度的改变会让大脑抗议,而微习惯因其微小而成之。</p><h1 id="第五章-微习惯的独特之处"><a href="#第五章-微习惯的独特之处" class="headerlink" title="第五章 微习惯的独特之处"></a>第五章 微习惯的独特之处</h1><p>微习惯能与现有习惯一较高下。大脑会抗拒大幅度改变,但是微习惯很小,被抗拒的程度会很小。</p><p>有些人在养成习惯的时候,会给自己设置一个期限。但是这是不科学的,因为习惯的形成因习惯、个体而定。微习惯策略没有截止时间,因为我们不知道需要多少时间,但是我们要寻找养成习惯的信号。</p><p>自我效能感是指对自己影响事件结果的能力的信念。微习惯能提升自我效能感。</p><p>微习惯能给我们更多的自主权,潜意识会恐惧控制,而微习惯通过将行动微小化,让潜意识意识到自己的控制权仍在,从而接受微小的变化。</p><p>微习惯能帮助自己远离恐惧、怀疑、胆怯或犹豫。行动是克服这些消极情绪的最佳武器,而微习惯能帮助自己行动。</p><p>正念是指对自己思维和行动有着清醒认识。正念是目标清晰地活着和敷衍地活着之间的区别。微习惯能让自己意识到在做什么,能培养自己的正念。</p><h1 id="第六章-彻底改变只需八步"><a href="#第六章-彻底改变只需八步" class="headerlink" title="第六章 彻底改变只需八步"></a>第六章 彻底改变只需八步</h1><ol><li>选择适合你的微习惯和计划<ul><li>不超过四个</li><li>微习惯要小到即使精疲力尽也可以执行</li><li>写下来</li></ul></li><li>挖掘每个微习惯的内在价值<ul><li>不断问自己为什么要养成这个习惯,寻找依据</li></ul></li><li>明确习惯依据,将其纳入日程<ul><li>根据时间制定</li><li>根据行为方式制定</li><li>非具体习惯</li></ul></li><li>创建奖励机制,以奖励提升成就感<ul><li>奖励,关联美好事物,比如npy、大声微笑、唱歌、吃东西、休息一下等</li><li>奖励时间点:在取得一定里程碑时,给自己奖励。在坚持不下去时,给自己奖励</li><li>奖励能提升意志力</li></ul></li><li>记录与追踪完成情况<ul><li>研究表明,把想法写在纸上时,会让其在大脑中更加突出,而打字就不具备同样的效应</li><li>可以采用纸质日历、电子数据等方式来追踪,关注自己在意的数据,比如完成天数/总天数</li></ul></li><li>微量开始,超额完成<ul><li>强化意志力</li><li>当下就取得进步</li><li>不耗尽意志力</li></ul></li><li>服从计划安排,摆脱高期待值<ul><li>我们常常在执行的过程会超额完成目标,可以记录这些数据</li><li>要把期待值和精力放在坚持目标上,而不要对任务量抱有较高的期待</li></ul></li><li>留意习惯养成的标志<ul><li>没有抵触情绪,做起来容易,不做反而更难</li><li>认同身份,比如我常运动,我喜欢阅读</li><li>行动无需考虑,不再担心,常态化</li><li>很无聊,好的习惯并不会让人兴奋,它们只是对我们有好处而已</li></ul></li></ol><h1 id="第七章-微习惯策略的八大规则"><a href="#第七章-微习惯策略的八大规则" class="headerlink" title="第七章 微习惯策略的八大规则"></a>第七章 微习惯策略的八大规则</h1><ol><li>绝不要自欺欺人。不要偷偷要求自己做得更多,要真正的微习惯</li><li>满意每一个进步。要满意,但别满足</li><li>经常回报自己,尤其在完成微习惯之后</li><li>保持头脑清醒。不管情绪是兴奋的,还是低落的,都要记住,要完成微习惯</li><li>感到强烈抵触时,后退并缩小目标。缩小目标,有利于降低意志力损耗,有利于行动的执行,开始执行要优于不执行,执行的过程会带来动力,形成正反馈</li><li>提醒自己这件事很轻松、很有趣。大脑喜欢轻松、有趣的事情</li><li>绝不要小看微步骤。不积跬步无以至千里</li><li>用多余精力超额完成任务,而不是制定更大目标</li></ol>]]></content>
<summary type="html"><p><a href="https://book.douban.com/subject/26877306/">豆瓣链接</a></p>
<p>本书主要写了微习惯是什么,以及通过微习惯来培养好习惯的依据和规则。</p>
<h1 id="第一章-微习惯是什么"><a href="#第</summary>
<category term="读书笔记" scheme="https://hyzgh.github.io/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
</entry>
<entry>
<title>Leetcode 每日一题6 Group Anagrams</title>
<link href="https://hyzgh.github.io/2020/04/06/leetcode-exercise-per-day-6-Group-Anagrams/"/>
<id>https://hyzgh.github.io/2020/04/06/leetcode-exercise-per-day-6-Group-Anagrams/</id>
<published>2020-04-06T08:30:17.000Z</published>
<updated>2020-04-06T08:47:15.176Z</updated>
<content type="html"><![CDATA[<h1 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h1><p>给一个数组,起元素为只包含英文小写字母的单词。</p><p>要求分组输出使用了相同字符构造的单词,比如tea和eat就是同类单词。</p><h1 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h1><p>将所有的单词遍历一遍,将同类单词放到同一个位置中去,关键是这个映射要怎么实现。对于一个长度为m的单词,我们可以用O(m)的时间遍历出各字符的使用次数。接着,我们将其转化成字符串,对于某个字符,其出现次数可以编码成cnt + char,然后按照英文字母表顺序连接起来。比如,teaa编码成2a1e1t。</p><p>上面的编码使用了一个优化,即对于出现次数的0的字母,不编码到字符串中。</p><p>假如追求极致,还可以对编码继续优化。</p><ul><li>对于出现次数为1的字母,可以不写上cnt。比如teaa可以写成2aet。</li><li>当cnt很大时,可以使用整形来表示。比如250,使用char来表示需要3个字节,使用整形只要1个字节。</li></ul><p>编码是个很有趣的知识点,上面的编码方式似乎都有专门的名称,以后有空的话专门写篇博客总结一下,这里留个坑:)</p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><figure class="highlight c++"><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"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> vector<vector<string>> <span class="built_in">groupAnagrams</span>(vector<string>& strs) {</span><br><span class="line"> vector<vector<string>> v;</span><br><span class="line"> unordered_map<string, vector<string>> m;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">auto</span> i: strs) {</span><br><span class="line"> m[<span class="built_in">hashWord</span>(i)].<span class="built_in">push_back</span>(i);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">auto</span> it: m) {</span><br><span class="line"> v.<span class="built_in">push_back</span>(it.second);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> v;</span><br><span class="line"> }</span><br><span class="line"> <span class="function">string <span class="title">hashWord</span><span class="params">(string s)</span> </span>{</span><br><span class="line"> <span class="function">vector<<span class="type">int</span>> <span class="title">cnt</span><span class="params">(<span class="number">26</span>, <span class="number">0</span>)</span></span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < s.<span class="built_in">length</span>(); i++) {</span><br><span class="line"> cnt[s[i] - <span class="string">'a'</span>]++;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> string hash;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < <span class="number">26</span>; i++) {</span><br><span class="line"> <span class="keyword">if</span> (!cnt[i])</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> hash += <span class="built_in">string</span>(cnt[i], <span class="string">'a'</span> + i);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> hash;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h1><p>给一个数组,起元素为只包含英文小写字母的单词。</p>
<p>要求分组输出使用了相同字符构造的单词,比如tea和eat就是同类单词。</p></summary>
<category term="Leetcode" scheme="https://hyzgh.github.io/tags/Leetcode/"/>
</entry>
<entry>
<title>Leetcode 每日一题5 Best Time to Buy and Sell Stock II</title>
<link href="https://hyzgh.github.io/2020/04/05/leetcode-exercise-per-day-5-Best-Time-to-Buy-and-Sell-Stock-II/"/>
<id>https://hyzgh.github.io/2020/04/05/leetcode-exercise-per-day-5-Best-Time-to-Buy-and-Sell-Stock-II/</id>
<published>2020-04-05T09:29:37.000Z</published>
<updated>2020-04-05T09:39:09.074Z</updated>
<content type="html"><![CDATA[<h1 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h1><p>给一个数组,表示每天的股票股价。可以进行无数次交易,但同一时刻只能持有一股的股票。求最大收益。</p><h1 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h1><p>假如股价在未来是涨的,那么就应该购买。假如是跌的,就不购买。</p><p>因此,我们扫描一遍数组,维护当前最低的股价,假如遇到较高的股价,就卖出,并更新最低股价。时间复杂度为O(n),空间复杂度为O(1)。</p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">maxProfit</span><span class="params">(vector<<span class="type">int</span>>& prices)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (prices.<span class="built_in">size</span>() == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="type">int</span> mi = prices[<span class="number">0</span>];</span><br><span class="line"> <span class="type">int</span> ans = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i < prices.<span class="built_in">size</span>(); i++) {</span><br><span class="line"> <span class="keyword">if</span> (prices[i] - mi > <span class="number">0</span>) {</span><br><span class="line"> ans += prices[i] - mi;</span><br><span class="line"> mi = prices[i];</span><br><span class="line"> }</span><br><span class="line"> mi = <span class="built_in">min</span>(mi, prices[i]);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ans;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h1><p>给一个数组,表示每天的股票股价。可以进行无数次交易,但同一时刻只能持有一股的股票。求最大收益。</p>
<h1 id="思路"><a hre</summary>
<category term="Leetcode" scheme="https://hyzgh.github.io/tags/Leetcode/"/>
</entry>
<entry>
<title>Leetcode 每日一题4 Move Zeroes</title>
<link href="https://hyzgh.github.io/2020/04/04/leetcode-exercise-per-day-4-Move-Zeroes/"/>
<id>https://hyzgh.github.io/2020/04/04/leetcode-exercise-per-day-4-Move-Zeroes/</id>
<published>2020-04-04T09:45:48.000Z</published>
<updated>2020-04-04T09:51:00.832Z</updated>
<content type="html"><![CDATA[<h1 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h1><blockquote><p>题目链接:<a href="https://leetcode.com/explore/featured/card/30-day-leetcoding-challenge/528/week-1/3286/">Move Zeroes</a></p></blockquote><p>给一个数组,通过交换将所有非零元素放置在前面,将所有零元素放置在后面。</p><p>要求In-place,即不能使用额外的空间存放数组元素。</p><p>另外还要求交换次数最少。</p><h1 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h1><p>为了满足题目要求,我们可以使用双指针法,找到一个零元素,以及在它后面的非零元素,将两者交换,不断重复这个过程。</p><p>假如不需要交换次数最少,有种更简洁的写法,即两次for循环,第一个for循环将非零元素放在数组前面,第二次for循环将非零元素放在数组后面。</p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><figure class="highlight c++"><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"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">moveZeroes</span><span class="params">(vector<<span class="type">int</span>>& nums)</span> </span>{</span><br><span class="line"> <span class="type">int</span> pZero = <span class="built_in">findNextZeroIndex</span>(nums, <span class="number">-1</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="type">int</span> pNonZero = <span class="built_in">findNextNonZeroIndex</span>(nums, pZero);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">while</span> (pZero < nums.<span class="built_in">size</span>() && pNonZero < nums.<span class="built_in">size</span>()) {</span><br><span class="line"> <span class="built_in">swap</span>(nums[pNonZero], nums[pZero]);</span><br><span class="line"> pZero = <span class="built_in">findNextZeroIndex</span>(nums, pZero);</span><br><span class="line"> pNonZero = <span class="built_in">findNextNonZeroIndex</span>(nums, pZero);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">findNextZeroIndex</span><span class="params">(vector<<span class="type">int</span>> &nums, <span class="type">int</span> pZero)</span> </span>{</span><br><span class="line"> pZero++;</span><br><span class="line"> <span class="keyword">while</span> (pZero < nums.<span class="built_in">size</span>() && nums[pZero] != <span class="number">0</span>) {</span><br><span class="line"> pZero++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> pZero;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">findNextNonZeroIndex</span><span class="params">(vector<<span class="type">int</span>> &nums, <span class="type">int</span> pNonZero)</span> </span>{</span><br><span class="line"> pNonZero++;</span><br><span class="line"> <span class="keyword">while</span> (pNonZero < nums.<span class="built_in">size</span>() && nums[pNonZero] == <span class="number">0</span>) {</span><br><span class="line"> pNonZero++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> pNonZero;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h1><blockquote>
<p>题目链接:<a href="https://leetcode.com/explore/featured/card/</summary>
<category term="Leetcode" scheme="https://hyzgh.github.io/tags/Leetcode/"/>
</entry>
<entry>
<title>Leetcode 每日一题3 Maximum Subarray</title>
<link href="https://hyzgh.github.io/2020/04/04/leetcode-exercise-per-day-3-Maximum-Subarray/"/>
<id>https://hyzgh.github.io/2020/04/04/leetcode-exercise-per-day-3-Maximum-Subarray/</id>
<published>2020-04-04T09:03:51.000Z</published>
<updated>2020-04-04T09:19:47.737Z</updated>
<content type="html"><![CDATA[<h1 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h1><blockquote><p>题目链接:<a href="https://leetcode.com/explore/featured/card/30-day-leetcoding-challenge/528/week-1/3285/">Maximum Subarray</a></p></blockquote><p>给一个数组,求该数组的最大子数组和。</p><h1 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h1><p>解法1:从左到右遍历一次,累加,若sum比现有的答案大,则更新。若sum为负数,则放弃这一段的元素,置为0。时间复杂度为O(n),空间复杂度为O(1)。</p><p>解法2:分治法。若我们将一个数组从中间分隔成两个数组,则它的答案是max(左数组的答案, 右数组的答案, 左数组的最大后缀和+右数组的最大前缀和+中间元素)。时间复杂度为O(nlogn),空间复杂度为O(logn)。</p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><p>解法1:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">maxSubArray</span><span class="params">(vector<<span class="type">int</span>>& nums)</span> </span>{</span><br><span class="line"> <span class="type">long</span> <span class="type">long</span> ans = LONG_LONG_MIN;</span><br><span class="line"> <span class="type">long</span> <span class="type">long</span> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">auto</span> i: nums) {</span><br><span class="line"> sum += i;</span><br><span class="line"> <span class="keyword">if</span> (sum > ans)</span><br><span class="line"> ans = sum;</span><br><span class="line"> <span class="keyword">if</span> (sum < <span class="number">0</span>)</span><br><span class="line"> sum = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ans;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>解法2:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">maxSubArray</span><span class="params">(vector<<span class="type">int</span>>& nums)</span> </span>{</span><br><span class="line"> <span class="type">int</span> l = <span class="number">0</span>, r = nums.<span class="built_in">size</span>();</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">maxSubArrayCore</span>(nums, l, r);</span><br><span class="line"> }</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">maxSubArrayCore</span><span class="params">(vector<<span class="type">int</span>>& nums, <span class="type">int</span> l, <span class="type">int</span> r)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (l >= r) {</span><br><span class="line"> <span class="keyword">return</span> INT_MIN;</span><br><span class="line"> }</span><br><span class="line"> <span class="type">int</span> mid = l + ((r - l) >> <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> leftMax = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = mid - <span class="number">1</span>, sum = <span class="number">0</span>; i >= l; i--) {</span><br><span class="line"> sum += nums[i];</span><br><span class="line"> leftMax = <span class="built_in">max</span>(leftMax, sum);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> rightMax = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = mid + <span class="number">1</span>, sum = <span class="number">0</span>; i < r; i++) {</span><br><span class="line"> sum += nums[i];</span><br><span class="line"> rightMax = <span class="built_in">max</span>(rightMax, sum);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">max</span>(leftMax + rightMax + nums[mid],</span><br><span class="line"> <span class="built_in">max</span>(<span class="built_in">maxSubArrayCore</span>(nums, l, mid), <span class="built_in">maxSubArrayCore</span>(nums, mid + <span class="number">1</span>, r)));</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h1><blockquote>
<p>题目链接:<a href="https://leetcode.com/explore/featured/card/</summary>
<category term="Leetcode" scheme="https://hyzgh.github.io/tags/Leetcode/"/>
</entry>
<entry>
<title>Leetcode 每日一题2 Happy Number</title>
<link href="https://hyzgh.github.io/2020/04/02/leetcode-exercise-per-day-2-Happy-Number/"/>
<id>https://hyzgh.github.io/2020/04/02/leetcode-exercise-per-day-2-Happy-Number/</id>
<published>2020-04-02T08:35:01.000Z</published>
<updated>2020-04-02T08:52:39.304Z</updated>
<content type="html"><![CDATA[<h1 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h1><blockquote><p>题目链接:<a href="https://leetcode.com/explore/featured/card/30-day-leetcoding-challenge/528/week-1/3284/">Happy Number</a></p></blockquote><p>给一个数字,判断它是不是Happy Number。假如一个数是Happy Number,则可以通过不断取各数位的平方和得到1。而假如不是Happy Number,则会陷入循环,不会得到1。</p><h1 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h1><p>这道题的关键在于怎么知道发生了循环,并在发生循环的时候及时结束。弗洛伊德判圈法(Floyd Cycle detection algorithm)是可以在O(1)的空间复杂度和O(n)的时间复杂度内,判断是否发生循环的一种算法,其中n是循环的次数。其思想是维护两个指针,令它们一开始都指向最开始的位置,然后进行<code>do while</code>循环,让其中一个指针一次走两步,另一个一次走一步,直到两者相等,就可以判定存在圈。</p><p>这道题一定存在循环,Happy Number在找到圈的时候,指向的数为1,而非Happy Number指向的数非1,得解。</p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">bool</span> <span class="title">isHappy</span><span class="params">(<span class="type">int</span> n)</span> </span>{</span><br><span class="line"> <span class="type">int</span> slow, fast;</span><br><span class="line"> slow = fast = n;</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> slow = <span class="built_in">getSum</span>(slow);</span><br><span class="line"> fast = <span class="built_in">getSum</span>(fast);</span><br><span class="line"> fast = <span class="built_in">getSum</span>(fast);</span><br><span class="line"> } <span class="keyword">while</span> (slow != fast);</span><br><span class="line"> <span class="keyword">return</span> slow == <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">getSum</span><span class="params">(<span class="type">int</span> d)</span> </span>{</span><br><span class="line"> <span class="type">int</span> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (d) {</span><br><span class="line"> <span class="type">int</span> mod = d % <span class="number">10</span>;</span><br><span class="line"> d /= <span class="number">10</span>;</span><br><span class="line"> sum += mod * mod;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> sum;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h1><blockquote>
<p>题目链接:<a href="https://leetcode.com/explore/featured/card/</summary>
<category term="Leetcode" scheme="https://hyzgh.github.io/tags/Leetcode/"/>
</entry>
<entry>
<title>Leetcode 每日一题01 Single Number</title>
<link href="https://hyzgh.github.io/2020/04/01/leetcode-exercise-per-day-1-Single-Number/"/>
<id>https://hyzgh.github.io/2020/04/01/leetcode-exercise-per-day-1-Single-Number/</id>
<published>2020-04-01T09:03:20.000Z</published>
<updated>2020-04-02T08:35:50.929Z</updated>
<content type="html"><![CDATA[<p>最近Leetcode举办了一个为期一个月的活动,每天会推出一道题目。最近这段时间正好有空,所以打算跟着参加一下,顺便写一下题解。使用的语言为C++。</p><h1 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h1><blockquote><p> 题目链接:<a href="https://leetcode.com/explore/featured/card/30-day-leetcoding-challenge/528/week-1/3283/">Single Number</a></p></blockquote><p>给一个非空的数组,其中有1个元素出现了1次,其他的都出现2次。时间要求为线性,不允许使用额外内存,求出现1次的元素。</p><h1 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h1><p>题目保证数组非空,所以不需要特殊处理。</p><p>两个相同的数异或等于0,因此我们可以将数组所有元素异或起来,最后得到的值即为出现1次的元素。</p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">singleNumber</span><span class="params">(vector<<span class="type">int</span>>& nums)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i < nums.<span class="built_in">size</span>(); i++) {</span><br><span class="line"> nums[<span class="number">0</span>] ^= nums[i];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> nums[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>最近Leetcode举办了一个为期一个月的活动,每天会推出一道题目。最近这段时间正好有空,所以打算跟着参加一下,顺便写一下题解。使用的语言为C++。</p>
<h1 id="题意"><a href="#题意" class="headerlink" title="题意"></</summary>
<category term="Leetcode" scheme="https://hyzgh.github.io/tags/Leetcode/"/>
</entry>
<entry>
<title>Go Lang slice 学习笔记</title>
<link href="https://hyzgh.github.io/2020/01/27/go-slice-learning-note/"/>
<id>https://hyzgh.github.io/2020/01/27/go-slice-learning-note/</id>
<published>2020-01-27T13:48:29.000Z</published>
<updated>2020-01-27T14:07:05.915Z</updated>
<content type="html"><![CDATA[<h1 id="切片的数据结构"><a href="#切片的数据结构" class="headerlink" title="切片的数据结构"></a>切片的数据结构</h1><p>切片,本身不存储实际数据。切片的数据结构,也被称为slice header,包含指向首元素的指针、切片长度、切片容量:</p><p><img src="https://s2.ax1x.com/2020/01/27/1uWu2n.png" alt="img"></p><p>当将切片作为实参时,只会传递slice header。这个传递是值传递,这意味着:</p><ul><li>假如在函数里面对slice的元素作赋值操作,由于有指针指着元素,所以原有slice指向的元素也会被改变。</li><li>假如在函数里面reslice,并不会影响原slice。</li></ul><p>假如想要改变原有slice,有两种写法:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第一种,将返回值赋值给原slice,这种方式也就是append的实现</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">SubtractOneFromLength</span><span class="params">(slice []<span class="type">byte</span>)</span></span> []<span class="type">byte</span> {</span><br><span class="line"> slice = slice[<span class="number">0</span> : <span class="built_in">len</span>(slice)<span class="number">-1</span>]</span><br><span class="line"> <span class="keyword">return</span> slice</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> newSlice := SubtractOneFromLength(slice)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 第二种,用slice指针</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">PtrSubtractOneFromLength</span><span class="params">(slicePtr *[]<span class="type">byte</span>)</span></span> {</span><br><span class="line"> slice := *slicePtr</span><br><span class="line"> *slicePtr = slice[<span class="number">0</span> : <span class="built_in">len</span>(slice)<span class="number">-1</span>]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这引发了另一个需要注意的点,对于slice的方法,假如需要reslice,要用slice指针:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> path []<span class="type">byte</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(p *path)</span></span> TruncateAtFinalSlash() {</span><br><span class="line"> i := bytes.LastIndex(*p, []<span class="type">byte</span>(<span class="string">"/"</span>))</span><br><span class="line"> <span class="keyword">if</span> i >= <span class="number">0</span> {</span><br><span class="line"> *p = (*p)[<span class="number">0</span>:i]</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> pathName := path(<span class="string">"/usr/bin/tso"</span>) <span class="comment">// Conversion from string to path.</span></span><br><span class="line"> pathName.TruncateAtFinalSlash()</span><br><span class="line"> fmt.Printf(<span class="string">"%s\n"</span>, pathName)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>切片,可以改变指向的范围,这是成本很低的操作:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> r := []<span class="type">bool</span>{<span class="literal">true</span>, <span class="literal">true</span>, <span class="literal">true</span>, <span class="literal">false</span>, <span class="literal">false</span>, <span class="literal">false</span>}</span><br><span class="line"> fmt.Println(r)</span><br><span class="line"> r = r[<span class="number">1</span>:<span class="number">2</span>]</span><br><span class="line"> fmt.Println(r)</span><br><span class="line"> r = r[<span class="number">0</span>:<span class="number">3</span>]</span><br><span class="line"> fmt.Println(r)</span><br><span class="line"> # 输出[<span class="literal">true</span> <span class="literal">true</span> <span class="literal">false</span>],即最开始的[<span class="number">1</span>,<span class="number">4</span>]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>切片,可以切片,即<code>s := s[[begin]:[end]]</code>,其中begin和end可以省略。当end超过实际cap时,会panic。</p><p><code>cap()</code> 查看容量,即从下界到数组最后一个元素的个数</p><p><code>len()</code>查看长度,即从下界到上界的个数</p><p>切片的零值为<code>nil</code>,此时<code>cap</code>和<code>len</code>都为0</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a []<span class="type">int</span> <span class="comment">// nil</span></span><br></pre></td></tr></table></figure><h1 id="切片的append"><a href="#切片的append" class="headerlink" title="切片的append"></a>切片的append</h1><p>可使用<code>append</code>函数向切片添加元素,对nil切片进行<code>append</code>是正确的。</p><p>当append超过切片容量时,会将容量翻倍,且利用内置函数copy进行数据的拷贝,该函数在切片重叠的情况下也可以保证正确拷贝。注意只有被append的slice的指针会指向这块新的区域,基于这个slice的其他slice并不会更新指针。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> s []<span class="type">int</span></span><br><span class="line"> s = <span class="built_in">append</span>(s, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>)</span><br><span class="line"> fmt.Println(s)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>还可以使用<code>append</code>函数向切片中添加切片:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">x := []<span class="type">int</span>{<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>}</span><br><span class="line">y := []<span class="type">int</span>{<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>}</span><br><span class="line">x = <span class="built_in">append</span>(x, y...)</span><br><span class="line">fmt.Println(x)</span><br></pre></td></tr></table></figure><h1 id="切片的内存管理"><a href="#切片的内存管理" class="headerlink" title="切片的内存管理"></a>切片的内存管理</h1><p>注意只要有一个slice使用着底层slice,即使这个上层slice只包含了很少的元素,底层slice的内存也不会被释放掉。这时候假如为了释放不必要的内存占用,需要将上层slice指向的数据拷贝到新的区域:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> digitRegexp = regexp.MustCompile(<span class="string">"[0-9]+"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 整个文件的字节都被加载进去了,且不释放</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">FindDigits</span><span class="params">(filename <span class="type">string</span>)</span></span> []<span class="type">byte</span> {</span><br><span class="line"> b, _ := ioutil.ReadFile(filename)</span><br><span class="line"> <span class="keyword">return</span> digitRegexp.Find(b)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 拷贝到c,释放多余的占用</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CopyDigits</span><span class="params">(filename <span class="type">string</span>)</span></span> []<span class="type">byte</span> {</span><br><span class="line"> b, _ := ioutil.ReadFile(filename)</span><br><span class="line"> b = digitRegexp.Find(b)</span><br><span class="line"> c := <span class="built_in">make</span>([]<span class="type">byte</span>, <span class="built_in">len</span>(b))</span><br><span class="line"> <span class="built_in">copy</span>(c, b)</span><br><span class="line"> <span class="keyword">return</span> c</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h1 id="切片的创建"><a href="#切片的创建" class="headerlink" title="切片的创建"></a>切片的创建</h1><p>使用<code>make</code>来创建一维切片</p><figure class="highlight go"><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">a := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">5</span>) <span class="comment">// len(a) == cap(a) == 5</span></span><br><span class="line">b := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">0</span>, <span class="number">5</span>) <span class="comment">// len(b) == 0, cap(b) == 5</span></span><br></pre></td></tr></table></figure><p>使用<code>make</code>创建二维切片</p><p>第一种方式,该方式允许第二维大小有所变化:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个位数为[dx][dy]的切片</span></span><br><span class="line">a := <span class="built_in">make</span>([][]<span class="type">uint8</span>, dx)</span><br><span class="line"><span class="keyword">for</span> i := <span class="keyword">range</span> a {</span><br><span class="line"> a[i] = <span class="built_in">make</span>([]<span class="type">uint8</span>, dy)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>第二种方式,不允许第二维大小有所变化,因为改变了就会有可能覆盖:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Allocate the top-level slice, the same as before.</span></span><br><span class="line">picture := <span class="built_in">make</span>([][]<span class="type">uint8</span>, YSize) <span class="comment">// One row per unit of y.</span></span><br><span class="line"><span class="comment">// Allocate one large slice to hold all the pixels.</span></span><br><span class="line">pixels := <span class="built_in">make</span>([]<span class="type">uint8</span>, XSize*YSize) <span class="comment">// Has type []uint8 even though picture is [][]uint8.</span></span><br><span class="line"><span class="comment">// Loop over the rows, slicing each row from the front of the remaining pixels slice.</span></span><br><span class="line"><span class="keyword">for</span> i := <span class="keyword">range</span> picture {</span><br><span class="line"> picture[i], pixels = pixels[:XSize], pixels[XSize:]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="切片的遍历"><a href="#切片的遍历" class="headerlink" title="切片的遍历"></a>切片的遍历</h1><p>可使用<code>range</code>遍历切片,每次循环会有两个值,一个是元素的下标,一个是元素的值。可使用<code>_</code>忽略其中一个。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> pow := []<span class="type">int</span>{<span class="number">4</span>, <span class="number">1</span>, <span class="number">5</span>}</span><br><span class="line"> <span class="keyword">for</span> _, value := <span class="keyword">range</span> pow {</span><br><span class="line"> fmt.Printf(<span class="string">"%d\n"</span>, value)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 只有一个值只会得到下标</span></span><br><span class="line"> <span class="keyword">for</span> idx := <span class="keyword">range</span> pow {</span><br><span class="line"> fmt.Printf(<span class="string">"%d\n"</span>, pow[idx])</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="切片的技巧"><a href="#切片的技巧" class="headerlink" title="切片的技巧"></a>切片的技巧</h1><p>优雅地往slice插入一个value:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Insert inserts the value into the slice at the specified index,</span></span><br><span class="line"><span class="comment">// which must be in range.</span></span><br><span class="line"><span class="comment">// The slice must have room for the new element.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Insert</span><span class="params">(slice []<span class="type">int</span>, index, value <span class="type">int</span>)</span></span> []<span class="type">int</span> {</span><br><span class="line"> <span class="comment">// Grow the slice by one element.</span></span><br><span class="line"> slice = slice[<span class="number">0</span> : <span class="built_in">len</span>(slice)+<span class="number">1</span>]</span><br><span class="line"> <span class="comment">// Use copy to move the upper part of the slice out of the way and open a hole.</span></span><br><span class="line"> <span class="built_in">copy</span>(slice[index+<span class="number">1</span>:], slice[index:])</span><br><span class="line"> <span class="comment">// Store the new value.</span></span><br><span class="line"> slice[index] = value</span><br><span class="line"> <span class="comment">// Return the result.</span></span><br><span class="line"> <span class="keyword">return</span> slice</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>优雅地合并两个切片:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">a := []<span class="type">string</span>{<span class="string">"John"</span>, <span class="string">"Paul"</span>}</span><br><span class="line">b := []<span class="type">string</span>{<span class="string">"George"</span>, <span class="string">"Ringo"</span>, <span class="string">"Pete"</span>}</span><br><span class="line">a = <span class="built_in">append</span>(a, b...) <span class="comment">// equivalent to "append(a, b[0], b[1], b[2])"</span></span><br><span class="line"><span class="comment">// a == []string{"John", "Paul", "George", "Ringo", "Pete"}</span></span><br></pre></td></tr></table></figure><p>优雅地copy:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">b = <span class="built_in">make</span>([]T, <span class="built_in">len</span>(a))</span><br><span class="line"><span class="built_in">copy</span>(b, a)</span><br><span class="line"><span class="comment">// or</span></span><br><span class="line">b = <span class="built_in">append</span>([]T(<span class="literal">nil</span>), a...)</span><br></pre></td></tr></table></figure><p>cut,优雅地删除掉slice中的某一个区间:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 普通版,对于元素为指针或者结构中含有指针,会存在内存泄漏,因为被删掉的元素仍属于slice</span></span><br><span class="line">a = <span class="built_in">append</span>(a[:i], a[j:]...)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 升级版,将指针设为nil,减少了内存泄漏,但是还是有元素属于slice</span></span><br><span class="line"><span class="built_in">copy</span>(a[i:], a[j:])</span><br><span class="line"><span class="keyword">for</span> k, n := <span class="built_in">len</span>(a)-j+i, <span class="built_in">len</span>(a); k < n; k++ {</span><br><span class="line"> a[k] = <span class="literal">nil</span> <span class="comment">// or the zero value of T</span></span><br><span class="line">}</span><br><span class="line">a = a[:<span class="built_in">len</span>(a)-j+i]</span><br></pre></td></tr></table></figure><p>filter:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">filter</span><span class="params">(a []<span class="type">int</span>)</span></span> []<span class="type">int</span> {</span><br><span class="line"> n := <span class="number">0</span></span><br><span class="line"> <span class="keyword">for</span> _, x := <span class="keyword">range</span> a {</span><br><span class="line"> <span class="keyword">if</span> keep(x) {</span><br><span class="line"> a[n] = x</span><br><span class="line"> n++</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> a = a[:n]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>reverse:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> i := <span class="built_in">len</span>(a)/<span class="number">2</span><span class="number">-1</span>; i >= <span class="number">0</span>; i-- {</span><br><span class="line"> opp := <span class="built_in">len</span>(a)<span class="number">-1</span>-i</span><br><span class="line"> a[i], a[opp] = a[opp], a[i]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>shuffling:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> i := <span class="built_in">len</span>(a) - <span class="number">1</span>; i > <span class="number">0</span>; i-- {</span><br><span class="line"> j := rand.Intn(i + <span class="number">1</span>)</span><br><span class="line"> a[i], a[j] = a[j], a[i]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或者直接用math/rand.Shuffle</span></span><br><span class="line"><span class="comment">// 使用了Fisher–Yates算法</span></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="切片的数据结构"><a href="#切片的数据结构" class="headerlink" title="切片的数据结构"></a>切片的数据结构</h1><p>切片,本身不存储实际数据。切片的数据结构,也被称为slice header,包含指向首元素的指针、切</summary>
<category term="Go" scheme="https://hyzgh.github.io/tags/Go/"/>
</entry>
<entry>
<title>Go Lang GC 学习笔记</title>
<link href="https://hyzgh.github.io/2020/01/11/go-garbage-collector-learning-note/"/>
<id>https://hyzgh.github.io/2020/01/11/go-garbage-collector-learning-note/</id>
<published>2020-01-11T15:42:50.000Z</published>
<updated>2020-01-11T15:51:57.048Z</updated>
<content type="html"><![CDATA[<p>GC的具体实现一直在改变,但是其模型是相对稳定的。</p><p>collection的三个阶段:</p><ul><li>Mark Setup - STW</li><li>Marking - Concurrent</li><li>Mark Termination - STW</li></ul><p>Mark Setup:当进行gc时,为了保证数据完整性,需要设置Write Barrier,这要求将所有的goroutine停止,正常情况下这个时间平均为10-30ms。但是当goroutine停止不下时,会让这个时间变长。调度器目前停止goroutine的时机是在goroutine执行函数调用的时候,因为这样做才能保证安全。假如某个goroutine一直在执行循环,而不执行函数调用,它就无法被停止,这不仅会耽误gc第一个阶段的完成,还会造成其他goroutine停止着。将在Go1.14引入 <a href="https://github.com/golang/go/issues/24543">preemptive</a> techniques 尝试对此进行优化。</p><p>Marking - Concurrent:当设置好Write Barrier后,collector会拿走25%可用的CPU,用于mark操作。mark操作,会遍历所有goroutine的栈,寻找指针指向的heap内存,将仍在使用的内存标记一下。这个时候,goroutine是可以并发执行的。在mark的过程,其他正在运行的goroutine可以分配内存,这可能导致内存不够用,这时候需要Mark Assits。Mark Assits,是指短暂停止正在运行的goroutine,让它们来帮忙mark,加快mark的进度。当然,这会影响这些goroutine的执行,collector的目标是尽可能减少Mark Assits的需要。</p><p>Mark Termination - STW:Mark完成后,需要关闭Write Barrier,进行多项清理工作,设定下次collection的目标。在进行这些工作前,仍要求所有的goroutine停止。这些工作平均持续60~90ms。虽然也可以设计成边Termination边正常运行一些gorutine的模式,但是设计者认为这样得到的收益很小,而增加的复杂性较高,所以选择了这种STW的实现方式。当Termination工作完成后,回到正常状态。</p><p>在collection完成后,会进行sweeping。</p><p>sweep是指将那些没有标记为使用的heap内存回收,它不是发生在一次collection中,而是被均摊到每次分配内存的时候。</p><p>以上所有行为只有在GC开始且正在进行的时候发生,GC Percentage对collection有很大影响。GC Pencentage,默认是100%。将GC Pencentage设置为100%,意味着下次GC会在heap达到当前GC的mark live内存的2倍时进行。</p><p>GC trace可用于追踪collection。<code>GODEBUG=gctrace=1 ./app</code>可查看GC信息,输出到<code>stderr</code>。</p><p>GC trace:</p><figure class="highlight bash"><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"><span class="comment"># 通过设置环境变量查看GC trace</span></span><br><span class="line">GODEBUG=gctrace=1 ./app</span><br><span class="line"></span><br><span class="line">gc 1405 @6.068s 11%: 0.058+1.2+0.083 ms clock, 0.70+2.5/1.5/0+0.99 ms cpu, 7->11->6 MB, 10 MB goal, 12 P</span><br><span class="line"></span><br><span class="line">gc 1406 @6.070s 11%: 0.051+1.8+0.076 ms clock, 0.61+2.0/2.5/0+0.91 ms cpu, 8->11->6 MB, 13 MB goal, 12 P</span><br><span class="line"></span><br><span class="line">gc 1407 @6.073s 11%: 0.052+1.8+0.20 ms clock, 0.62+1.5/2.2/0+2.4 ms cpu, 8->14->8 MB, 13 MB goal, 12 P</span><br><span class="line"></span><br><span class="line"><span class="comment"># GC trace含义</span></span><br><span class="line">gc 1405 @6.068s 11%: 0.058+1.2+0.083 ms clock, 0.70+2.5/1.5/0+0.99 ms cpu, 7->11->6 MB, 10 MB goal, 12 P</span><br><span class="line"></span><br><span class="line">// General</span><br><span class="line">gc 1404 : The 1404 GC run since the program started</span><br><span class="line">@6.068s : Six seconds since the program started</span><br><span class="line">11% : Eleven percent of the available CPU so far has been spent <span class="keyword">in</span> GC</span><br><span class="line"></span><br><span class="line">// Wall-Clock</span><br><span class="line">0.058ms : STW : Mark Start - Write Barrier on</span><br><span class="line">1.2ms : Concurrent : Marking</span><br><span class="line">0.083ms : STW : Mark Termination - Write Barrier off and clean up</span><br><span class="line"></span><br><span class="line">// CPU Time</span><br><span class="line">0.70ms : STW : Mark Start</span><br><span class="line">2.5ms : Concurrent : Mark - Assist Time (GC performed <span class="keyword">in</span> line with allocation)</span><br><span class="line">1.5ms : Concurrent : Mark - Background GC time</span><br><span class="line">0ms : Concurrent : Mark - Idle GC time</span><br><span class="line">0.99ms : STW : Mark Term</span><br><span class="line"></span><br><span class="line">// Memory</span><br><span class="line">7MB : Heap memory in-use before the Marking started</span><br><span class="line">11MB : Heap memory in-use after the Marking finished</span><br><span class="line">6MB : Heap memory marked as live after the Marking finished</span><br><span class="line">10MB : Collection goal <span class="keyword">for</span> heap memory in-use after Marking finished</span><br><span class="line"></span><br><span class="line">// Threads</span><br><span class="line">12P : Number of logical processors or threads used to run Goroutines</span><br></pre></td></tr></table></figure><p>(全文完)</p>]]></content>
<summary type="html"><p>GC的具体实现一直在改变,但是其模型是相对稳定的。</p>
<p>collection的三个阶段:</p>
<ul>
<li>Mark Setup - STW</li>
<li>Marking - Concurrent</li>
<li>Mark Termination -</summary>
<category term="Go" scheme="https://hyzgh.github.io/tags/Go/"/>
</entry>
<entry>
<title>perl 学习笔记</title>
<link href="https://hyzgh.github.io/2019/12/18/null/"/>
<id>https://hyzgh.github.io/2019/12/18/null/</id>
<published>2019-12-18T15:09:06.000Z</published>
<updated>2019-12-18T15:58:07.610Z</updated>
<content type="html"><![CDATA[<p>Perl是一门解释型语言,具有动态语言的特性,可以写得很随性。缺点是由于语法灵活导致难以维护,这和Go Lang形成了强烈的对比。Go Lang很适合用来团队协作开发,因为它写法很单一固定。Perl,更像是黑客的工具。</p><p>Perl的强大之处,在于它具有sed和awk的全部功能,以及众多语言的语法特性,以及非常强大的正则表达式,等等。</p><h1 id="Hello-World"><a href="#Hello-World" class="headerlink" title="Hello, World"></a>Hello, World</h1><p>Perl的Hello, World:</p><figure class="highlight perl"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">perl -e <span class="string">'print("Hello, World\n")'</span></span><br></pre></td></tr></table></figure><p>在安装了perl的计算机上,直接执行上述代码,即可输出<code>Hello, World</code>。</p><p>也可以将perl代码写在文件,以重复执行,具体可以这样写:</p><figure class="highlight perl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/perl</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出 "Hello, World"</span></span><br><span class="line"><span class="keyword">print</span>(<span class="string">"Hello, World\n"</span>);</span><br></pre></td></tr></table></figure><p>将上述代码保存成<code>learn.pl</code>后,加下执行权限,然后执行<code>./learn.pl</code>,可输出<code>Hello, World</code>。</p><h1 id="正则表达式"><a href="#正则表达式" class="headerlink" title="正则表达式"></a>正则表达式</h1><p>Perl的正式表达式非常强大,是常用编程语言里最顶尖的之一。下面来看几个例子。</p><h2 id="例子1"><a href="#例子1" class="headerlink" title="例子1"></a>例子1</h2><p><code>echo box.svc.content | perl -p -e 's/\.+/\//g'</code></p><p>将会输出<code>box/svc/content</code>。</p><p>解析:</p><p><code>-p</code>参数会将结果输出出来。</p><p>这里用到了一个常用的<strong>替换</strong>句型,<code>s/old/new/parameters</code>,表示将<code>old</code>替换成<code>new</code>。</p><p><code>s/\.+/\//g</code>的<code>s</code>表示替换,<code>\.+</code>表示至少一个<code>.</code>,<code>\/</code>表示一个<code>/</code>,<code>g</code>表示全局匹配。</p><h2 id="例子2"><a href="#例子2" class="headerlink" title="例子2"></a>例子2</h2><figure class="highlight perl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/perl </span></span><br><span class="line"></span><br><span class="line">$string = <span class="string">'welcome to w3cschool site.'</span>;</span><br><span class="line">$string =~ <span class="regexp">tr/a-z/A-z/</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">print</span> <span class="string">"$string\n"</span>;</span><br></pre></td></tr></table></figure><p>上面的代码将会输出<code>WELCOME TO W3CSCHOOL SITE.</code>。</p><p>这里用到了一个常用的<strong>转化</strong>句型,<code>tr/old/new/parameters</code>。表示将<code>old</code>转化成<code>new</code>。</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://www.w3cschool.cn/perl/perl-regular-expressions.html">Perl 正则表达式 - W3Cschool</a></p>]]></content>
<summary type="html"><p>Perl是一门解释型语言,具有动态语言的特性,可以写得很随性。缺点是由于语法灵活导致难以维护,这和Go Lang形成了强烈的对比。Go Lang很适合用来团队协作开发,因为它写法很单一固定。Perl,更像是黑客的工具。</p>
<p>Perl的强大之处,在于它具有sed和a</summary>
<category term="编程语言" scheme="https://hyzgh.github.io/tags/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/"/>
</entry>
<entry>
<title>图片文件格式 学习笔记</title>
<link href="https://hyzgh.github.io/2019/12/07/null/"/>
<id>https://hyzgh.github.io/2019/12/07/null/</id>
<published>2019-12-07T08:07:02.000Z</published>
<updated>2019-12-07T08:32:39.020Z</updated>
<content type="html"><![CDATA[<p>在互联网中,常见的图片文件格式有jpeg, png, bmp, gif等。</p><p>一副图片,具有很多的属性,比如分辨率,色彩空间等。可以通过<code>ImageMagick</code>这个程序来解析图片文件。</p><p>分辨率,比如1920x1080,是指图片的一行具有1920个像素,一列具有1080个像素。</p><p>色彩空间,是指描述一个像素点的方式。比如RGB,是指一个像素点用red, green, blue各8bit一共24bit来描述。而YUV,是指Y表示亮度,U和V一起表示色调和饱和度。</p><p>在互联网上最常见的大概是jpeg类的文件。jpeg文件之所以适合在互联网上传播,是因为它的压缩比较高,且能在压缩和图片还原度上保持平衡,使得一张图片既有小巧的体积,又能在肉眼级别上有良好的清晰度。</p><p>但是jpeg文件并不适合保存图标等图像内容,因为它不具有alpha通道,不能用来表示透明的背景。一般会用png来保存图标。由于png是无损压缩存储图片的,所以会比jpeg文件要大一些,也适合对于图像质量要求较高的情况。</p><p>bmp文件,它也是无损不压缩的,所以会比较大,比较少见。</p><p>git文件,可以存储动图,但是由于它只用了8bit来描述一个像素点,所以可用的色彩并不多,色彩丰富度低。</p><p>(全文完)</p>]]></content>
<summary type="html"><p>在互联网中,常见的图片文件格式有jpeg, png, bmp, gif等。</p>
<p>一副图片,具有很多的属性,比如分辨率,色彩空间等。可以通过<code>ImageMagick</code>这个程序来解析图片文件。</p>
<p>分辨率,比如1920x1080,是指图</summary>
<category term="文件格式" scheme="https://hyzgh.github.io/tags/%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F/"/>
</entry>
</feed>