-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
582 lines (280 loc) · 576 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Go语言并发控制</title>
<link href="/2022/03/28/go-yu-yan-bing-fa-kong-zhi/"/>
<url>/2022/03/28/go-yu-yan-bing-fa-kong-zhi/</url>
<content type="html"><![CDATA[<h1 id="Go语言并发控制"><a href="#Go语言并发控制" class="headerlink" title="Go语言并发控制"></a>Go语言并发控制</h1><blockquote><p>参考:<br>Go语言底层原理剖析 - 第17章 并发控制<br><a href="https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-sync-primitives/">Go语言设计与实现 - 6.2 同步原语与锁</a></p></blockquote><hr><p>在高并发场景下,必定涉及到协程之间的交流与并发控制。除了通道外,go语言还提供了context以及各种锁机制,下面逐一进行介绍。</p><br><br><h2 id="1-context"><a href="#1-context" class="headerlink" title="1. context"></a>1. context</h2><p>Go1.7中引入context包,使得父子协程之间可以进行交流,可以优雅地控制协程的退出。</p><h3 id="1-1-context使用方式"><a href="#1-1-context使用方式" class="headerlink" title="1.1 context使用方式"></a>1.1 context使用方式</h3><p>context是使用频率非常高的包,不仅Go源码中经常使用,很多Go编写的第三方包也有所使用。context一般作为接口的第一个参数传递超时信息,在Go源码中,net/http、net、sql包的使用方法如下:</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// net/http</span><span class="token keyword">func</span> <span class="token punctuation">(</span>r <span class="token operator">*</span>Request<span class="token punctuation">)</span> <span class="token function">WithContext</span><span class="token punctuation">(</span>ctx context<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token operator">*</span>Request<span class="token comment" spellcheck="true">// sql</span><span class="token keyword">func</span> <span class="token punctuation">(</span>db <span class="token operator">*</span>DB<span class="token punctuation">)</span> <span class="token function">BeginTx</span><span class="token punctuation">(</span>ctx context<span class="token punctuation">.</span>Context<span class="token punctuation">,</span> opts <span class="token operator">*</span>TxOptions<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token operator">*</span>Tx<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// net</span><span class="token keyword">func</span> <span class="token punctuation">(</span>d <span class="token operator">*</span>Dialer<span class="token punctuation">)</span> <span class="token function">DialContext</span><span class="token punctuation">(</span>ctx context<span class="token punctuation">.</span>Context<span class="token punctuation">,</span> network<span class="token punctuation">,</span> address <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>Conn<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><h3 id="1-2-context接口详解"><a href="#1-2-context接口详解" class="headerlink" title="1.2 context接口详解"></a>1.2 context接口详解</h3><h4 id="1-2-1-context接口结构"><a href="#1-2-1-context接口结构" class="headerlink" title="1.2.1 context接口结构"></a>1.2.1 context接口结构</h4><p><code>context.Context</code>本质是一个接口,提供了4种方法:</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> Context <span class="token keyword">interface</span> <span class="token punctuation">{</span> <span class="token function">Deadline</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>deadline time<span class="token punctuation">.</span>Time<span class="token punctuation">,</span> ok <span class="token builtin">bool</span><span class="token punctuation">)</span> <span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator"><-</span><span class="token keyword">chan</span> <span class="token keyword">struct</span><span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token function">Err</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">error</span> <span class="token function">Value</span><span class="token punctuation">(</span>key <span class="token keyword">interface</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">interface</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="/images/20220328/1648468559536-05c4145d-3397-4fd8-aaed-19e3e78e49eb-20220328233245645.png" alt="context-interface" style="zoom:67%;"><h4 id="1-2-2-context接口实现"><a href="#1-2-2-context接口实现" class="headerlink" title="1.2.2 context接口实现"></a>1.2.2 context接口实现</h4><p><code>context</code>只是一个接口,意味着需要有具体的实现。Go标准库对此进行了简单的实现,用户也可以按照接口定义的方法自己实现。<br>context包中定义了一个空的context类<code>emptyCtx</code>,本质是int别名,并对上述4种函数进行了实现。</p><pre class="line-numbers language-go"><code class="language-go">$GOPATH<span class="token operator">/</span>src<span class="token operator">/</span>context<span class="token operator">/</span>context<span class="token punctuation">.</span><span class="token keyword">go</span><span class="token comment" spellcheck="true">// An emptyCtx is never canceled, has no values, and has no deadline. It is not</span><span class="token comment" spellcheck="true">// struct{}, since vars of this type must have distinct addresses.</span><span class="token keyword">type</span> emptyCtx <span class="token builtin">int</span><span class="token keyword">func</span> <span class="token punctuation">(</span><span class="token operator">*</span>emptyCtx<span class="token punctuation">)</span> <span class="token function">Deadline</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>deadline time<span class="token punctuation">.</span>Time<span class="token punctuation">,</span> ok <span class="token builtin">bool</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span><span class="token operator">*</span>emptyCtx<span class="token punctuation">)</span> <span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator"><-</span><span class="token keyword">chan</span> <span class="token keyword">struct</span><span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span><span class="token operator">*</span>emptyCtx<span class="token punctuation">)</span> <span class="token function">Err</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">error</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span><span class="token operator">*</span>emptyCtx<span class="token punctuation">)</span> <span class="token function">Value</span><span class="token punctuation">(</span>key any<span class="token punctuation">)</span> any <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>e <span class="token operator">*</span>emptyCtx<span class="token punctuation">)</span> <span class="token function">String</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> e <span class="token punctuation">{</span> <span class="token keyword">case</span> background<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">"context.Background"</span> <span class="token keyword">case</span> todo<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">"context.TODO"</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token string">"unknown empty Context"</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>之后声明了两个emptyCtx的对象:<code>background</code>&<code>todo</code>,并提供相应的获取方式,妥妥的<code>单例模式-懒汉版本</code></p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">var</span> <span class="token punctuation">(</span> background <span class="token operator">=</span> <span class="token function">new</span><span class="token punctuation">(</span>emptyCtx<span class="token punctuation">)</span> todo <span class="token operator">=</span> <span class="token function">new</span><span class="token punctuation">(</span>emptyCtx<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// Background returns a non-nil, empty Context. It is never canceled, has no</span><span class="token comment" spellcheck="true">// values, and has no deadline. It is typically used by the main function,</span><span class="token comment" spellcheck="true">// initialization, and tests, and as the top-level Context for incoming</span><span class="token comment" spellcheck="true">// requests.</span><span class="token keyword">func</span> <span class="token function">Background</span><span class="token punctuation">(</span><span class="token punctuation">)</span> Context <span class="token punctuation">{</span> <span class="token keyword">return</span> background<span class="token punctuation">}</span><span class="token comment" spellcheck="true">// TODO returns a non-nil, empty Context. Code should use context.TODO when</span><span class="token comment" spellcheck="true">// it's unclear which Context to use or it is not yet available (because the</span><span class="token comment" spellcheck="true">// surrounding function has not yet been extended to accept a Context</span><span class="token comment" spellcheck="true">// parameter).</span><span class="token keyword">func</span> <span class="token function">TODO</span><span class="token punctuation">(</span><span class="token punctuation">)</span> Context <span class="token punctuation">{</span> <span class="token keyword">return</span> todo<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>调用context.Background函数或context.TODO函数会返回最简单的context实现。context.Background函数一般作为根对象存在,其不可以退出,也不能携带值。要具体地使用context的功能,需要派生出新的context,配套的使用函数如下,其中前三个函数用于处理退出。</p><img src="/images/20220328/派生context配套函数.png" alt="派生context配套函数" style="zoom:67%;"><br><br><h3 id="1-3-context原理"><a href="#1-3-context原理" class="headerlink" title="1.3 context原理"></a>1.3 context原理</h3><p>context在很大程度上利用了通道在close时会通知所有监听它的协程这一特性来实现。每个派生出的子协程都会创建一个新的退出通道,组织好context之间的关系即可实现继承链上退出的传递,图17-2所示的三个协程中,关闭通道A会连带关闭调用链上的通道B、通道C。</p><img src="/images/20220328/context退出.png" alt="context退出" style="zoom:67%;"><p>前面也已经提到,context包中定义了结构体emptyCtx,emptyCtx什么内容都没有,其不可以被退出,也不能携带值。<br><code>background</code>和<code>todo</code>是emptyCtx的实例化对象,可以通过<code>context.Background</code>函数或<code>context.TODO</code>函数获取到,一般作为最初始的根对象。</p><h4 id="1-3-1-WithCancel"><a href="#1-3-1-WithCancel" class="headerlink" title="1.3.1 WithCancel"></a>1.3.1 WithCancel</h4><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// WithCancel returns a copy of parent with a new Done channel. The returned</span><span class="token comment" spellcheck="true">// context's Done channel is closed when the returned cancel function is called</span><span class="token comment" spellcheck="true">// or when the parent context's Done channel is closed, whichever happens first.</span><span class="token comment" spellcheck="true">//</span><span class="token comment" spellcheck="true">// Canceling this context releases resources associated with it, so code should</span><span class="token comment" spellcheck="true">// call cancel as soon as the operations running in this Context complete.</span><span class="token keyword">func</span> <span class="token function">WithCancel</span><span class="token punctuation">(</span>parent Context<span class="token punctuation">)</span> <span class="token punctuation">(</span>ctx Context<span class="token punctuation">,</span> cancel CancelFunc<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> parent <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token function">panic</span><span class="token punctuation">(</span><span class="token string">"cannot create context from nil parent"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> c <span class="token operator">:=</span> <span class="token function">newCancelCtx</span><span class="token punctuation">(</span>parent<span class="token punctuation">)</span> <span class="token function">propagateCancel</span><span class="token punctuation">(</span>parent<span class="token punctuation">,</span> <span class="token operator">&</span>c<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">&</span>c<span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> c<span class="token punctuation">.</span><span class="token function">cancel</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">,</span> Canceled<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>当调用WithCancel函数时,会产生一个子context结构cancelCtx,并保留了父context的信息。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// newCancelCtx returns an initialized cancelCtx.</span><span class="token keyword">func</span> <span class="token function">newCancelCtx</span><span class="token punctuation">(</span>parent Context<span class="token punctuation">)</span> cancelCtx <span class="token punctuation">{</span> <span class="token keyword">return</span> cancelCtx<span class="token punctuation">{</span>Context<span class="token punctuation">:</span> parent<span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// A cancelCtx can be canceled. When canceled, it also cancels any children</span><span class="token comment" spellcheck="true">// that implement canceler.</span><span class="token keyword">type</span> cancelCtx <span class="token keyword">struct</span> <span class="token punctuation">{</span> Context mu sync<span class="token punctuation">.</span>Mutex <span class="token comment" spellcheck="true">// protects following fields</span> done atomic<span class="token punctuation">.</span>Value <span class="token comment" spellcheck="true">// of chan struct{}, created lazily, closed by first cancel call</span> children <span class="token keyword">map</span><span class="token punctuation">[</span>canceler<span class="token punctuation">]</span><span class="token keyword">struct</span><span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// set to nil by the first cancel call</span> err <span class="token builtin">error</span> <span class="token comment" spellcheck="true">// set to non-nil by the first cancel call</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>children字段保存当前context之后派生的子context的信息,每个context都会有一个新的done通道,这保证了子context的退出不会影响父context。</p><h4 id="1-3-2-WithTimeout"><a href="#1-3-2-WithTimeout" class="headerlink" title="1.3.2 WithTimeout"></a>1.3.2 WithTimeout</h4><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).</span><span class="token comment" spellcheck="true">//</span><span class="token comment" spellcheck="true">// Canceling this context releases resources associated with it, so code should</span><span class="token comment" spellcheck="true">// call cancel as soon as the operations running in this Context complete:</span><span class="token comment" spellcheck="true">//</span><span class="token comment" spellcheck="true">// func slowOperationWithTimeout(ctx context.Context) (Result, error) {</span><span class="token comment" spellcheck="true">// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)</span><span class="token comment" spellcheck="true">// defer cancel() // releases resources if slowOperation completes before timeout elapses</span><span class="token comment" spellcheck="true">// return slowOperation(ctx)</span><span class="token comment" spellcheck="true">// }</span><span class="token keyword">func</span> <span class="token function">WithTimeout</span><span class="token punctuation">(</span>parent Context<span class="token punctuation">,</span> timeout time<span class="token punctuation">.</span>Duration<span class="token punctuation">)</span> <span class="token punctuation">(</span>Context<span class="token punctuation">,</span> CancelFunc<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">WithDeadline</span><span class="token punctuation">(</span>parent<span class="token punctuation">,</span> time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>timeout<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>WithTimeout函数只是对时间的一个封装,最终会调用WithDeadline函数。</p><h4 id="1-3-3-WithDeadline"><a href="#1-3-3-WithDeadline" class="headerlink" title="1.3.3 WithDeadline"></a>1.3.3 WithDeadline</h4><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// WithDeadline returns a copy of the parent context with the deadline adjusted</span><span class="token comment" spellcheck="true">// to be no later than d. If the parent's deadline is already earlier than d,</span><span class="token comment" spellcheck="true">// WithDeadline(parent, d) is semantically equivalent to parent. The returned</span><span class="token comment" spellcheck="true">// context's Done channel is closed when the deadline expires, when the returned</span><span class="token comment" spellcheck="true">// cancel function is called, or when the parent context's Done channel is</span><span class="token comment" spellcheck="true">// closed, whichever happens first.</span><span class="token comment" spellcheck="true">//</span><span class="token comment" spellcheck="true">// Canceling this context releases resources associated with it, so code should</span><span class="token comment" spellcheck="true">// call cancel as soon as the operations running in this Context complete.</span><span class="token keyword">func</span> <span class="token function">WithDeadline</span><span class="token punctuation">(</span>parent Context<span class="token punctuation">,</span> d time<span class="token punctuation">.</span>Time<span class="token punctuation">)</span> <span class="token punctuation">(</span>Context<span class="token punctuation">,</span> CancelFunc<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> parent <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token function">panic</span><span class="token punctuation">(</span><span class="token string">"cannot create context from nil parent"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> cur<span class="token punctuation">,</span> ok <span class="token operator">:=</span> parent<span class="token punctuation">.</span><span class="token function">Deadline</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> ok <span class="token operator">&&</span> cur<span class="token punctuation">.</span><span class="token function">Before</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// The current deadline is already sooner than the new one.</span> <span class="token keyword">return</span> <span class="token function">WithCancel</span><span class="token punctuation">(</span>parent<span class="token punctuation">)</span> <span class="token punctuation">}</span> c <span class="token operator">:=</span> <span class="token operator">&</span>timerCtx<span class="token punctuation">{</span> cancelCtx<span class="token punctuation">:</span> <span class="token function">newCancelCtx</span><span class="token punctuation">(</span>parent<span class="token punctuation">)</span><span class="token punctuation">,</span> deadline<span class="token punctuation">:</span> d<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token function">propagateCancel</span><span class="token punctuation">(</span>parent<span class="token punctuation">,</span> c<span class="token punctuation">)</span> dur <span class="token operator">:=</span> time<span class="token punctuation">.</span><span class="token function">Until</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span> <span class="token keyword">if</span> dur <span class="token operator"><=</span> <span class="token number">0</span> <span class="token punctuation">{</span> c<span class="token punctuation">.</span><span class="token function">cancel</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">,</span> DeadlineExceeded<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// deadline has already passed</span> <span class="token keyword">return</span> c<span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> c<span class="token punctuation">.</span><span class="token function">cancel</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">,</span> Canceled<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> c<span class="token punctuation">.</span>mu<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">defer</span> c<span class="token punctuation">.</span>mu<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">if</span> c<span class="token punctuation">.</span>err <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> c<span class="token punctuation">.</span>timer <span class="token operator">=</span> time<span class="token punctuation">.</span><span class="token function">AfterFunc</span><span class="token punctuation">(</span>dur<span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> c<span class="token punctuation">.</span><span class="token function">cancel</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">,</span> DeadlineExceeded<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> c<span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> c<span class="token punctuation">.</span><span class="token function">cancel</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">,</span> Canceled<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>WithDeadline函数先判断父context是否比当前设置的超时参数d先退出,如果是,那么子协程会随着父context的退出而退出,没有必要再设置定时器。然后创建一个新的context,初始化通道。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// propagateCancel arranges for child to be canceled when parent is.</span><span class="token keyword">func</span> <span class="token function">propagateCancel</span><span class="token punctuation">(</span>parent Context<span class="token punctuation">,</span> child canceler<span class="token punctuation">)</span> <span class="token punctuation">{</span> done <span class="token operator">:=</span> parent<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">if</span> done <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token comment" spellcheck="true">// parent is never canceled</span> <span class="token punctuation">}</span> <span class="token keyword">select</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token operator"><-</span>done<span class="token punctuation">:</span> <span class="token comment" spellcheck="true">// parent is already canceled</span> child<span class="token punctuation">.</span><span class="token function">cancel</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">,</span> parent<span class="token punctuation">.</span><span class="token function">Err</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> p<span class="token punctuation">,</span> ok <span class="token operator">:=</span> <span class="token function">parentCancelCtx</span><span class="token punctuation">(</span>parent<span class="token punctuation">)</span><span class="token punctuation">;</span> ok <span class="token punctuation">{</span> p<span class="token punctuation">.</span>mu<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">if</span> p<span class="token punctuation">.</span>err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// parent has already been canceled</span> child<span class="token punctuation">.</span><span class="token function">cancel</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">,</span> p<span class="token punctuation">.</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> p<span class="token punctuation">.</span>children <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> p<span class="token punctuation">.</span>children <span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">map</span><span class="token punctuation">[</span>canceler<span class="token punctuation">]</span><span class="token keyword">struct</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> p<span class="token punctuation">.</span>children<span class="token punctuation">[</span>child<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">struct</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span> p<span class="token punctuation">.</span>mu<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> atomic<span class="token punctuation">.</span><span class="token function">AddInt32</span><span class="token punctuation">(</span><span class="token operator">&</span>goroutines<span class="token punctuation">,</span> <span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">go</span> <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">select</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token operator"><-</span>parent<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> child<span class="token punctuation">.</span><span class="token function">cancel</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">,</span> parent<span class="token punctuation">.</span><span class="token function">Err</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">case</span> <span class="token operator"><-</span>child<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>propagateCancel函数会将子context加入父协程的children哈希表中,并开启一个定时器。当定时器到期时,会调用cancel方法关闭通道。</p><h4 id="1-3-4-WithValue"><a href="#1-3-4-WithValue" class="headerlink" title="1.3.4 WithValue"></a>1.3.4 WithValue</h4><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// WithValue returns a copy of parent in which the value associated with key is</span><span class="token comment" spellcheck="true">// val.</span><span class="token comment" spellcheck="true">//</span><span class="token comment" spellcheck="true">// Use context Values only for request-scoped data that transits processes and</span><span class="token comment" spellcheck="true">// APIs, not for passing optional parameters to functions.</span><span class="token comment" spellcheck="true">//</span><span class="token comment" spellcheck="true">// The provided key must be comparable and should not be of type</span><span class="token comment" spellcheck="true">// string or any other built-in type to avoid collisions between</span><span class="token comment" spellcheck="true">// packages using context. Users of WithValue should define their own</span><span class="token comment" spellcheck="true">// types for keys. To avoid allocating when assigning to an</span><span class="token comment" spellcheck="true">// interface{}, context keys often have concrete type</span><span class="token comment" spellcheck="true">// struct{}. Alternatively, exported context key variables' static</span><span class="token comment" spellcheck="true">// type should be a pointer or interface.</span><span class="token keyword">func</span> <span class="token function">WithValue</span><span class="token punctuation">(</span>parent Context<span class="token punctuation">,</span> key<span class="token punctuation">,</span> val any<span class="token punctuation">)</span> Context <span class="token punctuation">{</span> <span class="token keyword">if</span> parent <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token function">panic</span><span class="token punctuation">(</span><span class="token string">"cannot create context from nil parent"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> key <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token function">panic</span><span class="token punctuation">(</span><span class="token string">"nil key"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token operator">!</span>reflectlite<span class="token punctuation">.</span><span class="token function">TypeOf</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Comparable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">panic</span><span class="token punctuation">(</span><span class="token string">"key is not comparable"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token operator">&</span>valueCtx<span class="token punctuation">{</span>parent<span class="token punctuation">,</span> key<span class="token punctuation">,</span> val<span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// A valueCtx carries a key-value pair. It implements Value for that key and</span><span class="token comment" spellcheck="true">// delegates all other calls to the embedded Context.</span><span class="token keyword">type</span> valueCtx <span class="token keyword">struct</span> <span class="token punctuation">{</span> Context key<span class="token punctuation">,</span> val any<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>WithValue返回一个valueCtx结构体实例。</p><hr><br><br><h2 id="2-数据争用-data-race"><a href="#2-数据争用-data-race" class="headerlink" title="2. 数据争用 data race"></a>2. 数据争用 data race</h2><h3 id="2-1-两个例子"><a href="#2-1-两个例子" class="headerlink" title="2.1 两个例子"></a>2.1 两个例子</h3><p>数据争用(data race)在Go语言中指两个协程同时访问相同的内存空间,并且至少有一个写操作的情况。这种情况常常是并发错误的根源,也是最难调试的并发错误之一。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"time"</span><span class="token punctuation">)</span><span class="token keyword">var</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token keyword">func</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> count <span class="token operator">+=</span> <span class="token number">1</span> time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span><span class="token number">10</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Millisecond<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">go</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">go</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">go</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>三个协程共同访问了全局变量count,乍看之下可能没有问题,但是该程序其实是有数据争用的,count的结果也是不明确的。count+=1操作看起来是一条指令,但是对CPU来说,需要先读取count的值,执行+1操作,再将count的值写回内存。</p><p>再举一例,Go语言中的经典数据争用错误。如下伪代码所示,在hash表中,存储了我们希望存储到Redis数据库中的数据(data)。但是在Go语言使用range时,变量k是一个堆上地址不变的对象,该地址存储的值会随着range遍历而发生变化。如果此时我们将变量k的地址放入协程save,以提供并发存储而不堵塞程序,那么最后的结果可能是后面的数据覆盖前面的数据,同时导致一些数据不被存储,并且每一次执行完存储的数据也是不明确的。</p><img src="/images/20220328/数据争用.png" alt="数据争用" style="zoom:67%;"><br><h3 id="2-2-检测工具race"><a href="#2-2-检测工具race" class="headerlink" title="2.2 检测工具race"></a>2.2 检测工具race</h3><p>Go 1.1后提供了强大的检查工具race来排查数据争用问题。race可以使用在多个Go指令中,当检测器在程序中找到数据争用时,将打印报告。对于第一个例子:</p><pre class="line-numbers language-bash"><code class="language-bash">go run -race main.gooutput:<span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span>WARNING: DATA RACERead at 0x000100799880 by goroutine 8: main.add<span class="token punctuation">(</span><span class="token punctuation">)</span> /Users/xuxiangfei/Code/Demos/test_demos/main.go:11 +0x2cPrevious <span class="token function">write</span> at 0x000100799880 by goroutine 7: main.add<span class="token punctuation">(</span><span class="token punctuation">)</span> /Users/xuxiangfei/Code/Demos/test_demos/main.go:11 +0x40Goroutine 8 <span class="token punctuation">(</span>running<span class="token punctuation">)</span> created at: main.main<span class="token punctuation">(</span><span class="token punctuation">)</span> /Users/xuxiangfei/Code/Demos/test_demos/main.go:17 +0x38Goroutine 7 <span class="token punctuation">(</span>running<span class="token punctuation">)</span> created at: main.main<span class="token punctuation">(</span><span class="token punctuation">)</span> /Users/xuxiangfei/Code/Demos/test_demos/main.go:16 +0x2c<span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span>3Found 1 data race<span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token keyword">exit</span> status 66<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>报错后输出的栈帧信息中能看出具体发生冲突的位置。Read at表明读取发生在main.go文件的第11行,而Previous write表明前一个写入也发生在main.go文件的第11行,从而非常快速地发现并定位数据争用问题。<br>竞争检测的成本因程序而异,对于典型的程序,内存使用量可能增加5~10倍,执行时间会增加2~20倍。</p><hr><br><br><h2 id="3-锁机制"><a href="#3-锁机制" class="headerlink" title="3. 锁机制"></a>3. 锁机制</h2><p>为了解决数据争用以及其他并发问题,Go语言提供了一系列的锁机制。</p><h3 id="3-1-原子锁"><a href="#3-1-原子锁" class="headerlink" title="3.1 原子锁"></a>3.1 原子锁</h3><p>所谓原子操作,就是“不可中断的一个或一系列操作”。对于2.1中的例子count+=1实际上并不是一个原子操作,读内存和写内存是两步原子操作。<br>需要有一种机制解决并发访问时数据冲突及内存操作乱序的问题,即提供一种原子性的操作。这通常依赖硬件的支持,例如X86指令集中的LOCK指令,对应Go语言中的<code>**sync/atomic**</code>包。下例使用了<code>atomic.AddInt64</code>函数将变量加1,这种原子操作不会发生并发时的数据争用问题。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"sync/atomic"</span> <span class="token string">"time"</span><span class="token punctuation">)</span><span class="token keyword">var</span> count <span class="token builtin">int64</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token keyword">func</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> atomic<span class="token punctuation">.</span><span class="token function">AddInt64</span><span class="token punctuation">(</span><span class="token operator">&</span>count<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span><span class="token number">10</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Millisecond<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">go</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">go</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">go</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>原子操作是底层最基础的同步保证,通过原子操作可以构建起许多同步原语,例如自旋锁、信号量、互斥锁等。</p><br><h3 id="3-2-自旋锁"><a href="#3-2-自旋锁" class="headerlink" title="3.2 自旋锁"></a>3.2 自旋锁</h3><p>在sync/atomic包中还有一个重要的操作—<code>CompareAndSwap</code>,与元素值进行对比并替换。使用该操作我们能够构建起<code>自旋锁</code>,只有获取该锁,才能执行区域中的代码。如下所示,使用一个for循环不断轮询原子操作,直到原子操作成功才获取该锁。<br>自旋锁会空转浪费资源,对系统对性能有很大的影响。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"sync/atomic"</span> <span class="token string">"time"</span><span class="token punctuation">)</span><span class="token keyword">var</span> flag <span class="token builtin">int64</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token keyword">var</span> count <span class="token builtin">int64</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token keyword">func</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> atomic<span class="token punctuation">.</span><span class="token function">CompareAndSwapInt64</span><span class="token punctuation">(</span><span class="token operator">&</span>flag<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> count<span class="token operator">++</span> atomic<span class="token punctuation">.</span><span class="token function">StoreInt64</span><span class="token punctuation">(</span><span class="token operator">&</span>flag<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span><span class="token number">10</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Millisecond<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">go</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">go</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">go</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><h3 id="3-3-互斥锁"><a href="#3-3-互斥锁" class="headerlink" title="3.3 互斥锁"></a>3.3 互斥锁</h3><p>通过原子操作构建起的互斥锁,虽然高效而且简单,但是其并不是万能的。例如,当某一个协程长时间霸占锁,其他协程抢占锁时将无意义地消耗CPU资源。同时当有许多正在获取锁的协程时,可能有协程一直抢占不到锁。</p><h4 id="3-3-1-思想介绍"><a href="#3-3-1-思想介绍" class="headerlink" title="3.3.1 思想介绍"></a>3.3.1 思想介绍</h4><p>为了解决这种问题,操作系统的锁接口提供了<strong>终止与唤醒</strong>的机制,例如Linux中的pthread mutex,避免了频繁自旋造成的浪费。<br>在操作系统内部会构建起锁的等待队列,以便之后依次被唤醒。调用操作系统级别的锁会锁住整个线程使之无法运行,另外锁的抢占还会涉及线程之间的上下文切换。</p><h4 id="3-3-2-使用说明"><a href="#3-3-2-使用说明" class="headerlink" title="3.3.2 使用说明"></a>3.3.2 使用说明</h4><p>Go语言拥有比线程更加轻量级的协程,在协程的基础上实现了一种比传统操作系统级别的锁更加轻量级的互斥锁,其使用方式如下所示。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">var</span> count <span class="token builtin">int64</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token keyword">var</span> m sync<span class="token punctuation">.</span>Mutex<span class="token keyword">func</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> m<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> count <span class="token operator">+=</span> <span class="token number">1</span> m<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>sync.Mutex构建起了互斥锁,在同一时刻,只会有一个获取锁的协程继续执行,而其他的协程将陷入等待状态,这和自旋锁的功能是类似的,但是其提供了更加复杂的机制避免自旋锁的争用问题。</p><h4 id="3-3-3-实现原理"><a href="#3-3-3-实现原理" class="headerlink" title="3.3.3 实现原理"></a>3.3.3 实现原理</h4><h5 id="3-3-3-1-数据结构"><a href="#3-3-3-1-数据结构" class="headerlink" title="3.3.3.1 数据结构"></a>3.3.3.1 数据结构</h5><p>互斥锁是一种混合锁,其实现方式包含了自旋锁,同时参考了操作系统锁的实现。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// A Mutex is a mutual exclusion lock.</span><span class="token comment" spellcheck="true">// The zero value for a Mutex is an unlocked mutex.</span><span class="token comment" spellcheck="true">//</span><span class="token comment" spellcheck="true">// A Mutex must not be copied after first use.</span><span class="token keyword">type</span> Mutex <span class="token keyword">struct</span> <span class="token punctuation">{</span> state <span class="token builtin">int32</span> sema <span class="token builtin">uint32</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>sync.Mutex结构比较简单,其包含了表示当前锁状态的state及信号量sema。</p><ul><li>state:通过位图的形式存储了当前锁的状态,如图所示,其中包含锁是否为锁定状态、正在等待被锁唤醒的协程数量、两个和饥饿模式有关的标志。</li></ul><img src="/images/20220328/Mutex.png" alt="Mutex" style="zoom:67%;"><ul><li>sema:互质锁中实现的信号量。</li></ul><p>为了解决某一个协程可能长时间无法获取锁的问题,Go 1.9之后使用了饥饿模式。在饥饿模式下,unlock会唤醒最先申请加速的协程,从而保证公平。</p><h5 id="3-3-3-1-正常模式-amp-饥饿模式"><a href="#3-3-3-1-正常模式-amp-饥饿模式" class="headerlink" title="3.3.3.1 正常模式 & 饥饿模式"></a>3.3.3.1 正常模式 & 饥饿模式</h5><p>Mutex有两种模式:正常模式 与 饥饿模式</p><p><strong>正常模式</strong>:锁的等待者会按照先进先出的顺序获取锁。刚被唤起的 Goroutine 与新创建的 Goroutine 竞争时,大概率会获取不到锁,为了减少这种情况的出现,一旦 Goroutine 超过 1ms 没有获取到锁,它就会将当前互斥锁切换饥饿模式,防止部分 Goroutine 被“饿死”。</p><p><strong>饥饿模式</strong>:互斥锁会直接交给等待队列最前面的 Goroutine。新的 Goroutine 在该状态下不能获取锁、也不会进入自旋状态,它们只会在队列的末尾等待。如果一个 Goroutine 获得了互斥锁并且它在队列的末尾或者它等待的时间少于 1ms,那么当前的互斥锁就会切换回正常模式。</p><h5 id="3-3-3-2-获取锁"><a href="#3-3-3-2-获取锁" class="headerlink" title="3.3.3.2 获取锁"></a>3.3.3.2 获取锁</h5><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// Lock locks m.</span><span class="token comment" spellcheck="true">// If the lock is already in use, the calling goroutine</span><span class="token comment" spellcheck="true">// blocks until the mutex is available.</span><span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Mutex<span class="token punctuation">)</span> <span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// Fast path: grab unlocked mutex.</span> <span class="token keyword">if</span> atomic<span class="token punctuation">.</span><span class="token function">CompareAndSwapInt32</span><span class="token punctuation">(</span><span class="token operator">&</span>m<span class="token punctuation">.</span>state<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> mutexLocked<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> race<span class="token punctuation">.</span>Enabled <span class="token punctuation">{</span> race<span class="token punctuation">.</span><span class="token function">Acquire</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// Slow path (outlined so that the fast path can be inlined)</span> m<span class="token punctuation">.</span><span class="token function">lockSlow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="/images/20220328/获取互斥锁.png" alt="获取互斥锁" style="zoom: 67%;"><br><h3 id="3-4-读写锁"><a href="#3-4-读写锁" class="headerlink" title="3.4 读写锁"></a>3.4 读写锁</h3><h4 id="3-4-1-思想介绍"><a href="#3-4-1-思想介绍" class="headerlink" title="3.4.1 思想介绍"></a>3.4.1 思想介绍</h4><p>读写锁通过两种锁来实现,一种为读锁,另一种为写锁。当进行读取操作时,需要加读锁,而进行写入操作时需要加写锁。</p><table><thead><tr><th></th><th>读锁</th><th>写锁</th></tr></thead><tbody><tr><td>读锁</td><td>不互斥</td><td>互斥</td></tr><tr><td>写锁</td><td>互斥</td><td>互斥</td></tr></tbody></table><ol><li>多个协程可以同时获得读锁并执行</li><li>如果此时有协程申请了写锁,那么该写锁会等待所有的读锁都释放后才能获取写锁继续执行</li><li>如果当前的协程申请读锁时已经存在写锁,那么读锁会等待写锁释放后再获取锁继续执行</li></ol><p>举个例子,哈希表并不是并发安全的,它只能够并发读取,并发写入时会出现冲突。一种简单的规避方式如下所示,可以在获取map中的数据时加入RLock读锁,在写入数据时使用Lock写锁。</p><h4 id="3-4-2-实现原理"><a href="#3-4-2-实现原理" class="headerlink" title="3.4.2 实现原理"></a>3.4.2 实现原理</h4><p>读写锁复用了互斥锁及信号量这两种机制。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// There is a modified copy of this file in runtime/rwmutex.go.</span><span class="token comment" spellcheck="true">// If you make any changes here, see if you should make them there.</span><span class="token comment" spellcheck="true">// A RWMutex is a reader/writer mutual exclusion lock.</span><span class="token comment" spellcheck="true">// The lock can be held by an arbitrary number of readers or a single writer.</span><span class="token comment" spellcheck="true">// The zero value for a RWMutex is an unlocked mutex.</span><span class="token comment" spellcheck="true">//</span><span class="token comment" spellcheck="true">// A RWMutex must not be copied after first use.</span><span class="token comment" spellcheck="true">//</span><span class="token comment" spellcheck="true">// If a goroutine holds a RWMutex for reading and another goroutine might</span><span class="token comment" spellcheck="true">// call Lock, no goroutine should expect to be able to acquire a read lock</span><span class="token comment" spellcheck="true">// until the initial read lock is released. In particular, this prohibits</span><span class="token comment" spellcheck="true">// recursive read locking. This is to ensure that the lock eventually becomes</span><span class="token comment" spellcheck="true">// available; a blocked Lock call excludes new readers from acquiring the</span><span class="token comment" spellcheck="true">// lock.</span><span class="token keyword">type</span> RWMutex <span class="token keyword">struct</span> <span class="token punctuation">{</span> w Mutex <span class="token comment" spellcheck="true">// held if there are pending writers</span> writerSem <span class="token builtin">uint32</span> <span class="token comment" spellcheck="true">// semaphore for writers to wait for completing readers</span> readerSem <span class="token builtin">uint32</span> <span class="token comment" spellcheck="true">// semaphore for readers to wait for completing writers</span> readerCount <span class="token builtin">int32</span> <span class="token comment" spellcheck="true">// number of pending readers</span> readerWait <span class="token builtin">int32</span> <span class="token comment" spellcheck="true">// number of departing readers</span><span class="token punctuation">}</span><span class="token keyword">const</span> rwmutexMaxReaders <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator"><<</span> <span class="token number">30</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol><li><strong>读锁加锁</strong><br>先通过原子操作将readerCount加1,如果readerCount≥0就直接返回,所以如果只有获取读取锁的操作,那么其成本只有一个原子操作。当readerCount<0时,说明当前有写锁,当前协程将借助信号量陷入等待状态,如果获取到信号量则立即退出,没有获取到信号量时的逻辑与互斥锁的逻辑相似。</li><li><strong>读锁解锁</strong><br>如果当前没有写锁,则其成本只有一个原子操作并直接退出。<br>如果当前有写锁正在等待,则调用rUnlockSlow判断当前是否为最后一个被释放的读锁,如果是则需要增加信号量并唤醒写锁。</li><li><strong>写锁加锁</strong><br>调用Lock方法,必须先获取互斥锁,因为它复用了互斥锁的功能。接着readerCount减去rwmutexMaxReaders阻止后续的读操作。<br>但获取互斥锁并不一定能直接获取写锁,如果当前已经有其他Goroutine持有互斥锁的读锁,那么当前协程会加入全局等待队列并进入休眠状态,当最后一个读锁被释放时,会唤醒该协程。</li><li><strong>写锁解锁</strong><br>调用Unlock方法。将readerCount加上rwmutexMaxReaders,表示不会堵塞后续的读锁,依次唤醒所有等待中的读锁。当所有的读锁唤醒完毕后会释放互斥锁。</li></ol><br><h3 id="3-5-WaitGroup"><a href="#3-5-WaitGroup" class="headerlink" title="3.5 WaitGroup"></a>3.5 WaitGroup</h3><h4 id="3-5-1-使用方式"><a href="#3-5-1-使用方式" class="headerlink" title="3.5.1 使用方式"></a>3.5.1 使用方式</h4><p><code>sync.WaitGroup</code> 可以等待一组 Goroutine 的返回,一个比较常见的使用场景是批量发出 RPC 或者 HTTP 请求:</p><pre class="line-numbers language-go"><code class="language-go">requests <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">*</span>Request<span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span>wg <span class="token operator">:=</span> <span class="token operator">&</span>sync<span class="token punctuation">.</span>WaitGroup<span class="token punctuation">{</span><span class="token punctuation">}</span>wg<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token function">len</span><span class="token punctuation">(</span>requests<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> request <span class="token operator">:=</span> <span class="token keyword">range</span> requests <span class="token punctuation">{</span> <span class="token keyword">go</span> <span class="token keyword">func</span><span class="token punctuation">(</span>r <span class="token operator">*</span>Request<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">defer</span> wg<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// res, err := service.call(r)</span> <span class="token punctuation">}</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">}</span>wg<span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>类似于操作系统中的 <code>fork-in</code> 模式,<code>WaitGroup.Wait()</code> 会hang住,直到 <code>len(requests)</code> 个子协程全部 <code>Done()</code> 了。</p><h4 id="3-5-2-实现原理"><a href="#3-5-2-实现原理" class="headerlink" title="3.5.2 实现原理"></a>3.5.2 实现原理</h4><h5 id="3-5-2-1-数据结构"><a href="#3-5-2-1-数据结构" class="headerlink" title="3.5.2.1 数据结构"></a>3.5.2.1 数据结构</h5><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> WaitGroup <span class="token keyword">struct</span> <span class="token punctuation">{</span> noCopy noCopy state1 <span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token builtin">uint32</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><code>WaitGroup</code>结构体中只包含两个成员变量:</p><ul><li><code>noCopy</code> — 保证 <code>WaitGroup</code>不会被开发者通过再赋值的方式拷贝</li><li><code>state1</code> — 存储着状态和信号量;</li></ul><img src="/images/20220328/golang-waitgroup-state.png" alt="golang-waitgroup-state" style="zoom:67%;"><p><code>WaitGroup</code>对外暴露了三个方法:</p><ol><li><code>WaitGroup.Add</code></li><li><code>WaitGroup.Wait</code> </li><li><code>WaitGroup.Done</code> </li></ol><h5 id="3-5-2-2-WaitGroup-Add"><a href="#3-5-2-2-WaitGroup-Add" class="headerlink" title="3.5.2.2 WaitGroup.Add"></a>3.5.2.2 WaitGroup.Add</h5><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">func</span> <span class="token punctuation">(</span>wg <span class="token operator">*</span>WaitGroup<span class="token punctuation">)</span> <span class="token function">Add</span><span class="token punctuation">(</span>delta <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> statep<span class="token punctuation">,</span> semap <span class="token operator">:=</span> wg<span class="token punctuation">.</span><span class="token function">state</span><span class="token punctuation">(</span><span class="token punctuation">)</span> state <span class="token operator">:=</span> atomic<span class="token punctuation">.</span><span class="token function">AddUint64</span><span class="token punctuation">(</span>statep<span class="token punctuation">,</span> <span class="token function">uint64</span><span class="token punctuation">(</span>delta<span class="token punctuation">)</span><span class="token operator"><<</span><span class="token number">32</span><span class="token punctuation">)</span> v <span class="token operator">:=</span> <span class="token function">int32</span><span class="token punctuation">(</span>state <span class="token operator">>></span> <span class="token number">32</span><span class="token punctuation">)</span> w <span class="token operator">:=</span> <span class="token function">uint32</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token keyword">if</span> v <span class="token operator"><</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token function">panic</span><span class="token punctuation">(</span><span class="token string">"sync: negative WaitGroup counter"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> v <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">||</span> w <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token operator">*</span>statep <span class="token operator">=</span> <span class="token number">0</span> <span class="token keyword">for</span> <span class="token punctuation">;</span> w <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">;</span> w<span class="token operator">--</span> <span class="token punctuation">{</span> <span class="token function">runtime_Semrelease</span><span class="token punctuation">(</span>semap<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>WaitGroup.Add</code> 可以更新 <code>WaitGroup</code> 中的计数器 <code>counter</code>。虽然 <code>WaitGroup.Add</code> 方法传入的参数可以为负数,但是计数器只能是非负数,一旦出现负数就会发生程序崩溃。当调用计数器归零,即所有任务都执行完成时,才会通过 <code>sync.runtime_Semrelease</code> 唤醒处于等待状态的 Goroutine。</p><h5 id="3-5-2-3-WaitGroup-Wait"><a href="#3-5-2-3-WaitGroup-Wait" class="headerlink" title="3.5.2.3 WaitGroup.Wait"></a>3.5.2.3 WaitGroup.Wait</h5><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">func</span> <span class="token punctuation">(</span>wg <span class="token operator">*</span>WaitGroup<span class="token punctuation">)</span> <span class="token function">Wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> statep<span class="token punctuation">,</span> semap <span class="token operator">:=</span> wg<span class="token punctuation">.</span><span class="token function">state</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">for</span> <span class="token punctuation">{</span> state <span class="token operator">:=</span> atomic<span class="token punctuation">.</span><span class="token function">LoadUint64</span><span class="token punctuation">(</span>statep<span class="token punctuation">)</span> v <span class="token operator">:=</span> <span class="token function">int32</span><span class="token punctuation">(</span>state <span class="token operator">>></span> <span class="token number">32</span><span class="token punctuation">)</span> <span class="token keyword">if</span> v <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> atomic<span class="token punctuation">.</span><span class="token function">CompareAndSwapUint64</span><span class="token punctuation">(</span>statep<span class="token punctuation">,</span> state<span class="token punctuation">,</span> state<span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">runtime_Semacquire</span><span class="token punctuation">(</span>semap<span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token operator">+</span>statep <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token function">panic</span><span class="token punctuation">(</span><span class="token string">"sync: WaitGroup is reused before previous Wait has returned"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>WaitGroup.Add</code> 会在计数器大于 0 并且不存在等待的 Goroutine 时,调用 <code>runtime.sync_runtime_Semacquire</code>陷入睡眠。</p><p>当 <code>WaitGroup</code> 的计数器归零时,陷入睡眠状态的 Goroutine 会被唤醒,上述方法也会立刻返回。</p><h5 id="3-5-2-4-WaitGroup-Done"><a href="#3-5-2-4-WaitGroup-Done" class="headerlink" title="3.5.2.4 WaitGroup.Done"></a>3.5.2.4 WaitGroup.Done</h5><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// Done decrements the WaitGroup counter by one.</span><span class="token keyword">func</span> <span class="token punctuation">(</span>wg <span class="token operator">*</span>WaitGroup<span class="token punctuation">)</span> <span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> wg<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><code>WaitGroup.Done</code> 只是对 <code>WaitGroup.Add</code> 方法的简单封装,我们可以向 <code>sync.WaitGroup.Add</code> 方法传入任意负数(需要保证计数器非负)快速将计数器归零以唤醒等待的 Goroutine;</p><br><h3 id="3-6-Once"><a href="#3-6-Once" class="headerlink" title="3.6 Once"></a>3.6 Once</h3><h4 id="3-6-1-使用方式"><a href="#3-6-1-使用方式" class="headerlink" title="3.6.1 使用方式"></a>3.6.1 使用方式</h4><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> o <span class="token operator">:=</span> <span class="token operator">&</span>sync<span class="token punctuation">.</span>Once<span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span> o<span class="token punctuation">.</span><span class="token function">Do</span><span class="token punctuation">(</span><span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"only once"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// only once</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p> <code>sync.Once</code>可以保证在 Go 程序运行期间的某段代码只会执行一次。</p><h4 id="3-6-2-实现原理"><a href="#3-6-2-实现原理" class="headerlink" title="3.6.2 实现原理"></a>3.6.2 实现原理</h4><h5 id="3-6-2-1-数据结构"><a href="#3-6-2-1-数据结构" class="headerlink" title="3.6.2.1 数据结构"></a>3.6.2.1 数据结构</h5><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> Once <span class="token keyword">struct</span> <span class="token punctuation">{</span> done <span class="token builtin">uint32</span> m Mutex<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li>done: 标识代码块是否执行过</li><li>m: 互斥锁</li></ul><h5 id="3-6-2-2-Once-Do"><a href="#3-6-2-2-Once-Do" class="headerlink" title="3.6.2.2 Once.Do"></a>3.6.2.2 Once.Do</h5><p><code>Once.Do</code>是 <code>Once</code>结构体对外唯一暴露的方法,该方法会接收一个入参为空的函数:</p><ul><li>如果传入的函数已经执行过,会直接返回;</li><li>如果传入的函数没有执行过,会调用 <code>Once.doSlow</code>执行传入的函数:</li></ul><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">func</span> <span class="token punctuation">(</span>o <span class="token operator">*</span>Once<span class="token punctuation">)</span> <span class="token function">Do</span><span class="token punctuation">(</span>f <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> atomic<span class="token punctuation">.</span><span class="token function">LoadUint32</span><span class="token punctuation">(</span><span class="token operator">&</span>o<span class="token punctuation">.</span>done<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span> o<span class="token punctuation">.</span><span class="token function">doSlow</span><span class="token punctuation">(</span>f<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>o <span class="token operator">*</span>Once<span class="token punctuation">)</span> <span class="token function">doSlow</span><span class="token punctuation">(</span>f <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> o<span class="token punctuation">.</span>m<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">defer</span> o<span class="token punctuation">.</span>m<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">if</span> o<span class="token punctuation">.</span>done <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token keyword">defer</span> atomic<span class="token punctuation">.</span><span class="token function">StoreUint32</span><span class="token punctuation">(</span><span class="token operator">&</span>o<span class="token punctuation">.</span>done<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol><li>为当前 Goroutine 获取互斥锁;</li><li>执行传入的无入参函数;</li><li>运行延迟函数调用,将成员变量 <code>done</code> 更新成 1;</li></ol><h3 id="3-6-总结"><a href="#3-6-总结" class="headerlink" title="3.6 总结"></a>3.6 总结</h3><p>Go语言中的互斥锁算一种混合锁,结合了原子操作、自旋、信号量、全局哈希表、等待队列、操作系统级别锁等多种技术,实现相对复杂。<br>但是Go语言的锁相对于操作系统级别的锁<strong>更快</strong>,因为在大部分情况下锁的争用停留在用户态。在有些场景下使用锁更简单,会<strong>比通道有更清晰的语义表达</strong>,需要结合具体的场景使用。</p>]]></content>
<categories>
<category> golang </category>
</categories>
<tags>
<tag> golang </tag>
<tag> 并发 </tag>
</tags>
</entry>
<entry>
<title>Go数据结构-channel</title>
<link href="/2022/03/27/go-shu-ju-jie-gou-channel/"/>
<url>/2022/03/27/go-shu-ju-jie-gou-channel/</url>
<content type="html"><![CDATA[<h1 id="Go数据结构-channel"><a href="#Go数据结构-channel" class="headerlink" title="Go数据结构-channel"></a>Go数据结构-channel</h1><blockquote><p>参考: Go语言底层原理剖析 - 第16章 通道于协程间通信</p></blockquote><br><br><h2 id="1-channel是干啥的"><a href="#1-channel是干啥的" class="headerlink" title="1. channel是干啥的"></a>1. channel是干啥的</h2><p>Go语言之父<a href="https://github.com/robpike">Rob Pike</a>有句名言:“Don’t communicate by sharing memory, share memory by communicating.”,即“不要通过共享内存来通信,要通过通信来共享内存。”。</p><img src="/images/20220327/joi3kf3.png" alt="Quotes by Rob Pike" style="zoom:67%;"><p><strong>通过共享内存来通信</strong>比较简单的方法就是,在内存中开辟一块空间,多个线程或者进程可以同时访问这块空间,直觉上会觉得这种方式非常方便,但是也一定会出现<strong>数据冲突</strong>。为了解决数据冲突,人们又发明了各种锁,而并发性能也会因此下降。</p><p><strong>通过通信来实现进程/线程/协程之间的交互</strong>给出了一个新的解决方案,其思想主要是:先提供一个或多个高性能队列,线程/进程/协程需要访问别人时,不能直接读写别人的数据,而要通过队列提出请求,然后在对方处理请求时再做相应处理。Go语言给这种方案提供了一个利器:channel!</p><p>channel即通道,是Go语言中<strong>进行协程间通信的数据结构</strong>,是进行并发编程的利器,也是Go语言遵循CSP并发编程模式的结果,这种模型最重要的思想是通过通道来传递消息。</p><hr><br><br><h2 id="2-channel如何使用"><a href="#2-channel如何使用" class="headerlink" title="2. channel如何使用"></a>2. channel如何使用</h2><h3 id="2-1-声明与初始化"><a href="#2-1-声明与初始化" class="headerlink" title="2.1 声明与初始化"></a>2.1 声明与初始化</h3><h4 id="2-1-1-声明"><a href="#2-1-1-声明" class="headerlink" title="2.1.1 声明"></a>2.1.1 声明</h4><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">var</span> name <span class="token keyword">chan</span> T<span class="token comment" spellcheck="true">// name: channel的名称</span><span class="token comment" spellcheck="true">// chan T: channel的类型</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>声明时可以用箭头 <code><-</code> 限制通道的读写:</p><ol><li><code>chan int</code> 表示通道可以读写int;</li><li><code>chan<- int</code> 表示通道只能写入int;</li><li><code><-chan int</code> 表示通道只能读取int;</li></ol><p>一个还未初始化的通道会被预置为nil初始化的通道会被预置为nil,无法向通道中写入或读取任何数据。要对通道进行操作,需要使用make操作符,make会初始化通道,在内存中分配通道的空间。</p><h4 id="2-1-2-初始化"><a href="#2-1-2-初始化" class="headerlink" title="2.1.2 初始化"></a>2.1.2 初始化</h4><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">var</span> c <span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 无缓冲区</span><span class="token keyword">var</span> c <span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 有缓冲区</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>初始化的通道可以设置其缓冲区的长度,默认为无缓冲区。</p><br><h3 id="2-2-读写数据"><a href="#2-2-读写数据" class="headerlink" title="2.2 读写数据"></a>2.2 读写数据</h3><p>Go语言设计者将箭头 <code><-</code> 作为操作符进行通道的读取和写入。</p><h4 id="2-2-1-读写无缓冲区通道"><a href="#2-2-1-读写无缓冲区通道" class="headerlink" title="2.2.1 读写无缓冲区通道"></a>2.2.1 读写无缓冲区通道</h4><p>对于无缓冲通道,能够向通道写入数据的前提是必须有另一个协程在读取通道。否则,当前的协程会陷入休眠状态,最终陷入死锁。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> c <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span> c <span class="token operator"><-</span> <span class="token number">100</span> <span class="token comment" spellcheck="true">// or <- c</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// fatal error: all goroutines are asleep - deadlock!</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>有读有写</strong></p><p>读取通道有两种返回值的形式,借助编译时将该形式转换为不同的处理函数。第1个返回值仍然为通道读取到的数据,第2个返回值为布尔类型,返回值为false代表当前通道已经关闭。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"time"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">sender</span><span class="token punctuation">(</span>c <span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> data<span class="token punctuation">,</span> ok <span class="token operator">:=</span> <span class="token operator"><-</span>c fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> ok<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> c <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token keyword">go</span> <span class="token function">sender</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> c <span class="token operator"><-</span> <span class="token number">100</span> time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// hold住main协程</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 100 true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="2-2-2-读写有缓冲区通道"><a href="#2-2-2-读写有缓冲区通道" class="headerlink" title="2.2.2 读写有缓冲区通道"></a>2.2.2 读写有缓冲区通道</h4><p>对有缓存区的通道读/写数据,如果缓存区为空/缓存区满了,才会发生死锁。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> c <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// <-c</span> c <span class="token operator"><-</span> <span class="token number">100</span> c <span class="token operator"><-</span> <span class="token number">100</span> c <span class="token operator"><-</span> <span class="token number">100</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><h3 id="2-3-通道关闭"><a href="#2-3-通道关闭" class="headerlink" title="2.3 通道关闭"></a>2.3 通道关闭</h3><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> c <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span> c <span class="token operator"><-</span> <span class="token number">100</span> <span class="token function">close</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> c <span class="token operator"><-</span> <span class="token number">100</span> <span class="token comment" spellcheck="true">// panic: send on closed channel</span> <span class="token operator"><-</span>c <span class="token comment" spellcheck="true">// 正常</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在正常读取的情况下,通道返回的ok为true。通道在关闭时仍然会返回,但是data为其类型的零值,ok也变为了false。和通道读取不同的是,不能向已经关闭的通道中写入数据。</p><p>试图重复关闭一个channel将导致panic异常,试图关闭一个nil值的channel也将导致panic异常。</p><p>有两个协程正在等待通道中的数据,当main协程关闭通道后,两个协程都会收到通知。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"time"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> c <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token keyword">go</span> <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> data<span class="token punctuation">,</span> ok <span class="token operator">:=</span> <span class="token operator"><-</span>c fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"goroutine one:"</span><span class="token punctuation">,</span> data<span class="token punctuation">,</span> ok<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">go</span> <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> data<span class="token punctuation">,</span> ok <span class="token operator">:=</span> <span class="token operator"><-</span>c fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"goroutine two:"</span><span class="token punctuation">,</span> data<span class="token punctuation">,</span> ok<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token function">close</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// goroutine two: 0 false</span><span class="token comment" spellcheck="true">// goroutine one: 0 false</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><br><br><h2 id="3-select多路复用"><a href="#3-select多路复用" class="headerlink" title="3. select多路复用"></a>3. select多路复用</h2><p>在实践中使用通道时,更多的时候会与select结合,因为时常会出现多个通道与多个协程进行通信的情况,我们当然不希望由于一个通道的读写陷入堵塞,影响其他通道的正常读写。select正是为了解决这一问题诞生的,select赋予了Go语言更加强大的功能。在使用方法上,select的语法类似switch,形式如下:</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">select</span> <span class="token punctuation">{</span><span class="token keyword">case</span> <span class="token operator"><-</span>ch1<span class="token punctuation">:</span> <span class="token comment" spellcheck="true">// ...</span><span class="token keyword">case</span> <span class="token operator"><-</span>ch2<span class="token punctuation">:</span> <span class="token comment" spellcheck="true">// ...</span><span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true">// ...</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>和switch不同的是,每个case语句都必须对应通道的读写操作。select语句会陷入堵塞,直到一个或多个通道能够正常读写才恢复。</p><p>需要注意的是,select在使用时有几个特性:</p><ol><li>select随机选择机制<br>当多个通道同时准备好执行读写操作时,select会选择哪一个case执行呢?答案是具有一定的随机性。</li><li>select堵塞与控制<br>如果select中没有任何的通道准备好,那么当前select所在的协程会永远陷入等待,直到有一个case中的通道准备好为止。在实践中,为了避免这种情况发生,有时会加上default分支。default分支的作用是当所有的通道都陷入堵塞时,正常执行default分支。</li><li>循环select<br>很多时候,我们不希望select执行完一个分支就退出,而是循环往复执行select中的内容,因此需要将for与select进行组合。</li><li>select与nil<br>一个为nil的通道,不管是读取还是写入都将陷入堵塞状态。当select语句的case对nil通道进行操作时,case分支将永远得不到执行。</li></ol><hr><br><br><h2 id="4-channel底层原理"><a href="#4-channel底层原理" class="headerlink" title="4. channel底层原理"></a>4. channel底层原理</h2><h3 id="4-1-hchan数据结构"><a href="#4-1-hchan数据结构" class="headerlink" title="4.1 hchan数据结构"></a>4.1 hchan数据结构</h3><p>通道在运行时是一个hchan结构体,存储了数据列表,等待读取和发送的协程列表等字段。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> hchan <span class="token keyword">struct</span> <span class="token punctuation">{</span> qcount <span class="token builtin">uint</span> <span class="token comment" spellcheck="true">// total data in the queue</span> dataqsiz <span class="token builtin">uint</span> <span class="token comment" spellcheck="true">// size of the circular queue</span> buf unsafe<span class="token punctuation">.</span>Pointer <span class="token comment" spellcheck="true">// points to an array of dataqsiz elements</span> elemsize <span class="token builtin">uint16</span> closed <span class="token builtin">uint32</span> elemtype <span class="token operator">*</span>_type <span class="token comment" spellcheck="true">// element type</span> sendx <span class="token builtin">uint</span> <span class="token comment" spellcheck="true">// send index</span> recvx <span class="token builtin">uint</span> <span class="token comment" spellcheck="true">// receive index</span> recvq waitq <span class="token comment" spellcheck="true">// list of recv waiters</span> sendq waitq <span class="token comment" spellcheck="true">// list of send waiters</span> <span class="token comment" spellcheck="true">// lock protects all fields in hchan, as well as several</span> <span class="token comment" spellcheck="true">// fields in sudogs blocked on this channel.</span> <span class="token comment" spellcheck="true">//</span> <span class="token comment" spellcheck="true">// Do not change another G's status while holding this lock</span> <span class="token comment" spellcheck="true">// (in particular, do not ready a G), as this can deadlock</span> <span class="token comment" spellcheck="true">// with stack shrinking.</span> lock mutex<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="/images/20220327/hchan.png" alt="hchan" style="zoom:50%;"><p>对于有缓存的通道,存储在buf中的数据虽然是线性的数组,但是用数组和序号recvx、recvq模拟了一个环形队列。recvx可以找到从buf哪个位置获取通道中的元素,而sendx能够找到写入时放入buf的位置,而sendx能够找到写入时放入buf的位置。</p><img src="/images/20220327/buf.png" alt="buf" style="zoom:50%;"><p>当到达循环队列的末尾时,sendx会置为0,以防止其下一次写入0号位置,开始循环利用空间。这同样意味着,当前的通道中只能放入指定大小的数据。当通道中的数据满了后,再次写入数据将陷入等待,直到第0号位置被取出后,才能继续写入。</p><br><h3 id="4-2-初始化步骤"><a href="#4-2-初始化步骤" class="headerlink" title="4.2 初始化步骤"></a>4.2 初始化步骤</h3><p>初始化即给通道分配所需的内存:</p><ol><li>当分配大小为0时,只需要在内存中分配hchan结构体的大小即可;</li><li>当通道的元素中不包含指针时,连续分配hchan结构体大小+size元素大小;</li><li>当通道的元素中包含指针时,需要单独分配内存空间,因为当元素中包含指针时,需要单独分配空间才能正常进行垃圾回收。</li></ol><img src="/images/20220327/通道初始化.png" alt="通道初始化" style="zoom: 50%;"><br><h3 id="4-3-通道写入"><a href="#4-3-通道写入" class="headerlink" title="4.3 通道写入"></a>4.3 通道写入</h3><img src="/images/20220327/通道写入.png" alt="通道写入" style="zoom: 50%;"><br><h3 id="4-4-通道读取"><a href="#4-4-通道读取" class="headerlink" title="4.4 通道读取"></a>4.4 通道读取</h3><img src="/images/20220327/通道读取.png" alt="通道读取" style="zoom:50%;"><br><br>]]></content>
<categories>
<category> golang </category>
</categories>
<tags>
<tag> golang </tag>
<tag> 并发 </tag>
<tag> channel </tag>
</tags>
</entry>
<entry>
<title>Go协程-goroutine</title>
<link href="/2022/02/16/go-xie-cheng-goroutine/"/>
<url>/2022/02/16/go-xie-cheng-goroutine/</url>
<content type="html"><![CDATA[<h1 id="Go协程-goroutine"><a href="#Go协程-goroutine" class="headerlink" title="Go协程-goroutine"></a>Go协程-goroutine</h1><blockquote><p>参考<br> Go语言底层原理剖析 - 第14、15章 协程</p></blockquote><hr><br><br><h2 id="1-协程基础"><a href="#1-协程基础" class="headerlink" title="1 协程基础"></a>1 协程基础</h2><h3 id="1-1-进程与线程"><a href="#1-1-进程与线程" class="headerlink" title="1.1 进程与线程"></a>1.1 进程与线程</h3><p>协程可以看作轻量的线程,因此为了深入理解协程,必须对进程、线程及上下文切换等概念有所了解。 </p><p>在计算机科学中,<strong>线程是可以由调度程序(通常是操作系统的一部分)独立管理的最小程序指令集</strong>,<strong>而进程是程序运行的实例</strong>。简单来说,<strong>线程是程序执行的基本单位,进程是资源管理的基本单位</strong>。<br>线程是进程的组成部分,一个进程可以包含多个线程,这些线程并发执行并共享进程的内存(例如全局变量)等资源。而进程之间相对独立,不同进程具有不同的内存地址空间、代表程序运行的机器码、进程状态、操作系统资源描述符等。</p><img src="/images/20220216/线程与进程的区别.png" alt="线程与进程的区别" style="zoom: 50%;"><br><h3 id="1-2-线程上下文切换"><a href="#1-2-线程上下文切换" class="headerlink" title="1.2 线程上下文切换"></a>1.2 线程上下文切换</h3><p>在多核CPU上,线程可以分布在多个CPU核心上,从而实现真正的并行处理。为了平衡每个线程能够被CPU处理的时间并最大化利用CPU资源,操作系统需要在适当的时间通过定时器中断(Timer Interrupt)、I/O设备中断、系统调用时执行上下文切换(Context Switch)。</p><p>当发生线程上下文切换时,需要从操作系统用户态转移到内核态,记录上一个线程的重要寄存器值(例如栈寄存器SP)、进程状态等信息,这些信息存储在操作系统线程控制块(Thread Control Block)中。当切换到下一个要执行的线程时,需要加载重要的CPU寄存器值,并从内核态转移到操作系统用户态。</p><br><h3 id="1-3-线程与协程"><a href="#1-3-线程与协程" class="headerlink" title="1.3 线程与协程"></a>1.3 线程与协程</h3><p>一般来说,协程被认为是轻量级的线程。下面从调度方式、上下文切换的速度、调度策略、栈的大小这四个方面分析线程与协程的不同。</p><h4 id="1-3-1-调度方式"><a href="#1-3-1-调度方式" class="headerlink" title="1.3.1 调度方式"></a>1.3.1 调度方式</h4><p>线程不同的是,协程是用户态的,操作系统内核感知不到协程的存在,协程的管理依赖Go语言运行时自身提供的调度器。Go语言中的协程是从属于某一个线程的,协程与线程的对应关系为M:N,即多对多。Go语言调度器可以将多个协程调度到一个线程中,一个协程也可能切换到多个线程中执行。</p><img src="/images/20220216/线程与协程的对应关系.png" alt="线程与协程的对应关系" style="zoom: 50%;"><h4 id="1-3-2-上下文切换的速度"><a href="#1-3-2-上下文切换的速度" class="headerlink" title="1.3.2 上下文切换的速度"></a>1.3.2 上下文切换的速度</h4><p>由于协程切换无须OS用户态和内核态的切换,只需要保留极少的状态和寄存器变量值(SP/BP/PC),而线程切换会保留额外的寄存器变量值(例如浮点寄存器),所以协程(0.2微秒)的速度要比线程快(1~2微秒)。</p><h4 id="1-3-3-调度策略"><a href="#1-3-3-调度策略" class="headerlink" title="1.3.3 调度策略"></a>1.3.3 调度策略</h4><p>线程的调度在大部分时间是抢占式的,操作系统调度器为了均衡每个线程的执行周期,会定时发出中断信号强制执行线程上下文切换。<br>而Go语言中的协程在一般情况下是协作式调度的,当一个协程处理完自己的任务后,可以主动将执行权限让渡给其他协程。这意味着协程可以更好地在规定时间内完成自己的工作,而不会轻易被抢占。当一个协程运行了过长时间时,Go语言调度器才会强制抢占其执行。</p><h4 id="1-3-4-栈的大小"><a href="#1-3-4-栈的大小" class="headerlink" title="1.3.4 栈的大小"></a>1.3.4 栈的大小</h4><p>线程的栈大小一般是在创建时指定的,为了避免出现栈溢出(Stack Overflow),默认的栈会相对较大(例如2MB)而且在运行时不能更改。<br>Go语言中的协程栈默认为2KB,在Go运行时的帮助下会动态检测栈的大小,并动态地进行扩容。</p><br><h3 id="1-4-并发与并行"><a href="#1-4-并发与并行" class="headerlink" title="1.4 并发与并行"></a>1.4 并发与并行</h3><p>并发指同时处理多个任务的能力,并不意味着同一时刻所有任务都在执行,而是在一个时间段内,所有的任务都能执行完毕。<br>并行指同时处理多个任务的状态,多核处理器是其必要条件。<br>由于Go语言中的协程依托于线程,所以即便处理器运行的是同一个线程,在线程内Go语言调度器也会切换多个协程执行,这时协程是并发的。如果多个协程被分配给了不同的线程,而这些线程同时被不同的CPU核心处理,那么这些协程就是并行处理的。</p><img src="/images/20220216/并发与并行的区别.png" alt="并发与并行的区别" style="zoom:50%;"><hr><br><br><h2 id="2-协程入门"><a href="#2-协程入门" class="headerlink" title="2 协程入门"></a>2 协程入门</h2><p>首先用一个例子来介绍如何使用协程,以及使用协程所带来的性能提升。</p><h3 id="2-1-协程实践"><a href="#2-1-协程实践" class="headerlink" title="2.1 协程实践"></a>2.1 协程实践</h3><h4 id="2-1-1-顺序执行"><a href="#2-1-1-顺序执行" class="headerlink" title="2.1.1 顺序执行"></a>2.1.1 顺序执行</h4><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"net/http"</span> <span class="token string">"time"</span><span class="token punctuation">)</span><span class="token keyword">var</span> links <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span> <span class="token string">"http://www.baidu.com"</span><span class="token punctuation">,</span> <span class="token string">"http://www.jd.com"</span><span class="token punctuation">,</span> <span class="token string">"https://www.taobao.com"</span><span class="token punctuation">,</span> <span class="token string">"https://www.163.com"</span><span class="token punctuation">,</span> <span class="token string">"https://www.sohu.com"</span><span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">checkLink</span><span class="token punctuation">(</span>link <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">:=</span> http<span class="token punctuation">.</span><span class="token function">Get</span><span class="token punctuation">(</span>link<span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>link<span class="token punctuation">,</span> <span class="token string">" --> might be down!"</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>link<span class="token punctuation">,</span> <span class="token string">" --> is up!"</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> start <span class="token operator">:=</span> time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> link <span class="token operator">:=</span> <span class="token keyword">range</span> links <span class="token punctuation">{</span> <span class="token function">checkLink</span><span class="token punctuation">(</span>link<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 顺序执行</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"cost:"</span><span class="token punctuation">,</span> time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Sub</span><span class="token punctuation">(</span>start<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// http://www.baidu.com --> is up!</span><span class="token comment" spellcheck="true">// http://www.jd.com --> is up!</span><span class="token comment" spellcheck="true">// https://www.taobao.com --> is up!</span><span class="token comment" spellcheck="true">// https://www.163.com --> is up!</span><span class="token comment" spellcheck="true">// https://www.sohu.com --> is up!</span><span class="token comment" spellcheck="true">// cost: 579.066375ms</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>对5个url进行顺序请求,必须等待前一个请求执行完毕,后一个请求才能继续执行。<br></p><h4 id="2-1-1-并行执行"><a href="#2-1-1-并行执行" class="headerlink" title="2.1.1 并行执行"></a>2.1.1 并行执行</h4><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"net/http"</span> <span class="token string">"sync"</span> <span class="token string">"time"</span><span class="token punctuation">)</span><span class="token keyword">var</span> wg sync<span class="token punctuation">.</span>WaitGroup<span class="token keyword">var</span> links <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span> <span class="token string">"http://www.baidu.com"</span><span class="token punctuation">,</span> <span class="token string">"http://www.jd.com"</span><span class="token punctuation">,</span> <span class="token string">"https://www.taobao.com"</span><span class="token punctuation">,</span> <span class="token string">"https://www.163.com"</span><span class="token punctuation">,</span> <span class="token string">"https://www.sohu.com"</span><span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">checkLink</span><span class="token punctuation">(</span>link <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">:=</span> http<span class="token punctuation">.</span><span class="token function">Get</span><span class="token punctuation">(</span>link<span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>link<span class="token punctuation">,</span> <span class="token string">" --> might be down!"</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>link<span class="token punctuation">,</span> <span class="token string">" --> is up!"</span><span class="token punctuation">)</span> wg<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> start <span class="token operator">:=</span> time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> link <span class="token operator">:=</span> <span class="token keyword">range</span> links <span class="token punctuation">{</span> wg<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">go</span> <span class="token function">checkLink</span><span class="token punctuation">(</span>link<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 并行执行</span> <span class="token punctuation">}</span> wg<span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"cost:"</span><span class="token punctuation">,</span> time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Sub</span><span class="token punctuation">(</span>start<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// http://www.baidu.com --> is up!</span><span class="token comment" spellcheck="true">// https://www.sohu.com --> is up!</span><span class="token comment" spellcheck="true">// https://www.163.com --> is up!</span><span class="token comment" spellcheck="true">// http://www.jd.com --> is up!</span><span class="token comment" spellcheck="true">// https://www.taobao.com --> is up!</span><span class="token comment" spellcheck="true">// cost: 241.047917ms</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上面对url并行请求,使用<code>waitgroup</code>控制子协程的<code>fork-in</code>,主协程需要等待子协程都完成才能退出。如果不使用<code>waitgroup</code>,主协程会直接退出并不会等待子协程。对比运行时间,并行执行要比顺序执行快得多。<br></p><hr><br><br><h2 id="3-协程进阶"><a href="#3-协程进阶" class="headerlink" title="3 协程进阶"></a>3 协程进阶</h2><h3 id="3-1-GMP模型"><a href="#3-1-GMP模型" class="headerlink" title="3.1 GMP模型"></a>3.1 GMP模型</h3><p>前面提到线程与协程之间是M:N的关系,GMP模型描述了协程是如何依托线程调度到CPU中执行的。</p><p>G代表的是Go语言中的协程(Goroutine),M代表的是实际的线程,而P代表的是Go逻辑处理器(Process)。Go语言为了方便协程调度与缓存,抽象出了逻辑处理器。</p><img src="/images/20220216/GMP模型.png" alt="GMP模型" style="zoom:50%;"><p>一个P可能在其本地包含多个G,同时,一个P在任一时刻只能绑定一个M。M与P、P与G之间并不是固定绑定的。</p><br><h3 id="3-2-协程调度-基础篇"><a href="#3-2-协程调度-基础篇" class="headerlink" title="3.2 协程调度-基础篇"></a>3.2 协程调度-基础篇</h3><h4 id="3-2-1-协程生命周期与状态转移"><a href="#3-2-1-协程生命周期与状态转移" class="headerlink" title="3.2.1 协程生命周期与状态转移"></a>3.2.1 协程生命周期与状态转移</h4><p>为了便于对协程进行管理,Go语言的调度器将协程分为多种状态。</p><img src="/images/20220216/协程的状态与转移.png" alt="协程的状态与转移" style="zoom: 67%;"><h4 id="3-2-2-g0协程与协程切换"><a href="#3-2-2-g0协程与协程切换" class="headerlink" title="3.2.2 g0协程与协程切换"></a>3.2.2 g0协程与协程切换</h4><p>每个线程中都有一个特殊的协程g0,协程g0运行在操作系统线程栈上,其作用主要是执行协程调度的一系列运行时代码,而一般的协程无差别地用于执行用户代码。</p><p>在用户协程退出或者被抢占时,意味着需要重新执行协程调度,这时需要从用户协程g切换到协程g0,g0再调度新的协程。g→g0→g的过程叫作协程的上下文切换,切换时需要保存执行现场,即三个寄存器:rsp、rip、rbp。rsp寄存器始终指向函数调用栈栈顶,rip寄存器指向程序要执行的下一条指令的地址,rbp存储了函数栈帧的起始位置。</p><img src="/images/20220216/g.gobuf.png" alt="g.gobuf" style="zoom: 50%;"><h4 id="3-2-3-线程绑定"><a href="#3-2-3-线程绑定" class="headerlink" title="3.2.3 线程绑定"></a>3.2.3 线程绑定</h4><p>线程本地存储的实际是结构体m中m.tls的地址,同时m.tls[0]会存储当前线程正在运行的协程g的地址,因此在任意一个线程内部,通过线程本地存储,都可以在任意时刻获取绑定到当前线程上的协程g、结构体m、逻辑处理器P、特殊协程g0等信息。</p><img src="/images/20220216/m.tls.png" alt="m.tls" style="zoom:50%;"><br><h3 id="3-3-协程调度-核心篇"><a href="#3-3-协程调度-核心篇" class="headerlink" title="3.3 协程调度-核心篇"></a>3.3 协程调度-核心篇</h3><h4 id="3-3-1-调度循环"><a href="#3-3-1-调度循环" class="headerlink" title="3.3.1 调度循环"></a>3.3.1 调度循环</h4><p>从协程g0调度到协程g,经历了从schedule函数到execute函数再到gogo函数的过程。</p><img src="/images/20220216/调度循环.png" alt="调度循环" style="zoom: 67%;"><h4 id="3-3-2-运行队列-全局-amp-局部"><a href="#3-3-2-运行队列-全局-amp-局部" class="headerlink" title="3.3.2 运行队列-全局&局部"></a>3.3.2 运行队列-全局&局部</h4><p>Go语言调度器将运行队列分为局部运行队列与全局运行队列。</p><p><strong>局部运行队列</strong>是每个P特有的长度为256的数组runq,该数组模拟了一个循环队列,其中runqhead标识了循环队列的开头,runqtail标识了循环队列的末尾。每次将G放入本地队列时,都从循环队列的末尾插入,而获取时从循环队列的头部获取。在每个P内部还有一个特殊的runnext字段,标识下一个要执行的协程。<br><strong>全局运行队列</strong>存储在schedt.runq中,被所有P共享。</p><img src="/images/20220216/改进后的GMP模型.png" alt="改进后的GMP模型" style="zoom: 33%;"><h4 id="3-3-3-调度过程"><a href="#3-3-3-调度过程" class="headerlink" title="3.3.3 调度过程"></a>3.3.3 调度过程</h4><img src="/images/20220216/调度协程的优先级与顺序.png" alt="调度协程的优先级与顺序" style="zoom:67%;"><h4 id="3-3-4-调度时机"><a href="#3-3-4-调度时机" class="headerlink" title="3.3.4 调度时机"></a>3.3.4 调度时机</h4><h5 id="3-3-4-1-主动调度"><a href="#3-3-4-1-主动调度" class="headerlink" title="3.3.4.1 主动调度"></a>3.3.4.1 主动调度</h5><p>协程可以选择主动让渡自己的执行权利,这主要是通过用户在代码中执行runtime.Gosched函数实现的。主动调度的原理比较简单,需要先从当前协程切换到协程g0,取消G与M之间的绑定关系,将G放入全局运行队列,并调用schedule函数开始新一轮的循环。</p><h5 id="3-3-4-2-被动调度"><a href="#3-3-4-2-被动调度" class="headerlink" title="3.3.4.2 被动调度"></a>3.3.4.2 被动调度</h5><p>被动调度指协程在休眠、channel通道堵塞、网络I/O堵塞、执行垃圾回收而暂停时,被动让渡自己执行权利的过程,可以保证最大化利用CPU资源。<br>和主动调度类似的是,被动调度需要先从当前协程切换到协程g0,更新协程的状态并解绑与M的关系,重新调度。和主动调度不同的是,被动调度不会将G放入全局运行队列,因为当前G的状态不是_Grunnable而是_Gwaiting,所以,被动调度需要一个额外的唤醒机制。</p><h5 id="3-3-4-3-抢占调度"><a href="#3-3-4-3-抢占调度" class="headerlink" title="3.3.4.3 抢占调度"></a>3.3.4.3 抢占调度</h5><p>为了让每个协程都有执行的机会,并且最大化利用CPU资源,Go语言在初始化时会启动一个特殊的线程来执行系统监控任务。系统监控在一个独立的M上运行,不用绑定逻辑处理器P,系统监控每隔10ms会检测是否有准备就绪的网络协程,并放置到全局队列中。和抢占调度相关的是,系统监控服务会判断当前协程是否运行时间过长,或者处于系统调用阶段,如果是,则会抢占当前G的执行。其核心逻辑位于runtime.retake函数中。<br></p><p>To:信号强制抢占</p><br><br>]]></content>
<categories>
<category> golang </category>
</categories>
<tags>
<tag> golang </tag>
<tag> goroutine </tag>
<tag> 并发 </tag>
</tags>
</entry>
<entry>
<title>MySQL索引</title>
<link href="/2022/01/25/mysql-suo-yin/"/>
<url>/2022/01/25/mysql-suo-yin/</url>
<content type="html"><![CDATA[<blockquote><p>参考<br><a href="https://www.bilibili.com/video/BV19y4y127h4?p=1">MySQL索引【编程不良人】</a><br>高性能MySQL第5章 - 创建高性能的索引<br><a href="https://www.cnblogs.com/lianzhilei/p/11250589.html">B树、B+树详解</a></p></blockquote><hr><h2 id="1-索引基础"><a href="#1-索引基础" class="headerlink" title="1. 索引基础"></a>1. 索引基础</h2><p>索引诞生的主要目的就是加速数据的查询,MySQL的索引类似于书的目录,通过目录可以快速找到页码,从而快速定位内容。除此之外,索引大大减少了机器需要扫描的数据量,可以将随机I/O变为顺序I/O。索引也是有缺点的:维护索引会消耗数据库资源包括空间资源和时间资源。</p><p>不同的数据库会在优缺点之间权衡,根据其适合的业务场景调整索引的结构。下面的介绍针对主流的MySQL的InnoDB引擎展开。</p><hr><br><h2 id="2-索引分类"><a href="#2-索引分类" class="headerlink" title="2. 索引分类"></a>2. 索引分类</h2><p>常用的InnoDB引擎主要支持四种索引:</p><ul><li>主键索引</li><li>唯一索引</li><li>单值索引</li><li>复合索引</li></ul><h3 id="2-1-主键索引"><a href="#2-1-主键索引" class="headerlink" title="2.1 主键索引"></a>2.1 主键索引</h3><p>将某字段设置为主键后,数据库会对该主键自动建立索引。若不设置主键则不会自动建立主键索引。</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true">-- 创建数据表,不设置主键 --</span><span class="token keyword">create</span> <span class="token keyword">table</span> user1<span class="token punctuation">(</span> id <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">,</span> name <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">,</span> age <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- 查看主键 --</span><span class="token keyword">show</span> <span class="token keyword">index</span> <span class="token keyword">from</span> user1<span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- 创建数据表,设置主键 --</span><span class="token keyword">create</span> <span class="token keyword">table</span> user2<span class="token punctuation">(</span> id <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">primary</span> <span class="token keyword">key</span><span class="token punctuation">,</span> name <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">,</span> age <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- 查看主键 --</span><span class="token keyword">show</span> <span class="token keyword">index</span> <span class="token keyword">from</span> user2<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="/images/20220125/%E4%B8%BB%E9%94%AE%E7%B4%A2%E5%BC%95.png" alt="主键索引"></p><br><p><code>user1</code>没有设置主键,因此没有索引。<br><code>user2</code>设置主键为id,自动对id建立了主键索引。图中可以看到<code>Key_name</code>为<code>PRIMARY</code>表示这是一个主键索引。</p><br><h3 id="2-2-唯一索引"><a href="#2-2-唯一索引" class="headerlink" title="2.2 唯一索引"></a>2.2 唯一索引</h3><p>与主键索引相同的是<strong>索引列的值必须唯一</strong>,不同的是主键索引列不允许空值,而唯一索引列允许有空值。</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true">-- 建表时创建唯一索引,unique(column_name1), unique(column_name2) ... --</span><span class="token keyword">create</span> <span class="token keyword">table</span> user1<span class="token punctuation">(</span> id <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">primary</span> <span class="token keyword">key</span><span class="token punctuation">,</span> name <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">,</span> age <span class="token keyword">int</span><span class="token punctuation">,</span> <span class="token keyword">unique</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- 建表后创建唯一索引 --</span><span class="token keyword">create</span> <span class="token keyword">table</span> user2<span class="token punctuation">(</span> id <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">primary</span> <span class="token keyword">key</span><span class="token punctuation">,</span> name <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">,</span> age <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">create</span> <span class="token keyword">unique</span> <span class="token keyword">index</span> unique_user2 <span class="token keyword">on</span> user2<span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- 删除索引 --</span><span class="token keyword">drop</span> <span class="token keyword">unique</span> <span class="token keyword">index</span> 索引名 <span class="token keyword">on</span> 表名<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="/images/20220125/%E5%94%AF%E4%B8%80%E7%B4%A2%E5%BC%95.png" alt="唯一索引"></p><p><br>通过<code>create unique index</code>语句创建索引,可以自定义索引的名称。索引列的值必须唯一,所以<code>Non_unique</code>为0。<br><br></p><h3 id="2-3-单值索引"><a href="#2-3-单值索引" class="headerlink" title="2.3 单值索引"></a>2.3 单值索引</h3><p>单值索引也称单列索引、普通索引。一个索引只包含单个列,一个表可以有多个单值索引。</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true">-- 建表时创建单值索引,key(column_name1), key(column_name2) ... --</span><span class="token keyword">create</span> <span class="token keyword">table</span> user1<span class="token punctuation">(</span> id <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">primary</span> <span class="token keyword">key</span><span class="token punctuation">,</span> name <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">,</span> age <span class="token keyword">int</span><span class="token punctuation">,</span> <span class="token keyword">key</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- 建表后创建单值索引 --</span><span class="token keyword">create</span> <span class="token keyword">table</span> user2<span class="token punctuation">(</span> id <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">primary</span> <span class="token keyword">key</span><span class="token punctuation">,</span> name <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">,</span> age <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">create</span> <span class="token keyword">index</span> index_user2 <span class="token keyword">on</span> user2<span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- 删除索引 --</span><span class="token keyword">drop</span> <span class="token keyword">index</span> 索引名 <span class="token keyword">on</span> 表名<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="/images/20220125/%E5%8D%95%E5%80%BC%E7%B4%A2%E5%BC%95.png" alt="单值索引"></p><p><br>通过<code>create index</code>语句创建索引,可以自定义索引的名称。索引列的值不用唯一,所以<code>Non_unique</code>为1。单值索引列允许为空,所以<code>Null</code>为YES。</p><br><h3 id="2-4-复合索引"><a href="#2-4-复合索引" class="headerlink" title="2.4 复合索引"></a>2.4 复合索引</h3><p>一个索引包含多个列。 </p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true">-- 建表时创建复合索引,key(column_name1, column_name2 ...) --</span><span class="token keyword">create</span> <span class="token keyword">table</span> user1<span class="token punctuation">(</span> id <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">primary</span> <span class="token keyword">key</span><span class="token punctuation">,</span> name <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">,</span> age <span class="token keyword">int</span><span class="token punctuation">,</span> <span class="token keyword">key</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> age<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- 建表后创建复合索引 --</span><span class="token keyword">create</span> <span class="token keyword">table</span> user2<span class="token punctuation">(</span> id <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">primary</span> <span class="token keyword">key</span><span class="token punctuation">,</span> name <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">,</span> age <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">create</span> <span class="token keyword">index</span> index_user2 <span class="token keyword">on</span> user2<span class="token punctuation">(</span>name<span class="token punctuation">,</span> age<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- 删除索引 --</span><span class="token keyword">drop</span> <span class="token keyword">index</span> 索引名 <span class="token keyword">on</span> 表名<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="/images/20220125/%E5%A4%8D%E5%90%88%E7%B4%A2%E5%BC%95.png" alt="复合索引"></p><p><br><code>Seq_in_index</code>表示索引列在索引中的顺序。<br><br></p><h4 id="2-4-1-最左前缀原则"><a href="#2-4-1-最左前缀原则" class="headerlink" title="2.4.1 最左前缀原则"></a>2.4.1 最左前缀原则</h4><p>创建复合索引时基于name和age字段,在查询时要遵循<strong>最左前缀原则</strong>,即必须复合最左前缀的顺序采用使用复合索引。 同时MySQL还做了一个优化,引擎在查询时为了更好利用索引,在查询过程中会动态调整查询字段顺序。<br>假设现在创建复合索引语句为:<code>create index index_name on user(name, age, tel)</code>,查询时:</p><ul><li>name age tel 可以利用索引,符合最左前缀原则</li><li>name tel age 可以利用索引,动态调整字段顺序后符合最左前缀原则</li><li>age tel 不可以利用索引</li><li>tel age name 可以利用索引,动态调整字段顺序后符合最左前缀原则</li><li>tel name 不可以利用索引</li></ul><hr><br><h2 id="3-索引底层原理"><a href="#3-索引底层原理" class="headerlink" title="3. 索引底层原理"></a>3. 索引底层原理</h2><p> 首先观察下面的例子,插入无序的数据。</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">create</span> <span class="token keyword">table</span> <span class="token keyword">user</span><span class="token punctuation">(</span> id <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">primary</span> <span class="token keyword">key</span><span class="token punctuation">,</span> name <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">,</span> age <span class="token keyword">int</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">insert</span> <span class="token keyword">into</span> <span class="token keyword">user</span> <span class="token keyword">values</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token string">'Tim'</span><span class="token punctuation">,</span> <span class="token number">18</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">insert</span> <span class="token keyword">into</span> <span class="token keyword">user</span> <span class="token keyword">values</span><span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">,</span> <span class="token string">'Tom'</span><span class="token punctuation">,</span> <span class="token number">14</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">insert</span> <span class="token keyword">into</span> <span class="token keyword">user</span> <span class="token keyword">values</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token string">'Bob'</span><span class="token punctuation">,</span> <span class="token number">31</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">insert</span> <span class="token keyword">into</span> <span class="token keyword">user</span> <span class="token keyword">values</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">'Andy'</span><span class="token punctuation">,</span> <span class="token number">18</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">insert</span> <span class="token keyword">into</span> <span class="token keyword">user</span> <span class="token keyword">values</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token string">'John'</span><span class="token punctuation">,</span> <span class="token number">24</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">insert</span> <span class="token keyword">into</span> <span class="token keyword">user</span> <span class="token keyword">values</span><span class="token punctuation">(</span><span class="token number">9</span><span class="token punctuation">,</span> <span class="token string">'Jodan'</span><span class="token punctuation">,</span> <span class="token number">11</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">insert</span> <span class="token keyword">into</span> <span class="token keyword">user</span> <span class="token keyword">values</span><span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">,</span> <span class="token string">'Kobe'</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="/images/20220125/有序输出.png" alt="有序输出" style="zoom:67%;"><p><br>在输出时,会按照主键的大小顺序输出。索引的底层实现决定了这种现象,下面介绍一下索引底层原理。<br></p><p><br></p><h3 id="3-1-B-树"><a href="#3-1-B-树" class="headerlink" title="3.1 B+树"></a>3.1 B+树</h3><p>MySQL 的 InnoDB 引擎使用 B+树 作为索引的数据结构,这是一种基于B树的改进。关于B-tree和B+tree的解释可以参考下面的博客。<br>B+树是为磁盘或其他直接存取辅助设备设计的一种平衡查找树,在B+树中,所有记录节点都是按键值的大小顺序存放在同一层的叶子节点,各叶子节点通过指针进行链接,形成一个链表。如下图所示,其高度为2,每页可存放4条记录,扇出(fan out)为5:</p><br><p><img src="/images/20220125/B+%E6%A0%91.png" alt="B+树"></p><p><br>B树的非叶子节点也可以存放数据,意味着每一页可放置的记录条数更少。当数据量很大时,B树的深度较大,查询时磁盘的I/O次数也会更多,进而会影响查询效率。<br></p><p>而B+树的数据是按照被索引字段,顺序存放在叶子节点的,非叶子节点只寸被索引字段和指针,可以放置的记录条数更多。上面例子中<code>select * from user</code>实际是对叶子结点链表的遍历,所以是顺序输出的。<br>在InnoDB存储引擎中,每个页的大小为16KB。B+树的高度一般都在2~4层,这意味着查找某一键值最多只需要2到4次IO操作,这还不错。因为现在一般的磁盘每秒至少可以做100次IO操作,2~4次的IO操作意味着查询时间只需0.02~0.04秒。<br><br><br></p><h3 id="3-2-聚簇索引-vs-非聚簇索引"><a href="#3-2-聚簇索引-vs-非聚簇索引" class="headerlink" title="3.2 聚簇索引 vs 非聚簇索引"></a>3.2 聚簇索引 vs 非聚簇索引</h3><ul><li><strong>聚簇索引</strong>:将索引与数据存储到一起,索引结构的叶子结点保存了行数据。聚簇索引指的不一定就是主键索引,但主键索引一定是聚簇索引。</li><li>**非聚簇索引(辅助索引)**:将索引与数据分开存储,索引结构的叶子结点指向了数据对应的位置。非聚簇索引存储的不再是行的物理位置,而是主键值,非聚簇索引访问数据总是需要二次查找。</li></ul><img src="/images/20220125/聚簇索引-非聚簇索引.png" alt="聚簇索引-非聚簇索引" style="zoom:67%;"><p><br><strong>聚簇索引默认是主键</strong>,如果表中没有定义主键,InnoDB会选择一个<strong>唯一且非空的索引</strong>代替。如果没有这样的索引,InnoDB会<strong>隐式定义一个主键</strong>(类似oracle中的Rowld)来作为聚簇索引。如果已经设置了主键为聚簇索引又希望再单独设置聚簇索引,必须先删除主键,然后添加我们想要的聚簇索引,最后恢复设置主键即可。<br></p><hr><br><h2 id="4-面试常见问题"><a href="#4-面试常见问题" class="headerlink" title="4. 面试常见问题"></a>4. 面试常见问题</h2><h3 id="4-1-聚簇索引需要注意什么?"><a href="#4-1-聚簇索引需要注意什么?" class="headerlink" title="4.1 聚簇索引需要注意什么?"></a>4.1 聚簇索引需要注意什么?</h3><ul><li>当使用主键为聚簇索引时,主键最好不要使用uuid,因为uuid的值太过离散,不适合排序且可能出现新增加记录的 uuid,会插入在索引树中间的位置,导致索引树调整复杂度变大,消耗更多的时间和资源。</li><li>建议使用int类型的自增,方便排序并且默认会在索引树的末尾增加主键值,对索引树的结构影响最小。而且,主键值占用的存储空间越大,辅助索引中保存的主键值也会跟着变大,占用存储空间,也会影响到IO操作读取到的数据量。</li></ul><h3 id="4-2-为什么主键通常建议使用自增id?"><a href="#4-2-为什么主键通常建议使用自增id?" class="headerlink" title="4.2 为什么主键通常建议使用自增id?"></a>4.2 为什么主键通常建议使用自增id?</h3><ul><li>聚簇索引的数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的。如果主键不是自增,那么可以想象,它会干些什么,不断地调整数据的物理地址、分页,当然也有其他一些措施来减少这些操作,但却无法彻底避免。但,如果是自增的,那就简单了,它只需要一页一页地写,索引结构相对紧凑,磁盘碎片少,效率也高。</li></ul><h3 id="4-3-什么情况下无法利用索引呢?"><a href="#4-3-什么情况下无法利用索引呢?" class="headerlink" title="4.3 什么情况下无法利用索引呢?"></a>4.3 什么情况下无法利用索引呢?</h3><ul><li>查询语句中使用LIKE关键字<br>在查询语句中使用LIKE关键字进行查询时,如果匹配字符串的第一个字符为“%”,索引不会被使用。如果“%”不是在第一个位置,索引就会被使用。</li><li>查询语句中使用多列索引<br>多列索引是在表的多个字段上创建一个索引,只有查询条件中使用了这些字段中的第一个字段,索引才会被使用。</li><li>查询语句中使用OR关键字<br>查询语句只有OR关键字时,如果OR前后的两个条件的列都是索引,那么查询中将使用索引。如果OR前后有一个条件的列不是索引,那么查询中将不使用索引。</li></ul><br><br>]]></content>
<categories>
<category> MySQL </category>
</categories>
<tags>
<tag> MySQL </tag>
<tag> 数据库索引 </tag>
</tags>
</entry>
<entry>
<title>Go语言项目布局</title>
<link href="/2022/01/24/go-yu-yan-xiang-mu-bu-ju/"/>
<url>/2022/01/24/go-yu-yan-xiang-mu-bu-ju/</url>
<content type="html"><![CDATA[<h1 id="Go语言项目布局"><a href="#Go语言项目布局" class="headerlink" title="Go语言项目布局"></a>Go语言项目布局</h1><blockquote><p>参考<br><a href="https://github.com/golang-standards/project-layout">Golang项目布局开源项目-29.1K Stars</a><br>《Go语言精进之路》第二部分<br><a href="https://www.youtube.com/watch?v=oL6JBUk6tj0">GopherCon 2018: Kat Zien - How Do You Structure Your Go Apps</a></p></blockquote><hr><br><br><h2 id="1-前言"><a href="#1-前言" class="headerlink" title="1. 前言"></a>1. 前言</h2><p>在项目中,合理的布局会让项目更加清晰。随着项目的增长,保持良好的项目结构非常重要,否则最终会得到一团混乱的代码。为了便于开发者协作,最好使用通用的结构,下面这个开源项目中介绍了 Go 语言项目布局的最佳实践,目前有接近 3 万个 star,对我后续的开发还是有一定参考价值的。</p><p><a href="https://github.com/golang-standards/project-layout">https://github.com/golang-standards/project-layout</a></p><p>从 <code>Go 1.14</code>开始,建议Go项目使用<a href="https://github.com/golang/go/wiki/Modules">Go Modules</a> 进行依赖管理,用 <code>go.mod</code> 来记录特定的依赖信息。</p><hr><br><br><h2 id="2-Go语言自身结构"><a href="#2-Go语言自身结构" class="headerlink" title="2. Go语言自身结构"></a>2. Go语言自身结构</h2><p>作为 Go 语言的创世项目,Go 的项目结构的布局对后续的 Go 语言项目具有重要的参考意义。下面的结构基于 <code>Go 1.16</code></p><pre class="line-numbers language-bash"><code class="language-bash">$ tree -LF 1 /usr/local/go/usr/local/go├── AUTHORS├── CONTRIBUTING.md├── CONTRIBUTORS├── LICENSE├── PATENTS├── README.md├── SECURITY.md├── VERSION├── api/├── bin/├── doc/├── favicon.ico├── lib/├── misc/├── pkg/├── robots.txt├── src/└── test/<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><p>尤其是早期 Go 项目中 <code>src</code>目录下面的结构,更是在后续被 Go 社区作为 Go 应用项目结构的模板广泛使用。</p><ul><li>代码构建的脚本源文件放在 <code>src</code>下面的顶层目录下。</li><li><code>src</code>下的二级目录 <code>cmd</code>下面存放着 Go 工具链相关的可执行文件(比如 <code>go</code> 、 <code>gofmt</code>等)的主目录以及它们的 <code>main</code>包源文件。</li><li><code>src</code>存放着上面 <code>cmd</code>下各工具链程序依赖的包、Go 运行时以及 Go 标准库的源文件。</li><li><code>src</code>下面的二级目录 <code>internal</code>,用于存放无法被外部导入、仅 Go 项目自用的包。</li><li><code>src</code>下面的 <code>go.mod</code>和 <code>go.sum</code>,实现了 Go 项目自身的 <code>go module</code>迁移。Go项目内所有包被放到名为 <code>std</code>的 <code>module</code>下面,其依赖的包依然是 <code>golang.org/x</code>下的各个包。</li></ul><pre class="line-numbers language-bash"><code class="language-bash">$ tree -LF 1 /usr/local/go/src/usr/local/go/src├── Make.dist├── README.vendor├── all.bash*├── all.bat├── all.rc*├── archive/├── bootstrap.bash*├── bufio/├── buildall.bash*├── builtin/├── bytes/├── clean.bash*├── clean.bat├── clean.rc*├── cmd/├── cmp.bash├── compress/├── container/├── context/├── crypto/├── database/├── debug/├── embed/├── encoding/├── errors/├── expvar/├── flag/├── fmt/├── go/├── go.mod├── go.sum├── hash/├── html/├── image/├── index/├── internal/├── io/├── log/├── make.bash*├── make.bat├── make.rc*├── math/├── mime/├── net/├── os/├── path/├── plugin/├── race.bash*├── race.bat├── reflect/├── regexp/├── run.bash*├── run.bat├── run.rc*├── runtime/├── sort/├── strconv/├── strings/├── sync/├── syscall/├── testdata/├── testing/├── text/├── time/├── unicode/├── unsafe/└── vendor/<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><br><br><h2 id="3-以构建二进制可执行文件为目的的-Go-项目结构"><a href="#3-以构建二进制可执行文件为目的的-Go-项目结构" class="headerlink" title="3. 以构建二进制可执行文件为目的的 Go 项目结构"></a>3. 以构建二进制可执行文件为目的的 Go 项目结构</h2><p>下图是一个支持(在 <code>cmd</code>下)构建二进制可执行文件的典型 Go 项目的结构。</p><img src="/images/20220124/项目结构.png" alt="项目结构" style="zoom:50%;"><ul><li><code>cmd目录</code>:项目的主干,存放项目要构建的可执行文件对应的 <code>main</code>包的源文件。如果有多个可执行文件需要构建,则将每个可执行文件的 <code>main</code>包单独放在一个子目录中,比如图中的 <code>app1、app2</code>。一些Go项目将 <code>cmd</code>这个名字改为 <code>app</code>,但其功能并没有变。<br>通常有一个小的 <code>main</code> 函数,从 <code>/internal</code> 和 <code>/pkg</code> 目录导入和调用代码。不要在这个目录中放置太多代码。如果你认为代码可以导入并在其他项目中使用,那么它应该位于 <code>/pkg</code> 目录中。如果代码不是可重用的,或者你不希望其他人重用它,请将该代码放到 <code>/internal</code> 目录中。</li><li><code>pkg目录</code>:该目录下的包可以被外部项目引用,算是项目导出包的一个聚合。对于一些规模稍大的项目,过多的包会让项目顶层目录不再简洁,显得很拥挤,因此对于复杂的 Go 项目建议保留 <code>pkg</code>目录。</li><li><code>internal目录</code>:对于不想暴露给外部引用,仅限项目内部使用的包,在项目结构上可以通过 Go 1.4 版本中引入的 <code>internal</code>包机制来实现。</li><li><code>go.mod</code>和 <code>go.sum</code>:Go 语言包依赖管理使用的配置文件。</li><li><code>Makefile</code>:项目构建工具所用脚本的“代表”,一般会放在 <code>build</code>目录下。</li></ul><hr><br><br><h2 id="4-通用应用目录"><a href="#4-通用应用目录" class="headerlink" title="4. 通用应用目录"></a>4. 通用应用目录</h2><ul><li><code>configs目录</code>:配置文件模板或默认配置。</li><li><code>init目录</code>:系统初始化文件目录。</li><li><code>scripts目录</code>:执行各种构建、安装、分析等操作的脚本。</li><li><code>build目录</code>:打包和持续集成。将你的云( AMI )、容器( Docker )、操作系统( deb、rpm、pkg )包配置和脚本放在 <code>/build/package</code> 目录下。将你的 CI (travis、circle、drone)配置和脚本放在 <code>/build/ci</code> 目录中。</li><li><code>test目录</code>:额外的外部测试应用程序和测试数据。</li></ul><hr><br><br><h2 id="5-其他目录"><a href="#5-其他目录" class="headerlink" title="5. 其他目录"></a>5. 其他目录</h2><ul><li><code>docs目录</code>:设计和用户文档(除了 <code>godoc</code> 生成的文档之外)。</li><li><code>tools目录</code>:这个项目的支持工具。注意,这些工具可以从 <code>/pkg</code> 和 <code>/internal</code> 目录导入代码。</li><li><code>examples目录</code>:应用程序和/或公共库的示例。</li><li><code>third_party目录</code> :外部辅助工具,分叉代码和其他第三方工具(例如 Swagger UI)。</li></ul><p>可以根据项目需求合理添加其他目录结构。</p><p>注意:有些 Go 项目确实有一个 <code>src</code> 文件夹,但这通常发生在开发人员有 Java 背景,在那里它是一种常见的模式。如果可以的话,尽量不要采用这种 Java 模式。你真的不希望你的 Go 代码或 Go 项目看起来像 Java</p><br><br>]]></content>
<categories>
<category> golang </category>
</categories>
<tags>
<tag> golang </tag>
<tag> 工程技术 </tag>
</tags>
</entry>
<entry>
<title>Go 数据结构 - interface</title>
<link href="/2022/01/18/go-shu-ju-jie-gou-interface/"/>
<url>/2022/01/18/go-shu-ju-jie-gou-interface/</url>
<content type="html"><![CDATA[<h1 id="Go-数据结构-interface"><a href="#Go-数据结构-interface" class="headerlink" title="Go 数据结构 - interface"></a>Go 数据结构 - interface</h1><blockquote><p>参考<br> <a href="https://www.liwenzhou.com/posts/Go/12_interface/">Go 语言基础之接口 - 李文周</a><br> <a href="https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-interface/">Go 语言设计与实现接口 - 面向信仰编程</a><br> <a href="https://qcrao91.gitbook.io/go/interface">Go 语言 interface - 码农桃花源</a><br> 《Go 语言底层原理》第 12 章</p></blockquote><hr><br><br><h2 id="1-接口概述"><a href="#1-接口概述" class="headerlink" title="1. 接口概述"></a>1. 接口概述</h2><img src="/images/20220118/接口.png" alt="接口" style="zoom: 67%;"><p>接口定义了一种规范,提供数据传输的渠道。不论是前后端交互的接口,还是程序设计中的接口,抑或是常用的 Type-A、Type-C 结构,只有符合接口的规范设计,才能让不同组件相互连接完成数据的传输。接口可以隔离底层的实现,用户只需要知道怎么用接口就行了。</p><br><h3 id="1-1-显式接口"><a href="#1-1-显式接口" class="headerlink" title="1.1 显式接口"></a>1.1 显式接口</h3><p>作为后端开发领域中的大哥大,Java 需要显示声明实现的接口。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">MyInterface</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> String hello <span class="token operator">=</span> <span class="token string">"Hello"</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">sayHello</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyInterfaceImpl</span> <span class="token keyword">implements</span> <span class="token class-name">MyInterface</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">sayHello</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>MyInterface<span class="token punctuation">.</span>hello<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>MyInterface</code> 接口中声明了 方法 <code>sayHello</code> 和 变量 <code>hello</code></p><p><code>MyInterfaceImpl</code> 类 使用 <code>implements</code> 关键字显式的声明实现了接口 <code>MyInterface</code></p><p>这是一种侵入式接口设计,实现类需要明确声明自己实现了某个接口。这也意味着对接口产生了依赖,当接口被删除时程序就无法运行,耦合度较高。</p><br><h3 id="1-2-隐式接口"><a href="#1-2-隐式接口" class="headerlink" title="1.2 隐式接口"></a>1.2 隐式接口</h3><p>Go 语言中接口的实现都是隐式的,只需实现接口中定义的方法,而无需显示的声明实现了该接口。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> MyInterface <span class="token keyword">interface</span> <span class="token punctuation">{</span> <span class="token function">sayHello</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">type</span> MyInterfaceImpl <span class="token keyword">struct</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>m MyInterfaceImpl<span class="token punctuation">)</span> <span class="token function">sayHello</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>MyInterface</code> 接口中声明了 方法 <code>sayHello</code></p><p><code>MyInterfaceImpl</code> 结构体实现了 方法 <code>sayHello</code> ,但是没有显示的声明。</p><p>这是一种非侵入式接口设计,只需要实现接口的所有方法就叫实现了该接口,即便该接口删掉了也不会影响。(鸭子类型,只要实现了鸭子叫、鸭子跑方法,就可以认定它是鸭子),耦合度很低。</p><hr><br><br><h2 id="2-接口实例"><a href="#2-接口实例" class="headerlink" title="2. 接口实例"></a>2. 接口实例</h2><h3 id="2-1-空接口-amp-带方法签名接口"><a href="#2-1-空接口-amp-带方法签名接口" class="headerlink" title="2.1 空接口 & 带方法签名接口"></a>2.1 空接口 & 带方法签名接口</h3><p>Go 语言中接口有两种类型,带方法签名的接口和空接口。</p><p>因为目前 <code>Go1.17</code> 前都没有范型,空接口作为伪范型用的非常多,如 <code>fmt</code> 中打印函数的入参都是空接口。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">Empty</span><span class="token punctuation">(</span>i <span class="token keyword">interface</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">Empty</span><span class="token punctuation">(</span><span class="token string">"xyz"</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// xyz</span> <span class="token function">Empty</span><span class="token punctuation">(</span><span class="token number">123</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 123</span> <span class="token function">Empty</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// true</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>下面介绍如何使用带方法签名的接口。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token string">"fmt"</span><span class="token keyword">type</span> Duck <span class="token keyword">interface</span> <span class="token punctuation">{</span> <span class="token function">Walk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token function">Quack</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">type</span> Zhouhei <span class="token keyword">struct</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>z Zhouhei<span class="token punctuation">)</span> <span class="token function">Walk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"周黑鸭 walk..."</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>z Zhouhei<span class="token punctuation">)</span> <span class="token function">Quack</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"周黑鸭 quack..."</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">type</span> Jiujiu <span class="token keyword">struct</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>j Jiujiu<span class="token punctuation">)</span> <span class="token function">Walk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"久久鸭 walk..."</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>j Jiujiu<span class="token punctuation">)</span> <span class="token function">Quack</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"久久鸭 quack..."</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// Duck interface 作为</span><span class="token keyword">func</span> <span class="token function">DuckWalk</span><span class="token punctuation">(</span>d Duck<span class="token punctuation">)</span> <span class="token punctuation">{</span> d<span class="token punctuation">.</span><span class="token function">Walk</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">DuckQuack</span><span class="token punctuation">(</span>d Duck<span class="token punctuation">)</span> <span class="token punctuation">{</span> d<span class="token punctuation">.</span><span class="token function">Quack</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> z <span class="token operator">:=</span> Zhouhei<span class="token punctuation">{</span><span class="token punctuation">}</span> j <span class="token operator">:=</span> Jiujiu<span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token function">DuckWalk</span><span class="token punctuation">(</span>z<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 周黑鸭 walk...</span> <span class="token function">DuckQuack</span><span class="token punctuation">(</span>z<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 周黑鸭 quack...</span> <span class="token function">DuckWalk</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 久久鸭 walk...</span> <span class="token function">DuckQuack</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 久久鸭 quack...</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上面是接口使用中很常见的例子, <code>Duck 接口 </code> 中定义了两个方法, <code>Zhouhei</code> 和 <code>Jiujiu</code> 两个结构体实现了这两个方法,即实现了 <code>Duck 接口 </code>。同时 <code>Duck 接口 </code> 作为 <code>DuckWalk</code> 和 <code>DuckQuack</code> 函数的入参,可以接收这两个结构体。</p><p>这种设计使得代码的扩展性比较高,如果再来一个结构体,只需实现接口中的方法即可通过 <code>DuckWalk</code> 和 <code>DuckQuack</code> 函数被调用。</p><br><h3 id="2-2-值接收者-amp-指针接收者"><a href="#2-2-值接收者-amp-指针接收者" class="headerlink" title="2.2 值接收者 & 指针接收者"></a>2.2 值接收者 & 指针接收者</h3><p>在结构体实现方法时,一般将结构体称为方法的接收者。接收者类型有两种: <code>值接收者</code> 和 <code>指针接收者</code>,上面例子中使用的就是值接收者。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// ============== 值接收 ===============</span><span class="token keyword">func</span> <span class="token punctuation">(</span>z Zhouhei<span class="token punctuation">)</span> <span class="token function">Walk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"周黑鸭 walk..."</span><span class="token punctuation">)</span><span class="token punctuation">}</span>z1 <span class="token operator">:=</span> Zhouhei<span class="token punctuation">{</span><span class="token punctuation">}</span>z2 <span class="token operator">:=</span> <span class="token operator">&</span>Zhouhei<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token function">DuckWalk</span><span class="token punctuation">(</span>z1<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 周黑鸭 walk...</span><span class="token function">DuckWalk</span><span class="token punctuation">(</span>z2<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 周黑鸭 walk...</span><span class="token comment" spellcheck="true">// ============== 指针接收 ===============</span><span class="token keyword">func</span> <span class="token punctuation">(</span>z <span class="token operator">*</span>Zhouhei<span class="token punctuation">)</span> <span class="token function">Walk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"周黑鸭 walk..."</span><span class="token punctuation">)</span><span class="token punctuation">}</span>z1 <span class="token operator">:=</span> Zhouhei<span class="token punctuation">{</span><span class="token punctuation">}</span>z2 <span class="token operator">:=</span> <span class="token operator">&</span>Zhouhei<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token function">DuckWalk</span><span class="token punctuation">(</span>z1<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 报错</span><span class="token comment" spellcheck="true">// cannot use z1 (type Zhouhei) as type Duck in argument to DuckWalk:</span><span class="token comment" spellcheck="true">// Zhouhei does not implement Duck (Walk method has pointer receiver)</span><span class="token function">DuckWalk</span><span class="token punctuation">(</span>z2<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 周黑鸭 walk...</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>当接收者为值类型: <code>Zhouhei</code> 值对象和指针对象都可以赋值给该接口变量。<br>因为 Go 语言中有对指针类型变量求值的语法糖,通过指针可以获取到对象,指针 <code>&Zhouhei{}</code> 内部会自动求值 <code>Zhouhei{}</code></li><li>当接收者为指针类型:值对象无法赋值给该接口变量。<br>因为值对象 <code>Zhouhei{}</code> 与方法接收者 指针对象 <code>*Zhouhei</code> 类型不一致</li></ul><br><p><strong>用值接收者还是指针接收者?</strong></p><ul><li>如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者;使用指针作为方法的接收者的理由:<ul><li>方法能够修改接收者指向的值。</li><li>避免在每次调用方法时复制该值,在值的类型为大型结构体时,这样做会更加高效。</li></ul></li><li>如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身。</li></ul><p>是使用值接收者还是指针接收者,不是由该方法是否修改了调用者(也就是接收者)来决定,而是应该基于该类型的 <code>本质</code>。</p><p>如果类型具备 “原始的本质”,也就是说它的成员都是由 Go 语言里内置的原始类型,如字符串,整型值等,那就 ** 定义值接收者类型的方法 **。像内置的引用类型,如 slice,map,interface,channel,这些类型比较特殊,声明他们的时候,实际上是创建了一个 <code>header</code>, 对于他们也是直接定义值接收者类型的方法。这样,调用函数时,是直接 copy 了这些类型的 <code>header</code>,而 <code>header</code> 本身就是为复制设计的。</p><p>如果类型具备非原始的本质,不能被安全地复制,这种类型总是应该被共享,那就 ** 定义指针接收者的方法 **。比如 go 源码里的文件结构体(struct File)就不应该被复制,应该只有一份 <code>实体</code>。</p><br><h3 id="2-3-接口类型断言"><a href="#2-3-接口类型断言" class="headerlink" title="2.3 接口类型断言"></a>2.3 接口类型断言</h3><p>一个接口的值(简称接口值)是由 <code>一个具体类型</code> 和 <code>具体类型的值</code> 两部分组成的。这两部分分别称为接口的 <code>动态类型</code> 和 <code>动态值</code>。</p><img src="/images/20220118/类型断言.png" alt="类型断言" style="zoom: 33%;"><p>可以使用语法 <code>i.(Type)</code> 在运行时获取存储在接口中的动态值。其中 i 代表接口,Type 代表实现此接口的动态类型。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token string">"fmt"</span><span class="token keyword">type</span> Duck <span class="token keyword">interface</span> <span class="token punctuation">{</span> <span class="token function">Walk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token function">Quack</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">type</span> Zhouhei <span class="token keyword">struct</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>z Zhouhei<span class="token punctuation">)</span> <span class="token function">Walk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"周黑鸭 walk..."</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>z Zhouhei<span class="token punctuation">)</span> <span class="token function">Quack</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"周黑鸭 quack..."</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">type</span> Jiujiu <span class="token keyword">struct</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>j Jiujiu<span class="token punctuation">)</span> <span class="token function">Walk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"久久鸭 walk..."</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> d Duck d <span class="token operator">=</span> Jiujiu<span class="token punctuation">{</span><span class="token punctuation">}</span> value <span class="token operator">:=</span> d<span class="token punctuation">.</span><span class="token punctuation">(</span>Zhouhei<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// cannot use Jiujiu{} (type Jiujiu) as type Duck in assignment:</span> <span class="token comment" spellcheck="true">// Jiujiu does not implement Duck (missing Quack method)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>首先声明一个接口变量作为 Jiujiu 对象的载体,然后通过类型断言语句判断是否为 Zhouhei 类型,产生错误信息:未实现 Quack 方法。</p><p>这种写法不够优雅,一般接口类型断言语句这样写: <code>value, ok := i.(Type)</code></p><p>断言语句经常会和 <code>switch</code> 一起用</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token string">"fmt"</span><span class="token keyword">func</span> <span class="token function">check</span><span class="token punctuation">(</span>arg <span class="token keyword">interface</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> f <span class="token operator">:=</span> arg<span class="token punctuation">.</span><span class="token punctuation">(</span><span class="token keyword">type</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token builtin">bool</span><span class="token punctuation">:</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"bool"</span><span class="token punctuation">)</span> <span class="token keyword">case</span> <span class="token builtin">string</span><span class="token punctuation">:</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"string"</span><span class="token punctuation">)</span> <span class="token keyword">default</span><span class="token punctuation">:</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"未识别类型"</span><span class="token punctuation">,</span> f<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">check</span><span class="token punctuation">(</span><span class="token string">"xyz"</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// string</span> <span class="token function">check</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// bool</span> <span class="token function">check</span><span class="token punctuation">(</span><span class="token number">123</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 未识别类型 123</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><br><br><h2 id="3-接口原理"><a href="#3-接口原理" class="headerlink" title="3. 接口原理"></a>3. 接口原理</h2><p>Go 语言根据接口类型是否包含一组方法将接口类型分成了两类:</p><ul><li>使用 <code>runtime.iface</code> 结构体表示包含方法的接口</li><li>使用 <code>runtime.eface</code> 结构体表示不包含任何方法的 <code>interface{}</code> 类型,它的结构也相对来说比较简单,只包含指向底层数据和类型的两个指针;</li></ul><img src="/images/20220118/接口结构体.png" alt="接口结构体" style="zoom:50%;"><p><code>data 字段 </code> 存储了接口中动态类型的函数指针。</p><p><code>tab 字段 </code> 存储了接口的类型、接口中的动态数据类型、动态数据类型的函数指针等。其类型为 <code>*itab</code>,这也是接口的核心。</p><img src="/images/20220118/itab结构体.png" alt="itab 结构体" style="zoom:50%;"><p><code>hash</code> 是对 <code>_type.hash</code> 的拷贝,当我们想将 <code>interface</code> 类型转换成具体类型时,可以使用该字段快速判断目标类型和具体类型 <code>runtime._type</code> 是否一致;</p><p><code>fun</code> 是一个动态大小的数组,它是一个用于动态派发的虚函数表,存储了一组函数指针。虽然该变量被声明成大小固定的数组,但是在使用时会通过原始指针获取其中的数据,所以 <code>fun</code> 数组中保存的元素数量是不确定的</p><img src="/images/20220118/接口底层结构.png" alt="接口底层结构" style="zoom:50%;"><br><h3 id="3-1-类型结构体"><a href="#3-1-类型结构体" class="headerlink" title="3.1 类型结构体"></a>3.1 类型结构体</h3><p><code>itab</code> 中的 <code>_type 字段 </code> 代表接口存储的动态类型。Go 语言的各种数据类型都是在_type 字段的基础上通过增加额外字段来管理的。</p><p>下面是运行时包中的结构体,其中包含了很多类型的元信息,例如:类型的大小、哈希、对齐以及种类等。</p><img src="/images/20220118/type结构体.png" alt="_type结构体" style="zoom:50%;"><ul><li><code>size</code> 字段存储了类型占用的内存空间,为内存空间的分配提供信息;</li><li><code>hash</code> 字段能够帮助我们快速确定类型是否相等;</li><li><code>equal</code> 字段用于判断当前类型的多个对象是否相等,该字段是为了减少 Go 语言二进制包大小从 <code>typeAlg</code> 结构体中迁移过来的;</li></ul><br><br>]]></content>
<categories>
<category> golang </category>
</categories>
<tags>
<tag> golang </tag>
<tag> interface </tag>
</tags>
</entry>
<entry>
<title>Go 数据结构 - map</title>
<link href="/2021/12/28/go-shu-ju-jie-gou-map/"/>
<url>/2021/12/28/go-shu-ju-jie-gou-map/</url>
<content type="html"><![CDATA[<h1 id="Go-数据结构-map"><a href="#Go-数据结构-map" class="headerlink" title="Go 数据结构 - map"></a>Go 数据结构 - map</h1><blockquote><p>参考<br> 《Go 语言底层原理剖析》第 8 章<br> <a href="https://mp.weixin.qq.com/s/2CDpE5wfoiNXm1agMAq4wA">深度解密 Go 语言之 map - 码农桃花源</a><br> <a href="https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-hashmap/">理解 Golang 哈希表 map 的原理</a><br> <a href="https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-hashmap/#332-%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84">Go 语言设计与实现</a> 注:本文部分内容从该博客中复制,博主写的太棒了,respect~</p></blockquote><p>map 作为一种基础数据结构,经常会用到,比如统计元素个数、去重等,但是 Go 内置的 map 有个缺点即不支持并发操作。下面将对 map 的使用方式以及底层原理进行介绍,最后再介绍如何实现一个支持并发操作的 map。</p><hr><br><br><h2 id="1-简介"><a href="#1-简介" class="headerlink" title="1. 简介"></a>1. 简介</h2><p>很多语言里都支持 map 数据结构,如 Go 的 map、Java 的 HashMap、Python 的 dict,它们逻辑上都是键值对,所以可以把 map 看作一种 key→value 的映射。实现上一般有两种:</p><ul><li>哈希表:Go 的 map 以及 Python 的 dict 底层是哈希表。最差查找效率是 O(n),平均查找效率是 O(1),遍历哈希表得到的是乱序序列。</li><li>搜索树:一般是 AVL 树和红黑树,如 Java1.8 之后 HashMap 的底层就是数组 + 链表 + 红黑树。自平衡搜索树法的最差搜索效率是 O(lgn),遍历自平衡搜索树,返回的 key 序列,一般会按照从小到大的顺序。</li></ul><hr><br><br><h2 id="2-基本操作"><a href="#2-基本操作" class="headerlink" title="2. 基本操作"></a>2. 基本操作</h2><p>map 支持增删改查这些基础操作,里面也有一些需要注意的点。</p><h3 id="2-1-声明与初始化"><a href="#2-1-声明与初始化" class="headerlink" title="2.1 声明与初始化"></a>2.1 声明与初始化</h3><p>需要注意的是 map 只有在初始化之后才能插入 k-v 对。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// 声明之后插入数据</span><span class="token keyword">var</span> m <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span>m<span class="token punctuation">[</span><span class="token string">"x"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span> <span class="token comment" spellcheck="true">// panic: assignment to entry in nil map</span><span class="token comment" spellcheck="true">// 初始化之后插入数据</span>m <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">int</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">)</span>m<span class="token punctuation">[</span><span class="token string">"x"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// map[x:1]</span><span class="token comment" spellcheck="true">// 字面量初始化</span>m <span class="token operator">:=</span> <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span> <span class="token string">"x"</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">"y"</span><span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span><span class="token punctuation">}</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// map[x:1 y:2]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>不难理解,声明时并没有给 map 分配内存空间,只有在通过 make 初始化 map(或使用字面量初始化) 后给其分配内存空间才能插入数据,下面来验证一下。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// 声明的 map 没有实际的地址</span><span class="token keyword">var</span> m <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span>fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%p, %v\n"</span><span class="token punctuation">,</span> m<span class="token punctuation">,</span> m<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 0x0, map[]</span><span class="token comment" spellcheck="true">// 初始化之后的 map 才有实际的地址</span>m <span class="token operator">=</span> <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token comment" spellcheck="true">// m = make(map[int]string, 10) 初始化时可以设置长度,降低扩容的成本</span>fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%p, %v\n"</span><span class="token punctuation">,</span> m<span class="token punctuation">,</span> m<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 0xc000100d20, map[]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><h3 id="2-2-增-插入键值对"><a href="#2-2-增-插入键值对" class="headerlink" title="2.2 增 - 插入键值对"></a>2.2 增 - 插入键值对</h3><p>给不存在的数据赋值即为插入操作,插入键值很简单,按照 map 的类型将 key 和 value 绑定起来。</p><pre class="line-numbers language-go"><code class="language-go">m <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">)</span>m<span class="token punctuation">[</span><span class="token string">"x"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span>m<span class="token punctuation">[</span><span class="token string">"y"</span><span class="token punctuation">]</span><span class="token operator">++</span> <span class="token comment" spellcheck="true">// 在 int 零值的基础上 + 1,在统计个数时很方便</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// map[x:1 y:1]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><br><h3 id="2-3-删-删除键值对"><a href="#2-3-删-删除键值对" class="headerlink" title="2.3 删 - 删除键值对"></a>2.3 删 - 删除键值对</h3><p>使用 <code>delete(mapName, keyName)</code> 即可根据键删除 map 中相应的键值对。</p><pre class="line-numbers language-go"><code class="language-go">m <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">)</span>m<span class="token punctuation">[</span><span class="token string">"x"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span>m<span class="token punctuation">[</span><span class="token string">"y"</span><span class="token punctuation">]</span><span class="token operator">++</span><span class="token function">delete</span><span class="token punctuation">(</span>m<span class="token punctuation">,</span> <span class="token string">"x"</span><span class="token punctuation">)</span><span class="token function">delete</span><span class="token punctuation">(</span>m<span class="token punctuation">,</span> <span class="token string">"z"</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 删除不存在的 kv 相当于不做任何操作</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// map[y:1]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><h3 id="2-4-改-修改值"><a href="#2-4-改-修改值" class="headerlink" title="2.4 改 - 修改值"></a>2.4 改 - 修改值</h3><p>给存在的数据赋值即为修改操作。</p><pre class="line-numbers language-go"><code class="language-go">m <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">)</span>m<span class="token punctuation">[</span><span class="token string">"x"</span><span class="token punctuation">]</span><span class="token operator">++</span>m<span class="token punctuation">[</span><span class="token string">"x"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">10</span> <span class="token comment" spellcheck="true">// 修改 value</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// map[x:10]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><br><h3 id="2-5-查-查询值"><a href="#2-5-查-查询值" class="headerlink" title="2.5 查 - 查询值"></a>2.5 查 - 查询值</h3><p>根据 key 查询值,可以得到 value 以及 key 是否存在的 bool 值,若 key 不存在则返回零值</p><pre class="line-numbers language-go"><code class="language-go">m <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">)</span>m<span class="token punctuation">[</span><span class="token string">"x"</span><span class="token punctuation">]</span><span class="token operator">++</span>v <span class="token operator">:=</span> m<span class="token punctuation">[</span><span class="token string">"x"</span><span class="token punctuation">]</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>v<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 1</span>v<span class="token punctuation">,</span> ok <span class="token operator">:=</span> m<span class="token punctuation">[</span><span class="token string">"x"</span><span class="token punctuation">]</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>v<span class="token punctuation">,</span> ok<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 1 true</span>v<span class="token punctuation">,</span> ok <span class="token operator">=</span> m<span class="token punctuation">[</span><span class="token string">"y"</span><span class="token punctuation">]</span> <span class="token comment" spellcheck="true">// y 不存在</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>v<span class="token punctuation">,</span> ok<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 0 false</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><br><br><h2 id="3-实现原理"><a href="#3-实现原理" class="headerlink" title="3. 实现原理"></a>3. 实现原理</h2><p>注:源码在 <code>src/runtime/map.go</code> 中</p><h3 id="3-1-map-结构体"><a href="#3-1-map-结构体" class="headerlink" title="3.1 map 结构体"></a>3.1 map 结构体</h3><h4 id="3-1-1-hmap"><a href="#3-1-1-hmap" class="headerlink" title="3.1.1 hmap"></a>3.1.1 hmap</h4><img src="/images/20211228/hmap.png" alt="hmap" style="zoom:50%;"><ul><li>count:元素个数</li><li>flags:代表当前 map 的状态(是否处于正在写入的状态等)</li><li>B:桶的数量: 2^B</li><li>noverflow:溢出桶的数量,当溢出的桶太多时,map 会进行 same-size map growth,其实质是避免溢出桶过大导致内存泄露</li><li>hash0:计算 key hash 时的种子</li><li>buckets:指向新桶</li><li>oldbuckets:指向旧桶,扩容时会用到,所有旧桶中的数据都已经转移到了新桶中时,则清空</li><li>nevacuate:指示扩容进度,小于此地址的 buckets 迁移完成</li><li>extra:存储 map 中的溢出桶</li></ul><h4 id="3-1-2-bmap"><a href="#3-1-2-bmap" class="headerlink" title="3.1.2 bmap"></a>3.1.2 bmap</h4><p>map 的核心是” 桶 “,这也是真正存储 key-value 的数据结构,buckets 和 oldbuckets 是一个指针,最终它指向的是桶的结构体 bmap。</p><img src="/images/20211228/bmap.png" alt="bmap" style="zoom: 50%;"><p>bmap 里存放 tophash 字段,即长度为 8 的数组,存储着 key 的 hash 值的前 8 位。但这只是表面 <code>src/runtime/map.go</code> 的结构,编译期间会给它加料,动态地创建一个新的结构:</p><img src="/images/20211228/bmap-gc.png" alt="bmap-gc" style="zoom:50%;"><p>bmap 桶里面会最多装 8 个 key,这些 key 之所以会落入同一个桶,是因为它们经过哈希计算后,哈希结果是 “一类” 的。在桶内,又会根据 key 计算出来的 hash 值的高 8 位来决定 key 到底落入桶内的哪个位置(一个桶内最多有 8 个位置)。</p><p><img src="/images/20211228/hmap%E7%BB%93%E6%9E%84.png" alt="hmap 结构"></p><p>Go 语言选择将 key 与 value 分开存储而不是以 key/value/key/value 的形式存储,是为了在字节对齐时压缩空间。在根据 key 找 value 的过程中,首先计算 key 的 hash 值从而找到桶的 index(hash%array_size), 找到桶的位置后遍历 tophash 数组,如果在数组中找到了相同的 hash,那么可以接着通过指针的寻址操作找到对应的 key 与 value。</p><p><img src="/images/20211228/map%E7%BB%93%E6%9E%84.png" alt="map 结构"></p><h4 id="3-1-3-mapextra"><a href="#3-1-3-mapextra" class="headerlink" title="3.1.3 mapextra"></a>3.1.3 mapextra</h4><p>在 Go 语言中还有一个溢出桶的概念,在执行 hash[key]=value 赋值操作时,当指定桶中的数据超过 8 个时,并不会直接开辟一个新桶,而是将数据放置到溢出桶中,每个桶的最后都存储了 overflow,即溢出桶的指针。在正常情况下,数据是很少会跑到溢出桶里面去的。</p><img src="/images/20211228/mapextra.png" alt="mapextra" style="zoom:50%;"><img src="/images/20211228/hmap-buckets.png" alt="hmap-buckets" style="zoom:50%;"><p>当发生以下两种情况之一时,map 会进行重建:</p><ul><li>map 超过了负载因子大小。</li><li>溢出桶的数量过多。</li></ul><p>在哈希表中有经典的负载因子的概念:<strong>负载因子 = 哈希表中的元素数量 / 桶的数量。</strong>负载因子的增大,意味着更多的元素会被分配到同一个桶中,此时效率会减慢。Go 语言中的负载因子为 6.5,当超过其大小后,map 会进行扩容,增大到旧表 2 倍的大小,旧桶的数据会存到 oldbuckets 字段中,并想办法分散转移到新桶中。当旧桶中的数据全部转移到新桶中后,旧桶就会被清空。</p><p>溢出桶的数量太多,这时 map 只会新建和原来相同大小的桶,目的是防止溢出桶的数量缓慢增长导致的内存泄露。</p><br><h3 id="3-2-初始化"><a href="#3-2-初始化" class="headerlink" title="3.2 初始化"></a>3.2 初始化</h3><p>Go 初始化 map 主要通过两种方式 — <strong>字面量</strong> & <strong>运行时</strong></p><h4 id="3-2-1-字面量初始化"><a href="#3-2-1-字面量初始化" class="headerlink" title="3.2.1 字面量初始化"></a>3.2.1 字面量初始化</h4><pre class="line-numbers language-go"><code class="language-go">m <span class="token operator">:=</span> <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span> <span class="token string">"x"</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">"y"</span><span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span><span class="token punctuation">}</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// map[x:1 y:2]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果 map 采取了字面量初始化的方式,那么它最终仍然需要转换为 make 操作。map 的长度被自动推断为字面量的长度,其核心逻辑位于 <a href="https://github.com/golang/go/blob/ac0ba6707c1655ea4316b41d06571a0303cc60eb/src/cmd/compile/internal/gc/sinit.go#L753">gc/sinit.go</a> 中,该函数专门用于处理各种类型的字面量。</p><p>若 <code>len(map) ≤ 25</code>,则会将字面量初始化的结构体转为以下代码,将 key-value 一次加入 map。</p><pre class="line-numbers language-go"><code class="language-go">m <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span>m<span class="token punctuation">[</span><span class="token string">"x"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span>m<span class="token punctuation">[</span><span class="token string">"y"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">2</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>若 <code>len(map) > 25</code>,编译器会构建两个数组专门存储 key 与 value,在运行时循环添加数据。</p><pre class="line-numbers language-go"><code class="language-go">m <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">26</span><span class="token punctuation">)</span>vstatk <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token string">"a"</span><span class="token punctuation">,</span> <span class="token string">"b"</span><span class="token punctuation">,</span> <span class="token string">"c"</span><span class="token punctuation">,</span> <span class="token operator">...</span> <span class="token punctuation">,</span> <span class="token string">"z"</span><span class="token punctuation">}</span>vstatv <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token operator">...</span> <span class="token punctuation">,</span> <span class="token number">26</span><span class="token punctuation">}</span><span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span><span class="token function">len</span><span class="token punctuation">(</span>vstak<span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span> m<span class="token punctuation">[</span>vstatk<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">=</span> vstatv<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="3-2-2-运行时初始化"><a href="#3-2-2-运行时初始化" class="headerlink" title="3.2.2 运行时初始化"></a>3.2.2 运行时初始化</h4><p><strong>小容量 hash 优化</strong>:当创建的哈希被分配到栈上并且其容量小于 <code>BUCKETSIZE = 8</code> 时,Go 语言在编译阶段会使用如下方式快速初始化哈希,这也是编译器对小容量的哈希做的优化。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">var</span> h <span class="token operator">*</span>hmap<span class="token keyword">var</span> hv hmap<span class="token keyword">var</span> bv bmaph <span class="token operator">:=</span> <span class="token operator">&</span>hvb <span class="token operator">:=</span> <span class="token operator">&</span>bvh<span class="token punctuation">.</span>buckets <span class="token operator">=</span> bh<span class="token punctuation">.</span>hash0 <span class="token operator">=</span> <span class="token function">fashtrand0</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>makemap</strong></p><p>初始化 map 最后调用的都是 <a href="https://github.com/golang/go/blob/ac0ba6707c1655ea4316b41d06571a0303cc60eb/src/runtime/map.go#L303">makemap 函数</a></p><ol><li>计算哈希占用的内存是否溢出或者超出能分配的最大值;</li><li>调用 <a href="https://github.com/golang/go/blob/41d8e61a6b9d8f9db912626eb2bbc535e929fefc/src/runtime/stubs.go#L109">runtime.fastrand</a> 获取一个随机的哈希种子;</li><li>根据传入的 <code>hint</code> 计算出需要的最小需要的桶的数量;</li><li>使用 <a href="https://github.com/golang/go/blob/ac0ba6707c1655ea4316b41d06571a0303cc60eb/src/runtime/map.go#L344">runtime.makeBucketArray</a> 创建用于保存桶的数组;</li></ol><img src="/images/20211228/makemap.png" alt="makemap" style="zoom:50%;"><p>makemap 函数会计算出需要的桶的数量,即 ${log_2N}$ ,并调用 makeBucketArray 函数生成桶和溢出桶。如果初始化时生成了溢出桶,则会放置到 map 的 extra 字段里去。</p><p><strong>makeBucketArray</strong></p><p><a href="https://github.com/golang/go/blob/ac0ba6707c1655ea4316b41d06571a0303cc60eb/src/runtime/map.go#L344">runtime.makeBucketArray</a> 会根据传入的 <code>B</code> 计算出的需要创建的桶数量并在内存中分配一片连续的空间用于存储数据:</p><img src="/images/20211228/makeBucketArray.png" alt="makeBucketArray" style="zoom:50%;"><ul><li>当桶的数量小于 2^4 时,由于数据较少、使用溢出桶的可能性较低,会省略创建的过程以减少额外开销;</li><li>当桶的数量多于 2^4 时,会额外创建 2^(B-4) 个溢出桶;</li></ul><br><h3 id="3-3-查-查询值"><a href="#3-3-查-查询值" class="headerlink" title="3.3 查 - 查询值"></a>3.3 查 - 查询值</h3><p>根据 key 查询 value 时有两种方式,因为 Go 没有函数重载所以会调用两个函数名不同的函数,但是内部逻辑基本相同。</p><pre class="line-numbers language-go"><code class="language-go">v <span class="token operator">:=</span> hash<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token comment" spellcheck="true">// => v := *mapaccess1(maptype, hash, &key)</span>v<span class="token punctuation">,</span> ok <span class="token operator">:=</span> hash<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token comment" spellcheck="true">// => v, ok := mapaccess2(maptype, hash, &key)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p><strong>mapaccess1</strong></p><p><a href="https://github.com/golang/go/blob/ac0ba6707c1655ea4316b41d06571a0303cc60eb/src/runtime/map.go#L394">mapaccess1</a> 会先算出 key 的 hash 值,再通过 <a href="https://github.com/golang/go/blob/ac0ba6707c1655ea4316b41d06571a0303cc60eb/src/runtime/map.go#L189">bucketMask</a> 和 <a href="https://github.com/golang/go/blob/ac0ba6707c1655ea4316b41d06571a0303cc60eb/src/runtime/stubs.go#L11">add</a> 拿到该键值对所在的桶序号和 hash 值高位的 8 位数字。之后会依次遍历正常桶和溢出桶中的数据,它会先比较 hash 值的高 8 位和桶中存储的 <code>tophash</code>,后比较传入的和桶中的值以加速数据的读写。</p><p>用于选择桶序号的是哈希的最低几位,而用于加速访问的是哈希的高 8 位,这种设计能够减少同一个桶中有大量相等 <code>tophash</code> 的概率影响性能。</p><img src="/images/20211228/mapaccess.png" alt="mapaccess" style="zoom:50%;"><p>如上图所示,每一个桶都是一整片的内存空间,当发现桶中的 <code>tophash</code> 与传入键的 <code>tophash</code> 匹配之后,我们会通过指针和偏移量获取哈希中存储的键 <code>keys[0]</code> 并与 <code>key</code> 比较,如果两者相同就会获取目标值的指针 <code>values[0]</code> 并返回。</p><p><strong>mapaccess2</strong></p><p><a href="https://github.com/golang/go/blob/ac0ba6707c1655ea4316b41d06571a0303cc60eb/src/runtime/map.go#L452">mapaccess2</a> 在 mapaccess1 的基础上多返回了一个标识键值对是否存在的 <code>bool</code> 值,使用 <code>v, ok := hash[k]</code> 的形式访问哈希表中元素时,我们能够通过这个布尔值更准确地知道当 <code>v == nil</code> 时,<code>v</code> 到底是哈希中存储的元素还是表示该键对应的元素不存在,所以在访问哈希时,更推荐使用这种方式判断元素是否存在。</p><br><h3 id="3-4-增-改-写入-修改键值对"><a href="#3-4-增-改-写入-修改键值对" class="headerlink" title="3.4 增 / 改 - 写入 / 修改键值对"></a>3.4 增 / 改 - 写入 / 修改键值对</h3><h4 id="3-4-1-操作流程"><a href="#3-4-1-操作流程" class="headerlink" title="3.4.1 操作流程"></a>3.4.1 操作流程</h4><p>当形如 <code>hash[k]</code> 的表达式出现在赋值符号左侧时,该表达式也会在编译期间转换成 <a href="https://github.com/golang/go/blob/ac0ba6707c1655ea4316b41d06571a0303cc60eb/src/runtime/map.go#L571">runtime.mapassign</a> 函数的调用。</p><img src="/images/20211228/overflow.png" alt="overflow" style="zoom:50%;"><ol><li>首先是函数会根据传入的键拿到对应的哈希和桶。</li><li>然后通过遍历比较桶中存储的 <code>tophash</code> 和键的哈希,如果找到了相同结果就会返回目标位置的地址。其中 <code>inserti</code> 表示目标元素的在桶中的索引,<code>insertk</code> 和 <code>val</code> 分别表示键值对的地址,获得目标地址之后会通过算术计算寻址获得键值对 <code>k</code> 和 <code>val</code>。</li><li>如果当前桶已经满了,哈希会调用 <a href="https://draveness.me/golang/tree/runtime.hmap.newoverflow">runtime.hmap.newoverflow</a> 创建新桶或者使用 <a href="https://draveness.me/golang/tree/runtime.hmap">runtime.hmap</a> 预先在 <code>noverflow</code> 中创建好的桶来保存数据,新创建的桶不仅会被追加到已有桶的末尾,还会增加哈希表的 <code>noverflow</code> 计数器。</li><li>如果当前键值对在哈希中不存在,哈希会为新键值对规划存储的内存地址,通过 <a href="https://draveness.me/golang/tree/runtime.typedmemmove">runtime.typedmemmove</a> 将键移动到对应的内存空间中并返回键对应值的地址 <code>val</code>。如果当前键值对在哈希中存在,那么就会直接返回目标区域的内存地址,哈希并不会在 <a href="https://draveness.me/golang/tree/runtime.mapassign">runtime.mapassign</a> 这个运行时函数中将值拷贝到桶中,该函数只会返回内存地址,真正的赋值操作是在编译期间插入的。</li></ol><h4 id="3-4-2-扩容"><a href="#3-4-2-扩容" class="headerlink" title="3.4.2 扩容"></a>3.4.2 扩容</h4><p>随着哈希表中元素的逐渐增加,哈希的性能会逐渐恶化,所以我们需要更多的桶和更大的内存保证哈希的读写性能。</p><p>以下两种情况发生时触发哈希的扩容:</p><ol><li>装载因子已经超过 6.5;</li><li>哈希使用了太多溢出桶;</li></ol><p>不过因为 Go 语言哈希的扩容不是一个原子的过程,所以 <a href="https://draveness.me/golang/tree/runtime.mapassign">runtime.mapassign</a> 还需要判断当前哈希是否已经处于扩容状态,避免二次扩容造成混乱。</p><p>根据触发的条件不同扩容的方式分成两种,如果这次扩容是溢出的桶太多导致的,那么这次扩容就是等量扩容 <code>sameSizeGrow</code>,<code>sameSizeGrow</code> 是一种特殊情况下发生的扩容,当我们持续向哈希中插入数据并将它们全部删除时,如果哈希表中的数据量没有超过阈值,就会不断积累溢出桶造成缓慢的内存泄漏 <a href="https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-hashmap/#fn:4">4</a>。<a href="https://github.com/golang/go/commit/9980b70cb460f27907a003674ab1b9bea24a847c">runtime: limit the number of map overflow buckets</a> 引入了 <code>sameSizeGrow</code> 通过复用已有的哈希扩容机制解决该问题,一旦哈希中出现了过多的溢出桶,它会创建新桶保存数据,垃圾回收会清理老的溢出桶并释放内存。</p><p>扩容的入口是 <a href="https://draveness.me/golang/tree/runtime.hashGrow">runtime.hashGrow</a> ,哈希在扩容的过程中会通过 <a href="https://draveness.me/golang/tree/runtime.makeBucketArray">runtime.makeBucketArray</a> 创建一组新桶和预创建的溢出桶,随后将原有的桶数组设置到 <code>oldbuckets</code> 上并将新的空桶设置到 <code>buckets</code> 上,溢出桶也使用了相同的逻辑更新,下图展示了触发扩容后的哈希:</p><img src="/images/20211228/hashgrow.png" alt="hashgrow" style="zoom:50%;"><p>我们在 <a href="https://draveness.me/golang/tree/runtime.hashGrow">runtime.hashGrow</a> 中还看不出来等量扩容和翻倍扩容的太多区别,等量扩容创建的新桶数量只是和旧桶一样,该函数中只是创建了新的桶,并没有对数据进行拷贝和转移。哈希表的数据迁移的过程在是 <a href="https://draveness.me/golang/tree/runtime.evacuate">runtime.evacuate</a> 中完成的,它会对传入桶中的元素进行再分配。</p><p><a href="https://draveness.me/golang/tree/runtime.evacuate">runtime.evacuate</a> 会将一个旧桶中的数据分流到两个新桶,所以它会创建两个用于保存分配上下文的 <a href="https://draveness.me/golang/tree/runtime.evacDst">runtime.evacDst</a> 结构体,这两个结构体分别指向了一个新桶。</p><img src="/images/20211228/evacuate.png" alt="evacuate" style="zoom:50%;"><p>我们简单总结一下哈希表扩容的设计和原理,哈希在存储元素过多时会触发扩容操作,每次都会将桶的数量翻倍,扩容过程不是原子的,而是通过 <a href="https://draveness.me/golang/tree/runtime.growWork">runtime.growWork</a> 增量触发的,在扩容期间访问哈希表时会使用旧桶,向哈希表写入数据时会触发旧桶元素的分流。除了这种正常的扩容之外,为了解决大量写入、删除造成的内存泄漏问题,哈希引入了 <code>sameSizeGrow</code> 这一机制,在出现较多溢出桶时会整理哈希的内存减少空间的占用。</p><br><h3 id="3-5-删-删除键值对"><a href="#3-5-删-删除键值对" class="headerlink" title="3.5 删 - 删除键值对"></a>3.5 删 - 删除键值对</h3><p>如果想要删除哈希中的元素,就需要使用 Go 语言中的 <code>delete</code> 关键字,这个关键字的唯一作用就是将某一个键对应的元素从哈希表中删除,无论是该键对应的值是否存在,这个内建的函数都不会返回任何的结果。</p><p>哈希表的删除逻辑与写入逻辑很相似,只是触发哈希的删除需要使用关键字,如果在删除期间遇到了哈希表的扩容,就会分流桶中的元素,分流结束之后会找到桶中的目标元素完成键值对的删除工作。</p><hr><br><br><h2 id="4-总结"><a href="#4-总结" class="headerlink" title="4 总结"></a>4 总结</h2><p>Go 语言使用拉链法来解决哈希碰撞的问题实现了哈希表,它的访问、写入和删除等操作都在编译期间转换成了运行时的函数或者方法。哈希在每一个桶中存储键对应哈希的前 8 位,当对哈希进行操作时,这些 <code>tophash</code> 就成为可以帮助哈希快速遍历桶中元素的缓存。</p><p>哈希表的每个桶都只能存储 8 个键值对,一旦当前哈希的某个桶超出 8 个,新的键值对就会存储到哈希的溢出桶中。随着键值对数量的增加,溢出桶的数量和哈希的装载因子也会逐渐升高,超过一定范围就会触发扩容,扩容会将桶的数量翻倍,元素再分配的过程也是在调用写操作时增量进行的,不会造成性能的瞬时巨大抖动。</p><br><br>]]></content>
<categories>
<category> golang </category>
</categories>
<tags>
<tag> golang </tag>
<tag> map </tag>
</tags>
</entry>
<entry>
<title>Go数据类型-数组&切片</title>
<link href="/2021/12/22/go-shu-ju-lei-xing-shu-zu-qie-pian/"/>
<url>/2021/12/22/go-shu-ju-lei-xing-shu-zu-qie-pian/</url>
<content type="html"><![CDATA[<h1 id="Go数据类型-数组-amp-切片"><a href="#Go数据类型-数组-amp-切片" class="headerlink" title="Go数据类型-数组&切片"></a>Go数据类型-数组&切片</h1><blockquote><p>参考<br> 《Go语言底层原理剖析》第6、7章<br> 《Go专家编程》第1章<br> 《Go语言学习笔记》第5章<br> 《Go语言高级编程》第1章<br> <a href="https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-array/">《Go语言设计与实现》第3章</a></p></blockquote><p>数组和切片作为Go中的基础数据类型,使用十分频繁。一般在处理函数参数时,切片用的比较多,因为Go是值传递,如果参数是数组那么会把数组复制一遍,开销比较大,如果参数是切片只会把切片结构体复制一遍,开销就很小了。</p><p>切片的底层是数组,只不过增加了<code>容量</code>和<code>长度</code>两个属性。下面分别介绍数组以及切片的使用方法以及底层实现原理。</p><hr><br><h2 id="1-数组"><a href="#1-数组" class="headerlink" title="1 数组"></a>1 数组</h2><h3 id="1-1-声明方式"><a href="#1-1-声明方式" class="headerlink" title="1.1 声明方式"></a>1.1 声明方式</h3><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"unsafe"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 两种声明方式</span> <span class="token keyword">var</span> arr1 <span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token builtin">int</span> <span class="token comment" spellcheck="true">// 指定长度</span> <span class="token keyword">var</span> arr2 <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 指定长度</span> arr3 <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token operator">...</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 推断长度</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"arr1: %p %T -> %v\n"</span><span class="token punctuation">,</span> <span class="token operator">&</span>arr1<span class="token punctuation">,</span> arr1<span class="token punctuation">,</span> arr1<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"arr2: %p %T -> %v\n"</span><span class="token punctuation">,</span> <span class="token operator">&</span>arr2<span class="token punctuation">,</span> arr2<span class="token punctuation">,</span> arr2<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"arr3: %p %T -> %v\n"</span><span class="token punctuation">,</span> <span class="token operator">&</span>arr3<span class="token punctuation">,</span> arr3<span class="token punctuation">,</span> arr3<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span><span class="token function">Sizeof</span><span class="token punctuation">(</span>arr1<span class="token punctuation">)</span><span class="token punctuation">,</span> unsafe<span class="token punctuation">.</span><span class="token function">Sizeof</span><span class="token punctuation">(</span>arr2<span class="token punctuation">)</span><span class="token punctuation">,</span> unsafe<span class="token punctuation">.</span><span class="token function">Sizeof</span><span class="token punctuation">(</span>arr3<span class="token punctuation">)</span><span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span><span class="token function">Sizeof</span><span class="token punctuation">(</span><span class="token function">int</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token function">len</span><span class="token punctuation">(</span>arr3<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 可以通过len得到数组的长度</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// arr1: 0xc0000180c0 [3]int -> [0 0 0]</span><span class="token comment" spellcheck="true">// arr2: 0xc0000180d8 [3]int -> [1 2 3]</span><span class="token comment" spellcheck="true">// arr3: 0xc0000180f0 [3]int -> [4 5 6]</span><span class="token comment" spellcheck="true">// 24 24 24</span><span class="token comment" spellcheck="true">// 8</span><span class="token comment" spellcheck="true">// 3</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>数组的声明方式主要有两种,都会产生一个固定长度的相同元素排列,且每一个元素的物理地址都是相邻的。</p><p>如上述例子中,每个数组的长度是 3,所占用的空间都是连续的 24 个字节。这是因为<code>int</code>类型本质上是<code>int64</code>,占用<code>64/8=8</code>个字节,三个<code>int</code>就是 24 字节。</p><p>可以通过<code>len</code>得到数组的长度,关于len的细节参考这篇blog:<a href="https://tpaschalis.github.io/golang-len/">https://tpaschalis.github.io/golang-len/</a></p><br><h3 id="1-2-数组复制"><a href="#1-2-数组复制" class="headerlink" title="1.2 数组复制"></a>1.2 数组复制</h3><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">var</span> arr1 <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">}</span>fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"arr1: %p %T -> %v\n"</span><span class="token punctuation">,</span> <span class="token operator">&</span>arr1<span class="token punctuation">,</span> arr1<span class="token punctuation">,</span> arr1<span class="token punctuation">)</span><span class="token function">copyArray</span><span class="token punctuation">(</span>arr1<span class="token punctuation">)</span>fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"arr1: %p %T -> %v\n"</span><span class="token punctuation">,</span> <span class="token operator">&</span>arr1<span class="token punctuation">,</span> arr1<span class="token punctuation">,</span> arr1<span class="token punctuation">)</span><span class="token comment" spellcheck="true">// arr1: 0xc0000b6000 [3]int -> [1 2 3]</span><span class="token comment" spellcheck="true">// a: 0xc0000b6060 [3]int -> [1 123 3] 与arr1地址不同</span><span class="token comment" spellcheck="true">// arr1: 0xc0000b6000 [3]int -> [1 2 3]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Go语言中的数组在赋值和函数调用时的形参都是<code>值复制</code>,在函数中修改数组实际上是修改数组副本。</p><br><h3 id="1-3-实现原理"><a href="#1-3-实现原理" class="headerlink" title="1.3 实现原理"></a>1.3 实现原理</h3><h4 id="1-3-1-创建数组"><a href="#1-3-1-创建数组" class="headerlink" title="1.3.1 创建数组"></a>1.3.1 创建数组</h4><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">NewArray</span><span class="token punctuation">(</span>elem <span class="token operator">*</span>Type<span class="token punctuation">,</span> bound <span class="token builtin">int64</span><span class="token punctuation">)</span> <span class="token operator">*</span>Type <span class="token punctuation">{</span> <span class="token keyword">if</span> bound <span class="token operator"><</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token function">Fatalf</span><span class="token punctuation">(</span><span class="token string">"NewArray: invalid bound %v"</span><span class="token punctuation">,</span> bound<span class="token punctuation">)</span> <span class="token punctuation">}</span> t <span class="token operator">:=</span> <span class="token function">New</span><span class="token punctuation">(</span>TARRAY<span class="token punctuation">)</span> t<span class="token punctuation">.</span>Extra <span class="token operator">=</span> <span class="token operator">&</span>Array<span class="token punctuation">{</span>Elem<span class="token punctuation">:</span> elem<span class="token punctuation">,</span> Bound<span class="token punctuation">:</span> bound<span class="token punctuation">}</span> t<span class="token punctuation">.</span><span class="token function">SetNotInHeap</span><span class="token punctuation">(</span>elem<span class="token punctuation">.</span><span class="token function">NotInHeap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> t<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>编译期间的数组类型是由上述的<a href="https://github.com/golang/go/blob/da54dfb6a1f3bef827b9ec3780c98fde77a97d11/src/cmd/compile/internal/types/type.go#L482">/src/cmd/compile/internal/types.NewArray</a> 函数生成的,该类型包含两个字段,分别是元素类型 <code>Elem</code> 和数组的大小 <code>Bound</code>,这两个字段共同构成了数组类型,而当前数组是否应该在堆栈中初始化也在编译期就确定了。</p><h4 id="1-3-2-数组初始化"><a href="#1-3-2-数组初始化" class="headerlink" title="1.3.2 数组初始化"></a>1.3.2 数组初始化</h4><p>Go 语言的数组有两种不同的创建方式,一种是显式的指定数组大小,另一种是使用 <code>[...]T</code> 声明数组,Go 语言会在编译期间通过源代码推导数组的大小,将后一种转换为前一种。</p><ol><li><strong>指定数组大小</strong>:使用上面提到的 <code>NewArray</code> 进行初始化。</li><li><strong>不指定数组大小</strong>:编译器会在对该数组的大小进行推导,通过遍历元素的方式来计算数组中元素的数量。</li></ol><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">typecheckcomplit</span><span class="token punctuation">(</span>n <span class="token operator">*</span>Node<span class="token punctuation">)</span> <span class="token punctuation">(</span>res <span class="token operator">*</span>Node<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token keyword">if</span> n<span class="token punctuation">.</span>Right<span class="token punctuation">.</span>Op <span class="token operator">==</span> OTARRAY <span class="token operator">&&</span> n<span class="token punctuation">.</span>Right<span class="token punctuation">.</span>Left <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token operator">&&</span> n<span class="token punctuation">.</span>Right<span class="token punctuation">.</span>Left<span class="token punctuation">.</span>Op <span class="token operator">==</span> ODDD <span class="token punctuation">{</span> n<span class="token punctuation">.</span>Right<span class="token punctuation">.</span>Right <span class="token operator">=</span> <span class="token function">typecheck</span><span class="token punctuation">(</span>n<span class="token punctuation">.</span>Right<span class="token punctuation">.</span>Right<span class="token punctuation">,</span> ctxType<span class="token punctuation">)</span> <span class="token keyword">if</span> n<span class="token punctuation">.</span>Right<span class="token punctuation">.</span>Right<span class="token punctuation">.</span>Type <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> n<span class="token punctuation">.</span>Type <span class="token operator">=</span> <span class="token boolean">nil</span> <span class="token keyword">return</span> n <span class="token punctuation">}</span> elemType <span class="token operator">:=</span> n<span class="token punctuation">.</span>Right<span class="token punctuation">.</span>Right<span class="token punctuation">.</span>Type <span class="token comment" spellcheck="true">// 通过遍历元素的方式来计算数组中元素的数量</span> length <span class="token operator">:=</span> <span class="token operator">*</span><span class="token operator">*</span>typecheckarraylit<span class="token operator">*</span><span class="token operator">*</span><span class="token punctuation">(</span>elemType<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> n<span class="token punctuation">.</span>List<span class="token punctuation">.</span><span class="token function">Slice</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"array literal"</span><span class="token punctuation">)</span> n<span class="token punctuation">.</span>Op <span class="token operator">=</span> OARRAYLIT n<span class="token punctuation">.</span>Type <span class="token operator">=</span> types<span class="token punctuation">.</span><span class="token function">NewArray</span><span class="token punctuation">(</span>elemType<span class="token punctuation">,</span> length<span class="token punctuation">)</span> n<span class="token punctuation">.</span>Right <span class="token operator">=</span> <span class="token boolean">nil</span> <span class="token keyword">return</span> n <span class="token punctuation">}</span> <span class="token operator">...</span> <span class="token keyword">switch</span> t<span class="token punctuation">.</span>Etype <span class="token punctuation">{</span> <span class="token keyword">case</span> TARRAY<span class="token punctuation">:</span> <span class="token function">typecheckarraylit</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span><span class="token function">Elem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> t<span class="token punctuation">.</span><span class="token function">NumElem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> n<span class="token punctuation">.</span>List<span class="token punctuation">.</span><span class="token function">Slice</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"array literal"</span><span class="token punctuation">)</span> n<span class="token punctuation">.</span>Op <span class="token operator">=</span> OARRAYLIT n<span class="token punctuation">.</span>Right <span class="token operator">=</span> <span class="token boolean">nil</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这个删减后的 <a href="https://github.com/golang/go/blob/5b0ec1a6ac0e644c89940e0fe5f79863ad2eafaa/src/cmd/compile/internal/gc/typecheck.go#L2793">cmd/compile/internal/gc.typecheckcomplit</a> 会调用 <a href="https://github.com/golang/go/blob/5b0ec1a6ac0e644c89940e0fe5f79863ad2eafaa/src/cmd/compile/internal/gc/typecheck.go#L3004">cmd/compile/internal/gc.typecheckarraylit</a> 通过遍历元素的方式来计算数组中元素的数量。</p><p>对于一个由字面量组成的数组,根据数组元素数量的不同,编译器会在负责初始化字面量的 <a href="https://github.com/golang/go/blob/da54dfb6a1f3bef827b9ec3780c98fde77a97d11/src/cmd/compile/internal/gc/sinit.go#L860">cmd/compile/internal/gc.anylit</a> 函数中做两种不同的优化:</p><ol><li>当元素数量小于或者等于 4 个时,会直接将数组中的元素放置在栈上;</li><li>当元素数量大于 4 个时,会将数组中的元素放置到静态区并在运行时取出;</li></ol><h4 id="1-3-3-越界检查"><a href="#1-3-3-越界检查" class="headerlink" title="1.3.3 越界检查"></a>1.3.3 越界检查</h4><p>数组访问越界是非常严重的错误,Go语言中对越界的判断有一部分是在编译期间类型检查阶段完成的,typecheck1函数会对访问数组的索引进行验证。</p><p>具体的验证逻辑如下:</p><ul><li>访问数组的索引是非整数时报错为<code>non-integer array index%v</code>。</li><li>访问数组的索引是负数时报错为<code>invalid array index%v(index must be non-negative)</code>。</li><li>访问数组的索引越界时报错为<code>invalid array index%v(out of bounds for%d-element array)</code>。</li></ul><hr><br><br><h2 id="2-切片"><a href="#2-切片" class="headerlink" title="2 切片"></a>2 切片</h2><p>Go语言程序中很少使用数组,基本上都是用的切片<code>slice</code>。slice的底层还是数组,但是slice的长度是可变的,下面对slice进行介绍。</p><h3 id="2-1-声明方式"><a href="#2-1-声明方式" class="headerlink" title="2.1 声明方式"></a>2.1 声明方式</h3><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">var</span> slice1 <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span> <span class="token comment" spellcheck="true">// 类似与数组声明,只不过不设定长度</span>slice2 <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span>slice3 <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">7</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// make(type, len, cap)</span>slice4 <span class="token operator">:=</span> slice3<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token number">3</span><span class="token punctuation">]</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>slice1<span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>slice1<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">cap</span><span class="token punctuation">(</span>slice1<span class="token punctuation">)</span><span class="token punctuation">)</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>slice2<span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>slice2<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">cap</span><span class="token punctuation">(</span>slice2<span class="token punctuation">)</span><span class="token punctuation">)</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>slice3<span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>slice3<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">cap</span><span class="token punctuation">(</span>slice3<span class="token punctuation">)</span><span class="token punctuation">)</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>slice4<span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>slice4<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">cap</span><span class="token punctuation">(</span>slice4<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// [] 0 0</span><span class="token comment" spellcheck="true">// [0 0 0 0 0] 5 5</span><span class="token comment" spellcheck="true">// [0 0 0 0 0] 5 7</span><span class="token comment" spellcheck="true">// [0 0] 2 6</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>slice声明有两种方式:</p><ol><li><code>[]int</code> 类似与数组声明,只不过不设定长度</li><li><code>make(type, len, cap)</code> 设定slice的长度和容量,为了减少后面扩容带来的损耗通常一次分配内存。需要注意的是,当长度不等0时会给slice的前len个元素分配默认值,如slice3的前5个元素都是0。</li></ol><p>也可以从已有的slice或数组中通过截取 <code>[left:right]</code>的方式获得新的切片(左闭右开),后面详细介绍。</p><p><code>Slice数据结构</code></p><p>slice的结构体定义在 <a href="https://github.com/golang/go/blob/41d8e61a6b9d8f9db912626eb2bbc535e929fefc/src/reflect/value.go#L1994">/src/reflect/value.go#L1994</a> 中,包含三个字段:</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// SliceHeader is the runtime representation of a slice.</span><span class="token comment" spellcheck="true">// It cannot be used safely or portably and its representation may</span><span class="token comment" spellcheck="true">// change in a later release.</span><span class="token comment" spellcheck="true">// Moreover, the Data field is not sufficient to guarantee the data</span><span class="token comment" spellcheck="true">// it references will not be garbage collected, so programs must keep</span><span class="token comment" spellcheck="true">// a separate, correctly typed pointer to the underlying data.</span><span class="token keyword">type</span> SliceHeader <span class="token keyword">struct</span> <span class="token punctuation">{</span> Data <span class="token builtin">uintptr</span> Len <span class="token builtin">int</span> Cap <span class="token builtin">int</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>Data</code>指向底层的数组,<code>Len</code>为切片长度,<code>Cap</code>为切片容量。</p><p><img src="/images/20211222/Slice.png" alt="Slice"></p><br><h3 id="2-2-切片截取"><a href="#2-2-切片截取" class="headerlink" title="2.2 切片截取"></a>2.2 切片截取</h3><pre class="line-numbers language-go"><code class="language-go">slice1 <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">7</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token number">9</span><span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// slice</span>array <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token operator">...</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">7</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token number">9</span><span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// array</span>slice2 <span class="token operator">:=</span> slice1<span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">:</span><span class="token number">7</span><span class="token punctuation">]</span>slice3 <span class="token operator">:=</span> array<span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">:</span><span class="token number">7</span><span class="token punctuation">]</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>slice2<span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>slice2<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">cap</span><span class="token punctuation">(</span>slice2<span class="token punctuation">)</span><span class="token punctuation">)</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>slice3<span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>slice3<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">cap</span><span class="token punctuation">(</span>slice3<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// [5 6] 2 5</span><span class="token comment" spellcheck="true">// [5 6] 2 5</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>切片截取可以从切片里截也可以从数组里截,截取的规则遵循左闭右开,例如上面 <code>[5:7]</code> 表示从下标为 5 到 下标为6,不包括下标为 7 的元素。</p><p>不难理解截取出来的 slice 长度为 2,但是为什么容量 cap 等于 5 呢?这是因为截取的 slice 依然是指向被截取的 slice 或 array,二者底层共用一个数组。</p><p><img src="/images/20211222/%E5%88%87%E7%89%87%E6%88%AA%E5%8F%96.png" alt="切片截取"></p><pre class="line-numbers language-go"><code class="language-go">slice1 <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">7</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token number">9</span><span class="token punctuation">}</span>slice2 <span class="token operator">:=</span> slice1<span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">:</span><span class="token number">7</span><span class="token punctuation">]</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>slice2<span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>slice2<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">cap</span><span class="token punctuation">(</span>slice2<span class="token punctuation">)</span><span class="token punctuation">)</span>slice1<span class="token punctuation">[</span><span class="token number">6</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">10</span>slice1 <span class="token operator">=</span> <span class="token function">append</span><span class="token punctuation">(</span>slice1<span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>slice1<span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>slice1<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">cap</span><span class="token punctuation">(</span>slice1<span class="token punctuation">)</span><span class="token punctuation">)</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>slice2<span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>slice2<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">cap</span><span class="token punctuation">(</span>slice2<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// [5 6]</span><span class="token comment" spellcheck="true">// [0 1 2 3 4 5 10 7 8 9 10] 11 20</span><span class="token comment" spellcheck="true">// [5 10] 2 5</span>slice1 <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">7</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token number">9</span><span class="token punctuation">}</span>slice2 <span class="token operator">:=</span> slice1<span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">:</span><span class="token number">7</span><span class="token punctuation">:</span><span class="token number">8</span><span class="token punctuation">]</span> <span class="token comment" spellcheck="true">// [start:end:cap] 设置切片的容量</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>slice2<span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>slice2<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">cap</span><span class="token punctuation">(</span>slice2<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// [5 6] 2 3</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如上修改原切片里的元素,截取出来的切片能够感知到该切片的改动,说明二者共用一个底层。可以观察到 slice1 在 append 一个元素后其 cap 变成了 20,而 slice2 的 cap 还是 5。</p><p>这就涉及到 slice 的扩容机制了,在这种情况下, slice 的 cap 翻倍并且指向一个新的底层数组,但是 slice2 还是指向原来的底层数组。</p><br><h3 id="2-3-扩容"><a href="#2-3-扩容" class="headerlink" title="2.3 扩容"></a>2.3 扩容</h3><p>slice的扩容机制其实很简单,对于一个 slice ,如果其<code>len<cap</code>说明还有空间可以存放新的元素,直接放进 slice 即可。但是如果<code>len==cap</code>,则说明没有空间了即需要扩充容量。</p><p><img src="/images/20211222/%E6%89%A9%E5%AE%B9.png" alt="扩容"></p><p><a href="https://github.com/golang/go/blob/ac0ba6707c1655ea4316b41d06571a0303cc60eb/src/runtime/slice.go#L125">src/runtime/slice.go#L125</a> 中实现了扩容机制:</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">growslice</span><span class="token punctuation">(</span>et <span class="token operator">*</span>_type<span class="token punctuation">,</span> old slice<span class="token punctuation">,</span> <span class="token builtin">cap</span> <span class="token builtin">int</span><span class="token punctuation">)</span> slice <span class="token punctuation">{</span> <span class="token operator">...</span> newcap <span class="token operator">:=</span> old<span class="token punctuation">.</span><span class="token builtin">cap</span> doublecap <span class="token operator">:=</span> newcap <span class="token operator">+</span> newcap <span class="token keyword">if</span> <span class="token builtin">cap</span> <span class="token operator">></span> doublecap <span class="token punctuation">{</span> newcap <span class="token operator">=</span> <span class="token builtin">cap</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> old<span class="token punctuation">.</span><span class="token builtin">len</span> <span class="token operator"><</span> <span class="token number">1024</span> <span class="token punctuation">{</span> newcap <span class="token operator">=</span> doublecap <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token number">0</span> <span class="token operator"><</span> newcap <span class="token operator">&&</span> newcap <span class="token operator"><</span> <span class="token builtin">cap</span> <span class="token punctuation">{</span> newcap <span class="token operator">+=</span> newcap <span class="token operator">/</span> <span class="token number">4</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> newcap <span class="token operator"><=</span> <span class="token number">0</span> <span class="token punctuation">{</span> newcap <span class="token operator">=</span> <span class="token builtin">cap</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token operator">...</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>扩容操作只关心容量,会把原Slice数据拷贝到新 Slice,追加数据由 append 在扩容结束后完成。上图可见,扩容后新的 Slice 长度仍然是 5,但容量由 5 提升到了 10,原 Slice 的数据也都拷贝到了新 Slice 指向的数组中。<br>扩容容量的选择遵循以下规则:</p><ul><li>如果原Slice容量小于 1024,则新Slice容量将扩大为原来的 2 倍;</li><li>如果原Slice容量大于等于 1024,则新Slice容量将扩大为原来的 1.25 倍;</li></ul><p>使用append()向Slice添加一个元素的实现步骤如下:</p><ol><li>假如 Slice容量够用,则将新元素追加进去,Slice.len++,返回原 Slice</li><li>原 Slice 容量不够,则将 Slice 先扩容,扩容后得到新 Slice</li><li>将新元素追加进新 Slice,Slice.len++,返回新的 Slice</li></ol><p>上述代码片段仅会确定切片的大致容量,下面还需要根据切片中的元素大小对齐内存,当数组中元素所占的字节大小为 1、8 或者 2 的倍数时,运行时会对齐内存。</p><br><h3 id="2-4-深拷贝"><a href="#2-4-深拷贝" class="headerlink" title="2.4 深拷贝"></a>2.4 深拷贝</h3><p>复制的切片不会改变指向底层的数据源,但有些时候我们希望建一个新的数组,并且与旧数组不共享相同的数据源,这时可以使用<code>copy</code>函数。</p><pre class="line-numbers language-go"><code class="language-go">slice1 <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">7</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token number">9</span><span class="token punctuation">}</span>slice2 <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>slice1<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">cap</span><span class="token punctuation">(</span>slice1<span class="token punctuation">)</span><span class="token punctuation">)</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>slice2<span class="token punctuation">)</span><span class="token function">copy</span><span class="token punctuation">(</span>slice2<span class="token punctuation">,</span> slice1<span class="token punctuation">)</span>slice1<span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">10</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>slice2<span class="token punctuation">)</span><span class="token comment" spellcheck="true">// [0 0 0 0 0 0 0 0 0 0]</span><span class="token comment" spellcheck="true">// [0 1 2 3 4 5 6 7 8 9]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><br>]]></content>
<categories>
<category> golang </category>
</categories>
<tags>
<tag> golang </tag>
<tag> 数组 </tag>
<tag> 切片 </tag>
</tags>
</entry>
<entry>
<title>Go如何构建CLI工具</title>
<link href="/2021/12/21/go-ru-he-gou-jian-cli-gong-ju/"/>
<url>/2021/12/21/go-ru-he-gou-jian-cli-gong-ju/</url>
<content type="html"><![CDATA[<h1 id="Go如何构建CLI工具"><a href="#Go如何构建CLI工具" class="headerlink" title="Go如何构建CLI工具"></a>Go如何构建CLI工具</h1><blockquote><p>参考<br> <a href="https://pkg.go.dev/flag#hdr-Usage">flag官方文档</a><br> <a href="https://cobra.dev/">cobra官方文档</a><br> <a href="https://github.com/spf13/cobra">cobra github</a><br> 《Go语言编程之旅》第一章命令行应用</p></blockquote><p>很多Go的应用程序是通过命令行进行交互,即<code>CLI(command-line interface)</code>命令行接口,go的标准库<code>flag</code>提供了命令行参数解析的能力,让我们在开发过程中能够非常方便地解析和处理命令行参数。目前也有许多开源项目提供了快速构建CLI应用程序的能力,如<code>Cobra</code>,下面介绍如何使用这些库构建CLI工具。</p><hr><h2 id="1-flag包"><a href="#1-flag包" class="headerlink" title="1 flag包"></a>1 flag包</h2><h3 id="1-1-使用方式"><a href="#1-1-使用方式" class="headerlink" title="1.1 使用方式"></a>1.1 使用方式</h3><h4 id="1-1-1-基本使用"><a href="#1-1-1-基本使用" class="headerlink" title="1.1.1 基本使用"></a>1.1.1 基本使用</h4><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"flag"</span> <span class="token string">"fmt"</span><span class="token punctuation">)</span><span class="token keyword">var</span> <span class="token punctuation">(</span> intFlag <span class="token builtin">int</span> boolFlag <span class="token builtin">bool</span> stringFlag <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> flag<span class="token punctuation">.</span><span class="token function">IntVar</span><span class="token punctuation">(</span><span class="token operator">&</span>intFlag<span class="token punctuation">,</span> <span class="token string">"intFlag"</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token string">"int flag"</span><span class="token punctuation">)</span> flag<span class="token punctuation">.</span><span class="token function">BoolVar</span><span class="token punctuation">(</span><span class="token operator">&</span>boolFlag<span class="token punctuation">,</span> <span class="token string">"boolFlag"</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token string">"bool flag"</span><span class="token punctuation">)</span> flag<span class="token punctuation">.</span><span class="token function">StringVar</span><span class="token punctuation">(</span><span class="token operator">&</span>stringFlag<span class="token punctuation">,</span> <span class="token string">"stringFlag"</span><span class="token punctuation">,</span> <span class="token string">"default"</span><span class="token punctuation">,</span> <span class="token string">"string flag"</span><span class="token punctuation">)</span> flag<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>intFlag<span class="token punctuation">,</span> boolFlag<span class="token punctuation">,</span> stringFlag<span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>flag</code>提供了<code>intVar</code>、<code>StringVar</code>等方法,可以对命令行参数进行解析和绑定,函数签名如下:</p><img src="/images/20211221/flag.IntVar.png" alt="flag.IntVar" style="zoom: 50%;"><p>各个形参的含义分别为命令行标识位的名称、默认值和帮助信息。</p><p>命令行参数支持如下三种命令行标志语法:</p><ul><li><code>-flag</code>:仅支持布尔类型。</li><li><code>-flag x</code>:仅支持非布尔类型。</li><li><code>-flag=x</code>:都支持。</li></ul><pre class="line-numbers language-bash"><code class="language-bash">$ go run main.go -intFlag 10 -boolFlag -stringFlag xxfoutput: 10 <span class="token boolean">true</span> xxf该例子中使用了-flag 和 -flag x,其中-boolFlag表示该布尔值为True$ go run main.go -intFlag 10 -stringFlag xxfoutput: 10 <span class="token boolean">false</span> xxf该例子中使用了-flag 和 -flag x,其中没有-boolFlag则默认该布尔值为False$ go run main.go -intFlag<span class="token operator">=</span>10 -boolFlag<span class="token operator">=</span>true -stringFlag<span class="token operator">=</span>xxfoutput: 10 <span class="token boolean">true</span> xxf该例子中使用了-flag<span class="token operator">=</span>x<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><h4 id="1-1-2-长短选项"><a href="#1-1-2-长短选项" class="headerlink" title="1.1.2 长短选项"></a>1.1.2 长短选项</h4><pre class="line-numbers language-go"><code class="language-go">flag<span class="token punctuation">.</span><span class="token function">IntVar</span><span class="token punctuation">(</span><span class="token operator">&</span>intFlag<span class="token punctuation">,</span> <span class="token string">"intFlag"</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token string">"int flag"</span><span class="token punctuation">)</span>flag<span class="token punctuation">.</span><span class="token function">IntVar</span><span class="token punctuation">(</span><span class="token operator">&</span>intFlag<span class="token punctuation">,</span> <span class="token string">"i"</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token string">"int flag"</span><span class="token punctuation">)</span>$ <span class="token keyword">go</span> run main<span class="token punctuation">.</span><span class="token keyword">go</span> <span class="token operator">-</span>i <span class="token number">10</span> <span class="token operator">-</span>boolFlag<span class="token operator">=</span><span class="token boolean">true</span> <span class="token operator">-</span>stringFlag<span class="token operator">=</span>xxfoutput<span class="token punctuation">:</span> <span class="token number">10</span> <span class="token boolean">true</span> xxf<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>一个命令行参数的标志位有长短选项是常规需求,使用flag的话可以调用两次绑定操作。</p><br><h4 id="1-1-3-子命令"><a href="#1-1-3-子命令" class="headerlink" title="1.1.3 子命令"></a>1.1.3 子命令</h4><p>在日常使用的CLI应用程序中,最常见的功能是子命令的使用。一个工具可能包含了大量相关联的功能命令,以此形成工具集,可以说是刚需,那么这个功能在标准库flag中是如何实现的呢?</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"flag"</span> <span class="token string">"fmt"</span><span class="token punctuation">)</span><span class="token keyword">var</span> <span class="token punctuation">(</span> intFlag <span class="token builtin">int</span> boolFlag <span class="token builtin">bool</span> stringFlag <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> gocmd <span class="token operator">:=</span> flag<span class="token punctuation">.</span><span class="token function">NewFlagSet</span><span class="token punctuation">(</span><span class="token string">"go"</span><span class="token punctuation">,</span> flag<span class="token punctuation">.</span>ExitOnError<span class="token punctuation">)</span> gocmd<span class="token punctuation">.</span><span class="token function">IntVar</span><span class="token punctuation">(</span><span class="token operator">&</span>intFlag<span class="token punctuation">,</span> <span class="token string">"intFlag"</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token string">"int flag"</span><span class="token punctuation">)</span> gocmd<span class="token punctuation">.</span><span class="token function">BoolVar</span><span class="token punctuation">(</span><span class="token operator">&</span>boolFlag<span class="token punctuation">,</span> <span class="token string">"boolFlag"</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token string">"bool flag"</span><span class="token punctuation">)</span> phpcmd <span class="token operator">:=</span> flag<span class="token punctuation">.</span><span class="token function">NewFlagSet</span><span class="token punctuation">(</span><span class="token string">"php"</span><span class="token punctuation">,</span> flag<span class="token punctuation">.</span>ExitOnError<span class="token punctuation">)</span> phpcmd<span class="token punctuation">.</span><span class="token function">StringVar</span><span class="token punctuation">(</span><span class="token operator">&</span>stringFlag<span class="token punctuation">,</span> <span class="token string">"stringFlag"</span><span class="token punctuation">,</span> <span class="token string">"default"</span><span class="token punctuation">,</span> <span class="token string">"string flag"</span><span class="token punctuation">)</span> flag<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span><span class="token punctuation">)</span> args <span class="token operator">:=</span> flag<span class="token punctuation">.</span><span class="token function">Args</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token function">len</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span> <span class="token operator"><=</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token keyword">switch</span> args<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">"go"</span><span class="token punctuation">:</span> <span class="token boolean">_</span> <span class="token operator">=</span> gocmd<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span>args<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">case</span> <span class="token string">"php"</span><span class="token punctuation">:</span> <span class="token boolean">_</span> <span class="token operator">=</span> phpcmd<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span>args<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>intFlag<span class="token punctuation">,</span> boolFlag<span class="token punctuation">,</span> stringFlag<span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-bash"><code class="language-bash">$ go run main.go go -intFlag<span class="token operator">=</span>10 -boolFlag<span class="token operator">=</span>true10 <span class="token boolean">true</span> default$ go run main.go php -stringFlag<span class="token operator">=</span>xxf0 <span class="token boolean">false</span> xxf<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>由于需要处理子命令,因此调用了<code>flag.NewFlagSet</code>方法。该方法会返回带有指定名称和错误处理属性的空命令集,相当于创建一个新的命令集去支持子命令。</p><br><h3 id="1-2-实现原理"><a href="#1-2-实现原理" class="headerlink" title="1.2 实现原理"></a>1.2 实现原理</h3><h4 id="1-2-1-参数解析流"><a href="#1-2-1-参数解析流" class="headerlink" title="1.2.1 参数解析流"></a>1.2.1 参数解析流</h4><img src="/images/20211221/参数解析流.png" alt="参数解析流" style="zoom: 50%;"><p>还是以命令 <code>go run main.go -intFlag=10 -boolFlag -stringFlag xxf</code> 为例介绍参数解析流。</p><pre class="line-numbers language-go"><code class="language-go">fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>os<span class="token punctuation">.</span>Args<span class="token punctuation">)</span><span class="token comment" spellcheck="true">// [/var/folders/95/d2xwj_5n4l94l392w1p6tsv00000gn/T/go-build2398467023/b001/exe/main -intFlag=10 -boolFlag -stringFlag xxf]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><h5 id="1-2-1-1-flag-Parse"><a href="#1-2-1-1-flag-Parse" class="headerlink" title="1.2.1.1 flag.Parse"></a>1.2.1.1 flag.Parse</h5><p>命令行输入参数后,首先会调用<code>flag.Parse</code>,其功能是解析并绑定命令行参数。</p><img src="/images/20211221/flag.Parse.png" alt="flag.Parse" style="zoom:50%;"><img src="/images/20211221/CommandLine.png" alt="CommandLine" style="zoom:50%;"><p><code>flag</code>包中存在一个全局变量<code>CommandLine</code>即一个空的命令集,后续命令的添加都是对这个命令集合的扩展。 </p><p><code>os.Args[1:] = [-intFlag=10 -boolFlag -stringFlag xxf]</code></p><h5 id="1-2-1-2-FlagSet-Parse"><a href="#1-2-1-2-FlagSet-Parse" class="headerlink" title="1.2.1.2 FlagSet.Parse"></a>1.2.1.2 FlagSet.Parse</h5><p><code>FlagSet.Parse</code>是对解析方法的进一步封装,实际上解析逻辑放在了<code>parseOne</code>中,而解析过程中遇到的一些特殊情况,如重复解析、异常处理等,均直接由<code>FlagSet.Parse</code>进行处理。实际上,这是一个分层明显、结构清晰的方法设计,值得我们参考。</p><img src="/images/20211221/FlagSet.Parse.png" alt="FlagSet.Parse" style="zoom:50%;"><h5 id="1-2-1-3-FlagSet-parseOne"><a href="#1-2-1-3-FlagSet-parseOne" class="headerlink" title="1.2.1.3 FlagSet.parseOne"></a>1.2.1.3 FlagSet.parseOne</h5><p><code>FlagSet.parseOne </code>是命令行解析的核心方法,所有的命令最后都会流转到<code> FlagSet.parseOne</code>中进行处理,这个函数有 73 行,这里只放部分代码。</p><img src="/images/20211221/FlagSet.parseOne.png" alt="FlagSet.parseOne" style="zoom:50%;"><p>在上述代码中,主要是针对一些不符合命令行参数绑定规则的校验进行处理,大致分为以下四种情况:</p><ul><li>命令行参数长度为 0。</li><li>长度小于 2 或不满足 flag 标识符“-”。</li><li>如果flag标志位为“–”,则中断处理,并跳过该字符,也就是后续会以“-”进行处理。</li><li>在处理flag标志位后,如果取到的参数名不符合规则,则也将中断处理。</li></ul><p>在定位命令行参数节点上,采用的依据是根据“-”的索引定位解析出上下参数的名(name)和参数的值(value)。在设置参数值时,会对值类型进行判断。若是布尔类型,则调用定制的boolFlag类型进行判断和处理。最后,通过该flag提供的Value.Set方法将参数值设置到对应的flag中。</p><br><h4 id="1-2-2-flag定义流"><a href="#1-2-2-flag定义流" class="headerlink" title="1.2.2 flag定义流"></a>1.2.2 flag定义流</h4><p>这部分比较简单,以IntVar为例。</p><img src="/images/20211221/IntVar.png" alt="IntVar" style="zoom:50%;"><img src="/images/20211221/newIntValue.png" alt="newIntValue" style="zoom:50%;"><img src="/images/20211221/Var.png" alt="Var" style="zoom:50%;"><p>首先调用<code>newIntValue</code>将<code>value</code>与指针联系起来,然后创建一个<code>Flag</code>对象并将其绑定到FlagSet对象中,通过<code>map</code>存储与去重。</p><p><code>FlagSet</code>结构体定义</p><img src="/images/20211221/FlagSet.png" alt="FlagSet" style="zoom:50%;"><hr><br><h2 id="2-cobra框架"><a href="#2-cobra框架" class="headerlink" title="2 cobra框架"></a>2 cobra框架</h2><p><code>flag</code>作为官方命令行工具用起来很方便,但是也存在功能上的不足,如不支持长短选项,得重复定义比较繁琐。</p><p><code>Cobra</code>是Go的CLI框架。它包含一个用于创建强大的现代CLI应用程序的库,以及一个快速生成基于Cobra的应用程序和命令文件的工具。用起来十分简单方便,有很多著名的框架都是基于cobra的,截止 2021-12-21 在github上已经有 24k 个star了。</p><p><a href="https://cobra.dev/">Cobra.Dev</a></p><p><a href="https://github.com/spf13/cobra">https://github.com/spf13/cobra</a></p><p><strong>安装</strong></p><p><code>go get -u github.com/spf13/cobra/cobra</code></p><p><code>import "github.com/spf13/cobra"</code></p><br><h3 id="2-1-使用方式"><a href="#2-1-使用方式" class="headerlink" title="2.1 使用方式"></a>2.1 使用方式</h3><h4 id="2-1-1-组织结构"><a href="#2-1-1-组织结构" class="headerlink" title="2.1.1 组织结构"></a>2.1.1 组织结构</h4><p>一般在项目中,会将命令行指令封装在<code>cmd</code> 目录下,然后在<code>main.go</code>中初始化</p><pre class="line-numbers language-go"><code class="language-go">▾ appName<span class="token operator">/</span> ▾ cmd<span class="token operator">/</span> commands<span class="token punctuation">.</span><span class="token keyword">go</span> here<span class="token punctuation">.</span><span class="token keyword">go</span> main<span class="token punctuation">.</span><span class="token keyword">go</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"{pathToYourApp}/cmd"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cmd<span class="token punctuation">.</span><span class="token function">Execute</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><h4 id="2-1-2-example"><a href="#2-1-2-example" class="headerlink" title="2.1.2 example"></a>2.1.2 example</h4><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"os"</span> <span class="token string">"github.com/spf13/cobra"</span><span class="token punctuation">)</span><span class="token keyword">var</span> <span class="token punctuation">(</span> version <span class="token builtin">string</span> boolFlag <span class="token builtin">bool</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// flag在init函数中调用,可以设置长短命令</span><span class="token keyword">func</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> rootCmd<span class="token punctuation">.</span><span class="token function">Flags</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">BoolVarP</span><span class="token punctuation">(</span><span class="token operator">&</span>boolFlag<span class="token punctuation">,</span> <span class="token string">"boolFlag"</span><span class="token punctuation">,</span> <span class="token string">"b"</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token string">"The bool flag"</span><span class="token punctuation">)</span> rootCmd<span class="token punctuation">.</span><span class="token function">Flags</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">StringVarP</span><span class="token punctuation">(</span><span class="token operator">&</span>version<span class="token punctuation">,</span> <span class="token string">"version"</span><span class="token punctuation">,</span> <span class="token string">"v"</span><span class="token punctuation">,</span> <span class="token string">"1.0.1"</span><span class="token punctuation">,</span> <span class="token string">"The tool version"</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 创建cobra实例</span><span class="token keyword">var</span> rootCmd <span class="token operator">=</span> <span class="token operator">&</span>cobra<span class="token punctuation">.</span>Command<span class="token punctuation">{</span> Use<span class="token punctuation">:</span> <span class="token string">"hugo"</span><span class="token punctuation">,</span> Short<span class="token punctuation">:</span> <span class="token string">"Hugo is a very fast static site generator"</span><span class="token punctuation">,</span> Long<span class="token punctuation">:</span> <span class="token string">`A Fast and Flexible Static Site Generator built with love by spf13 and friends in Go. Complete documentation is available at http://hugo.spf13.com`</span><span class="token punctuation">,</span> Run<span class="token punctuation">:</span> <span class="token keyword">func</span><span class="token punctuation">(</span>cmd <span class="token operator">*</span>cobra<span class="token punctuation">.</span>Command<span class="token punctuation">,</span> args <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"boolFlag"</span><span class="token punctuation">,</span> boolFlag<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"version"</span><span class="token punctuation">,</span> version<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">Execute</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> err <span class="token operator">:=</span> rootCmd<span class="token punctuation">.</span><span class="token function">Execute</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> os<span class="token punctuation">.</span><span class="token function">Exit</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">Execute</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>]]></content>
<categories>
<category> golang </category>
</categories>
<tags>
<tag> golang </tag>
<tag> CLI </tag>
<tag> Cobra </tag>
</tags>
</entry>
<entry>
<title>Go如何处理时间</title>
<link href="/2021/12/18/go-ru-he-chu-li-shi-jian/"/>
<url>/2021/12/18/go-ru-he-chu-li-shi-jian/</url>
<content type="html"><![CDATA[<h1 id="Go如何处理时间"><a href="#Go如何处理时间" class="headerlink" title="Go如何处理时间"></a>Go如何处理时间</h1><blockquote><p>参考<br> 《Go语言入门经典》23章 Go语言时间编程<br> <a href="https://zhuanlan.zhihu.com/p/47754783">理解Golang的Time结构</a><br> <a href="https://studygolang.com/articles/11975">深入理解GO时间处理(time.Time)</a><br> <a href="https://blog.csdn.net/Star_CSU/article/details/86650684">golang 定时任务方面time.Sleep和time.Tick的优劣对比</a><br> <a href="https://pkg.go.dev/time">官方文档time包</a> </p></blockquote><p>开发中经常会使用时间函数,如测试程序的运行时间。<code>Golang</code>提供了标准库 <code>time</code>,提供了一系列时间处理的方法。</p><br><h2 id="0-从一个例子开始"><a href="#0-从一个例子开始" class="headerlink" title="0 从一个例子开始"></a>0 从一个例子开始</h2><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"time"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 2021-12-16 21:02:30.094398 +0800 CST m=+0.000321334</span><span class="token comment" spellcheck="true">// 2021 December 16</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>常见时间单位换算:<br>1秒=1000毫秒(ms)<br>1秒=1,000,000 微秒(μs)<br>1秒=1,000,000,000 纳秒(ns)</p><p>上述代码利用 <code>time</code> 打印了当前时间和日期,其中打印日期还用到了类似链式编程的方法,打印出来的时间是什么意思呢?内部是如何实现的呢?下面对此进行分析。</p><br><br><h2 id="1-基础篇-时间对象"><a href="#1-基础篇-时间对象" class="headerlink" title="1 基础篇-时间对象"></a>1 基础篇-时间对象</h2><h3 id="1-1-Time结构体"><a href="#1-1-Time结构体" class="headerlink" title="1.1 Time结构体"></a>1.1 Time结构体</h3><h4 id="1-1-1-before-go-1-9"><a href="#1-1-1-before-go-1-9" class="headerlink" title="1.1.1 before go 1.9"></a>1.1.1 before go 1.9</h4><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> Time <span class="token keyword">struct</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// sec gives the number of seconds elapsed since</span> <span class="token comment" spellcheck="true">// January 1, year 1 00:00:00 UTC.</span> sec <span class="token builtin">int64</span> <span class="token comment" spellcheck="true">// nsec specifies a non-negative nanosecond</span> <span class="token comment" spellcheck="true">// offset within the second named by Seconds.</span> <span class="token comment" spellcheck="true">// It must be in the range [0, 999999999].</span> nsec <span class="token builtin">int32</span> <span class="token comment" spellcheck="true">// loc specifies the Location that should be used to</span> <span class="token comment" spellcheck="true">// determine the minute, hour, month, day, and year</span> <span class="token comment" spellcheck="true">// that correspond to this Time.</span> <span class="token comment" spellcheck="true">// The nil location means UTC.</span> <span class="token comment" spellcheck="true">// All UTC times are represented with loc==nil, never loc==&utcLoc.</span> loc <span class="token operator">*</span>Location<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>sec</code> 表示从公元 <code>1年1月1日00:00:00 UTC</code>到要表示的整数秒数,<code>nsec</code>表示余下的纳秒数,<code>loc</code>表示时区。<code>sec</code>和<code>nsec</code>处理没有歧义的时间值, <code>loc</code>处理偏移量</p><br><h4 id="1-1-2-after-go-1-9"><a href="#1-1-2-after-go-1-9" class="headerlink" title="1.1.2 after go 1.9"></a>1.1.2 after go 1.9</h4><p>因为 2017 年闰一秒, 国际时钟调整, Go 程序两次取<code>time.Now()</code>相减的时间差得到了意料之外的负数, 导致 cloudFlare 的 CDN 服务中断, 详见**<a href="https://blog.cloudflare.com/how-and-why-the-leap-second-affected-cloudflare-dns/">https://blog.cloudflare.com/how-and-why-the-leap-second-affected-cloudflare-dns/</a>**, go1.9 在不影响已有应用代码的情况下修改了time.Time的实现。go1.9 的 time.Time 定义为</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> Time <span class="token keyword">struct</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// wall and ext encode the wall time seconds, wall time nanoseconds,</span> <span class="token comment" spellcheck="true">// and optional monotonic clock reading in nanoseconds.</span> <span class="token comment" spellcheck="true">//</span> <span class="token comment" spellcheck="true">// From high to low bit position, wall encodes a 1-bit flag (hasMonotonic),</span> <span class="token comment" spellcheck="true">// a 33-bit seconds field, and a 30-bit wall time nanoseconds field.</span> <span class="token comment" spellcheck="true">// The nanoseconds field is in the range [0, 999999999].</span> <span class="token comment" spellcheck="true">// If the hasMonotonic bit is 0, then the 33-bit field must be zero</span> <span class="token comment" spellcheck="true">// and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext.</span> <span class="token comment" spellcheck="true">// If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit</span> <span class="token comment" spellcheck="true">// unsigned wall seconds since Jan 1 year 1885, and ext holds a</span> <span class="token comment" spellcheck="true">// signed 64-bit monotonic clock reading, nanoseconds since process start.</span> wall <span class="token builtin">uint64</span> ext <span class="token builtin">int64</span> <span class="token comment" spellcheck="true">// loc specifies the Location that should be used to</span> <span class="token comment" spellcheck="true">// determine the minute, hour, month, day, and year</span> <span class="token comment" spellcheck="true">// that correspond to this Time.</span> <span class="token comment" spellcheck="true">// The nil location means UTC.</span> <span class="token comment" spellcheck="true">// All UTC times are represented with loc==nil, never loc==&utcLoc.</span> loc <span class="token operator">*</span>Location<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>Time</code> 结构体主要包含三个字段:</p><ul><li>wall</li><li>ext</li><li>loc</li></ul><img src="/images/20211218/Time.png" alt="Time" style="zoom: 67%;"><p>Go语言的Time中存储了两种时钟:</p><ol><li><strong>Wall Clocks</strong> 用来报时,表示墙上挂的钟,即我们平时理解的时间,存储的形式是自 1885 年 1 月 1 日 0 时 0 分 0 秒以来的时间戳(不像很多语言保存的是Unix时间戳即表示到 1970 年 1 月 1 日)。关于这个 1885 年怎么来的,因为 Go 是可以表示超过 int32 的 unixtimestamp 的时间的,1885 应该是扩展出来的能表示的最小值。</li><li><strong>Monotonic Clocks</strong> 用来测量时间,单调时间,这个时间是自进程启动以来的时间戳,只会增长</li></ol><pre class="line-numbers language-go"><code class="language-go">tick <span class="token operator">:=</span> time<span class="token punctuation">.</span><span class="token function">Tick</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span><span class="token keyword">for</span> <span class="token keyword">range</span> tick <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 2021-12-17 16:57:45.84885 +0800 CST m=+1.007167459</span><span class="token comment" spellcheck="true">// 2021-12-17 16:57:46.848403 +0800 CST m=+2.006741001</span><span class="token comment" spellcheck="true">// 2021-12-17 16:57:47.846837 +0800 CST m=+3.005196626</span><span class="token comment" spellcheck="true">// 2021-12-17 16:57:48.847365 +0800 CST m=+4.005746209</span><span class="token comment" spellcheck="true">// 2021-12-17 16:57:49.848387 +0800 CST m=+5.006789584</span><span class="token comment" spellcheck="true">// 2021-12-17 16:57:50.848322 +0800 CST m=+6.006746042</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><h3 id="1-2-Location结构体"><a href="#1-2-Location结构体" class="headerlink" title="1.2 Location结构体"></a>1.2 Location结构体</h3><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// A Location maps time instants to the zone in use at that time.</span><span class="token comment" spellcheck="true">// Typically, the Location represents the collection of time offsets</span><span class="token comment" spellcheck="true">// in use in a geographical area. For many Locations the time offset varies</span><span class="token comment" spellcheck="true">// depending on whether daylight savings time is in use at the time instant.</span><span class="token keyword">type</span> Location <span class="token keyword">struct</span> <span class="token punctuation">{</span> name <span class="token builtin">string</span> zone <span class="token punctuation">[</span><span class="token punctuation">]</span>zone tx <span class="token punctuation">[</span><span class="token punctuation">]</span>zoneTrans <span class="token comment" spellcheck="true">// The tzdata information can be followed by a string that describes</span> <span class="token comment" spellcheck="true">// how to handle DST transitions not recorded in zoneTrans.</span> <span class="token comment" spellcheck="true">// The format is the TZ environment variable without a colon; see</span> <span class="token comment" spellcheck="true">// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html.</span> <span class="token comment" spellcheck="true">// Example string, for America/Los_Angeles: PST8PDT,M3.2.0,M11.1.0</span> extend <span class="token builtin">string</span> <span class="token comment" spellcheck="true">// Most lookups will be for the current time.</span> <span class="token comment" spellcheck="true">// To avoid the binary search through tx, keep a</span> <span class="token comment" spellcheck="true">// static one-element cache that gives the correct</span> <span class="token comment" spellcheck="true">// zone for the time when the Location was created.</span> <span class="token comment" spellcheck="true">// if cacheStart <= t < cacheEnd,</span> <span class="token comment" spellcheck="true">// lookup can return cacheZone.</span> <span class="token comment" spellcheck="true">// The units for cacheStart and cacheEnd are seconds</span> <span class="token comment" spellcheck="true">// since January 1, 1970 UTC, to match the argument</span> <span class="token comment" spellcheck="true">// to lookup.</span> cacheStart <span class="token builtin">int64</span> cacheEnd <span class="token builtin">int64</span> cacheZone <span class="token operator">*</span>zone<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>要消除时区的影响,可参照世界标准时间<code>(Coordinated Universal Time,UTC)</code>。UTC是时间标准而非时区,它让不同地区的计算机有相同的参照物,而不用考虑相对时区。</p><p>如果本地时间比 UTC 时间快,例如中国的时间比 UTC 快 8 小时,就会写作 UTC+8,俗称东八区。相反,如果本地时间比 UTC 时间慢,例如夏威夷的时间比 UTC 时间慢 10 小时,就会写作 UTC-10,俗称西十区。中国虽然横跨 5 个时区(东五~东九),但是按照北京时间进行统一(东八)。</p><hr><br><br><h2 id="2-入门篇-时间操作"><a href="#2-入门篇-时间操作" class="headerlink" title="2 入门篇-时间操作"></a>2 入门篇-时间操作</h2><p>时间操作的方法非常多,建议参考官方文档<a href="https://pkg.go.dev/time#pkg-index">time</a></p><p>下面主要针对经常使用的时间操作进行介绍:</p><h3 id="2-1-time-Now-获取当前时间"><a href="#2-1-time-Now-获取当前时间" class="headerlink" title="2.1 time.Now()获取当前时间"></a>2.1 time.Now()获取当前时间</h3><pre class="line-numbers language-go"><code class="language-go">fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// 2021-12-18 14:21:15.653623 +0800 CST m=+0.000057626</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p><code>time.Now()</code>返回本地时间,以上结果看出:<code>time.Now()</code>输出默认<code>CST</code>时区时间,<code>Local</code>是东八区的时间所以是<code>+0800</code>,<code>CST</code>是中部标准时间在中国是以东八区为标准,<code>m=+0.000057626</code>表示在当前进程的运行时间。</p><img src="/images/20211218/time.Now.png" alt="time.Now" style="zoom:50%;"><br><h3 id="2-2-设置时区"><a href="#2-2-设置时区" class="headerlink" title="2.2 设置时区"></a>2.2 设置时区</h3><pre class="line-numbers language-go"><code class="language-go">loc<span class="token punctuation">,</span> <span class="token boolean">_</span> <span class="token operator">:=</span> time<span class="token punctuation">.</span><span class="token function">LoadLocation</span><span class="token punctuation">(</span><span class="token string">"America/New_York"</span><span class="token punctuation">)</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>loc<span class="token punctuation">)</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">In</span><span class="token punctuation">(</span>loc<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// America/New_York</span><span class="token comment" spellcheck="true">// 2021-12-18 02:25:26.68076 -0500 EST</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>LoadLocation(name string) (*Location, error)</code>可以设置时区,获得一个时区对象。</p><p><code>In(loc *Location) Time</code>可以传入一个时区对象返回该时区的时间。</p><br><h3 id="2-3-时间运算"><a href="#2-3-时间运算" class="headerlink" title="2.3 时间运算"></a>2.3 时间运算</h3><p>时间运算的前提是两个时间属于同一时区。</p><pre class="line-numbers language-go"><code class="language-go">a<span class="token punctuation">,</span> b <span class="token operator">:=</span> time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token operator">*</span>time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 时间加减</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>a<span class="token punctuation">.</span><span class="token function">After</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// a是否在b之后</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>a<span class="token punctuation">.</span><span class="token function">Before</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// a是否在b之后前</span><span class="token comment" spellcheck="true">// 2021-12-18 15:38:29.399566 +0800 CST m=+0.000076168</span><span class="token comment" spellcheck="true">// 2021-12-18 15:38:31.399567 +0800 CST m=+2.000076334</span><span class="token comment" spellcheck="true">// false</span><span class="token comment" spellcheck="true">// true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><h3 id="2-4-序列化与反序列化"><a href="#2-4-序列化与反序列化" class="headerlink" title="2.4 序列化与反序列化"></a>2.4 序列化与反序列化</h3><pre class="line-numbers language-go"><code class="language-go">format <span class="token operator">:=</span> <span class="token string">"20060102150405"</span>t1 <span class="token operator">:=</span> time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>t1<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 2021-12-18 15:57:42.834868 +0800 CST m=+0.000058543</span>t2 <span class="token operator">:=</span> t1<span class="token punctuation">.</span><span class="token function">Format</span><span class="token punctuation">(</span>format<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 20211218155742</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>t2<span class="token punctuation">)</span>t3<span class="token punctuation">,</span> <span class="token boolean">_</span> <span class="token operator">:=</span> time<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span>format<span class="token punctuation">,</span> t2<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>t3<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 2021-12-18 15:57:42 +0000 UTC</span><span class="token comment" spellcheck="true">// format := "2006-01-02 15:04:05"</span><span class="token comment" spellcheck="true">// 2021-12-18 15:58:38.113167 +0800 CST m=+0.000058292</span><span class="token comment" spellcheck="true">// 2021-12-18 15:58:38</span><span class="token comment" spellcheck="true">// 2021-12-18 15:58:38 +0000 UTC</span><span class="token comment" spellcheck="true">// format := "2006/01/02 Monday January 03:04:05"</span><span class="token comment" spellcheck="true">// 2021-12-18 16:03:15.01808 +0800 CST m=+0.000077043</span><span class="token comment" spellcheck="true">// 2021/12/18 Saturday December 04:03:15</span><span class="token comment" spellcheck="true">// 2021-12-18 04:03:15 +0000 UTC</span><span class="token comment" spellcheck="true">// format := "2006年1月2日15点4分5秒 Mon Jan"</span><span class="token comment" spellcheck="true">// 2021-12-18 16:09:02.987355 +0800 CST m=+0.000073418</span><span class="token comment" spellcheck="true">// 2021年12月18日16点9分2秒 Sat Dec</span><span class="token comment" spellcheck="true">// 2021-12-18 16:09:02 +0000 UTC</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在按照指定格式打印时间时,首先需要记住一个时间 <code>Mon Jan 2 15:04:05 -0700 MST 2006</code> 即<code>2006年1月2日下午3点4分5秒 星期一</code>,这实际上是凑出来的时间,可能是为了方便记忆,但是为什不直接用<code>yyyy-MM-dd HH:mm:ss</code>呢?</p><p>有博客说这是golang开源的时间,这是错的,golang 的生日是 2009 年 11 月 10 日。</p><hr><br><br><h2 id="3-提升篇-定时器"><a href="#3-提升篇-定时器" class="headerlink" title="3 提升篇-定时器"></a>3 提升篇-定时器</h2><p>定时器在开发中用的很多,尤其是并发编程中,context 包中也有用到(超时控制)。</p><h3 id="3-1-time-After"><a href="#3-1-time-After" class="headerlink" title="3.1 time.After()"></a>3.1 time.After()</h3><p><code>After(d Duration) <-chan Time</code>一般用来控制超时,开一个协程如果运行时间超过 1 秒,select 就会收到 After 通道的数据,将不再接收该协程的结果。</p><pre class="line-numbers language-go"><code class="language-go">ch <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token keyword">go</span> <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span>Second <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span> ch <span class="token operator"><-</span> <span class="token string">"result"</span><span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token keyword">select</span> <span class="token punctuation">{</span><span class="token keyword">case</span> res <span class="token operator">:=</span> <span class="token operator"><-</span>ch<span class="token punctuation">:</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token keyword">case</span> <span class="token operator"><-</span>time<span class="token punctuation">.</span><span class="token function">After</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span>Second <span class="token operator">*</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">:</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"Timeout!"</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><h3 id="3-2-time-Tick-d-time-Duration"><a href="#3-2-time-Tick-d-time-Duration" class="headerlink" title="3.2 time.Tick(d time.Duration)"></a>3.2 time.Tick(d time.Duration)</h3><h4 id="3-2-1-Tick"><a href="#3-2-1-Tick" class="headerlink" title="3.2.1 Tick"></a>3.2.1 Tick</h4><p>使用<code>ticker</code>可让代码每隔特定的时间就重复执行一次。需要在很长的时间内定期执行任务时,这么做很有用,调用<code>Tick</code>函数会返回一个时间类型的<code>channel</code>。</p><pre class="line-numbers language-go"><code class="language-go">ticker <span class="token operator">:=</span> time<span class="token punctuation">.</span><span class="token function">Tick</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span><span class="token keyword">for</span> t <span class="token operator">:=</span> <span class="token keyword">range</span> ticker <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 2021-12-18 16:36:00.083872 +0800 CST m=+1.001646043</span><span class="token comment" spellcheck="true">// 2021-12-18 16:36:01.08731 +0800 CST m=+2.005127543</span><span class="token comment" spellcheck="true">// 2021-12-18 16:36:02.087241 +0800 CST m=+3.005102168</span><span class="token comment" spellcheck="true">// 2021-12-18 16:36:03.087233 +0800 CST m=+4.005137335</span><span class="token comment" spellcheck="true">// ...</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>time.Tick</code>是对<code>NewTicker</code>的封装,也可以直接使用<code>NewTicker</code>。</p><img src="/images/20211218/time.Tick.png" alt="time.Tick" style="zoom:50%;"><br><h4 id="3-2-2-Sleep"><a href="#3-2-2-Sleep" class="headerlink" title="3.2.2 Sleep"></a>3.2.2 Sleep</h4><p>实际上也可以使用<code>time.Sleep(d time.Duration)</code>来实现循环执行的定时任务:</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">for</span> <span class="token punctuation">{</span> time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>那么两种实现方式之间有什么区别呢?哪种效率更高?</p><br><h4 id="3-2-3-Tick-vs-Sleep"><a href="#3-2-3-Tick-vs-Sleep" class="headerlink" title="3.2.3 Tick vs Sleep"></a>3.2.3 Tick vs Sleep</h4><p><strong>先说结论</strong></p><p>调用<code>Tick</code>函数会返回一个时间类型的<code>channel</code>,如果对<code>channel</code>稍微有些了解的话,我们首先会想到,既然是返回一个<code>channel</code>,在调用 <code>Tick</code>方法的过程中,必然创建了<code>goroutine</code>,该<code>goroutine</code>负责发送数据,唤醒被阻塞的定时任务。</p><p><code>Tick</code>,<code>Sleep</code>,包括<code>time.After</code>函数,都使用的<code>timer</code>结构体,都会被放在同一个协程中统一处理,这样看起来使用<code>Tick</code>,<code>Sleep</code>并没有什么区别。实际上是有区别的,<code>Sleep</code>是使用睡眠完成定时任务,需要被调度唤醒。<code>Tick</code>函数是使用<code>channel</code>阻塞当前协程,完成定时任务的执行。当前并不清楚<code>golang </code>阻塞和睡眠对资源的消耗会有什么区别,这方面不能给出建议。</p><p>但是使用<code>channel</code>阻塞协程完成定时任务比较灵活,可以结合<code>select</code>设置超时时间以及默认执行方法,而且可以设置<code>timer</code>的主动关闭,以及不需要每次都生成一个<code>timer</code>(这方面节省系统内存,垃圾收回也需要时间)。</p><p>所以,建议使用<code>time.Tick</code>完成定时任务。</p><p><strong>实现原理对比</strong></p><p>有空再补,先参考</p><p><a href="https://www.cyhone.com/articles/analysis-of-golang-timer/#Timer-%E7%9A%84%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0">https://www.cyhone.com/articles/analysis-of-golang-timer/#Timer-%E7%9A%84%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0</a></p><br><br><br>]]></content>
<categories>
<category> golang </category>
</categories>
<tags>
<tag> golang </tag>
<tag> 时间处理 </tag>
</tags>
</entry>
<entry>
<title>Go如何处理文件</title>
<link href="/2021/12/15/go-ru-he-chu-li-wen-jian/"/>
<url>/2021/12/15/go-ru-he-chu-li-wen-jian/</url>
<content type="html"><![CDATA[<h1 id="Go如何处理文件"><a href="#Go如何处理文件" class="headerlink" title="Go如何处理文件"></a>Go如何处理文件</h1><ul><li><p>参考<br>《Go语言入门经典》21章 处理文件</p><p> <a href="https://segmentfault.com/a/1190000017918542">Golang读写文件</a></p></li></ul><p>开发过程中经常会涉及到文件处理,如读取配置文件、写日志文件。下面归纳总结一下Go处理文件的方式,包括文件创建、读取、写入、删除等,对比不同的文件操作包如: <code>ioutil</code>、 <code>os</code>、 <code>bufio</code></p><hr><br><h2 id="1-使用-io-ioutil包"><a href="#1-使用-io-ioutil包" class="headerlink" title="1 使用 io/ioutil包"></a>1 使用 <code>io/ioutil</code>包</h2><p><code>io/ioutil</code>是标准库,但是其底层用的还是 <code>os</code> 包,提供的方法本质上是对 <code>os</code> 的封装。</p><ul><li>创建文件</li><li>写入文件</li><li>读取文件</li><li>读取目录内容</li></ul><h3 id="1-1-创建-amp-写入文件-WriteFile"><a href="#1-1-创建-amp-写入文件-WriteFile" class="headerlink" title="1.1 创建&写入文件 WriteFile"></a>1.1 创建&写入文件 <code>WriteFile</code></h3><p><code>WriteFile</code> 方法在写入文件时,如果文件不存在则会自动创建文件,如果文件存在则会覆盖写入。方法有三个参数:</p><ul><li>文件名(文件路径)</li><li>写入的数据(字节数组 <code>[]byte</code>)</li><li>文件模式(读写执行权限)</li></ul><p>当写入数据为 <code>nil</code> 时,创建的文件为空文件;</p><img src="/images/20211215/文件模式.png" alt="文件模式" style="zoom:50%;"><p>文件模式用四位数字表达如 0644 分别代表文件类型(普通文件)、文件所有者的权限、文件同组用户的权限、其他用户的权限。rwx分别代表读权限(4)、写权限(2)、执行权限(1),如0644中的6代表文件所有者有文件的读写(4+2)权限。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"io/ioutil"</span> <span class="token string">"log"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> err <span class="token operator">:=</span> ioutil<span class="token punctuation">.</span><span class="token function">WriteFile</span><span class="token punctuation">(</span><span class="token string">"./example.txt"</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token number">0644</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 创建空文件</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> data <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"hello golang! !!"</span><span class="token punctuation">)</span> err <span class="token operator">=</span> ioutil<span class="token punctuation">.</span><span class="token function">WriteFile</span><span class="token punctuation">(</span><span class="token string">"./example.txt"</span><span class="token punctuation">,</span> data<span class="token punctuation">,</span> <span class="token number">0644</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 创建文件并写入数据</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="/images/20211215/os.WiteFile.png" alt="os.WiteFile" style="zoom: 33%;"><p>内部调用的是 <code>os.WriteFile</code></p><br><br><h3 id="1-2-读取文件-ReadFile"><a href="#1-2-读取文件-ReadFile" class="headerlink" title="1.2 读取文件 ReadFile"></a>1.2 读取文件 <code>ReadFile</code></h3><p><code>ReadFile</code> 方法的参数是文件名(文件路径),返回一个字节数组,将文件一次性读入内存。</p><pre class="line-numbers language-go"><code class="language-go">data<span class="token punctuation">,</span> err <span class="token operator">:=</span> ioutil<span class="token punctuation">.</span><span class="token function">ReadFile</span><span class="token punctuation">(</span><span class="token string">"./example.txt"</span><span class="token punctuation">)</span><span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">}</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// [104 101 108 108 111 32 103 111 108 97 110 103 33 32 239 188 129 239 188 129]</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// hello golang! !!</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="/images/20211215/os.ReadFile.png" alt="os.ReadFile" style="zoom:33%;"><p>内部调用的是 <code>os.ReadFile</code></p><br><br><h3 id="1-3-读取目录内容-ReadDir"><a href="#1-3-读取目录内容-ReadDir" class="headerlink" title="1.3 读取目录内容 ReadDir"></a>1.3 读取目录内容 <code>ReadDir</code></h3><p><code>ReadDir</code> 方法的参数是目录路径,返回值是目录下的文件信息,实际上目录也是一种文件。</p><pre class="line-numbers language-go"><code class="language-go">dirData<span class="token punctuation">,</span> err <span class="token operator">:=</span> ioutil<span class="token punctuation">.</span><span class="token function">ReadDir</span><span class="token punctuation">(</span><span class="token string">"./"</span><span class="token punctuation">)</span><span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> file <span class="token operator">:=</span> <span class="token keyword">range</span> dirData <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>file<span class="token punctuation">.</span><span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> file<span class="token punctuation">.</span><span class="token function">Mode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> file<span class="token punctuation">.</span><span class="token function">Size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// example.txt -rw-r--r-- 20</span><span class="token comment" spellcheck="true">// float.go -rw-r--r-- 1257</span><span class="token comment" spellcheck="true">// go.mod -rw-r--r-- 46</span><span class="token comment" spellcheck="true">// json.go -rw-r--r-- 706</span><span class="token comment" spellcheck="true">// main.go -rw-r--r-- 233</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="/images/20211215/os.ReadDir.png" alt="os.ReadDir" style="zoom:33%;"><p>首先读目录文件,然后调用os的Readdir方法读取子文件信息,最后按文件名对文件进行排序。</p><p><code>FileInfo</code> 的定义如下:</p><img src="/images/20211215/FileInfo.png" alt="FileInfo" style="zoom:33%;"><p>实际上 <code>ioutil</code> 包的源码一共就83行(go 1.6),也就封装了5个函数,除了上面的三个还有 <code>ReadAll(r io.Reader)</code> 和 <code>NopCloser(r io.Reader)</code> ,后面再说。</p><hr><br><br><br><h2 id="2-使用-os包"><a href="#2-使用-os包" class="headerlink" title="2 使用 os包"></a>2 使用 <code>os</code>包</h2><p>上面的<code>io/ioutil</code> 都是基于 <code>os</code> 包的封装,下面看看如何使用 <code>os</code> 包处理文件。源文件有 706 行(go 1.6),支持的文件操作不胜枚举,举几个基础的操作。关键是要理解这些方法操作的对象是文件结构体 <code>File</code></p><ul><li>创建文件</li><li>读取文件</li><li>写入文件</li></ul><br><h3 id="2-1-创建文件"><a href="#2-1-创建文件" class="headerlink" title="2.1 创建文件"></a>2.1 创建文件</h3><p>创建文件可以通过以下方法:</p><ul><li><code>Create(name string)</code></li><li><code>OpenFile(name string, flag int, perm FileMode)</code></li></ul><br><h4 id="2-1-1-Create"><a href="#2-1-1-Create" class="headerlink" title="2.1.1 Create"></a>2.1.1 <code>Create</code></h4><p><code>Create(name string)</code> 是创建文件的专用方法,参数是方法名(路径),返回值是文件结构体。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"log"</span> <span class="token string">"os"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> f<span class="token punctuation">,</span> err <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token string">"./test.txt"</span><span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>f<span class="token punctuation">.</span><span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> f<span class="token punctuation">.</span><span class="token function">Fd</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// ./test.txt 3</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="/images/20211215/os.Create.png" alt="os.Create" style="zoom:33%;"><p>实际上是对 <code>OpenFile(name string, flag int, perm FileMode)</code> 的封装。</p><p>返回的 <code>File</code> 的定义如下,其中FD是指文件描述符。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// /usr/local/go/src/os/types.go</span><span class="token comment" spellcheck="true">// File represents an open file descriptor.</span><span class="token keyword">type</span> File <span class="token keyword">struct</span> <span class="token punctuation">{</span> <span class="token operator">*</span>file <span class="token comment" spellcheck="true">// os specific</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// /usr/local/go/src/os/file_unix.go</span><span class="token comment" spellcheck="true">// file is the real representation of *File.</span><span class="token comment" spellcheck="true">// The extra level of indirection ensures that no clients of os</span><span class="token comment" spellcheck="true">// can overwrite this data, which could cause the finalizer</span><span class="token comment" spellcheck="true">// to close the wrong file descriptor.</span><span class="token keyword">type</span> file <span class="token keyword">struct</span> <span class="token punctuation">{</span> pfd poll<span class="token punctuation">.</span>FD name <span class="token builtin">string</span> dirinfo <span class="token operator">*</span>dirInfo <span class="token comment" spellcheck="true">// nil unless directory being read</span> nonblock <span class="token builtin">bool</span> <span class="token comment" spellcheck="true">// whether we set nonblocking mode</span> stdoutOrErr <span class="token builtin">bool</span> <span class="token comment" spellcheck="true">// whether this is stdout or stderr</span> appendMode <span class="token builtin">bool</span> <span class="token comment" spellcheck="true">// whether file is opened for appending</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><h4 id="2-1-2-OpenFile"><a href="#2-1-2-OpenFile" class="headerlink" title="2.1.2 OpenFile"></a>2.1.2 <code>OpenFile</code></h4><p><code>OpenFile(name string, flag int, perm FileMode)</code>方法返回值也是文件结构体,有三个参数:</p><ul><li>文件路径</li><li>文件的打开方式</li><li>文件模式</li></ul><p>文件的打开方式有:</p><img src="/images/20211215/打开方式.png" alt="打开方式" style="zoom:33%;"><p>上面的 <code>Create</code> 方法就使用了可读可写|创建文件|缩短文件,表示创建了文件后了文件结构体,可以通过该文件结构体对文件进行读写操作。</p><img src="/images/20211215/os.OpenFile.png" alt="os.OpenFile" style="zoom:33%;"><pre class="line-numbers language-go"><code class="language-go">f<span class="token punctuation">,</span> err <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">OpenFile</span><span class="token punctuation">(</span><span class="token string">"./test.txt"</span><span class="token punctuation">,</span> os<span class="token punctuation">.</span>O_CREATE<span class="token punctuation">,</span> <span class="token number">0644</span><span class="token punctuation">)</span><span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">}</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>f<span class="token punctuation">.</span><span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> f<span class="token punctuation">.</span><span class="token function">Fd</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// ./test.txt 3</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><br><h3 id="2-2-读取文件"><a href="#2-2-读取文件" class="headerlink" title="2.2 读取文件"></a>2.2 读取文件</h3><p>读取文件实际上是对文件结构体 <code>File</code> 进行操作,首先 <code>Open</code>打开文件(记得手动关闭),然后以返回的 <code>File</code> 对象为入口读取数据。</p><p>读文件又可以分成 <code>带缓冲的读取</code> 和<code>不带缓冲的读取</code></p><br><h4 id="2-2-1-不带缓冲的读取"><a href="#2-2-1-不带缓冲的读取" class="headerlink" title="2.2.1 不带缓冲的读取"></a>2.2.1 不带缓冲的读取</h4><p><strong>方法一:使用 <code>ioutil.ReadAll(r io.Reader)</code> 读取</strong></p><p><code>io.Reader</code> 是一个声明了 <code>Read</code> 方法的接口,因为<code>File</code> 结构体实现了<code>Read</code> 所以可以直接作为参数(鸭子模型)。</p><pre class="line-numbers language-go"><code class="language-go">file<span class="token punctuation">,</span> err <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span><span class="token string">"./example.txt"</span><span class="token punctuation">)</span><span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">defer</span> file<span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span>content<span class="token punctuation">,</span> err <span class="token operator">:=</span> ioutil<span class="token punctuation">.</span><span class="token function">ReadAll</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span><span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">}</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span>content<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// hello golang! !!</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>方法二:使用<code>os.ReadFile(name string)</code> 读取</strong></p><p><code>os</code> 本身也将上述逻辑封装成了 <code>ReadFile(name string) ([]byte, error)</code> 方法,这是不带缓冲的读取,即将数据一次性读入内存,其中循环将数据写入字节数组,还是看不太懂为什么得这样做(留个坑~~~)</p><pre class="line-numbers language-go"><code class="language-go">content<span class="token punctuation">,</span> err <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">ReadFile</span><span class="token punctuation">(</span><span class="token string">"./example.txt"</span><span class="token punctuation">)</span><span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">}</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span>content<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// hello golang! !!</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// ReadFile reads the named file and returns the contents.</span><span class="token comment" spellcheck="true">// A successful call returns err == nil, not err == EOF.</span><span class="token comment" spellcheck="true">// Because ReadFile reads the whole file, it does not treat an EOF from Read</span><span class="token comment" spellcheck="true">// as an error to be reported.</span><span class="token keyword">func</span> <span class="token function">ReadFile</span><span class="token punctuation">(</span>name <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> f<span class="token punctuation">,</span> err <span class="token operator">:=</span> <span class="token function">Open</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err <span class="token punctuation">}</span> <span class="token keyword">defer</span> f<span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">var</span> size <span class="token builtin">int</span> <span class="token keyword">if</span> info<span class="token punctuation">,</span> err <span class="token operator">:=</span> f<span class="token punctuation">.</span><span class="token function">Stat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> size64 <span class="token operator">:=</span> info<span class="token punctuation">.</span><span class="token function">Size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token function">int64</span><span class="token punctuation">(</span><span class="token function">int</span><span class="token punctuation">(</span>size64<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">==</span> size64 <span class="token punctuation">{</span> size <span class="token operator">=</span> <span class="token function">int</span><span class="token punctuation">(</span>size64<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> size<span class="token operator">++</span> <span class="token comment" spellcheck="true">// one byte for final read at EOF</span> <span class="token comment" spellcheck="true">// If a file claims a small size, read at least 512 bytes.</span> <span class="token comment" spellcheck="true">// In particular, files in Linux's /proc claim size 0 but</span> <span class="token comment" spellcheck="true">// then do not work right if read in small pieces,</span> <span class="token comment" spellcheck="true">// so an initial read of 1 byte would not work correctly.</span> <span class="token keyword">if</span> size <span class="token operator"><</span> <span class="token number">512</span> <span class="token punctuation">{</span> size <span class="token operator">=</span> <span class="token number">512</span> <span class="token punctuation">}</span> data <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> size<span class="token punctuation">)</span> <span class="token keyword">for</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token function">len</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token function">cap</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token punctuation">{</span> d <span class="token operator">:=</span> <span class="token function">append</span><span class="token punctuation">(</span>data<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token function">cap</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> data <span class="token operator">=</span> d<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token function">len</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token punctuation">}</span> n<span class="token punctuation">,</span> err <span class="token operator">:=</span> f<span class="token punctuation">.</span><span class="token function">Read</span><span class="token punctuation">(</span>data<span class="token punctuation">[</span><span class="token function">len</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token function">cap</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span> data <span class="token operator">=</span> data<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token function">len</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token operator">+</span>n<span class="token punctuation">]</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> err <span class="token operator">==</span> io<span class="token punctuation">.</span>EOF <span class="token punctuation">{</span> err <span class="token operator">=</span> <span class="token boolean">nil</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> data<span class="token punctuation">,</span> err <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><h4 id="2-2-2-带缓冲的读取"><a href="#2-2-2-带缓冲的读取" class="headerlink" title="2.2.2 带缓冲的读取"></a>2.2.2 带缓冲的读取</h4><p>一次性读入内存,系统可能会因为文件过大而卡死,此时可以采用类似于流的形式,每次读一部分。</p><pre class="line-numbers language-go"><code class="language-go">file<span class="token punctuation">,</span> err <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span><span class="token string">"./example.txt"</span><span class="token punctuation">)</span><span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">defer</span> file<span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span>chunks <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>buf <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 每次读5个字节</span><span class="token keyword">for</span> <span class="token punctuation">{</span> n<span class="token punctuation">,</span> err <span class="token operator">:=</span> file<span class="token punctuation">.</span><span class="token function">Read</span><span class="token punctuation">(</span>buf<span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token operator">&&</span> err <span class="token operator">!=</span> io<span class="token punctuation">.</span>EOF <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> n <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token keyword">break</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span><span class="token punctuation">:</span>n<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> chunks <span class="token operator">=</span> <span class="token function">append</span><span class="token punctuation">(</span>chunks<span class="token punctuation">,</span> buf<span class="token punctuation">[</span><span class="token punctuation">:</span>n<span class="token punctuation">]</span><span class="token operator">...</span><span class="token punctuation">)</span><span class="token punctuation">}</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span>chunks<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// hello</span><span class="token comment" spellcheck="true">// gola</span><span class="token comment" spellcheck="true">// ng!!!</span><span class="token comment" spellcheck="true">// hello golang!!!</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>针对带缓存的读写, <code>bufio</code> 包中封装了很多好用的函数,可以按字节读也可以按行读,上面的代码可以进行如下改写:</p><pre class="line-numbers language-go"><code class="language-go">file<span class="token punctuation">,</span> err <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span><span class="token string">"./example.txt"</span><span class="token punctuation">)</span><span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">defer</span> file<span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span>r <span class="token operator">:=</span> bufio<span class="token punctuation">.</span><span class="token function">NewReader</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span>chunks <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>buf <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 每次读五个字节</span><span class="token keyword">for</span> <span class="token punctuation">{</span> n<span class="token punctuation">,</span> err <span class="token operator">:=</span> r<span class="token punctuation">.</span><span class="token function">Read</span><span class="token punctuation">(</span>buf<span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token operator">&&</span> err <span class="token operator">!=</span> io<span class="token punctuation">.</span>EOF <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> n <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token keyword">break</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span><span class="token punctuation">:</span>n<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> chunks <span class="token operator">=</span> <span class="token function">append</span><span class="token punctuation">(</span>chunks<span class="token punctuation">,</span> buf<span class="token punctuation">[</span><span class="token punctuation">:</span>n<span class="token punctuation">]</span><span class="token operator">...</span><span class="token punctuation">)</span><span class="token punctuation">}</span>fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span>chunks<span class="token punctuation">)</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>按行读文件</p><pre class="line-numbers language-go"><code class="language-go">file<span class="token punctuation">,</span> err <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span><span class="token string">"./example.txt"</span><span class="token punctuation">)</span><span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">defer</span> file<span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span>r <span class="token operator">:=</span> bufio<span class="token punctuation">.</span><span class="token function">NewReader</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span><span class="token keyword">for</span> <span class="token punctuation">{</span> line<span class="token punctuation">,</span> err <span class="token operator">:=</span> r<span class="token punctuation">.</span><span class="token function">ReadString</span><span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">)</span> line <span class="token operator">=</span> strings<span class="token punctuation">.</span><span class="token function">TrimSpace</span><span class="token punctuation">(</span>line<span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> err <span class="token operator">==</span> io<span class="token punctuation">.</span>EOF <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"File read ok!"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"Read file error!"</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">break</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span>line<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// hello golang!!!</span><span class="token comment" spellcheck="true">// hello bufio!!!</span><span class="token comment" spellcheck="true">// File read ok!</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><br><br><h3 id="2-3-写入文件"><a href="#2-3-写入文件" class="headerlink" title="2.3 写入文件"></a>2.3 写入文件</h3><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"log"</span> <span class="token string">"os"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">checkFileIsExist</span><span class="token punctuation">(</span>filename <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token builtin">bool</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Stat</span><span class="token punctuation">(</span>filename<span class="token punctuation">)</span><span class="token punctuation">;</span> os<span class="token punctuation">.</span><span class="token function">IsNotExist</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> filename <span class="token operator">=</span> <span class="token string">"./example.txt"</span> <span class="token keyword">var</span> f <span class="token operator">*</span>os<span class="token punctuation">.</span>File <span class="token keyword">var</span> str <span class="token operator">=</span> <span class="token string">"hello os!!!\n"</span> <span class="token keyword">var</span> err <span class="token builtin">error</span> <span class="token keyword">if</span> <span class="token function">checkFileIsExist</span><span class="token punctuation">(</span>filename<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//如果文件存在</span> f<span class="token punctuation">,</span> err <span class="token operator">=</span> os<span class="token punctuation">.</span><span class="token function">OpenFile</span><span class="token punctuation">(</span>filename<span class="token punctuation">,</span> os<span class="token punctuation">.</span>O_WRONLY<span class="token operator">|</span>os<span class="token punctuation">.</span>O_APPEND<span class="token punctuation">,</span> <span class="token number">0644</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//打开文件</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"文件存在"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> f<span class="token punctuation">,</span> err <span class="token operator">=</span> os<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>filename<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//创建文件</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"文件不存在"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">defer</span> f<span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span> n<span class="token punctuation">,</span> err <span class="token operator">:=</span> f<span class="token punctuation">.</span><span class="token function">Write</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//写入字节数组</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"[]byte写入%d个字节\n"</span><span class="token punctuation">,</span> n<span class="token punctuation">)</span> n<span class="token punctuation">,</span> err <span class="token operator">=</span> f<span class="token punctuation">.</span><span class="token function">WriteString</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//写入文件字符串</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"string写入%d个字节\n"</span><span class="token punctuation">,</span> n<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// f.Sync() // 将文件扇入磁盘</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>也可以使用 <code>bufio.WriteFile</code> 改写</p><hr><br><br><h2 id="3-性能对比"><a href="#3-性能对比" class="headerlink" title="3 性能对比"></a>3 性能对比</h2><p>留个坑 有时间再写吧</p><br><br><br>]]></content>
<categories>
<category> golang </category>
</categories>
<tags>
<tag> golang </tag>
<tag> 文件处理 </tag>
</tags>
</entry>
<entry>
<title>Go如何处理JSON</title>
<link href="/2021/12/13/go-ru-he-chu-li-json/"/>
<url>/2021/12/13/go-ru-he-chu-li-json/</url>
<content type="html"><![CDATA[<h1 id="Go如何处理JSON"><a href="#Go如何处理JSON" class="headerlink" title="Go如何处理JSON"></a>Go如何处理JSON</h1><blockquote><p>参考《Go语言入门经典》第20章 处理JSON</p></blockquote><p>JSON作为前后端交互的通用格式,在日常开发中经常会使用到,尤其对于后端开发者要弄清楚如何完成JSON的编码与解码,以及如何通过HTTP请求的读写JSON数据。</p><h2 id="1-JSON简介"><a href="#1-JSON简介" class="headerlink" title="1 JSON简介"></a>1 JSON简介</h2><p>JSON(JavaScript Object Notion)即JavaScript对象表示法,类似于txt是一种数据存储格式。最初是JavaScript的一个子集,JavaScript天然支持JSON。</p><p>JSON数据主要以key-value的形式进行组织,包含在一个JSON对象中,支持7种数据类型(6种基本类型+1种空类型)</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"zhangsan"</span><span class="token punctuation">,</span> // String <span class="token property">"sex"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> // Boolean <span class="token property">"age"</span><span class="token operator">:</span> <span class="token number">18</span><span class="token punctuation">,</span> // Number <span class="token property">"height"</span><span class="token operator">:</span> <span class="token number">176.5</span><span class="token punctuation">,</span> // Number <span class="token property">"hobby"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"pingpon"</span><span class="token punctuation">,</span> <span class="token string">"basketball"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> // Array <span class="token property">"father"</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"lisi"</span><span class="token punctuation">,</span> <span class="token property">"age"</span><span class="token operator">:</span> <span class="token number">48</span><span class="token punctuation">}</span><span class="token punctuation">,</span> // Object <span class="token property">"grilfriend"</span><span class="token operator">:</span> <span class="token null">null</span> // <span class="token null">NULL</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>注意:整数和浮点数都是Number</p><hr><h2 id="2-Go处理JSON"><a href="#2-Go处理JSON" class="headerlink" title="2 Go处理JSON"></a>2 Go处理JSON</h2><p>Go语言十分适合服务端开发,提供了 <code>encoding/json</code> 包用于JSON数据的编码和解码。</p><h3 id="2-1-编码"><a href="#2-1-编码" class="headerlink" title="2.1 编码"></a>2.1 编码</h3><p>encoding/json包提供了函数 <code>Marshal</code>,返回编码后的字节数组,用于将Go数据编码为JSON。</p><p>需要注意的是struct字段的首字母需要大写,不然无法编码。比如下面Person对象中的age和hobby无法导出。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> Person <span class="token keyword">struct</span> <span class="token punctuation">{</span> Name <span class="token builtin">string</span> age <span class="token builtin">int</span> hobby <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> hobby <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token string">"pingpon"</span><span class="token punctuation">,</span> <span class="token string">"basketball"</span><span class="token punctuation">}</span> p <span class="token operator">:=</span> Person<span class="token punctuation">{</span> Name<span class="token punctuation">:</span> <span class="token string">"zhangsan"</span><span class="token punctuation">,</span> age<span class="token punctuation">:</span> <span class="token number">18</span><span class="token punctuation">,</span> hobby<span class="token punctuation">:</span> hobby<span class="token punctuation">,</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%+v\n"</span><span class="token punctuation">,</span> p<span class="token punctuation">)</span> jsonByteData<span class="token punctuation">,</span> <span class="token boolean">_</span> <span class="token operator">:=</span> json<span class="token punctuation">.</span><span class="token function">Marshal</span><span class="token punctuation">(</span>p<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span>jsonByteData<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// {Name:zhangsan age:18 hobby:[pingpon basketball]}</span><span class="token comment" spellcheck="true">// {"Name":"zhangsan"}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="2-1-1-struct字段设置可导出"><a href="#2-1-1-struct字段设置可导出" class="headerlink" title="2.1.1 struct字段设置可导出"></a>2.1.1 struct字段设置可导出</h4><p>把Person的定义改成如下形式,即可全部导出。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> Person <span class="token keyword">struct</span> <span class="token punctuation">{</span> Name <span class="token builtin">string</span> Age <span class="token builtin">int</span> Hobby <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// {Name:zhangsan Age:18 Hobby:[pingpon basketball]}</span><span class="token comment" spellcheck="true">// {"Name":"zhangsan","Age":18,"Hobby":["pingpon","basketball"]}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>但是这样有个问题,导出json数据的key是首字母大写,如果需要小写怎么办呢?</p><h4 id="2-1-2-自定义导出的key"><a href="#2-1-2-自定义导出的key" class="headerlink" title="2.1.2 自定义导出的key"></a>2.1.2 自定义导出的key</h4><p>给字段加上标签tag即可自定义导出的key</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> Person <span class="token keyword">struct</span> <span class="token punctuation">{</span> Name <span class="token builtin">string</span> <span class="token string">`json:"name"`</span> Age <span class="token builtin">int</span> <span class="token string">`json:"age"`</span> Hobby <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span> <span class="token string">`json:"hobbies"`</span> <span class="token comment" spellcheck="true">// 自定义</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// {Name:zhangsan Age:18 Hobby:[pingpon basketball]}</span><span class="token comment" spellcheck="true">// {"name":"zhangsan","age":18,"hobbies":["pingpon","basketball"]}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="2-1-3-空类型"><a href="#2-1-3-空类型" class="headerlink" title="2.1.3 空类型"></a>2.1.3 空类型</h4><p>如果go数据为nil,那么编码出来的JSON也是空类型, 想要忽略空类型的话,在struct的tag里添加omitempty即可</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// 不忽略空类型</span><span class="token keyword">type</span> Person <span class="token keyword">struct</span> <span class="token punctuation">{</span> Name <span class="token builtin">string</span> <span class="token string">`json:"name"`</span> Age <span class="token builtin">int</span> <span class="token string">`json:"age"`</span> Hobby <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span> <span class="token string">`json:"hobbies"`</span><span class="token punctuation">}</span>p <span class="token operator">:=</span> Person<span class="token punctuation">{</span> Name<span class="token punctuation">:</span> <span class="token string">"zhangsan"</span><span class="token punctuation">,</span> Age<span class="token punctuation">:</span> <span class="token number">18</span><span class="token punctuation">,</span> Hobby<span class="token punctuation">:</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%+v\n"</span><span class="token punctuation">,</span> p<span class="token punctuation">)</span> jsonByteData<span class="token punctuation">,</span> <span class="token boolean">_</span> <span class="token operator">:=</span> json<span class="token punctuation">.</span><span class="token function">Marshal</span><span class="token punctuation">(</span>p<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span>jsonByteData<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// {Name:zhangsan Age:18 Hobby:[]}</span><span class="token comment" spellcheck="true">// {"name":"zhangsan","age":18,"hobbies":null}</span><span class="token comment" spellcheck="true">// -----------------------------------------------------</span><span class="token comment" spellcheck="true">// 忽略空类型 omitempty</span><span class="token keyword">type</span> Person <span class="token keyword">struct</span> <span class="token punctuation">{</span> Name <span class="token builtin">string</span> <span class="token string">`json:"name,omitempty"`</span> Age <span class="token builtin">int</span> <span class="token string">`json:"age,omitempty"`</span> Hobby <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span> <span class="token string">`json:"hobbies,omitempty"`</span><span class="token punctuation">}</span>p <span class="token operator">:=</span> Person<span class="token punctuation">{</span> Name<span class="token punctuation">:</span> <span class="token string">"zhangsan"</span><span class="token punctuation">,</span> Age<span class="token punctuation">:</span> <span class="token number">18</span><span class="token punctuation">,</span> Hobby<span class="token punctuation">:</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%+v\n"</span><span class="token punctuation">,</span> p<span class="token punctuation">)</span> jsonByteData<span class="token punctuation">,</span> <span class="token boolean">_</span> <span class="token operator">:=</span> json<span class="token punctuation">.</span><span class="token function">Marshal</span><span class="token punctuation">(</span>p<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span>jsonByteData<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// {Name:zhangsan Age:18 Hobby:[]}</span><span class="token comment" spellcheck="true">// {"name":"zhangsan","age":18}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-2-解码"><a href="#2-2-解码" class="headerlink" title="2.2 解码"></a>2.2 解码</h3><p>encoding/json包提供了函数 <code>Unmarshal</code>,接受一个字节切片以及一个指定要将数据解码为何种格式的接口,用于将JSON解码为Go数据。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> Person <span class="token keyword">struct</span> <span class="token punctuation">{</span> Name <span class="token builtin">string</span> <span class="token string">`json:"name,omitempty"`</span> Age <span class="token builtin">int</span> <span class="token string">`json:"age,omitempty"`</span> Hobby <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span> <span class="token string">`json:"hobbies,omitempty"`</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> jsonString <span class="token operator">:=</span> <span class="token string">`{"name":"zhangsan","age":18,"hobbies":["pingpon","basketball"]}`</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>jsonString<span class="token punctuation">)</span> p <span class="token operator">:=</span> Person<span class="token punctuation">{</span><span class="token punctuation">}</span> err <span class="token operator">:=</span> json<span class="token punctuation">.</span><span class="token function">Unmarshal</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span>jsonString<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token operator">&</span>p<span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%+v\n"</span><span class="token punctuation">,</span> p<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// {"name":"zhangsan","age":18,"hobbies":["pingpon","basketball"]}</span><span class="token comment" spellcheck="true">// {Name:zhangsan Age:18 Hobby:[pingpon basketball]}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="/images/20211213/Untitled.png" alt="JSON和Go之间映射数据类型" style="zoom: 50%;"><hr><h2 id="3-通过HTTP读写JSON"><a href="#3-通过HTTP读写JSON" class="headerlink" title="3 通过HTTP读写JSON"></a>3 通过HTTP读写JSON</h2><h3 id="3-1-读出JSON"><a href="#3-1-读出JSON" class="headerlink" title="3.1 读出JSON"></a>3.1 读出JSON</h3><p>在Go语言中,通过HTTP请求获取JSON时,收到的数据为流而不是字符串或字节切片。在这种情况下,应使用encoding/json包中的另一个方法 <code>NewDecoder</code>。</p><p><code>NewDecoder</code>函数接受一个io.Reader(这正是http.Get返回的类型),并返回一个Decoder。通过对返回的Decoder调用方法Decode,可将数据解码为结构体。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> User <span class="token keyword">struct</span> <span class="token punctuation">{</span> Id <span class="token builtin">int64</span> <span class="token string">`json:"id,omitempty"`</span> Name <span class="token builtin">string</span> <span class="token string">`json:"name,omitempty"`</span> CreatedAt <span class="token builtin">string</span> <span class="token string">`json:"created_at,omitempty"`</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> res<span class="token punctuation">,</span> err <span class="token operator">:=</span> http<span class="token punctuation">.</span><span class="token function">Get</span><span class="token punctuation">(</span><span class="token string">"https://api.github.com/users/xxf0512"</span><span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">defer</span> res<span class="token punctuation">.</span>Body<span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">var</span> u User err <span class="token operator">=</span> json<span class="token punctuation">.</span><span class="token function">NewDecoder</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>Body<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Decode</span><span class="token punctuation">(</span><span class="token operator">&</span>u<span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%+v\n"</span><span class="token punctuation">,</span> u<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// {Id:49829039 Name:xxf CreatedAt:2019-04-21T06:14:00Z}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-2-写入JSON"><a href="#3-2-写入JSON" class="headerlink" title="3.2 写入JSON"></a>3.2 写入JSON</h3><p>模拟发送一个post请求,其中写入JSON</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"encoding/json"</span> <span class="token string">"fmt"</span> <span class="token string">"io/ioutil"</span> <span class="token string">"log"</span> <span class="token string">"net/http"</span> <span class="token string">"strings"</span><span class="token punctuation">)</span><span class="token keyword">type</span> Weather <span class="token keyword">struct</span> <span class="token punctuation">{</span> UserId <span class="token builtin">int</span> <span class="token string">`json:"userId"`</span> Body <span class="token builtin">string</span> <span class="token string">`json:"body"`</span> Title <span class="token builtin">string</span> <span class="token string">`json:"title"`</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> w <span class="token operator">:=</span> Weather<span class="token punctuation">{</span> UserId<span class="token punctuation">:</span> <span class="token number">100</span><span class="token punctuation">,</span> Body<span class="token punctuation">:</span> <span class="token string">"This is body"</span><span class="token punctuation">,</span> Title<span class="token punctuation">:</span> <span class="token string">"This is title"</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> str<span class="token punctuation">,</span> <span class="token boolean">_</span> <span class="token operator">:=</span> json<span class="token punctuation">.</span><span class="token function">Marshal</span><span class="token punctuation">(</span>w<span class="token punctuation">)</span> postData <span class="token operator">:=</span> <span class="token function">string</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"postData: "</span><span class="token punctuation">,</span> postData<span class="token punctuation">)</span> res<span class="token punctuation">,</span> err <span class="token operator">:=</span> http<span class="token punctuation">.</span><span class="token function">Post</span><span class="token punctuation">(</span><span class="token string">"https://jsonplaceholder.typicode.com/posts"</span><span class="token punctuation">,</span> <span class="token string">"application/x-www-form-urlencoded"</span><span class="token punctuation">,</span> strings<span class="token punctuation">.</span><span class="token function">NewReader</span><span class="token punctuation">(</span>postData<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">defer</span> res<span class="token punctuation">.</span>Body<span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span> body<span class="token punctuation">,</span> err <span class="token operator">:=</span> ioutil<span class="token punctuation">.</span><span class="token function">ReadAll</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>Body<span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token function">string</span><span class="token punctuation">(</span>body<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这里Content-Type是application/x-www-form-urlencoded,关于Content-Type可以参考下面的文章,后面再就Content-Type写一篇博客。</p><p><a href="https://www.cnblogs.com/fighter007/p/10917026.html">Content-Type四种常见取值</a></p>]]></content>
<categories>
<category> golang </category>
</categories>
<tags>
<tag> golang </tag>
<tag> json </tag>
</tags>
</entry>
<entry>
<title>浮点数设计原理</title>
<link href="/2021/12/12/fu-dian-shu-she-ji-yuan-li/"/>
<url>/2021/12/12/fu-dian-shu-she-ji-yuan-li/</url>
<content type="html"><![CDATA[<h1 id="浮点数设计原理"><a href="#浮点数设计原理" class="headerlink" title="浮点数设计原理"></a>浮点数设计原理</h1><blockquote><p>参考</p><p>《Go语言底层原理剖析》第2章 浮点数设计原理与使用方法 </p><p><a href="https://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html">浮点数的二进制表示 - 阮一峰的网络日志</a></p></blockquote><h2 id="1-从一个例子说起"><a href="#1-从一个例子说起" class="headerlink" title="1 从一个例子说起"></a>1 从一个例子说起</h2><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token string">"fmt"</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> d1<span class="token punctuation">,</span> d2 <span class="token builtin">float64</span> d1 <span class="token operator">=</span> <span class="token number">0.3</span> d2 <span class="token operator">=</span> <span class="token number">0.6</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>d1 <span class="token operator">+</span> d2<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 0.8999999999999999</span> <span class="token keyword">var</span> f1 f2 <span class="token builtin">float32</span> f1 <span class="token operator">=</span> <span class="token number">0.3</span> f2 <span class="token operator">=</span> <span class="token number">0.6</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>f1 <span class="token operator">+</span> f2<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 0.90000004</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上面的浮点数计算的结果与我们想象的好像不太一样,<strong>为什么0.3+0.6的结果不是0.9呢?</strong></p><p>下面将展开讲解浮点数在计算机中的 <strong>存储方式</strong> 以及 精度损失 的概念,最后对这个🌰进行解释。</p><hr><h2 id="2-存储方式"><a href="#2-存储方式" class="headerlink" title="2 存储方式"></a>2 存储方式</h2><p>众所周知,所有数据在计算机中都是以二进制的形式进行存储的。整数比较简单,转换为二进制数即可,而小数怎么转换呢?尤其是3.141592…这种无限小数如何利用有限的二进制位进行存储呢?</p><p>小数目前有两种存储方式:定点数 & 浮点数。计算机中采用的是浮点数。</p><h3 id="2-1-定点数"><a href="#2-1-定点数" class="headerlink" title="2.1 定点数"></a>2.1 定点数</h3><p>定点数比较简单,<strong>固定小数点的位置</strong>,小数点前的二进制数表示整数部分 & 小数点后的二进制数表示小数部分。</p><p>但是对于0.1234…这种整数部分很小或者…12345.1这种整数小数部分精度很低的数据,定点数就不适合了,它的扩展性很差。</p><h3 id="2-2-浮点数"><a href="#2-2-浮点数" class="headerlink" title="2.2 浮点数"></a>2.2 浮点数</h3><p>浮点数小数点的位置是浮动的(不固定),采用科学计数法来表示:<br>$$<br>V = (-1)^S \times M \times R^E<br>$$<br>S表示符号位、M表示尾数、R表示基数、E表示指数</p><p>例如十进制3.1415小数用科学计数法可以表示为:<br>$$<br>3.1415 = 3.1415 * 10^0<br>$$<br>$$<br>3.1415 = 31.415 * 10^{-1}<br>$$<br>$$<br>3.1415 = 314.15 * 10^{-2}<br>$$<br>那么采用哪种方式来表示?指数位和尾数位各应该是多少?这就得引入 <strong>IEEE754 浮点数标准</strong></p><h3 id="2-3-IEEE754浮点数标准"><a href="#2-3-IEEE754浮点数标准" class="headerlink" title="2.3 IEEE754浮点数标准"></a>2.3 IEEE754浮点数标准</h3><p>该标准规定科学计数法底数R=2:<br>$$<br>V = (-1)^S \times M \times 2^E<br>$$<br>还规定<strong>对于32位浮点数。最高1位是符号位S,中间8位是指数E,最后23位是尾数M</strong></p><img src="/images/20211212/floa32.png" alt="float32" style="zoom:50%;"><p><strong>对于64位浮点数。最高1位是符号位S,中间11位是指数E,最后52位是尾数M</strong></p><img src="/images/20211212/float64.png" alt="float64" style="zoom: 50%;"><img src="/images/20211212/2to10.png" alt="2to10" style="zoom: 50%;"><p>需要注意的是,转化为二进制后M是一个01串,可以将其向左平移至整数位为1,此时M的第一位始终为1,为了节省空间从而增大精度可以直接把1去掉。</p><p>举个🌰:0.75的二进制为0.11000…,向左平移后:1.100…,再把整数位的1去掉:0.100…,于是0.75的尾数为1000…</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"math"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">calfloat</span><span class="token punctuation">(</span><span class="token number">0.75</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">calfloat</span><span class="token punctuation">(</span>x <span class="token builtin">float32</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> f <span class="token operator">:=</span> math<span class="token punctuation">.</span><span class="token function">Float32bits</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> bits <span class="token operator">:=</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"%.32b"</span><span class="token punctuation">,</span> f<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"float32:"</span><span class="token punctuation">,</span> x<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"bits:"</span><span class="token punctuation">,</span> bits<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"S:"</span><span class="token punctuation">,</span> bits<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"E:"</span><span class="token punctuation">,</span> bits<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token number">9</span><span class="token punctuation">]</span><span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"M:"</span><span class="token punctuation">,</span> bits<span class="token punctuation">[</span><span class="token number">9</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span> bias <span class="token operator">:=</span> <span class="token number">127</span> <span class="token comment" spellcheck="true">// 偏移量</span> sign <span class="token operator">:=</span> f <span class="token operator">&</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator"><<</span> <span class="token number">31</span><span class="token punctuation">)</span> exponentRaw <span class="token operator">:=</span> <span class="token function">int</span><span class="token punctuation">(</span>f <span class="token operator">>></span> <span class="token number">23</span><span class="token punctuation">)</span> exponent <span class="token operator">:=</span> exponentRaw <span class="token operator">-</span> bias <span class="token keyword">var</span> mantissa <span class="token builtin">float32</span> <span class="token keyword">for</span> index<span class="token punctuation">,</span> bit <span class="token operator">:=</span> <span class="token keyword">range</span> bits<span class="token punctuation">[</span><span class="token number">9</span><span class="token punctuation">:</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> bit <span class="token operator">==</span> <span class="token string">'1'</span> <span class="token punctuation">{</span> bitValue <span class="token operator">:=</span> math<span class="token punctuation">.</span><span class="token function">Pow</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token function">float64</span><span class="token punctuation">(</span>index<span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> mantissa <span class="token operator">+=</span> <span class="token function">float32</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">/</span> bitValue<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> value <span class="token operator">:=</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">+</span> mantissa<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token function">float32</span><span class="token punctuation">(</span>math<span class="token punctuation">.</span><span class="token function">Pow</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token function">float64</span><span class="token punctuation">(</span>exponent<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"sign: %d, Exponent: %d(%d) Mantissa: %f Value: %f \n"</span><span class="token punctuation">,</span> sign<span class="token punctuation">,</span> exponentRaw<span class="token punctuation">,</span> exponent<span class="token punctuation">,</span> mantissa<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="/images/20211212/075.png" alt="0.75" style="zoom: 50%;"><p>其中bias表示偏移量,这是为了表达负数,对于32位浮点数,把[0, 2^8-1]即[0, 255]偏移到[-127, 128]需要减去127。对于64位浮点数bias=1023</p><hr><h2 id="3-浮点数精度"><a href="#3-浮点数精度" class="headerlink" title="3 浮点数精度"></a>3 浮点数精度</h2><p>一般讨论浮点数精度针对的是十进制下的浮点数,实际上浮点数的精度是不固定的,单精度浮点数float32的精度为6~8位,双精度浮点数float64的精度为15~17位。</p><p>浮点数的定义:在一个范围内,将d位十进制数(按照科学计数法表达)转换为二进制数,再将二进制数转换为d位十进制数,如果数据转换不发生损失,则意味着在此范围内有d位精度。</p><img src="/images/20211212/jingdu.png" alt="精度" style="zoom:50%;"><p>精度存在的原因在于,数据在进制之间相互转换时,不是精准匹配的,而是匹配到一个最接近的值。</p><p>如图(a)所示,十进制数转换为二进制数,二进制数又转换为十进制数,如果能够还原为最初的值,那么转换精度是无损的,说明在当前范围内浮点数是有d位精度的。反之,如图(b)所示,d位十进制数转换为二进制数,二进制数又转换为d位十进制数,得到的并不是原来的值,那么说明在该范围内浮点数没有d位精度。</p><hr><h2 id="4-回到最初的例子"><a href="#4-回到最初的例子" class="headerlink" title="4 回到最初的例子"></a>4 回到最初的例子</h2><p>为什么单精度浮点数&多精度浮点数表达0.3+0.6会有不同的结果呢?</p><p>首先需要明确的是<code>fmt.Println</code> <code>fmt.Printf</code> 内部对浮点数进行了复杂的运算,将其转换为了最接近的十进制数。</p><pre class="line-numbers language-go"><code class="language-go"> x <span class="token operator">:=</span> <span class="token function">float32</span><span class="token punctuation">(</span><span class="token number">0.90000000</span><span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%f\n"</span><span class="token punctuation">,</span> x<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 0.900000</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%.7f\n"</span><span class="token punctuation">,</span> x<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 0.9000000</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%.8f\n"</span><span class="token punctuation">,</span> x<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 0.89999998</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"%.9f\n"</span><span class="token punctuation">,</span> x<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 0.899999976</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>默认打印小数点后6位,单精度浮点数在存储和表示0.9时精度为7,小数点后八位开始就有精度问题了。</p><p>实际上,除了浮点数表示会丢失精度,浮点数计算也会丢失精度,那么例子里是什么原因导致精度丢失到呢?下面再做个实验</p><pre class="line-numbers language-go"><code class="language-go"> <span class="token keyword">var</span> d1<span class="token punctuation">,</span> d2 <span class="token builtin">float64</span> d1 <span class="token operator">=</span> <span class="token number">0.4</span> d2 <span class="token operator">=</span> <span class="token number">0.5</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>d1 <span class="token operator">+</span> d2<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 0.9</span> <span class="token keyword">var</span> f1<span class="token punctuation">,</span> f2 <span class="token builtin">float32</span> f1 <span class="token operator">=</span> <span class="token number">0.4</span> f2 <span class="token operator">=</span> <span class="token number">0.5</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>f1 <span class="token operator">+</span> f2<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 0.9</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>0.4+0.5并没有丢失精度,那么可以基本判断例子中是因为浮点数计算导致的精度丢失。</p><p><strong>对于单精度浮点数</strong></p><img src="/images/20211212/float32_03.png" alt="float32-0.3" style="zoom:50%;"><img src="/images/20211212/float32_06.png" alt="float32-0.6" style="zoom:50%;"><p>0.3的二进制:(1.00110011001100110011010x2^-2) -> 0.0100110011001100110011010 -> (0.30000001192092896)实际上不等于0.3<br>0.6的二进制:(1.00110011001100110011010x2^-1) -> 0.10110011001100110011010 -> (0.7000000476837158) 实际上不等于0.6<br>0.3+0.6= :0.1110011001100110011001110<br>转为十进制为 <strong>0.90000004</strong></p><p><strong>对于双精度浮点数</strong></p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">calfloat</span><span class="token punctuation">(</span>x <span class="token builtin">float64</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> f <span class="token operator">:=</span> math<span class="token punctuation">.</span><span class="token function">Float64bits</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> bits <span class="token operator">:=</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"%.64b"</span><span class="token punctuation">,</span> f<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"float64:"</span><span class="token punctuation">,</span> x<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"bits:"</span><span class="token punctuation">,</span> bits<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"S:"</span><span class="token punctuation">,</span> bits<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"E:"</span><span class="token punctuation">,</span> bits<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token number">12</span><span class="token punctuation">]</span><span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"M:"</span><span class="token punctuation">,</span> bits<span class="token punctuation">[</span><span class="token number">12</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span> bias <span class="token operator">:=</span> <span class="token number">1023</span> sign <span class="token operator">:=</span> f <span class="token operator">&</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator"><<</span> <span class="token number">63</span><span class="token punctuation">)</span> exponentRaw <span class="token operator">:=</span> <span class="token function">int</span><span class="token punctuation">(</span>f <span class="token operator">>></span> <span class="token number">52</span><span class="token punctuation">)</span> exponent <span class="token operator">:=</span> exponentRaw <span class="token operator">-</span> bias <span class="token keyword">var</span> mantissa <span class="token builtin">float64</span> <span class="token keyword">for</span> index<span class="token punctuation">,</span> bit <span class="token operator">:=</span> <span class="token keyword">range</span> bits<span class="token punctuation">[</span><span class="token number">12</span><span class="token punctuation">:</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> bit <span class="token operator">==</span> <span class="token string">'1'</span> <span class="token punctuation">{</span> mantissa <span class="token operator">+=</span> math<span class="token punctuation">.</span><span class="token function">Pow</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token operator">*</span><span class="token function">float64</span><span class="token punctuation">(</span>index<span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> value <span class="token operator">:=</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">+</span> mantissa<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token function">float64</span><span class="token punctuation">(</span>math<span class="token punctuation">.</span><span class="token function">Pow</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token function">float64</span><span class="token punctuation">(</span>exponent<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"sign: %d, Exponent: %d(%d) Mantissa: %f Value: %f \n"</span><span class="token punctuation">,</span> sign<span class="token punctuation">,</span> exponentRaw<span class="token punctuation">,</span> exponent<span class="token punctuation">,</span> mantissa<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="/images/20211212/float64_03.png" alt="float64-0.3" style="zoom:50%;"><img src="/images/20211212/float64_06.png" alt="float64-0.6" style="zoom:50%;"><p>0.3的二进制:0.010011001100110011001100110011001100110011001100110011 实际上!=0.3<br>0.6的二进制:0.10011001100110011001100110011001100110011001100110011 实际上!=0.6<br>0.3+0.6= :0.111001100110011001100110011001100110011001100110011001<br>转为十进制为 <strong>0.8999999999999999</strong></p><p>由上可知,因为0.3和0.6都不能无损存储为二进制数,最后的计算结果也有精度的损失。</p><hr><h2 id="5-大数运算库"><a href="#5-大数运算库" class="headerlink" title="5 大数运算库"></a>5 大数运算库</h2><p>当float64的精度无法满足需求时,可以考虑使用math/big库,著名区块链项目以太坊即用该库来实现货币的存储和计算。虽然实测0.3+0.6还是有精度损失。</p><pre class="line-numbers language-go"><code class="language-go"> d1<span class="token punctuation">,</span> d2 <span class="token operator">:=</span> big<span class="token punctuation">.</span><span class="token function">NewFloat</span><span class="token punctuation">(</span><span class="token number">0.3</span><span class="token punctuation">)</span><span class="token punctuation">,</span> big<span class="token punctuation">.</span><span class="token function">NewFloat</span><span class="token punctuation">(</span><span class="token number">0.6</span><span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>d1<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>d1<span class="token punctuation">,</span> d2<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 0.8999999999999999</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>但是该库对于大整数运算还是很好用的</p><pre class="line-numbers language-go"><code class="language-go"> a<span class="token punctuation">,</span> b <span class="token operator">:=</span> big<span class="token punctuation">.</span><span class="token function">NewInt</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> big<span class="token punctuation">.</span><span class="token function">NewInt</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> limit <span class="token operator">:=</span> a<span class="token punctuation">.</span><span class="token function">Exp</span><span class="token punctuation">(</span>big<span class="token punctuation">.</span><span class="token function">NewInt</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">,</span> big<span class="token punctuation">.</span><span class="token function">NewInt</span><span class="token punctuation">(</span><span class="token number">99</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span> <span class="token keyword">for</span> a<span class="token punctuation">.</span><span class="token function">Cmp</span><span class="token punctuation">(</span>limit<span class="token punctuation">)</span> <span class="token operator"><</span> <span class="token number">0</span> <span class="token punctuation">{</span> a<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span> b<span class="token punctuation">)</span> a<span class="token punctuation">,</span> b <span class="token operator">=</span> b<span class="token punctuation">,</span> a <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>]]></content>
<categories>
<category> 计算机组成原理 </category>
</categories>
<tags>
<tag> golang </tag>
<tag> 计算机组成原理 </tag>
</tags>
</entry>
<entry>
<title>使用Docker部署项目</title>
<link href="/2021/11/21/shi-yong-docker-bu-shu-xiang-mu/"/>
<url>/2021/11/21/shi-yong-docker-bu-shu-xiang-mu/</url>
<content type="html"><![CDATA[<h1 id="使用Docker部署项目"><a href="#使用Docker部署项目" class="headerlink" title="使用Docker部署项目"></a>使用Docker部署项目</h1><blockquote><p>参考资料<br><a href="https://www.liwenzhou.com/posts/Go/how_to_deploy_go_app_using_docker/">如何使用Docker部署Go Web应用</a></p></blockquote><h2 id="1-Why-Docker"><a href="#1-Why-Docker" class="headerlink" title="1. Why Docker"></a>1. Why Docker</h2><p>之前部署项目都比较直接,本地编译好然后把可执行文件丢到服务器上跑。</p><ul><li>对于后端,先把服务器数据库环境配好<ul><li>Java项目用maven打个Jar包</li><li>Golang项目交叉编译个Linux平台的可执行文件</li></ul></li><li>对于前端,先把服务器nignx环境配好<ul><li>把项目打包成静态资源,一般是放到dist目录下</li></ul></li></ul><p>但是这样有个问题,在多台服务器上部署相同项目时都得先去配好环境,非常麻烦。在微服务架构中,一个应用可能会拆成几十个微服务,每个服务都有开发、测试、生产几套环境需要搭建,如果采用传统的部署方式,工作量就太大了。</p><img src="/images/20211121/why_docker.png" alt="docker能做什么" style="zoom: 33%;"><p>使用Docker部署的话,可以将应用程序打包封装到一个容器中,容器包含了应用程序的代码、运行环境、依赖库、配置文件等必须的资源。容器之前相互隔离,互不影响。</p><p>用上Docker后,可以实现开发、测试和生产环境的统一化和标准化。镜像作为标准的交付件,可在开发、测试和生产环境上以容器来运行,最终实现三套环境上的应用以及运行所依赖内容的完全一致。</p><p>下面用一个例子记录一下如何用Docker部署项目</p><hr><h2 id="2-使用Docker部署Go-Web应用"><a href="#2-使用Docker部署Go-Web应用" class="headerlink" title="2. 使用Docker部署Go Web应用"></a>2. 使用Docker部署Go Web应用</h2><h3 id="2-1-编写Dockerfile"><a href="#2-1-编写Dockerfile" class="headerlink" title="2.1 编写Dockerfile"></a>2.1 编写Dockerfile</h3><p>先在项目根目录新建一个 <code>Dockerfile</code></p><pre class="line-numbers language-dockerfile"><code class="language-dockerfile">FROM golang:alpine AS builder# 为我们的镜像设置必要的环境变量ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64# 移动到工作目录:/buildWORKDIR /build# 将代码复制到容器中COPY . .# 将我们的代码编译成二进制可执行文件 appRUN go build -o app .# 接下来创建一个小镜像FROM scratch# 将项目的配置文件移动到配置文件中COPY ./configs /configs# 从builder镜像中把/dist/app 拷贝到当前目录COPY --from=builder /build/app /# 声明服务端口EXPOSE 3344# 需要运行的命令ENTRYPOINT ["/app", "configs/config.yaml"]<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-2-构建镜像"><a href="#2-2-构建镜像" class="headerlink" title="2.2 构建镜像"></a>2.2 构建镜像</h3><p>创建镜像并名为为 <code>goweb_app</code></p><pre class="line-numbers language-shell"><code class="language-shell">docker build . -t goweb_app<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="2-3-运行镜像"><a href="#2-3-运行镜像" class="headerlink" title="2.3 运行镜像"></a>2.3 运行镜像</h3><pre class="line-numbers language-shell"><code class="language-shell">docker run -d -p 8080:3344 goweb_app<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><code>-d</code> 表示在后台运行、 <code>-p 3344:3344</code> 表示将宿主机的3344端口(前)绑定到容器的3344端口(后)。</p><h3 id="2-4-分阶段构建"><a href="#2-4-分阶段构建" class="headerlink" title="2.4 分阶段构建"></a>2.4 分阶段构建</h3><p>Docker的最佳实践之一是通过仅保留二进制文件来减小镜像大小,为此,我们将使用一种称为多阶段构建的技术,这意味着我们将通过多个步骤构建镜像。</p><p>使用这种技术,我们剥离了使用<code>golang:alpine</code>作为编译镜像来编译得到二进制可执行文件的过程,并基于<code>scratch</code>生成一个简单的、非常小的新镜像。我们将二进制文件从命名为<code>builder</code>的第一个镜像中复制到新创建的<code>scratch</code>镜像中。</p><hr><h2 id="3-使用Docker部署MySQL"><a href="#3-使用Docker部署MySQL" class="headerlink" title="3. 使用Docker部署MySQL"></a>3. 使用Docker部署MySQL</h2><p>直接启动mysql容器</p><pre class="line-numbers language-shell"><code class="language-shell">docker run --name mysqlxk -p 13306:3306 -e MYSQL_ROOT_PASSWORD=xxf -v /Users/xxf/docker/mysql:/var/lib/mysql -d mysql:8.0.19<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><code>--name mysqlxk</code> 设置容器名称为mysqlxk</p><p><code>-e MYSQL_ROOT_PASSWORD=xxf</code> 设置root用户密码为xxf</p><p><code>-v /Users/xxf/docker/mysql:/var/lib/mysql</code> 挂载容器中的<code>/var/lib/mysql</code> 到本地的<code>/Users/xxf/docker/mysql</code> </p><p>之前go web里的配置也要改改,其中Host要改成mysql容器的名称</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">Database</span><span class="token punctuation">:</span> <span class="token key atrule">DBType</span><span class="token punctuation">:</span> mysql <span class="token key atrule">Username</span><span class="token punctuation">:</span> root <span class="token key atrule">Password</span><span class="token punctuation">:</span> xxf <span class="token key atrule">Host</span><span class="token punctuation">:</span> mysqlxk<span class="token punctuation">:</span><span class="token number">3306</span> <span class="token key atrule">DBName</span><span class="token punctuation">:</span> xk <span class="token key atrule">Charset</span><span class="token punctuation">:</span> utf8 <span class="token key atrule">ParseTime</span><span class="token punctuation">:</span> <span class="token boolean important">true</span> <span class="token key atrule">MaxIdleConns</span><span class="token punctuation">:</span> <span class="token number">10</span> <span class="token key atrule">MaxOpenConns</span><span class="token punctuation">:</span> <span class="token number">30</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>重新构建go web镜像后再运行,使用 <code>—-link</code> 与mysqlxk容器关联起来</p><pre class="line-numbers language-shell"><code class="language-shell">docker run -d --link=mysqlxk:mysqlxk -p 3344:3344 goweb_app<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><hr><h2 id="4-使用Docker部署Vue项目"><a href="#4-使用Docker部署Vue项目" class="headerlink" title="4. 使用Docker部署Vue项目"></a>4. 使用Docker部署Vue项目</h2><p>部署前端就更简单了,首先本地编译项目生成静态文件<code>vue build</code> ,然后创建 <code>Dockerfile</code></p><pre class="line-numbers language-dockerfile"><code class="language-dockerfile">FROM nginxCOPY dist/ /usr/share/nginx/html<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>之后构建镜像</p><pre class="line-numbers language-shell"><code class="language-shell">docker build --platform linux/amd64 . -t xxf0512/xkfrontend:1.0<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><code>--platform linux/amd64</code> 是指目标主机的系统</p><hr><p>上面说了几种构建镜像的方式,构建完成后将镜像直接传到服务器上。或者先在本地push到镜像仓库,然后在服务器上拉取镜像运行即可。</p><p>直接传到话先把镜像打个包:</p><pre class="line-numbers language-shell"><code class="language-shell">docker save -o goweb_app.tar goweb_appdocker save -o xkfrontend.tar xxf0512/xkfrontend:1.0<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>]]></content>
<categories>
<category> docker </category>
</categories>
<tags>
<tag> docker </tag>
<tag> 容器 </tag>
<tag> 项目部署 </tag>
</tags>
</entry>
<entry>
<title>设计模式-访问者模式</title>
<link href="/2021/11/05/she-ji-mo-shi-fang-wen-zhe-mo-shi/"/>
<url>/2021/11/05/she-ji-mo-shi-fang-wen-zhe-mo-shi/</url>
<content type="html"><![CDATA[<h1 id="设计模式-访问者模式"><a href="#设计模式-访问者模式" class="headerlink" title="设计模式-访问者模式"></a>设计模式-访问者模式</h1><blockquote><p>参考资料<br><a href="https://refactoringguru.cn/design-patterns/visitor">访问者设计模式</a><br><a href="https://refactoringguru.cn/design-patterns/visitor/go/example">Go 访问者模式讲解和代码示例</a></p></blockquote><p>最近学习AST(abstract static tree抽象语法树)和阅读<a href="https://github.com/sivachokkapu/revive-cc/blob/master/rule/blank-imports.go#L41">revive-cc</a>源码时发现,在对AST进行遍历&对每个node进行操作时,使用了访问者模式。每个节点都是不同类的对象,在对这些属于不同类的一组对象进行同一操作时,访问者模式会使相关实现变得更加优雅。<br>下面对访问者模式进行介绍,然后实现一个小demo,最后看看<a href="https://github.com/mgechev/revive">revive</a>和<a href="https://github.com/sivachokkapu/revive-cc">revive-cc</a>这两个针对golang的静态分析库是如何使用访问者模式的。</p><h2 id="1-访问者模式"><a href="#1-访问者模式" class="headerlink" title="1 访问者模式"></a>1 访问者模式</h2><h3 id="1-1-概念介绍"><a href="#1-1-概念介绍" class="headerlink" title="1.1 概念介绍"></a>1.1 概念介绍</h3><blockquote><p>访问者模式是一种行为设计模式,它能将<strong>算法</strong>与其所作用的<strong>对象</strong>隔离开来。</p></blockquote><img src="/images/20211105/访问者模式结构.png" alt="访问者模式结构" style="zoom: 40%;"><p>乍一看上去很难懂~</p><img src="/images/20211105/举个栗子.png" alt="举个栗子" style="zoom:60%;"><p>某个小区里有若干个住户,物业需要进住户家里查水表、查电表、查煤气表。。。但是住户家里可不是谁都能进的,怎么验证身份呢。<br>一种方法是让业主来验证,先带水工挨个给业主认识一下,再带电工挨个给业主认识一下,过了几天需要查煤气表了,再带煤气工给挨个业主认识一下。实在是太麻烦了<br>其实有一种更简单的办法,怎么做呢?业主家门上有密码锁,物业把密码表给各个值得信赖的工人看一下,他们自己进去就行了。</p><p>在这里:<br>访问者接口(<code>Visitor interface</code>)声明了访问方法(<code>visit</code>),即拿到每个住户房门的密码;<br>访问者实体(<code>ConcreteVistors</code>)就是工人,他们拿到了密码表就相当于实现了访问方法;<br>元素接口(<code>Element interface</code>)是住户密码锁的抽象,接受拥有访问能力的人来访问(实现了访问者接口的工人);<br>各个用户的密码锁是元素实体(<code>ElementA、ElementB</code>),他们有各自的特征,即密码不一致。拥有访问能力的工人可通过该实体的visit方法进行访问。</p><h3 id="1-2-应用场景"><a href="#1-2-应用场景" class="headerlink" title="1.2 应用场景"></a>1.2 应用场景</h3><ul><li><strong>如果你需要对一个复杂对象结构(例如对象树)中的所有元素执行某些操作,可使用访问者模式。</strong><br>访问者模式通过在访问者对象中为多个目标类提供相同操作的变体, 让你能在属于不同类的一组对象上执行同一操作。</li><li><strong>可使用访问者模式来清理辅助行为的业务逻辑。</strong><br>该模式会将所有非主要的行为抽取到一组访问者类中, 使得程序的主要类能更专注于主要的工作。</li><li><strong>当某个行为仅在类层次结构中的一些类中有意义,而在其他类中没有意义时,可使用该模式。</strong><br>你可将该行为抽取到单独的访问者类中, 只需实现接收相关类的对象作为参数的访问者方法并将其他方法留空即可。</li></ul><h3 id="1-3-优点-amp-缺点"><a href="#1-3-优点-amp-缺点" class="headerlink" title="1.3 优点&缺点"></a>1.3 优点&缺点</h3><table><thead><tr><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>开闭原则。 你可以引入在不同类对象上执行的新行为, 且无需对这些类做出修改。</td><td>每次在元素层次结构中添加或移除一个类时, 你都要更新所有的访问者。</td></tr><tr><td>单一职责原则。 可将同一行为的不同版本移到同一个类中。</td><td>在访问者同某个元素进行交互时, 它们可能没有访问元素私有成员变量和方法的必要权限。</td></tr><tr><td>访问者对象可以在与各种对象交互时收集一些有用的信息。 当你想要遍历一些复杂的对象结构 (例如对象树), 并在结构中的每个对象上应用访问者时, 这些信息可能会有所帮助。</td><td></td></tr></tbody></table><hr><h2 id="2-Demo-如何使用访问者模式"><a href="#2-Demo-如何使用访问者模式" class="headerlink" title="2 Demo-如何使用访问者模式"></a>2 Demo-如何使用访问者模式</h2><p>访问者模式允许你在结构体中添加行为,而又不会对结构体造成实际变更。下面用一个Demo来说明如何使用访问者模式。</p><h3 id="2-1-初始状态"><a href="#2-1-初始状态" class="headerlink" title="2.1 初始状态"></a>2.1 初始状态</h3><p>现在有一个Shape接口和三个形状结构体,三个结构体实现了接口的Name()函数,在golang中可以认为结构体implement了接口。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"math"</span><span class="token punctuation">)</span><span class="token keyword">type</span> Shape <span class="token keyword">interface</span> <span class="token punctuation">{</span> <span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span><span class="token punctuation">}</span><span class="token keyword">type</span> Square <span class="token keyword">struct</span> <span class="token punctuation">{</span> side <span class="token builtin">float64</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>s Square<span class="token punctuation">)</span> <span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string">"square"</span><span class="token punctuation">}</span><span class="token keyword">type</span> Circle <span class="token keyword">struct</span> <span class="token punctuation">{</span> radius <span class="token builtin">float64</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>c Circle<span class="token punctuation">)</span> <span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string">"circle"</span><span class="token punctuation">}</span><span class="token keyword">type</span> Triangle <span class="token keyword">struct</span> <span class="token punctuation">{</span> sideA<span class="token punctuation">,</span> sideB<span class="token punctuation">,</span> sideC <span class="token builtin">float64</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>t Triangle<span class="token punctuation">)</span> <span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string">"triangle"</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> shapes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>Shape<span class="token punctuation">{</span> Square<span class="token punctuation">{</span><span class="token number">1.2</span><span class="token punctuation">}</span><span class="token punctuation">,</span> Circle<span class="token punctuation">{</span><span class="token number">1.5</span><span class="token punctuation">}</span><span class="token punctuation">,</span> Triangle<span class="token punctuation">{</span><span class="token number">3.0</span><span class="token punctuation">,</span> <span class="token number">4.0</span><span class="token punctuation">,</span> <span class="token number">5.0</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> shape <span class="token operator">:=</span> <span class="token keyword">range</span> shapes <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>shape<span class="token punctuation">.</span><span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>现在来了个新需求,需要计算每个图形的面积</strong>。一种直接的方式就是向Name()方法一样,在<code>interface Shape</code>中定义<code>getArea()</code>,然后每个struct实现getArea()。</p><h3 id="2-2-每个结构体都实现一遍新方法"><a href="#2-2-每个结构体都实现一遍新方法" class="headerlink" title="2.2 每个结构体都实现一遍新方法"></a>2.2 每个结构体都实现一遍新方法</h3><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> Shape <span class="token keyword">interface</span> <span class="token punctuation">{</span> <span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span> <span class="token function">getArea</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">float64</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>s Square<span class="token punctuation">)</span> <span class="token function">getArea</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> s<span class="token punctuation">.</span>side <span class="token operator">*</span> s<span class="token punctuation">.</span>side<span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>c Circle<span class="token punctuation">)</span> <span class="token function">getArea</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> math<span class="token punctuation">.</span>Pi <span class="token operator">*</span> c<span class="token punctuation">.</span>radius <span class="token operator">*</span> c<span class="token punctuation">.</span>radius<span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>t Triangle<span class="token punctuation">)</span> <span class="token function">getArea</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> s <span class="token operator">:=</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span>sideA <span class="token operator">+</span> t<span class="token punctuation">.</span>sideB <span class="token operator">+</span> t<span class="token punctuation">.</span>sideC<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2</span> <span class="token keyword">return</span> math<span class="token punctuation">.</span><span class="token function">Sqrt</span><span class="token punctuation">(</span>s <span class="token operator">*</span> <span class="token punctuation">(</span>s <span class="token operator">-</span> t<span class="token punctuation">.</span>sideA<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span>s <span class="token operator">-</span> t<span class="token punctuation">.</span>sideB<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span>s <span class="token operator">-</span> t<span class="token punctuation">.</span>sideC<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这种方式比较直观,也比较简单,但是可扩展性不高。之后如果再有新需求,比如求个周长之类的,还得再把相关方法实现一遍,同时需要修改现有的interface,还可能会引入安全风险。在版本迭代时,应当确定一个原则:<strong>不要改之前的代码,要在之前的基础上进行扩展。</strong><br>在这种情况下,访问者模式可以很优雅地解决问题。</p><h3 id="2-3-用访问者模式进行改造"><a href="#2-3-用访问者模式进行改造" class="headerlink" title="2.3 用访问者模式进行改造"></a>2.3 用访问者模式进行改造</h3><img src="/images/20211105/demo结构.png" alt="demo结构" style="zoom:40%;"><p>图是白嫖的,基本意思差不多</p><ol><li><strong>定义一个访问者接口,声明访问者需要有访问哪些对象的能力</strong><br>接口声明了一系列以以对象结构的具体元素为参数的访问者方法<strong>,</strong>这些方法可以理解为访问不同对象的入口,由于golang不支持重载,所以方法名都不一样。在Java这些支持重载的语言中,可以统一用一个方法名。</li></ol><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> Visitor <span class="token keyword">interface</span> <span class="token punctuation">{</span> <span class="token function">visitForSquare</span><span class="token punctuation">(</span><span class="token operator">*</span>Square<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token function">visitForCircle</span><span class="token punctuation">(</span><span class="token operator">*</span>Circle<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token function">visitForTriangle</span><span class="token punctuation">(</span><span class="token operator">*</span>Triangle<span class="token punctuation">)</span> <span class="token builtin">float64</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="2"><li><strong>Shape接口声明一个方法来接收访问者,相当于对象给访问者开了个门</strong><br>如果访问者有访问该对象的能力(访问者实现了对该对象操作的方法),就可以进行访问。<br>所有继承该Shape接口的类去实现这个方法<strong>。</strong>需要注意,访问者模式必须修改原interface,但是这种修改只需要一次,后面的扩展比如求面积、求周长都可以通过这一个方法进行。</li></ol><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> Shape <span class="token keyword">interface</span> <span class="token punctuation">{</span> <span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span> <span class="token function">Accept</span><span class="token punctuation">(</span>Visitor<span class="token punctuation">)</span> <span class="token builtin">float64</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>s <span class="token operator">*</span>Square<span class="token punctuation">)</span> <span class="token function">Accept</span><span class="token punctuation">(</span>v Visitor<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> v<span class="token punctuation">.</span><span class="token function">visitForSquare</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>c <span class="token operator">*</span>Circle<span class="token punctuation">)</span> <span class="token function">Accept</span><span class="token punctuation">(</span>v Visitor<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> v<span class="token punctuation">.</span><span class="token function">visitForCircle</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>t <span class="token operator">*</span>Triangle<span class="token punctuation">)</span> <span class="token function">Accept</span><span class="token punctuation">(</span>v Visitor<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> v<span class="token punctuation">.</span><span class="token function">visitForTriangle</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="3"><li><strong>实现具体访问者,同时教会这个访问者如何去访问各个对象</strong><br>可以定义一个struct/class作为访问者,然后这个访问者去实现访问者接口定义的方法,他就有了访问对象的能力。</li></ol><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> areaCalculator <span class="token keyword">struct</span> <span class="token punctuation">{</span> area <span class="token builtin">float64</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>a <span class="token operator">*</span>areaCalculator<span class="token punctuation">)</span> <span class="token function">visitForSquare</span><span class="token punctuation">(</span>s <span class="token operator">*</span>Square<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> s<span class="token punctuation">.</span>side <span class="token operator">*</span> s<span class="token punctuation">.</span>side<span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>a <span class="token operator">*</span>areaCalculator<span class="token punctuation">)</span> <span class="token function">visitForCircle</span><span class="token punctuation">(</span>c <span class="token operator">*</span>Circle<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> math<span class="token punctuation">.</span>Pi <span class="token operator">*</span> c<span class="token punctuation">.</span>radius <span class="token operator">*</span> c<span class="token punctuation">.</span>radius<span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>a <span class="token operator">*</span>areaCalculator<span class="token punctuation">)</span> <span class="token function">visitForTriangle</span><span class="token punctuation">(</span>t <span class="token operator">*</span>Triangle<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> s <span class="token operator">:=</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span>sideA <span class="token operator">+</span> t<span class="token punctuation">.</span>sideB <span class="token operator">+</span> t<span class="token punctuation">.</span>sideC<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2</span> <span class="token keyword">return</span> math<span class="token punctuation">.</span><span class="token function">Sqrt</span><span class="token punctuation">(</span>s <span class="token operator">*</span> <span class="token punctuation">(</span>s <span class="token operator">-</span> t<span class="token punctuation">.</span>sideA<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span>s <span class="token operator">-</span> t<span class="token punctuation">.</span>sideB<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span>s <span class="token operator">-</span> t<span class="token punctuation">.</span>sideC<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="4"><li><strong>访问者从对象的门里进去,完成访问操作</strong></li></ol><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> areaCalculator <span class="token operator">:=</span> <span class="token operator">&</span>areaCalculator<span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token keyword">var</span> shapes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>Shape<span class="token punctuation">{</span> <span class="token operator">&</span>Square<span class="token punctuation">{</span><span class="token number">1.2</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token operator">&</span>Circle<span class="token punctuation">{</span><span class="token number">1.5</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token operator">&</span>Triangle<span class="token punctuation">{</span><span class="token number">3.0</span><span class="token punctuation">,</span> <span class="token number">4.0</span><span class="token punctuation">,</span> <span class="token number">5.0</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> shape <span class="token operator">:=</span> <span class="token keyword">range</span> shapes <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>shape<span class="token punctuation">.</span><span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> shape<span class="token punctuation">.</span><span class="token function">Accept</span><span class="token punctuation">(</span>areaCalculator<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>之后再扩展就非常方便了,只需要定义一个struct/class,然后实现访问者interface声明的所有方法,他就成了一个访问者。<strong>不同的是每个访问者实现的方法体具体做什么操作可以定制化。</strong><br>下面是整体代码</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">package</span> main<span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"math"</span><span class="token punctuation">)</span><span class="token keyword">type</span> Shape <span class="token keyword">interface</span> <span class="token punctuation">{</span> <span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span> <span class="token function">Accept</span><span class="token punctuation">(</span>Visitor<span class="token punctuation">)</span> <span class="token builtin">float64</span><span class="token punctuation">}</span><span class="token keyword">type</span> Square <span class="token keyword">struct</span> <span class="token punctuation">{</span> side <span class="token builtin">float64</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>s <span class="token operator">*</span>Square<span class="token punctuation">)</span> <span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string">"square"</span><span class="token punctuation">}</span><span class="token keyword">type</span> Circle <span class="token keyword">struct</span> <span class="token punctuation">{</span> radius <span class="token builtin">float64</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>c <span class="token operator">*</span>Circle<span class="token punctuation">)</span> <span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string">"circle"</span><span class="token punctuation">}</span><span class="token keyword">type</span> Triangle <span class="token keyword">struct</span> <span class="token punctuation">{</span> sideA<span class="token punctuation">,</span> sideB<span class="token punctuation">,</span> sideC <span class="token builtin">float64</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>t <span class="token operator">*</span>Triangle<span class="token punctuation">)</span> <span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">string</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string">"triangle"</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>s <span class="token operator">*</span>Square<span class="token punctuation">)</span> <span class="token function">Accept</span><span class="token punctuation">(</span>v Visitor<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> v<span class="token punctuation">.</span><span class="token function">visitForSquare</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>c <span class="token operator">*</span>Circle<span class="token punctuation">)</span> <span class="token function">Accept</span><span class="token punctuation">(</span>v Visitor<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> v<span class="token punctuation">.</span><span class="token function">visitForCircle</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>t <span class="token operator">*</span>Triangle<span class="token punctuation">)</span> <span class="token function">Accept</span><span class="token punctuation">(</span>v Visitor<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> v<span class="token punctuation">.</span><span class="token function">visitForTriangle</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">type</span> Visitor <span class="token keyword">interface</span> <span class="token punctuation">{</span> <span class="token function">visitForSquare</span><span class="token punctuation">(</span><span class="token operator">*</span>Square<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token function">visitForCircle</span><span class="token punctuation">(</span><span class="token operator">*</span>Circle<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token function">visitForTriangle</span><span class="token punctuation">(</span><span class="token operator">*</span>Triangle<span class="token punctuation">)</span> <span class="token builtin">float64</span><span class="token punctuation">}</span><span class="token keyword">type</span> areaCalculator <span class="token keyword">struct</span> <span class="token punctuation">{</span> area <span class="token builtin">float64</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>a <span class="token operator">*</span>areaCalculator<span class="token punctuation">)</span> <span class="token function">visitForSquare</span><span class="token punctuation">(</span>s <span class="token operator">*</span>Square<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> s<span class="token punctuation">.</span>side <span class="token operator">*</span> s<span class="token punctuation">.</span>side<span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>a <span class="token operator">*</span>areaCalculator<span class="token punctuation">)</span> <span class="token function">visitForCircle</span><span class="token punctuation">(</span>c <span class="token operator">*</span>Circle<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> math<span class="token punctuation">.</span>Pi <span class="token operator">*</span> c<span class="token punctuation">.</span>radius <span class="token operator">*</span> c<span class="token punctuation">.</span>radius<span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>a <span class="token operator">*</span>areaCalculator<span class="token punctuation">)</span> <span class="token function">visitForTriangle</span><span class="token punctuation">(</span>t <span class="token operator">*</span>Triangle<span class="token punctuation">)</span> <span class="token builtin">float64</span> <span class="token punctuation">{</span> s <span class="token operator">:=</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span>sideA <span class="token operator">+</span> t<span class="token punctuation">.</span>sideB <span class="token operator">+</span> t<span class="token punctuation">.</span>sideC<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2</span> <span class="token keyword">return</span> math<span class="token punctuation">.</span><span class="token function">Sqrt</span><span class="token punctuation">(</span>s <span class="token operator">*</span> <span class="token punctuation">(</span>s <span class="token operator">-</span> t<span class="token punctuation">.</span>sideA<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span>s <span class="token operator">-</span> t<span class="token punctuation">.</span>sideB<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span>s <span class="token operator">-</span> t<span class="token punctuation">.</span>sideC<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> areaCalculator <span class="token operator">:=</span> <span class="token operator">&</span>areaCalculator<span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token keyword">var</span> shapes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>Shape<span class="token punctuation">{</span> <span class="token operator">&</span>Square<span class="token punctuation">{</span><span class="token number">1.2</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token operator">&</span>Circle<span class="token punctuation">{</span><span class="token number">1.5</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token operator">&</span>Triangle<span class="token punctuation">{</span><span class="token number">3.0</span><span class="token punctuation">,</span> <span class="token number">4.0</span><span class="token punctuation">,</span> <span class="token number">5.0</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> shape <span class="token operator">:=</span> <span class="token keyword">range</span> shapes <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>shape<span class="token punctuation">.</span><span class="token function">Name</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> shape<span class="token punctuation">.</span><span class="token function">Accept</span><span class="token punctuation">(</span>areaCalculator<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h2 id="3-revive和revive-cc中的访问者模式"><a href="#3-revive和revive-cc中的访问者模式" class="headerlink" title="3 revive和revive-cc中的访问者模式"></a>3 revive和revive-cc中的访问者模式</h2><h3 id="3-1-Visitor-interface"><a href="#3-1-Visitor-interface" class="headerlink" title="3.1 Visitor interface"></a>3.1 Visitor interface</h3><p>在<code>go/ast/walk.go:12</code>定义了<code>Visitor interface</code>,声明了方法<code>Visit(node Node)</code>,其中Node也是一个interface。<strong>该方法提供了访问Node节点的能力,</strong>会被Walk函数用来访问节点。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// go/ast/walk.go:12</span><span class="token comment" spellcheck="true">// A Visitor's Visit method is invoked for each node encountered by Walk.</span><span class="token comment" spellcheck="true">// If the result visitor w is not nil, Walk visits each of the children</span><span class="token comment" spellcheck="true">// of node with the visitor w, followed by a call of w.Visit(nil).</span><span class="token keyword">type</span> Visitor <span class="token keyword">interface</span> <span class="token punctuation">{</span> <span class="token function">Visit</span><span class="token punctuation">(</span>node Node<span class="token punctuation">)</span> <span class="token punctuation">(</span>w Visitor<span class="token punctuation">)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>Walk函数</strong><br>walk函数通过dfs对ast进行遍历,通过visit方法访问ast上的node。</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// go/ast/walk.go</span><span class="token comment" spellcheck="true">// Walk traverses an AST in depth-first order: It starts by calling</span><span class="token comment" spellcheck="true">// v.Visit(node); node must not be nil. If the visitor w returned by</span><span class="token comment" spellcheck="true">// v.Visit(node) is not nil, Walk is invoked recursively with visitor</span><span class="token comment" spellcheck="true">// w for each of the non-nil children of node, followed by a call of</span><span class="token comment" spellcheck="true">// w.Visit(nil).</span><span class="token comment" spellcheck="true">//</span><span class="token keyword">func</span> <span class="token function">Walk</span><span class="token punctuation">(</span>v Visitor<span class="token punctuation">,</span> node Node<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> v <span class="token operator">=</span> v<span class="token punctuation">.</span><span class="token function">Visit</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> v <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// walk children</span> <span class="token comment" spellcheck="true">// (the order of the cases matches the order</span> <span class="token comment" spellcheck="true">// of the corresponding node types in ast.go)</span> <span class="token keyword">switch</span> n <span class="token operator">:=</span> node<span class="token punctuation">.</span><span class="token punctuation">(</span><span class="token keyword">type</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// Comments and fields</span> <span class="token keyword">case</span> <span class="token operator">*</span>Comment<span class="token punctuation">:</span> <span class="token comment" spellcheck="true">// nothing to do</span> <span class="token keyword">case</span> <span class="token operator">*</span>CommentGroup<span class="token punctuation">:</span> <span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> c <span class="token operator">:=</span> <span class="token keyword">range</span> n<span class="token punctuation">.</span>List <span class="token punctuation">{</span> <span class="token function">Walk</span><span class="token punctuation">(</span>v<span class="token punctuation">,</span> c<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token operator">...</span> <span class="token operator">...</span> <span class="token operator">...</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-2-Concrete-Visitor"><a href="#3-2-Concrete-Visitor" class="headerlink" title="3.2 Concrete Visitor"></a>3.2 Concrete Visitor</h3><p>revive-cc这个项目是利用<code>revive</code>做静态分析和缺陷检测的。主要实现的就是<code>Concrete Visitor</code>,在rule目录下定义了一系列规则,在规则文件中实现了访问者实体。<br>如<code>rule/imports-blacklist.go</code>中实现了接口中声明的visit函数</p><pre class="line-numbers language-go"><code class="language-go"><span class="token keyword">type</span> blacklistedImports <span class="token keyword">struct</span> <span class="token punctuation">{</span> file <span class="token operator">*</span>lint<span class="token punctuation">.</span>File fileAst <span class="token operator">*</span>ast<span class="token punctuation">.</span>File onFailure <span class="token keyword">func</span><span class="token punctuation">(</span>lint<span class="token punctuation">.</span>Failure<span class="token punctuation">)</span> blacklist <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">bool</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>w blacklistedImports<span class="token punctuation">)</span> <span class="token function">Visit</span><span class="token punctuation">(</span><span class="token boolean">_</span> ast<span class="token punctuation">.</span>Node<span class="token punctuation">)</span> ast<span class="token punctuation">.</span>Visitor <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> is <span class="token operator">:=</span> <span class="token keyword">range</span> w<span class="token punctuation">.</span>fileAst<span class="token punctuation">.</span>Imports <span class="token punctuation">{</span> <span class="token keyword">if</span> is<span class="token punctuation">.</span>Path <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token operator">&&</span> <span class="token operator">!</span>w<span class="token punctuation">.</span>file<span class="token punctuation">.</span><span class="token function">IsTest</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&&</span> w<span class="token punctuation">.</span>blacklist<span class="token punctuation">[</span>is<span class="token punctuation">.</span>Path<span class="token punctuation">.</span>Value<span class="token punctuation">]</span> <span class="token punctuation">{</span> w<span class="token punctuation">.</span><span class="token function">onFailure</span><span class="token punctuation">(</span>lint<span class="token punctuation">.</span>Failure<span class="token punctuation">{</span> Confidence<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span> Failure<span class="token punctuation">:</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"should not use the following blacklisted import: %s"</span><span class="token punctuation">,</span> is<span class="token punctuation">.</span>Path<span class="token punctuation">.</span>Value<span class="token punctuation">)</span><span class="token punctuation">,</span> Node<span class="token punctuation">:</span> is<span class="token punctuation">,</span> Category<span class="token punctuation">:</span> <span class="token string">"imports"</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-3-Element"><a href="#3-3-Element" class="headerlink" title="3.3 Element"></a>3.3 Element</h3><p>被访问的对象元素就是前面提到的Node,他们定义的方法不是之前的<code>accept</code>。但也只是名字不同,本质应该还是一样的。<br>Todo:目前还有个问题没搞清楚,为什么interface中声明的函数没有参数,安装之前所学,应该有个Visitor来接收访问者。还在思考,后续补上</p><pre class="line-numbers language-go"><code class="language-go"><span class="token comment" spellcheck="true">// go/ast/ast.go</span><span class="token comment" spellcheck="true">// All node types implement the Node interface.</span><span class="token keyword">type</span> Node <span class="token keyword">interface</span> <span class="token punctuation">{</span> <span class="token function">Pos</span><span class="token punctuation">(</span><span class="token punctuation">)</span> token<span class="token punctuation">.</span>Pos <span class="token comment" spellcheck="true">// position of first character belonging to the node</span> <span class="token function">End</span><span class="token punctuation">(</span><span class="token punctuation">)</span> token<span class="token punctuation">.</span>Pos <span class="token comment" spellcheck="true">// position of first character immediately after the node</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// All expression nodes implement the Expr interface. 表达式:expression</span><span class="token keyword">type</span> Expr <span class="token keyword">interface</span> <span class="token punctuation">{</span> Node <span class="token function">exprNode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// All statement nodes implement the Stmt interface. 语句:statement</span><span class="token keyword">type</span> Stmt <span class="token keyword">interface</span> <span class="token punctuation">{</span> Node <span class="token function">stmtNode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// All declaration nodes implement the Decl interface. 声明:declaration</span><span class="token keyword">type</span> Decl <span class="token keyword">interface</span> <span class="token punctuation">{</span> Node <span class="token function">declNode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// Pos and End implementations for expression/type nodes.</span><span class="token keyword">func</span> <span class="token punctuation">(</span>x <span class="token operator">*</span>BadExpr<span class="token punctuation">)</span> <span class="token function">Pos</span><span class="token punctuation">(</span><span class="token punctuation">)</span> token<span class="token punctuation">.</span>Pos <span class="token punctuation">{</span> <span class="token keyword">return</span> x<span class="token punctuation">.</span>From <span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>x <span class="token operator">*</span>Ident<span class="token punctuation">)</span> <span class="token function">Pos</span><span class="token punctuation">(</span><span class="token punctuation">)</span> token<span class="token punctuation">.</span>Pos <span class="token punctuation">{</span> <span class="token keyword">return</span> x<span class="token punctuation">.</span>NamePos <span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>x <span class="token operator">*</span>Ellipsis<span class="token punctuation">)</span> <span class="token function">Pos</span><span class="token punctuation">(</span><span class="token punctuation">)</span> token<span class="token punctuation">.</span>Pos <span class="token punctuation">{</span> <span class="token keyword">return</span> x<span class="token punctuation">.</span>Ellipsis <span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span>x <span class="token operator">*</span>BasicLit<span class="token punctuation">)</span> <span class="token function">Pos</span><span class="token punctuation">(</span><span class="token punctuation">)</span> token<span class="token punctuation">.</span>Pos <span class="token punctuation">{</span> <span class="token keyword">return</span> x<span class="token punctuation">.</span>ValuePos <span class="token punctuation">}</span><span class="token comment" spellcheck="true">// exprNode() ensures that only expression/type nodes can be</span><span class="token comment" spellcheck="true">// assigned to an Expr.</span><span class="token keyword">func</span> <span class="token punctuation">(</span><span class="token operator">*</span>BadExpr<span class="token punctuation">)</span> <span class="token function">exprNode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span><span class="token operator">*</span>Ident<span class="token punctuation">)</span> <span class="token function">exprNode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span><span class="token operator">*</span>Ellipsis<span class="token punctuation">)</span> <span class="token function">exprNode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token keyword">func</span> <span class="token punctuation">(</span><span class="token operator">*</span>BasicLit<span class="token punctuation">)</span> <span class="token function">exprNode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>]]></content>
<categories>
<category> 设计模式 </category>
</categories>
<tags>
<tag> 设计模式 </tag>
<tag> 静态分析 </tag>
<tag> 缺陷检测 </tag>
</tags>
</entry>
<entry>
<title>CSS弹性盒子布局</title>
<link href="/2021/10/16/css-dan-xing-he-zi-bu-ju/"/>
<url>/2021/10/16/css-dan-xing-he-zi-bu-ju/</url>
<content type="html"><![CDATA[<h1 id="CSS布局-弹性盒子"><a href="#CSS布局-弹性盒子" class="headerlink" title="CSS布局-弹性盒子"></a>CSS布局-弹性盒子</h1><blockquote><p>参考:</p><ol><li><a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/">css-tricks 弹性盒子</a></li><li><a href="https://juejin.cn/post/6867413038371667976">最详细完整的flex弹性布局</a></li></ol></blockquote><img src="/images/20211016/flexbox02.jpg" alt="flexbox" style="zoom:20%;"><p> flex 是flexible box的缩写,意为弹性布局,用来为盒装模型提供最大的灵活性,任何一个容器都可以指定为flex布局。</p><h2 id="1-基本概念"><a href="#1-基本概念" class="headerlink" title="1. 基本概念"></a>1. 基本概念</h2><img src="/images/20211016/layout.jpg" alt="layout" style="zoom:50%;"><p>主轴(<code>main axis</code>):默认是水平轴,方向自左向右。<br>交叉轴(<code>cross axis</code>):默认是垂直轴,方向自上向下。<br>容器中的子元素叫做<code>flex item</code>,其占据的主轴空间叫做<code>main size</code>,占据的交叉轴空间叫做<code>cross size</code>。</p><h2 id="2-弹性容器属性"><a href="#2-弹性容器属性" class="headerlink" title="2. 弹性容器属性"></a>2. 弹性容器属性</h2><h3 id="2-1-flex-direction"><a href="#2-1-flex-direction" class="headerlink" title="2.1 flex-direction"></a>2.1 flex-direction</h3><img src="/images/20211016/flex-direction.png" alt="flex-direction" style="zoom: 50%;"><p><code>flex-direction</code>决定了主轴的方向。一般不写也行,默认为水平方向,自左向右。</p><pre class="line-numbers language-css"><code class="language-css"><span class="token selector"><span class="token class">.box</span> </span><span class="token punctuation">{</span> <span class="token property">flex-direction</span><span class="token punctuation">:</span> row | row-reverse | column | column-reverse<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="2-2-justify-content"><a href="#2-2-justify-content" class="headerlink" title="2.2 justify-content"></a>2.2 justify-content</h3><img src="/images/20211016/justify-content.png" alt="justify-content" style="zoom: 33%;"><p><code>justify-content</code>决定了flex item在main axis上的对齐方式,默认flex-start,与main start对齐。</p><pre class="line-numbers language-css"><code class="language-css"><span class="token selector"><span class="token class">.container</span> </span><span class="token punctuation">{</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> flex-start | flex-end | center | space-between | space-around | space-evenly | start | end | left | right <span class="token number">...</span> + safe | unsafe<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="2-3-align-items"><a href="#2-3-align-items" class="headerlink" title="2.3 align-items"></a>2.3 align-items</h3><img src="/images/20211016/align-items.png" alt="align-items" style="zoom:33%;"><p><code>align-items</code>决定flex items在cross axis上的对齐方式,</p><pre class="line-numbers language-css"><code class="language-css"><span class="token selector"><span class="token class">.container</span> </span><span class="token punctuation">{</span> <span class="token property">align-items</span><span class="token punctuation">:</span> stretch | flex-start | flex-end | center | baseline | first baseline | last baseline | start | end | self-start | self-end + <span class="token number">...</span> safe | unsafe<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="2-4-flex-wrap"><a href="#2-4-flex-wrap" class="headerlink" title="2.4 flex-wrap"></a>2.4 flex-wrap</h3><img src="/images/20211016/flex-wrap.png" alt="flex-wrap" style="zoom: 50%;"><p><code>flex-wrap</code>决定了flex container 是单行还是多行。默认nowrap是在一行上,不换行。<br>nowrap:所有flex items在一行<br>wrap:所有flex items在多行,方向从上到下<br>wrap-reverse:所有flex items在多行,方向从下到上</p><pre class="line-numbers language-css"><code class="language-css"><span class="token selector"><span class="token class">.container</span> </span><span class="token punctuation">{</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> nowrap | wrap | wrap-reverse<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="2-5-align-content"><a href="#2-5-align-content" class="headerlink" title="2.5 align-content"></a>2.5 align-content</h3><img src="/images/20211016/align-content.png" alt="align-content" style="zoom: 33%;"><p><code>align-content</code>决定了多行flex items在cross axis的对齐方式,用法与justify-content相似,一个是横轴,一个控制竖轴。</p><pre class="line-numbers language-css"><code class="language-css"><span class="token selector"><span class="token class">.container</span> </span><span class="token punctuation">{</span> <span class="token property">align-content</span><span class="token punctuation">:</span> flex-start | flex-end | center | space-between | space-around | space-evenly | stretch | start | end | baseline | first baseline | last baseline + <span class="token number">...</span> safe | unsafe<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="2-6-flew-flow"><a href="#2-6-flew-flow" class="headerlink" title="2.6 flew-flow"></a>2.6 flew-flow</h3><p><code>flew-flow</code>是flex-direction与flex-wrap的简写。</p><pre class="line-numbers language-css"><code class="language-css"><span class="token selector"><span class="token class">.container</span> </span><span class="token punctuation">{</span> <span class="token property">flex-flow</span><span class="token punctuation">:</span> column wrap<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h2 id="3-子元素-flex-item-属性"><a href="#3-子元素-flex-item-属性" class="headerlink" title="3. 子元素(flex item)属性"></a>3. 子元素(flex item)属性</h2><p>注意⚠️:<code>float</code>, <code>clear</code> and <code>vertical-align</code>对flex item无效。</p><h3 id="3-1-order"><a href="#3-1-order" class="headerlink" title="3.1 order"></a>3.1 order</h3><img src="/images/20211016/order.png" alt="order" style="zoom:33%;"><p><code>order</code>决定flex items的排布顺序 (用的不多),可以设置为任意整数(正整数、负整数、0),值越小越排在前面。</p><pre class="line-numbers language-css"><code class="language-css"><span class="token selector"><span class="token class">.item</span> </span><span class="token punctuation">{</span> <span class="token property">order</span><span class="token punctuation">:</span> <span class="token number">5</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/* default is 0 */</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="3-2-align-self"><a href="#3-2-align-self" class="headerlink" title="3.2 align-self"></a>3.2 align-self</h3><img src="/images/20211016/align-self.png" alt="align-self" style="zoom:50%;"><p><code>align-self</code>相当于继承父元素的align-items属性,如果没有父元素,则等同于stretch。</p><pre class="line-numbers language-css"><code class="language-css"><span class="token selector"><span class="token class">.item</span> </span><span class="token punctuation">{</span> <span class="token property">align-self</span><span class="token punctuation">:</span> auto | flex-start | flex-end | center | baseline | stretch<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="3-3-flex-grow"><a href="#3-3-flex-grow" class="headerlink" title="3.3 flex-grow"></a>3.3 flex-grow</h3><img src="/images/20211016/flex-grow.png" alt="flex-grow" style="zoom:50%;"><p><code>flex-grow</code>属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。</p><pre class="line-numbers language-css"><code class="language-css"><span class="token selector"><span class="token class">.item</span> </span><span class="token punctuation">{</span> <span class="token property">flex-grow</span><span class="token punctuation">:</span> <span class="token number">4</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/* default 0 */</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>Negative numbers are invalid.</p><h3 id="3-4-flex-shrink"><a href="#3-4-flex-shrink" class="headerlink" title="3.4 flex-shrink"></a>3.4 flex-shrink</h3><p><code>flex-shrink</code> (shrink收缩)与flex-grow相似,一个扩展,一个伸缩 </p><pre class="line-numbers language-css"><code class="language-css"><span class="token selector"><span class="token class">.item</span> </span><span class="token punctuation">{</span> <span class="token property">flex-shrink</span><span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/* default 1 */</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>Negative numbers are invalid.</p><h3 id="3-5-flex-basis"><a href="#3-5-flex-basis" class="headerlink" title="3.5 flex-basis"></a>3.5 flex-basis</h3><p><code>flex-basis</code>用来设置flex items 在 main axis方向上的base size。默认为auto,可以设置具体的宽度数值。flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目(item)的本来大小。也可以设置跟width,height一样的宽高,表示item将占据固定的空间!</p><pre class="line-numbers language-css"><code class="language-css"><span class="token selector"><span class="token class">.item</span> </span><span class="token punctuation">{</span> <span class="token property">flex-basis</span><span class="token punctuation">:</span> | auto<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/* default auto */</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="3-6-flex"><a href="#3-6-flex" class="headerlink" title="3.6 flex"></a>3.6 flex</h3><p><code>flex</code> 是flex-grow || flex-shink || flex-basis的简写。可以指定1 2 3个值 依次按照上述顺序!默认值为 0 1 auto。</p><pre class="line-numbers language-css"><code class="language-css"><span class="token selector"><span class="token class">.item</span> </span><span class="token punctuation">{</span> <span class="token property">flex</span><span class="token punctuation">:</span> none | [ <<span class="token string">'flex-grow'</span>> <<span class="token string">'flex-shrink'</span>>? || <<span class="token string">'flex-basis'</span>> ]<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> css </tag>
</tags>
</entry>
<entry>
<title>Vue基础语法</title>
<link href="/2021/10/07/vue-ji-chu/"/>
<url>/2021/10/07/vue-ji-chu/</url>
<content type="html"><![CDATA[<h1 id="Vue基础语法"><a href="#Vue基础语法" class="headerlink" title="Vue基础语法"></a>Vue基础语法</h1><h2 id="1-前置知识"><a href="#1-前置知识" class="headerlink" title="1. 前置知识"></a>1. 前置知识</h2><h3 id="1-1-methods方法绑定this"><a href="#1-1-methods方法绑定this" class="headerlink" title="1.1 methods方法绑定this"></a>1.1 methods方法绑定this</h3><ol><li>method函数不能使用箭头函数<br>this != window;<br>而箭头函数中的this就是window,因为箭头函数中不绑定this,箭头函数会在自己的<strong>上层作用域</strong>中查找this,即script作用域中的this,所以就是window;</li><li>this指向什么内容<br>Vue源码对methods中的所有函数进行了遍历,并且通过bind绑定了this</li></ol><h3 id="1-2-VSCode创建代码片段"><a href="#1-2-VSCode创建代码片段" class="headerlink" title="1.2 VSCode创建代码片段"></a>1.2 VSCode创建代码片段</h3><ol><li>先在网站<a href="https://snippet-generator.app/%E4%B8%AD%E7%94%9F%E6%88%90%E4%BB%A3%E7%A0%81%E7%89%87%E6%AE%B5%E3%80%82">https://snippet-generator.app/中生成代码片段。</a></li><li>VSCode配置:<code>code -> 首选项 -> 用户配置 -> 选择html</code>,把代码片段粘进去即可。</li></ol><h2 id="2-模版语法"><a href="#2-模版语法" class="headerlink" title="2. 模版语法"></a>2. 模版语法</h2><pre class="line-numbers language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>template</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span>{{ value }}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>template</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>类似于上述代码中将DOM和底层组件实例的数据绑定在一起的语法,Vue大多数基于模版语法</p><h3 id="2-1-插值语法"><a href="#2-1-插值语法" class="headerlink" title="2.1 插值语法"></a>2.1 插值语法</h3><h4 id="2-1-1-mustache语法"><a href="#2-1-1-mustache语法" class="headerlink" title="2.1.1 mustache语法"></a>2.1.1 mustache语法</h4><p>把数据显示到模板(template)中,使用最多的语法是 <code>Mustache语法 (双大括号)</code> 的文本插值</p><pre class="line-numbers language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span><span class="token punctuation">></span></span>{{ value }}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>// value可以是数值、表达式但不能是赋值语句<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><h4 id="2-1-2-v-once"><a href="#2-1-2-v-once" class="headerlink" title="2.1.2 v-once"></a>2.1.2 v-once</h4><p>单例模式,只在加载的时候渲染一次,无视数据的更新</p><pre class="line-numbers language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>app<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>template</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>my-app<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span> <span class="token attr-name">v-once</span><span class="token punctuation">></span></span>{{value}}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>add<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>+1<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>template</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>../js/vue.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script language-javascript"></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span><span class="token punctuation">></span></span> const App = { template: "#my-app", data() { return { value: 0, }; }, methods: { add() { this.value++; }, }, }; Vue.createApp(App).mount("#app"); // value会+1, 但渲染出的value一直为0<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> Vue </tag>
</tags>
</entry>
<entry>
<title>Vue & Vue3</title>
<link href="/2021/09/27/vue-vue3/"/>
<url>/2021/09/27/vue-vue3/</url>
<content type="html"><![CDATA[<h1 id="Vue-amp-Vue3"><a href="#Vue-amp-Vue3" class="headerlink" title="Vue & Vue3"></a>Vue & Vue3</h1><p>**<a href="https://v3.cn.vuejs.org/">Vue</a>**一套用于构建用户界面的<code>渐进式框架</code>: 在项目中可以一点点来引入和使用Vue,不一定需要全部使用Vue来开发整个项目。</p><p><code>Vue3</code> <strong>更好的性能、更小的包体积、更好的TypeScript集成、更优秀的API设计</strong></p><p>变化</p><ol><li>源代码采用MonoRepo来进行管理,将许多项目的代码存储在同一个repository</li><li>源码使用TypeScript重写</li><li>使用Proxy进行数据劫持</li><li>删除了一些不必要的API,移除了$on.$off和$once以及内联模版等</li><li>编译优化:生成BlockTree、Slot编译优化、diff算法优化</li><li><span class="github-emoji"><span>⭐</span><img src="https://github.githubassets.com/images/icons/emoji/unicode/2b50.png?v8" aria-hidden="true" onerror="this.parent.classList.add('github-emoji-fallback')"></span> 新的API,Options API -> Composition API</li></ol><h2 id="如何使用Vue3"><a href="#如何使用Vue3" class="headerlink" title="如何使用Vue3"></a>如何使用Vue3</h2><h3 id="1-页面中通过CDN引入"><a href="#1-页面中通过CDN引入" class="headerlink" title="1. 页面中通过CDN引入"></a>1. 页面中通过CDN引入</h3><p><code>CDN</code> Content Delivery/Distribution Network(内容分发网络) 从最近的服务器里获取数据提高效率</p><p>HTML中引入</p><pre class="line-numbers language-html"><code class="language-html"><span class="token doctype"><!DOCTYPE html></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>html</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>en<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>head</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>meta</span> <span class="token attr-name">charset</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UTF-8<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>meta</span> <span class="token attr-name">http-equiv</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>X-UA-Compatible<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>IE<span class="token punctuation">=</span>edge<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>meta</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>viewport<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>width<span class="token punctuation">=</span>device-width, initial-scale<span class="token punctuation">=</span>1.0<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span>Document<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>head</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>body</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>app<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span> <span class="token comment" spellcheck="true"><!-- cdn引入 --></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>https://unpkg.com/vue@next<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script language-javascript"></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span><span class="token punctuation">></span></span><span class="token script language-javascript"> <span class="token keyword">const</span> obj <span class="token operator">=</span> <span class="token punctuation">{</span> template<span class="token punctuation">:</span> <span class="token string">"<h1>Hello Vue!</h1>"</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 调用cdn脚本中的函数 -> 根据一个对象创建app -> 挂载到#app</span> Vue<span class="token punctuation">.</span><span class="token function">createApp</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">mount</span><span class="token punctuation">(</span><span class="token string">"#app"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>html</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-下载Vue的JavaScript文件,手动引入"><a href="#2-下载Vue的JavaScript文件,手动引入" class="headerlink" title="2. 下载Vue的JavaScript文件,手动引入"></a>2. 下载Vue的JavaScript文件,手动引入</h3><p>类似于cdn,不过是将Vue脚本存储到本地再调用函数创建app对象</p><h3 id="3-通过npm安装使用"><a href="#3-通过npm安装使用" class="headerlink" title="3. 通过npm安装使用"></a>3. 通过npm安装使用</h3><p>Todo:后续补上</p><h3 id="4-通过Vue-CLI创建项目"><a href="#4-通过Vue-CLI创建项目" class="headerlink" title="4. 通过Vue CLI创建项目"></a>4. 通过Vue CLI创建项目</h3><p>Todo:后续补上</p><h2 id="声明式-amp-命令式"><a href="#声明式-amp-命令式" class="headerlink" title="声明式 & 命令式"></a>声明式 & 命令式</h2><p>两种不同的编程范式,声明式关注“What to do”,命令式关注“How to do”。</p><p>Vue属于声明式编程,框架封装了相关函数完成相应操作。声明数据,声明函数,声明模版,框架会自己将数据渲染到模版中。<br>命令式编程由指令流组成,面向过程进行相关指令操作</p><h2 id="MVC-amp-MVVM"><a href="#MVC-amp-MVVM" class="headerlink" title="MVC & MVVM"></a>MVC & MVVM</h2><p><code>MVC</code>和<code>MVVM</code>是不同的软件体系结构<br><code>MVC</code> Model-View-Controller,前期主流架构模式如IOS、前端<br>对于一个简单的HTML页面,有HTML代码、有JS代码。<br>其中HTML语义化代码就是<code>View</code>;JS代码是<code>Controller</code>;在两者之间起到渲染载体的变量是<code>Model</code>(与后端的定义有差异)</p><p><code>MVVM</code> Model-View-ViewModel,目前主流架构模式<br>Vue的设计受到<code>MVVM</code>的启发</p><p><img src="/images/20210927/Vue_MVVM.jpg" alt="Vue MVCC"></p><h2 id="data属性"><a href="#data属性" class="headerlink" title="data属性"></a>data属性</h2><p>data属性是传入一个函数,并且该函数需要返回一个对象<br>Vue2.x中,也可以传入一个对象;在Vue3.x中,必须传入一个函数,否则报错<br>data函数返回的对象会被Vue的响应式系统劫持</p><h2 id="methods属性"><a href="#methods属性" class="headerlink" title="methods属性"></a>methods属性</h2><p>methods属性是一个对象,通常会在这个对象中定义很多方法,且<strong>这些方法不能使用箭头函数</strong><br>这些方法可以被绑定到template模版中;在该方法中,可以使用this关键字直接访问data中返回对象的属性</p>]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> Vue </tag>
</tags>
</entry>
<entry>
<title>TypeScript入门</title>
<link href="/2021/09/27/typescript-ru-men/"/>
<url>/2021/09/27/typescript-ru-men/</url>
<content type="html"><![CDATA[<h1 id="TypeScript入门"><a href="#TypeScript入门" class="headerlink" title="TypeScript入门"></a>TypeScript入门</h1><h2 id="introduction"><a href="#introduction" class="headerlink" title="introduction"></a>introduction</h2><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/28ca61cc160c417c8497a00defdca5f0~tplv-k3u1fbpfcp-watermark.image" alt="TS与JS.png" style="zoom:67%;"><p><strong>TypeScript 是 JavaScript 的一个超集</strong>,主要提供了<strong>类型系统</strong>和<strong>对 ES6+ 的支持</strong>,它由 Microsoft 开发,代码开源于<a href="https://github.com/Microsoft/TypeScript">GitHub</a>上<br>始于JavaScript,归于JavaScript |强大的类型系统 |流行且适用于大型项目和基础库 |极大提高开发效率<br>现在主流的框架都推荐使用<code>typescript</code>如<code>Vue3</code>、<code>React</code>,其中<code>React</code>与<code>typescript</code>配合的更好一些(现在我也没感受出来,记个TODO)</p><h2 id="Installation"><a href="#Installation" class="headerlink" title="Installation"></a>Installation</h2><p>全局安装 & 查看版本</p><pre class="line-numbers language-sh"><code class="language-sh">npm install -g typescripttsc -v<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><h2 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h2><h3 id="VSCode-自动编译配置"><a href="#VSCode-自动编译配置" class="headerlink" title="VSCode 自动编译配置"></a>VSCode 自动编译配置</h3><pre><code>1). 生成配置文件tsconfig.json tsc --init2). 修改tsconfig.json配置 "outDir": "./js", //生成js的路径 "strict": false, // 是否启用强制类型检查3). 启动监视任务: 终端 -> 运行任务 -> 监视tsconfig.json之后修改ts文件保存后会重新编译生成js文件</code></pre><h3 id="chestnut"><a href="#chestnut" class="headerlink" title=":chestnut:"></a><span class="github-emoji"><span>🌰</span><img src="https://github.githubassets.com/images/icons/emoji/unicode/1f330.png?v8" aria-hidden="true" onerror="this.parent.classList.add('github-emoji-fallback')"></span></h3><pre class="line-numbers language-typescript"><code class="language-typescript"><span class="token comment" spellcheck="true">// 类 成员变量、构造函数</span><span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span> fullName<span class="token punctuation">:</span> <span class="token keyword">string</span> firstName<span class="token punctuation">:</span> <span class="token keyword">string</span> lastName<span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token keyword">constructor</span><span class="token punctuation">(</span>firstName<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span> lastName<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>firstName <span class="token operator">=</span> firstName <span class="token keyword">this</span><span class="token punctuation">.</span>lastName <span class="token operator">=</span> lastName <span class="token keyword">this</span><span class="token punctuation">.</span>fullName <span class="token operator">=</span> firstName <span class="token operator">+</span> <span class="token string">' '</span> <span class="token operator">+</span> lastName <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 接口 这里的interface 类似于 Golang中的interface 可以作为范型来使用</span><span class="token comment" spellcheck="true">// 用接口来描述拥有两个字段的对象</span><span class="token keyword">interface</span> <span class="token class-name">Person</span> <span class="token punctuation">{</span> firstName<span class="token punctuation">:</span> <span class="token keyword">string</span> lastName<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// User类中包含了Person接口中定义的两个字段,Person对User是适配的</span><span class="token keyword">function</span> <span class="token function">greeter</span><span class="token punctuation">(</span>person<span class="token punctuation">:</span> Person<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string">'Hello, '</span> <span class="token operator">+</span> person<span class="token punctuation">.</span>firstName <span class="token operator">+</span> <span class="token string">' '</span> <span class="token operator">+</span> person<span class="token punctuation">.</span>lastName<span class="token punctuation">}</span><span class="token keyword">let</span> user <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">User</span><span class="token punctuation">(</span><span class="token string">'Jay'</span><span class="token punctuation">,</span> <span class="token string">'Chou'</span><span class="token punctuation">)</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">greeter</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>编译生成的<code>javascript</code></p><pre class="line-numbers language-javascript"><code class="language-javascript"><span class="token comment" spellcheck="true">// 类 成员变量、构造函数</span><span class="token keyword">var</span> User <span class="token operator">=</span> <span class="token comment" spellcheck="true">/** @class */</span> <span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">function</span> <span class="token function">User</span><span class="token punctuation">(</span>firstName<span class="token punctuation">,</span> lastName<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>firstName <span class="token operator">=</span> firstName<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>lastName <span class="token operator">=</span> lastName<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>fullName <span class="token operator">=</span> firstName <span class="token operator">+</span> <span class="token string">' '</span> <span class="token operator">+</span> lastName<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> User<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// User类中包含了Person接口中定义的两个字段,Person对User是适配的</span><span class="token keyword">function</span> <span class="token function">greeter</span><span class="token punctuation">(</span>person<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string">'Hello, '</span> <span class="token operator">+</span> person<span class="token punctuation">.</span>firstName <span class="token operator">+</span> <span class="token string">' '</span> <span class="token operator">+</span> person<span class="token punctuation">.</span>lastName<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">var</span> user <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">User</span><span class="token punctuation">(</span><span class="token string">'Jay'</span><span class="token punctuation">,</span> <span class="token string">'Chou'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">greeter</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>关于TypeScript的数据类型以及常用语法后续再补充。。。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ol><li><a href="https://24kcs.github.io/vue3_study/">Vue3+TS 快速上手</a></li></ol>]]></content>
<categories>
<category> 前端 </category>
</categories>
<tags>
<tag> 前端 </tag>
<tag> TypeScript </tag>
</tags>
</entry>
<entry>
<title>暂时的释然</title>
<link href="/2021/09/26/zan-shi-de-shi-ran/"/>
<url>/2021/09/26/zan-shi-de-shi-ran/</url>
<content type="html"><![CDATA[<h1 id="暂时的释然"><a href="#暂时的释然" class="headerlink" title="暂时的释然"></a>暂时的释然</h1><p>来到学校有几天了,本来就很抵制研究生生活,宿舍、食堂、校园环境的落差让我更加失望。</p><p>今天上午和爸妈打了个电话,哭诉现在绝望的状态,也明里暗里怪他们当时要求我读研,说想退学balabala的话。说完就很后悔,决定读研的是我自己,选择东南软院的是我自己,找到现在不放实习的导师也是我自己。是我自己把牌打烂了,现在还怪他们,把压力给到他们,可能他们晚上又睡不着了吧。我很心酸,觉得对不起他们。<br>退学是不可能退学的,损失太大了,没有学历、没有落户资格、没有应届生身份、浪费一年时间。一定得撑到毕业。</p><p>晚上和霜玉打了电话,聊了一个多小时。跟她讲我现在的生活,以及我当下极度难过的状态。她跟我说了她的事情。</p><ol><li>就算自己做了错误的决定,就算自己一无所有,但是还有家人给自己兜底,还有那么多爱自己的人,自己必须要振作起来;</li><li>她还说了她的一些同学现在过的很惨,但也在努力奋斗,不要更比自己好的比较,想想那些过的比自己惨的多的人,自己现在已经很幸福了;</li><li>从现在开始一定不要沉浸在伤心的情绪里,这样又有什么用呢,自己要学会走出来,向前看;</li><li>从现在开始一定不能浑浑噩噩的活着了,要有自己的思想,学会管理自己的生活,做事情不要分心,做一件是一件,不要做着做着就玩手机去了,要提高效率,可以强制要求自己集中一段时间做一件事情,之后再去玩手机;</li><li>开始做ToDoList,活得更有目标一些,学习的时候要学会总结,把自己学到的东西记录下来,不要因为懒或者觉得学的太少太浅薄就不去记录,后续可以再补充,比如我最近在学Vue,但是决定自己没学到位,而且学的很杂,同时自己也很懒所以一直没有记录,但实际上学的没有效果,过段时间就会忘记,等于白学了,其实可以从如何创建一个vue项目入手来记录自己的学习成果,即使是一个很小的知识点,也可以放在一个分类专题里。</li><li>要开始锻炼身体了,把身体先保重好,以后时间还很长,用三年换一个健康的身体也很不错。<br>从明天开始就振作起来吧。</li></ol><p>聊完之后,我好像没那么难受了,现在把当下的感受记录下来,以后自闭了再回来看吧。</p>]]></content>
<categories>
<category> 随笔 </category>
</categories>
<tags>
<tag> 自言自语 </tag>
</tags>
</entry>
<entry>
<title>多希望这一年可以重来</title>
<link href="/2021/09/16/duo-xi-wang-zhe-yi-nian-ke-yi-tui-dao-chong-lai/"/>
<url>/2021/09/16/duo-xi-wang-zhe-yi-nian-ke-yi-tui-dao-chong-lai/</url>
<content type="html"><![CDATA[<h1 id="多希望这一年可以推倒重来"><a href="#多希望这一年可以推倒重来" class="headerlink" title="多希望这一年可以推倒重来"></a>多希望这一年可以推倒重来</h1><img src="/images/20210916/library_1.jpg" alt="library" style="zoom: 40%;"><p><strong>前言</strong><br>大二开始写博客,大三就断更了。翻看这一年的博客,好像回到了那个早上8点一个人到实验室,晚上12点一群人回宿舍的阶段。这些博客记录了那段不太美好但很温暖的回忆、记录了我在ACM队的心酸史、也记录了我自学的各种算法(虽然现在都忘光了)。<br>昨天我把之前的所有博客归了档,还换了个新主题,希望后面继续用博客的方式记录我的生活、学习,最重要的是记录当下的感受,无论好坏,未来再回头看都会身临其境。<br>第一篇就记录一下从大三断更到现在的经历吧~</p><img src="/images/20210916/library_2.jpg" alt="library" style="zoom: 35%;"><h2 id="一、饱受煎熬的2020"><a href="#一、饱受煎熬的2020" class="headerlink" title="一、饱受煎熬的2020"></a>一、饱受煎熬的2020</h2><p>2020年初的疫情打乱了我的生活节奏,居家抗疫、延迟开学、线上授课,大三的我在家里呆了7个多月,天天一个人在房间里从各种网站上收集着保研的信息,加了很多群,关注了很多公众号,了解了很多信息,人也越来越焦虑。可能在别人看来我保研很稳,但是我还是非常焦虑。可能这种焦虑来自于我对考研的抗拒。就这样一边上网课,一边收集信息,一边又准备考研,三线程并发执行。</p><p>六月份,一边准备线上期末考试一边海投简历。线上考试嘛,大家各凭本事,最后大家的成绩也是高的离谱。2020年所有高校的夏令营都在线上进行,没有了线下的舟车劳顿,内卷十分严重,厉害的同学手里五六个offer,菜🐔的话一个都没有。和大家一样,我也把珠三角的所有985投了个遍。</p><p>7月份,上大可以返校,我以考研的名义回到上大,实际上一方面是不想在家呆了,另一方面是想早点见到女朋友。回到学校后开始了图书馆、食堂、宿舍三点一线的生活。这段时间我收到了浙大软院、东南软院和南大软院的入营通知,一边准备夏令营面试,一边复习考研做两手准备。</p><p>8月份,我拿到了东南软院的offer,南大软院给了个安慰奖,浙大软院通过了机试,但是面试前几天,我心情压抑郁郁寡欢无心准备面试,于是放弃。但是听同校同学说参加面试的大多都拿到offer了<span class="github-emoji"><span>😢</span><img src="https://github.githubassets.com/images/icons/emoji/unicode/1f622.png?v8" aria-hidden="true" onerror="this.parent.classList.add('github-emoji-fallback')"></span></p><p>9月份,学校开学,图书馆终于有点人味了。推免政策我从月初等到月末,却迟迟不来。往年都是月初公布的名额以及积分规则一直拖到月底才公布。这段时间,我每天都在关注了教务处网站,隔半个小时刷一次,每次都是失望的结果。另一边,耗费我大量心血的软件著作权证书陆续发出,每天都在计算着何时能收到快递,能不能在交保研材料之前拿到证书,只为了能在综评上多加两分。月底,推免文件公布给了我当头一棒,我的优势项目全部被砍,小程序竞赛、校级竞赛、软件著作权全部取消。旧规则下我的综评能够排进前五,曾经还为此窃窃自喜而一度放弃复习考研。而现在,我之前的努力都白费了,几张费尽心思得到的证书都变成了废纸。好在最终我保研成功了。</p><p>10月份,推免正式录取推迟到了国庆之后,期间我收到了之前当作备胎的华师大发来的面试通知,可能是我当时嫌弃华师大牌子不太行于是放弃。10月12号,我正式被东南录取。历时三年的保研之路终于告一段落,录取的那一刻我很开心,还发了朋友圈炫耀。但是几分钟后,我又陷入了焦虑,焦虑似乎是我的常态。于我而言,伤心,难过,懊悔这一系列负面情绪占据了人生中的多数时间,而开心快乐的感觉总是很少出现,即使出现了也会很快消失,不知道这是心理问题还是生理问题。</p><p>11月份,无所事事的我开始找实习,运气比较好第一场面试就很成功,进入一家小公司<code>Graviti</code>写Python代码,做数据处理。实习三个月靠自己的劳动赚到人生第一桶金,不到两万块。这份工作让我比较满意的是同事们人特别好,对我也特别照顾,手把手教我怎么用git,怎么写一个工程项目。不满意的是吃饭问题,没有食堂,每天中午出去吃饭时间就要接近两个小时。年前,我辞退了实习,当时还有点伤感,刚和同事们熟悉起来就要离别。这段时间我对未来的职业发展也逐渐清晰起来,我想做后端。</p><img src="/images/20210916/graduation.jpg" alt="graduation" style="zoom: 50%;"><h2 id="二、昏昏噩噩的2021"><a href="#二、昏昏噩噩的2021" class="headerlink" title="二、昏昏噩噩的2021"></a>二、昏昏噩噩的2021</h2><p>寒假期间我又重新开始学后端,之前学过Java,也接触过Golang。听说字节跳动后端逐渐转向Go,而且用Go写项目确实很快很舒服。我希望之后能去字节工作,一方面字节福利确实很好,另一方面女朋友也在那里。于是我开始系统的学Go,语言语法、内部实现、运行原理、组建框架。</p><p>3月份,回到学校开始做毕业设计,导师给了个数据库相关的题目,她自己都不懂几乎零指导,于是我硬生生把数据库索引设计变成了个后端项目,用几个开源组件拼凑起来草草了事。这段时间一直在学习Golang相关的东西,很遗憾没有写博客记录下来。</p><p>研究生学校那边也开始选导师了,当时我一心想要找个放实习的导师,研究生期间锻炼一下开发能力,于是找了个方向偏工程的导师。看到研究生网站上导师方向,选了个做可行化软件开发的导师。现在看来,我当时还是不太懂,竟然看官网选导师,事实证明我要为当时的无知买单。就这样昏昏噩噩地确定了决定我研究生三年命运的人。</p><p>做毕业设计期间一直和女朋友呆在图书馆,一起去食堂吃饭,一起回宿舍,这些时光可能是我大学期间最快乐的时刻了。</p><p>转眼到了6月份,毕设顺利答辩完,毕业季到了。总的来说,大学四年是我学生时代最开心的阶段,在这里我感受到了温暖。很怀念新世纪、很怀念图书馆、很怀念东区、很怀念食堂、很怀念在这里努力过的日日夜夜,好想再来四年啊。6月底,我拿到了字节的暑期实习offer,可以不回家了,可以继续留在上海了。</p><p>7月初,毕业典礼结束。我把行李搬到了女朋友租的屋子里,开始了两个多月的暑期实习。在字节的两个月过的还是很舒服的,免费三餐,好吃的下午茶以及对我特别好的mentor。我参与亚瑟设计师平台的重构工作,将Python项目用Golang重写,负责了需求流转lark通知模块前置审核模块的需求开发,我做的需求相对比较简单没有遇到什么大的困难,可能初期对项目不熟悉做需求比较难。实习的那段时间正好碰上亚瑟交接,人员变动需求减少,后期我几乎没什么活,负责老项目的oncall(<strong>这段留着以后面试用</strong>)</p><p>很快到了九月份,实习结束,我开始参加研究生的组会。四年前对于上海大学我充满期待,而对于研究生的生活我十分抗拒。交着学硕的双倍学费却不放实习、导师组会上对学长学姐的斥责、给导师免费打工还要挨骂。我对学术毫无兴趣,未来满足家人的期待选择读研,现在我也没办法克服消极的情绪。研一苏州校区上一年课远离导师可以接受,研二暑假可以实习,研三上半年做毕业设计下半年实习毕业,真正让我觉得抵触的是研二去南京待一年,想开点的话浪费就浪费一年,企业实习的话研三毕业论文会比较痛苦,这一年至少让后面的毕业论文比较轻松。</p><img src="/images/20210916/bytedance.jpg" alt="graduation" style="zoom: 10%;"><h2 id="三、一片漆黑的未来"><a href="#三、一片漆黑的未来" class="headerlink" title="三、一片漆黑的未来"></a>三、一片漆黑的未来</h2><p>日子在过,没有办法只能接受,慢慢熬也可以熬完三年。这三年好好学技术,自我沉淀,多看书多写博客多总结。好好规划每一天,抓住时间变得很强。后面要做一个总体的规划,可以学习一下字节的OKR,让这三年过的有尽可能有意义一点。</p>]]></content>
<categories>
<category> 随笔 </category>
</categories>
<tags>
<tag> 自言自语 </tag>
</tags>
</entry>
</search>