-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
893 lines (804 loc) · 201 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
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Nacos集群配置及Nginx反向代理</title>
<url>/2020/10/20/Nacos%E9%9B%86%E7%BE%A4%E9%85%8D%E7%BD%AE%E5%8F%8ANginx%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/</url>
<content><![CDATA[<p>Nacos集群配置以及Nginx反向代理的一些配置</p>
<a id="more"></a>
<h2 id="安装Nacos"><a href="#安装Nacos" class="headerlink" title="安装Nacos"></a>安装Nacos</h2><p><strong>前提是需要一个虚拟机,本文基于CentOS 7</strong></p>
<ol>
<li><p>下载Nacos</p>
<p> <a href="https://github.com/alibaba/nacos/releases/tag/1.3.2">下载地址</a></p>
</li>
<li><p>配置Nacos</p>
<ul>
<li><p>创建mynacos文件夹;将tar包解压到mynacos文件夹中</p>
</li>
<li><p>配置cluster.conf</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cp</span> cluster.conf.example cluster.conf</span><br><span class="line">vim cluster.conf</span><br></pre></td></tr></table></figure>
</li>
<li><p>添加以下内容(根据自己的<strong>主机ip</strong>来填</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="number">192.168</span>.<span class="number">81.129</span>:<span class="number">3333</span></span><br><span class="line"><span class="number">192.168</span>.<span class="number">81.129</span>:<span class="number">4444</span></span><br><span class="line"><span class="number">192.168</span>.<span class="number">81.129</span>:<span class="number">5555</span></span><br></pre></td></tr></table></figure>
<!-- more --></li>
<li><p>修改数据库文件,<strong>vim application.properties</strong></p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">spring.datasource.platform=mysql</span><br><span class="line">db.num=<span class="number">1</span></span><br><span class="line">db.url.<span class="number">0</span>=jdbc:mysql://<span class="number">127.0</span>.<span class="number">0.1</span>:<span class="number">3306</span>/nacos_config?characterEncoding=utf8&connectTimeout=<span class="number">1000</span>&socketTimeout=<span class="number">3000</span>&autoReconnect=true</span><br><span class="line">db.user=root</span><br><span class="line">db.password=<span class="number">6</span>yhn^YHN</span><br></pre></td></tr></table></figure>
</li>
<li><p>关闭防火墙或者开放端口(为了省事,我关闭了和防火墙)</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">systemctl stop firewalld</span><br></pre></td></tr></table></figure>
</li>
</ul>
</li>
</ol>
<ol start="3">
<li><p>启动Nacos</p>
<p> <strong>TIPS:因为是一台机器,所以以多个实例来设置集群,以端口区分,需要启动三个nacos;如果是多台机器那就正常一个机器起一个nacos即可</strong></p>
<ul>
<li><p>复制<strong>startup.sh</strong></p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cp</span> startup.sh.bak startup<span class="literal">-3333</span>.sh</span><br><span class="line"><span class="built_in">cp</span> startup.sh.bak startup<span class="literal">-4444</span>.sh</span><br><span class="line"><span class="built_in">cp</span> startup.sh.bak startup<span class="literal">-5555</span>.sh</span><br></pre></td></tr></table></figure>
</li>
<li><p>修改<strong>vim startup-3333.sh</strong>(一个示例,4444和5555 都按照此修改),增加-Dserver.port=3333启动时指定端口号</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="comment"># start</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"<span class="variable">$JAVA</span> <span class="variable">$</span>{JAVA_OPT}"</span> > <span class="variable">$</span>{BASE_DIR}/logs/start.out <span class="number">2</span>>&<span class="number">1</span> &</span><br><span class="line">nohup <span class="variable">$JAVA</span> <span class="literal">-Dserver</span>.port=<span class="number">3333</span> <span class="variable">$</span>{JAVA_OPT} nacos.nacos >> <span class="variable">$</span>{BASE_DIR}/logs/start.out <span class="number">2</span>>&<span class="number">1</span> &</span><br><span class="line"><span class="built_in">echo</span> <span class="string">"nacos is starting,you can check the <span class="variable">$</span>{BASE_DIR}/logs/start.out"</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>启动nacos</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">./startup<span class="literal">-3333</span>.sh</span><br><span class="line">./startup<span class="literal">-4444</span>.sh</span><br><span class="line">./startup<span class="literal">-5555</span>.sh</span><br></pre></td></tr></table></figure>
</li>
</ul>
</li>
</ol>
<h2 id="安装Nginx"><a href="#安装Nginx" class="headerlink" title="安装Nginx"></a>安装Nginx</h2><p>贴个安装nginx教程,并且配置stream负载均衡 <a href="http://xiaohost.com/2754.html">转向网址</a></p>
<ol>
<li><p>配置Nginx</p>
<p> 由于我是yum安装的Nginx 配置文件 <strong>vim /etc/nginx/nginx.confg</strong></p>
<ul>
<li><p>设置upstream cluster</p>
</li>
<li><p>设置location根路径。<strong>proxy_pass <a href="http://cluster/">http://cluster</a>;</strong></p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">#gzip on;</span><br><span class="line">upstream cluster{</span><br><span class="line"> server 192.168.81.129:3333;</span><br><span class="line"> server 192.168.81.129:4444;</span><br><span class="line"> server 192.168.81.129:5555;</span><br><span class="line">}</span><br><span class="line">server {</span><br><span class="line"> listen 1111;</span><br><span class="line"> server_name localhost;</span><br><span class="line"></span><br><span class="line"> #charset koi8-r;</span><br><span class="line"></span><br><span class="line"> #access_log logs/host.access.log main;</span><br><span class="line"></span><br><span class="line"> location / {</span><br><span class="line"> root html;</span><br><span class="line"> index index.html index.htm;</span><br><span class="line"> proxy_pass http://cluster;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
</li>
</ul>
</li>
<li><p>启动Nginx</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">systemctl start nginx.service</span><br></pre></td></tr></table></figure></li>
<li><p>登录查看<a href="http://192.168.81.129:1111/nacos/#/login">http://192.168.81.129:1111/nacos/#/login</a></p>
</li>
</ol>
]]></content>
<categories>
<category>Java</category>
<category>Nacos集群配置</category>
</categories>
<tags>
<tag>Nacos集群配置</tag>
<tag>Nginx反向代理</tag>
</tags>
</entry>
<entry>
<title>Ubuntu 自动挂载SD卡</title>
<url>/2020/12/27/Ubuntu%20%E8%87%AA%E5%8A%A8%E6%8C%82%E8%BD%BDSD%E5%8D%A1/</url>
<content><![CDATA[<p>最近开发过程中遇见了一个问题,Ubuntu 16.04 自动挂载SD卡报错,<code>mounted filesystem with ordered data mode. Opts: (null)</code>以此记录一下</p>
<a id="more"></a>
<ol>
<li><p>查看分区</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"> </span><br><span class="line">root@ubuntu16:~<span class="comment"># fdisk -l</span></span><br><span class="line"></span><br><span class="line">中间省略无关东西;主要是为了挂载 /dev/mmcblk0p1</span><br><span class="line"></span><br><span class="line">Disk /dev/mmcblk0: <span class="number">29.7</span> GiB, <span class="number">31914983424</span> bytes, <span class="number">62333952</span> sectors</span><br><span class="line">Units: sectors of <span class="number">1</span> * <span class="number">512</span> = <span class="number">512</span> bytes</span><br><span class="line">Sector size (logical/physical): <span class="number">512</span> bytes / <span class="number">512</span> bytes</span><br><span class="line">I/O size (minimum/optimal): <span class="number">512</span> bytes / <span class="number">512</span> bytes</span><br><span class="line">Disklabel <span class="built_in">type</span>: dos</span><br><span class="line">Disk identifier: <span class="number">0</span>x00000000</span><br><span class="line"></span><br><span class="line">Device Boot <span class="built_in">Start</span> <span class="keyword">End</span> Sectors Size Id <span class="built_in">Type</span></span><br><span class="line">/dev/mmcblk0p1 <span class="number">2048</span> <span class="number">62333951</span> <span class="number">62331904</span> <span class="number">29.7</span>G <span class="number">83</span> Linux</span><br><span class="line">root@ubuntu16:~<span class="comment"># </span></span><br></pre></td></tr></table></figure>
<p> sd卡文件格式的原先是fat32位,由于内核是裁剪版,没有fsck.vfat 修复(如果有,可以直接修复),直接将其格式化成ext4 <code>记得备份里面内容</code></p>
</li>
<li><p>手动挂载SD卡( 成功 )</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mount</span> /dev/mmcblk0p1 /mnt/sd/</span><br></pre></td></tr></table></figure>
<p> <strong>df -h</strong>查看挂载成功</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">root@ubuntu16:~<span class="comment"># df -h</span></span><br><span class="line">Filesystem Size Used Avail Use% Mounted on</span><br><span class="line">ubi0:rootfs <span class="number">437</span>M <span class="number">384</span>M <span class="number">54</span>M <span class="number">88</span>% /</span><br><span class="line">devtmpfs <span class="number">121</span>M <span class="number">0</span> <span class="number">121</span>M <span class="number">0</span>% /dev</span><br><span class="line">tmpfs <span class="number">122</span>M <span class="number">0</span> <span class="number">122</span>M <span class="number">0</span>% /dev/shm</span><br><span class="line">tmpfs <span class="number">122</span>M <span class="number">1.7</span>M <span class="number">120</span>M <span class="number">2</span>% /run</span><br><span class="line">tmpfs <span class="number">5.0</span>M <span class="number">4.0</span>K <span class="number">5.0</span>M <span class="number">1</span>% /run/lock</span><br><span class="line">tmpfs <span class="number">122</span>M <span class="number">0</span> <span class="number">122</span>M <span class="number">0</span>% /sys/fs/cgroup</span><br><span class="line">/dev/mmcblk0p1 <span class="number">30</span>G <span class="number">153</span>M <span class="number">28</span>G <span class="number">1</span>% /mnt/sd</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p> 手动挂载SD卡可以成功,但是自动挂载却失败了</p>
</li>
</ol>
<h3 id="以下自动挂载过程"><a href="#以下自动挂载过程" class="headerlink" title="以下自动挂载过程"></a>以下自动挂载过程</h3><ol>
<li><p>修改<code>/etc/fstab</code>文件( 失败 )</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">root@ubuntu16:~<span class="comment"># vim /etc/fstab </span></span><br><span class="line"><span class="comment"># <file system> <dir> <type> <options> <dump> <pass></span></span><br><span class="line">/dev/mmcblk0p2 / ext4 defaults,noatime,errors=remount<span class="literal">-ro</span> <span class="number">0</span> <span class="number">1</span></span><br><span class="line">/dev/mmcblk0p1 /mnt/sd ext4 defaults <span class="number">0</span> <span class="number">0</span></span><br><span class="line">~ </span><br></pre></td></tr></table></figure>
<p> 查看dmesg日志信息,报错如下(裁剪版,连syslog也没有)</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">[<span class="number">24987.953468</span>] EXT4<span class="literal">-fs</span> (mmcblk0p1): mounted filesystem with ordered <span class="keyword">data</span> mode. Opts: (null)</span><br><span class="line">[<span class="number">25051.864722</span>] EXT4<span class="literal">-fs</span> (mmcblk0p1): mounted filesystem with ordered <span class="keyword">data</span> mode. Opts: (null)</span><br><span class="line">[<span class="number">25327.264935</span>] EXT4<span class="literal">-fs</span> (mmcblk0p1): mounted filesystem with ordered <span class="keyword">data</span> mode. Opts: (null)</span><br><span class="line">[<span class="number">25392.234106</span>] EXT4<span class="literal">-fs</span> (mmcblk0p1): mounted filesystem with ordered <span class="keyword">data</span> mode. Opts: (null)</span><br><span class="line">[<span class="number">171378.249554</span>] EXT4<span class="literal">-fs</span> (mmcblk0p1): mounted filesystem with ordered <span class="keyword">data</span> mode. Opts: (null)</span><br><span class="line"></span><br></pre></td></tr></table></figure>
</li>
<li><p>修改<code>/etc/rc.local</code>文件( 失败 )<br> 加入以下内容</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mount</span> /dev/mmcblk0p1 /mnt/sd</span><br></pre></td></tr></table></figure>
<p> 重启之后依然报上述错误</p>
</li>
<li><p>手动指定挂在文件类型( 成功 )</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mount</span> <span class="literal">-t</span> ext4 /dev/mmcblk0p1 /mnt/sd</span><br></pre></td></tr></table></figure>
<p> 重启机器之后,<code>df -h</code>查看挂载成功</p>
</li>
</ol>
<h3 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h3><ol>
<li>文件系统有问题,可以使用<code>fsck.XX</code>参数去修复</li>
<li>期间也尝试了在<code>/etc/fstab</code>使用UUID 来指定,依然报错</li>
<li>手动指定挂在文件类型<code>mount -t ext4 /dev/mmcblk0p1 /mnt/sd</code></li>
</ol>
]]></content>
<categories>
<category>Ubuntu</category>
</categories>
<tags>
<tag>Ubuntu挂载SD卡</tag>
</tags>
</entry>
<entry>
<title>fasterxml中string字符串转对象json格式单引号错误</title>
<url>/2021/01/28/fasterxml%E4%B8%ADstring%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E5%AF%B9%E8%B1%A1json%E6%A0%BC%E5%BC%8F%E5%8D%95%E5%BC%95%E5%8F%B7%E9%94%99%E8%AF%AF/</url>
<content><![CDATA[<p>在处理fasterxml中string字符串转对象json格式,标准的json都是双引号 (” “);由于数据是单引号(‘ ‘)格式。导致了如下报错,特此记录</p>
<p><strong>com.fasterxml.jackson.core.JsonParseException: Unexpected character (‘s’ (code 115)): was expecting double-quote to start field name</strong></p>
<a id="more"></a>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">com.fasterxml.jackson.databind.ObjectMapper </span><br><span class="line">mapper = new ObjectMapper();</span><br><span class="line"></span><br><span class="line">//增加这行配置</span><br><span class="line">//允许使用单引号,默认是false </span><br><span class="line">mapper.configure(Feature.ALLOW_SINGLE_QUOTES, true);</span><br><span class="line"></span><br><span class="line">mapper.readValue(payload, bean.class);</span><br></pre></td></tr></table></figure>
<p>除了<strong>ALLOW_UNQUOTED_FIELD_NAMES,ALLOW_SINGLE_QUOTES</strong>还有其它的设置,有用到试试。</p>
]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>踩坑</tag>
</tags>
</entry>
<entry>
<title>flask登陆之后跳转回原先的页面</title>
<url>/2020/10/20/flask%E7%99%BB%E9%99%86%E4%B9%8B%E5%90%8E%E8%B7%B3%E8%BD%AC%E5%9B%9E%E5%8E%9F%E5%85%88%E7%9A%84%E9%A1%B5%E9%9D%A2/</url>
<content><![CDATA[<p>在使用Flask_login,使用装饰器@login_required时,如果没有登录,会跳转登录页面登陆之后不会返回原来的页面</p>
<a id="more"></a>
<ul>
<li><p>在登录函数中,redirect更换以下代码</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">next_page_url = request.args.get(<span class="string">'next'</span>)</span><br><span class="line"> <span class="comment"># 如果 next_page_url 为空,直接返回首页</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> next_page_url <span class="keyword">or</span> url_parse(next_page_url).netloc != <span class="string">''</span>:</span><br><span class="line"> <span class="keyword">return</span> redirect(next_page_url(<span class="string">'首页'</span>))</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">return</span> redirect(next_page_url)</span><br></pre></td></tr></table></figure>
</li>
<li><p>html中也需要修改,在url_for中增加<code>next=request.args.next</code></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><form <span class="string">" method="</span>POST<span class="string">" action="</span>{{url_for(<span class="string">'auth.login'</span>,<span class="built_in">next</span>=request.args.<span class="built_in">next</span>)}}<span class="string">"></span></span><br></pre></td></tr></table></figure>
</li>
</ul>
]]></content>
<categories>
<category>Python</category>
<category>Flask</category>
</categories>
<tags>
<tag>踩坑</tag>
</tags>
</entry>
<entry>
<title>lambda表达式对对象集合进行分组并求和</title>
<url>/2020/12/14/lambda%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%88%86%E7%BB%84%E5%B9%B6%E6%B1%82%E5%92%8C-md/</url>
<content><![CDATA[<p>最近业务需求,要求对集合按照多个属性分组,并且对某一个属性求和,利用lambda表达式进行分组求和;示例如下</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">list.parallelStream()</span><br><span class="line"> .collect(Collectors.groupingBy(o -> (o.getName() + o.getAge()), Collectors.toList()))</span><br><span class="line"> .forEach((id, transfer) -> transfer.stream().reduce((a, b) -> new StudentDemo(a.getName(), a.getAge(), a.getScore() + b.getScore())).ifPresent(studentDemoList::add));</span><br></pre></td></tr></table></figure>
<a id="more"></a>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">package com.yang.springboot.lamda.groupBy;</span><br><span class="line"></span><br><span class="line">import java.util.ArrayList;</span><br><span class="line">import java.util.List;</span><br><span class="line">import java.util.stream.Collectors;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * @author Yang Hao</span><br><span class="line"> * @date 2020-11-19 19:41</span><br><span class="line"> */</span><br><span class="line">public class Demo {</span><br><span class="line"></span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * lambda表达式: 根据姓名和年龄分组之后再求和总分</span><br><span class="line"> *</span><br><span class="line"> * [ [</span><br><span class="line"> * StudentDemo(name=张三, age=20, score=12), StudentDemo(name=张三, age=20, score=92),</span><br><span class="line"> * StudentDemo(name=张三, age=20, score=80), =========> StudentDemo(name=王五, age=20, score=33),</span><br><span class="line"> * StudentDemo(name=王五, age=20, score=33), =========> StudentDemo(name=李四, age=29, score=90),</span><br><span class="line"> * StudentDemo(name=李四, age=18, score=50), StudentDemo(name=李四, age=18, score=50)</span><br><span class="line"> * StudentDemo(name=李四, age=29, score=90) ]</span><br><span class="line"> * ]</span><br><span class="line"> */</span><br><span class="line"></span><br><span class="line"> List<StudentDemo> list = new ArrayList<>();</span><br><span class="line"> list.add(new StudentDemo("张三", "20", 12));</span><br><span class="line"> list.add(new StudentDemo("张三", "20", 80));</span><br><span class="line"> list.add(new StudentDemo("王五", "20", 33));</span><br><span class="line"> list.add(new StudentDemo("李四", "18", 50));</span><br><span class="line"> list.add(new StudentDemo("李四", "29", 90));</span><br><span class="line"> System.out.println("分组求和之前 " + list);</span><br><span class="line"></span><br><span class="line"> List<StudentDemo> studentDemoList = new ArrayList<>();</span><br><span class="line"> list.parallelStream()</span><br><span class="line"> .collect(Collectors.groupingBy(o -> (o.getName() + o.getAge()), Collectors.toList()))</span><br><span class="line"> .forEach((id, transfer) -> transfer.stream().reduce((a, b) -> new StudentDemo(a.getName(), a.getAge(), a.getScore() + b.getScore())).ifPresent(studentDemoList::add));</span><br><span class="line"> System.out.println("分组求和之后 " + studentDemoList);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category>Java</category>
<category>lambda</category>
</categories>
</entry>
<entry>
<title>ConcurrentHashMap 源码分析</title>
<url>/2021/03/10/ConcurrentHashMap%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/</url>
<content><![CDATA[<p>这一篇文章是关于HashMap 源码分析 <a href="/2021/02/26/HashMap%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/" title="HashMap 源码分析">HashMap 源码分析</a></p>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><h3 id="为什么要用ConcurrentHashMap"><a href="#为什么要用ConcurrentHashMap" class="headerlink" title="为什么要用ConcurrentHashMap?"></a>为什么要用ConcurrentHashMap?</h3><p><strong>1. 线程不安全的HashMap</strong></p>
<p>在多线程环境下,使用HashMap的put操作会引起死循环,原因是多线程会导致HashMap的Entry链表形成环形数据结构,导致Entry的next节点永远不为空,就会产生死循环获取Entry</p>
<a id="more"></a>
<p><strong>2. 效率低下的HashTable</strong></p>
<p>HashTable容器使用sychronized来保证线程安全,采取锁住整个表结构来达到同步目的,在线程竞争激烈的情况下,当一个线程访问HashTable的同步方法,其他线程也访问同步方法时,会进入阻塞或轮询状态;如线程1使用put方法时,其他线程既不能使用put方法,也不能使用get方法,效率非常低下。</p>
<p><strong>3. ConcurrentHashMap的锁分段技术可提升并发访问效率</strong></p>
<p>首先将数据分成一段一段地存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。</p>
<h2 id="JDK-1-7"><a href="#JDK-1-7" class="headerlink" title="JDK 1.7"></a>JDK 1.7</h2><h3 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h3><p><img src="ConcurrentHashMap-1.7.jpg"></p>
<p>Java 7 中 <code>ConcurrentHashMap</code> 的存储结构如上图, <code>ConcurrnetHashMap</code> 由很多个 <code>Segment</code> 组合,而每一个 <code>Segment</code> 是一个类似于 <code>HashMap</code> 的结构,所以每一个 <code>HashMap</code> 的内部可以进行扩容。但是 <code>Segment</code> 的个数一旦<strong>初始化就不能改变</strong>,默认 <code>Segment</code> 的个数是 16 个,你也可以认为 <code>ConcurrentHashMap</code> 默认支持最多 16 个线程并发。</p>
<h3 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h3><p>通过 <code>ConcurrentHashMap</code> 的无参构造探寻 <code>ConcurrentHashMap</code> 的初始化流程。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Creates a new, empty map with a default initial capacity (16),</span></span><br><span class="line"><span class="comment"> * load factor (0.75) and concurrencyLevel (16).</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ConcurrentHashMap</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>无参构造中调用了有参构造,传入了三个参数的默认值;</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 默认初始化容量</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DEFAULT_INITIAL_CAPACITY = <span class="number">16</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 默认负载因子</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">float</span> DEFAULT_LOAD_FACTOR = <span class="number">0.75f</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 默认并发级别</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DEFAULT_CONCURRENCY_LEVEL = <span class="number">16</span>;</span><br></pre></td></tr></table></figure>
<p>接着看下这个有参构造函数的内部实现逻辑;</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ConcurrentHashMap</span><span class="params">(<span class="keyword">int</span> initialCapacity,<span class="keyword">float</span> loadFactor, <span class="keyword">int</span> concurrencyLevel)</span> </span>{</span><br><span class="line"> <span class="comment">// 参数校验</span></span><br><span class="line"> <span class="keyword">if</span> (!(loadFactor > <span class="number">0</span>) || initialCapacity < <span class="number">0</span> || concurrencyLevel <= <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException();</span><br><span class="line"> <span class="comment">// 校验并发级别大小,大于 1<<16,重置为 65536</span></span><br><span class="line"> <span class="keyword">if</span> (concurrencyLevel > MAX_SEGMENTS)</span><br><span class="line"> concurrencyLevel = MAX_SEGMENTS;</span><br><span class="line"> <span class="comment">// Find power-of-two sizes best matching arguments</span></span><br><span class="line"> <span class="comment">// 2的多少次方</span></span><br><span class="line"> <span class="keyword">int</span> sshift = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> ssize = <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 这个循环可以找到 concurrencyLevel 之上最近的 2的次方值</span></span><br><span class="line"> <span class="keyword">while</span> (ssize < concurrencyLevel) {</span><br><span class="line"> ++sshift;</span><br><span class="line"> ssize <<= <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 记录段偏移量</span></span><br><span class="line"> <span class="keyword">this</span>.segmentShift = <span class="number">32</span> - sshift;</span><br><span class="line"> <span class="comment">// 记录段掩码</span></span><br><span class="line"> <span class="keyword">this</span>.segmentMask = ssize - <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 设置容量</span></span><br><span class="line"> <span class="keyword">if</span> (initialCapacity > MAXIMUM_CAPACITY)</span><br><span class="line"> initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line"> <span class="comment">// c = 容量 / ssize ,默认 16 / 16 = 1,这里是计算每个 Segment 中的类似于 HashMap 的容量</span></span><br><span class="line"> <span class="keyword">int</span> c = initialCapacity / ssize;</span><br><span class="line"> <span class="keyword">if</span> (c * ssize < initialCapacity)</span><br><span class="line"> ++c;</span><br><span class="line"> <span class="keyword">int</span> cap = MIN_SEGMENT_TABLE_CAPACITY;</span><br><span class="line"> <span class="comment">//Segment 中的类似于 HashMap 的容量至少是2或者2的倍数</span></span><br><span class="line"> <span class="keyword">while</span> (cap < c)</span><br><span class="line"> cap <<= <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// create segments and segments[0]</span></span><br><span class="line"> <span class="comment">// 创建 Segment 数组,设置 segments[0]</span></span><br><span class="line"> Segment<K,V> s0 = <span class="keyword">new</span> Segment<K,V>(loadFactor, (<span class="keyword">int</span>)(cap * loadFactor),</span><br><span class="line"> (HashEntry<K,V>[])<span class="keyword">new</span> HashEntry[cap]);</span><br><span class="line"> Segment<K,V>[] ss = (Segment<K,V>[])<span class="keyword">new</span> Segment[ssize];</span><br><span class="line"> UNSAFE.putOrderedObject(ss, SBASE, s0); <span class="comment">// ordered write of segments[0]</span></span><br><span class="line"> <span class="keyword">this</span>.segments = ss;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>总结:在 Java 7 中 <code>ConcurrnetHashMap</code> 的初始化逻辑。</strong></p>
<ul>
<li>必要的参数校验。</li>
<li>校验并发级别 <code>concurrencyLevel</code> 大小,如果大于最大值,重置为最大值。无惨构造<strong>默认值是 16</strong></li>
<li>寻找并发级别 <code>concurrencyLevel</code> 之上最近的 <strong>2 的幂次方</strong>值,作为初始化容量大小,<strong>默认是 16</strong>。</li>
<li>记录 <code>segmentShift</code> 偏移量,这个值为【容量 = 2 的N次方】中的 N,在后面 Put 时计算位置时会用到。<strong>默认是 32 - sshift = 28</strong>.</li>
<li>记录 <code>segmentMask</code>,默认是 ssize - 1 = 16 -1 = 15.</li>
<li><strong>初始化 <code>segments[0]</code>**,</strong>默认大小为 2<strong>,</strong>负载因子 0.75<strong>,</strong>扩容阀值是 2*0.75=1.5**,插入第二个值时才会进行扩容。</li>
</ul>
<h3 id="put方法"><a href="#put方法" class="headerlink" title="put方法"></a>put方法</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>{</span><br><span class="line"> Segment<K,V> s;</span><br><span class="line"> <span class="keyword">if</span> (value == <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line"> <span class="keyword">int</span> hash = hash(key);</span><br><span class="line"> <span class="comment">// hash 值无符号右移 28位(初始化时获得),然后与 segmentMask=15 做与运算</span></span><br><span class="line"> <span class="comment">// 其实也就是把高4位与segmentMask(1111)做与运算</span></span><br><span class="line"> <span class="keyword">int</span> j = (hash >>> segmentShift) & segmentMask;</span><br><span class="line"> <span class="keyword">if</span> ((s = (Segment<K,V>)UNSAFE.getObject <span class="comment">// nonvolatile; recheck</span></span><br><span class="line"> (segments, (j << SSHIFT) + SBASE)) == <span class="keyword">null</span>) <span class="comment">// in ensureSegment</span></span><br><span class="line"> <span class="comment">// 如果查找到的 Segment 为空,初始化</span></span><br><span class="line"> s = ensureSegment(j);</span><br><span class="line"> <span class="keyword">return</span> s.put(key, hash, value, <span class="keyword">false</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> Segment<K,V> <span class="title">ensureSegment</span><span class="params">(<span class="keyword">int</span> k)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Segment<K,V>[] ss = <span class="keyword">this</span>.segments;</span><br><span class="line"> <span class="keyword">long</span> u = (k << SSHIFT) + SBASE; <span class="comment">// raw offset</span></span><br><span class="line"> Segment<K,V> seg;</span><br><span class="line"> <span class="comment">// 判断 u 位置的 Segment 是否为null</span></span><br><span class="line"> <span class="keyword">if</span> ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == <span class="keyword">null</span>) {</span><br><span class="line"> Segment<K,V> proto = ss[<span class="number">0</span>]; <span class="comment">// use segment 0 as prototype</span></span><br><span class="line"> <span class="comment">// 获取0号 segment 里的 HashEntry<K,V> 初始化长度</span></span><br><span class="line"> <span class="keyword">int</span> cap = proto.table.length;</span><br><span class="line"> <span class="comment">// 获取0号 segment 里的 hash 表里的扩容负载因子,所有的 segment 的 loadFactor 是相同的</span></span><br><span class="line"> <span class="keyword">float</span> lf = proto.loadFactor;</span><br><span class="line"> <span class="comment">// 计算扩容阀值</span></span><br><span class="line"> <span class="keyword">int</span> threshold = (<span class="keyword">int</span>)(cap * lf);</span><br><span class="line"> <span class="comment">// 创建一个 cap 容量的 HashEntry 数组</span></span><br><span class="line"> HashEntry<K,V>[] tab = (HashEntry<K,V>[])<span class="keyword">new</span> HashEntry[cap];</span><br><span class="line"> <span class="keyword">if</span> ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == <span class="keyword">null</span>) { <span class="comment">// recheck</span></span><br><span class="line"> <span class="comment">// 再次检查 u 位置的 Segment 是否为null,因为这时可能有其他线程进行了操作</span></span><br><span class="line"> Segment<K,V> s = <span class="keyword">new</span> Segment<K,V>(lf, threshold, tab);</span><br><span class="line"> <span class="comment">// 自旋检查 u 位置的 Segment 是否为null</span></span><br><span class="line"> <span class="keyword">while</span> ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))</span><br><span class="line"> == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 使用CAS 赋值,只会成功一次</span></span><br><span class="line"> <span class="keyword">if</span> (UNSAFE.compareAndSwapObject(ss, u, <span class="keyword">null</span>, seg = s))</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> seg;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>上面的源码分析了 ConcurrentHashMap 在 put 一个数据时的处理流程;</p>
<ul>
<li><p>计算要 put 的 key 的位置,获取指定位置的 <code>Segment。</code></p>
</li>
<li><p>如果指定位置的 <code>Segment</code> 为空,则初始化这个 <code>Segment</code>.</p>
<p> <strong>初始化 Segment 流程:</strong></p>
<ul>
<li>检查计算得到的位置的 <code>Segment</code> 是否为null.</li>
<li>为 null 继续初始化,使用 <code>Segment[0]</code> 的容量和负载因子创建一个 HashEntry 数组。</li>
<li>再次检查计算得到的指定位置的 <code>Segment</code> 是否为null.</li>
<li>使用创建的 <code>HashEntry</code> 数组初始化这个 <code>Segment</code>.</li>
<li>自旋判断计算得到的指定位置的 <code>Segment</code> 是否为null,使用 CAS 在这个位置赋值为 <code>Segment</code>.</li>
</ul>
</li>
<li><p><code>Segment.put</code> 插入 key,value 值。</p>
</li>
</ul>
<p>上面探究了获取 <code>Segment</code> 段和初始化 <code>Segment</code> 段的操作。最后一行的 <code>Segment</code> 的 put 方法还没有查看,继续分析。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> V <span class="title">put</span><span class="params">(K key, <span class="keyword">int</span> hash, V value, <span class="keyword">boolean</span> onlyIfAbsent)</span> </span>{</span><br><span class="line"> <span class="comment">// 获取 ReentrantLock 独占锁,获取不到,scanAndLockForPut 获取。</span></span><br><span class="line"> HashEntry<K,V> node = tryLock() ? <span class="keyword">null</span> : scanAndLockForPut(key, hash, value);</span><br><span class="line"> V oldValue;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> HashEntry<K,V>[] tab = table;</span><br><span class="line"> <span class="comment">// 计算要put的数据位置</span></span><br><span class="line"> <span class="keyword">int</span> index = (tab.length - <span class="number">1</span>) & hash;</span><br><span class="line"> <span class="comment">// CAS 获取 index 坐标的值</span></span><br><span class="line"> HashEntry<K,V> first = entryAt(tab, index);</span><br><span class="line"> <span class="keyword">for</span> (HashEntry<K,V> e = first;;) {</span><br><span class="line"> <span class="keyword">if</span> (e != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 检查是否 key 已经存在,如果存在,则遍历链表寻找位置,找到后替换 value</span></span><br><span class="line"> K k;</span><br><span class="line"> <span class="keyword">if</span> ((k = e.key) == key ||</span><br><span class="line"> (e.hash == hash && key.equals(k))) {</span><br><span class="line"> oldValue = e.value;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent) {</span><br><span class="line"> e.value = value;</span><br><span class="line"> ++modCount;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> e = e.next;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// first 有值没说明 index 位置已经有值了,有冲突,链表头插法。</span></span><br><span class="line"> <span class="keyword">if</span> (node != <span class="keyword">null</span>)</span><br><span class="line"> node.setNext(first);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> node = <span class="keyword">new</span> HashEntry<K,V>(hash, key, value, first);</span><br><span class="line"> <span class="keyword">int</span> c = count + <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 容量大于扩容阀值,小于最大容量,进行扩容</span></span><br><span class="line"> <span class="keyword">if</span> (c > threshold && tab.length < MAXIMUM_CAPACITY)</span><br><span class="line"> rehash(node);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="comment">// index 位置赋值 node,node 可能是一个元素,也可能是一个链表的表头</span></span><br><span class="line"> setEntryAt(tab, index, node);</span><br><span class="line"> ++modCount;</span><br><span class="line"> count = c;</span><br><span class="line"> oldValue = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> unlock();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> oldValue;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>由于 <code>Segment</code> 继承了 <code>ReentrantLock</code>,所以 <code>Segment</code> 内部可以很方便的获取锁,put 流程就用到了这个功能。</p>
<ul>
<li><p><code>tryLock()</code> 获取锁,获取不到使用 <strong><code>scanAndLockForPut</code></strong> 方法继续获取。</p>
</li>
<li><p>计算 put 的数据要放入的 index 位置,然后获取这个位置上的 <code>HashEntry</code> 。</p>
</li>
<li><p>遍历 put 新元素,为什么要遍历?因为这里获取的 <code>HashEntry</code> 可能是一个空元素,也可能是链表已存在,所以要区别对待。</p>
<p> 如果这个位置上的 <strong><code>HashEntry</code> 不存在</strong>:</p>
<ul>
<li><p>如果当前容量大于扩容阀值,小于最大容量,<strong>进行扩容</strong>。</p>
</li>
<li><p>直接头插法插入。</p>
<p>如果这个位置上的 <strong><code>HashEntry</code> 存在</strong>:</p>
</li>
<li><p>判断链表当前元素 Key 和 hash 值是否和要 put 的 key 和 hash 值一致。一致则替换值</p>
</li>
<li><p>不一致,获取链表下一个节点,直到发现相同进行值替换,或者链表表里完毕没有相同的。</p>
<ul>
<li>如果当前容量大于扩容阀值,小于最大容量,<strong>进行扩容</strong>。</li>
<li>直接链表头插法插入。</li>
</ul>
</li>
</ul>
</li>
<li><p>如果要插入的位置之前已经存在,替换后返回旧值,否则返回 null.</p>
</li>
</ul>
<p>这里面的第一步中的 <code>scanAndLockForPut</code> 操作这里没有介绍,这个方法做的操作就是不断的自旋 <code>tryLock()</code> 获取锁。当自旋次数大于指定次数时,使用 <code>lock()</code> 阻塞获取锁。在自旋时顺表获取下 hash 位置的 HashEntry。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> HashEntry<K,V> <span class="title">scanAndLockForPut</span><span class="params">(K key, <span class="keyword">int</span> hash, V value)</span> </span>{</span><br><span class="line"> HashEntry<K,V> first = entryForHash(<span class="keyword">this</span>, hash);</span><br><span class="line"> HashEntry<K,V> e = first;</span><br><span class="line"> HashEntry<K,V> node = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">int</span> retries = -<span class="number">1</span>; <span class="comment">// negative while locating node</span></span><br><span class="line"> <span class="comment">// 自旋获取锁</span></span><br><span class="line"> <span class="keyword">while</span> (!tryLock()) {</span><br><span class="line"> HashEntry<K,V> f; <span class="comment">// to recheck first below</span></span><br><span class="line"> <span class="keyword">if</span> (retries < <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (e == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (node == <span class="keyword">null</span>) <span class="comment">// speculatively create node</span></span><br><span class="line"> node = <span class="keyword">new</span> HashEntry<K,V>(hash, key, value, <span class="keyword">null</span>);</span><br><span class="line"> retries = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (key.equals(e.key))</span><br><span class="line"> retries = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> e = e.next;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (++retries > MAX_SCAN_RETRIES) {</span><br><span class="line"> <span class="comment">// 自旋达到指定次数后,阻塞等到只到获取到锁</span></span><br><span class="line"> lock();</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((retries & <span class="number">1</span>) == <span class="number">0</span> &&</span><br><span class="line"> (f = entryForHash(<span class="keyword">this</span>, hash)) != first) {</span><br><span class="line"> e = first = f; <span class="comment">// re-traverse if entry changed</span></span><br><span class="line"> retries = -<span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<h3 id="rehash-扩容"><a href="#rehash-扩容" class="headerlink" title="rehash 扩容"></a>rehash 扩容</h3><p><code>ConcurrentHashMap</code> 的扩容只会扩容到原来的两倍。老数组里的数据移动到新的数组时,位置要么不变,要么变为 <code>index+ oldSize</code>,参数里的 node 会在扩容之后使用链表<strong>头插法</strong>插入到指定位置。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">rehash</span><span class="params">(HashEntry<K,V> node)</span> </span>{</span><br><span class="line"> HashEntry<K,V>[] oldTable = table;</span><br><span class="line"> <span class="comment">// 老容量</span></span><br><span class="line"> <span class="keyword">int</span> oldCapacity = oldTable.length;</span><br><span class="line"> <span class="comment">// 新容量,扩大两倍</span></span><br><span class="line"> <span class="keyword">int</span> newCapacity = oldCapacity << <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 新的扩容阀值 </span></span><br><span class="line"> threshold = (<span class="keyword">int</span>)(newCapacity * loadFactor);</span><br><span class="line"> <span class="comment">// 创建新的数组</span></span><br><span class="line"> HashEntry<K,V>[] newTable = (HashEntry<K,V>[]) <span class="keyword">new</span> HashEntry[newCapacity];</span><br><span class="line"> <span class="comment">// 新的掩码,默认2扩容后是4,-1是3,二进制就是11。</span></span><br><span class="line"> <span class="keyword">int</span> sizeMask = newCapacity - <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < oldCapacity ; i++) {</span><br><span class="line"> <span class="comment">// 遍历老数组</span></span><br><span class="line"> HashEntry<K,V> e = oldTable[i];</span><br><span class="line"> <span class="keyword">if</span> (e != <span class="keyword">null</span>) {</span><br><span class="line"> HashEntry<K,V> next = e.next;</span><br><span class="line"> <span class="comment">// 计算新的位置,新的位置只可能是不便或者是老的位置+老的容量。</span></span><br><span class="line"> <span class="keyword">int</span> idx = e.hash & sizeMask;</span><br><span class="line"> <span class="keyword">if</span> (next == <span class="keyword">null</span>) <span class="comment">// Single node on list</span></span><br><span class="line"> <span class="comment">// 如果当前位置还不是链表,只是一个元素,直接赋值</span></span><br><span class="line"> newTable[idx] = e;</span><br><span class="line"> <span class="keyword">else</span> { <span class="comment">// Reuse consecutive sequence at same slot</span></span><br><span class="line"> <span class="comment">// 如果是链表了</span></span><br><span class="line"> HashEntry<K,V> lastRun = e;</span><br><span class="line"> <span class="keyword">int</span> lastIdx = idx;</span><br><span class="line"> <span class="comment">// 新的位置只可能是不便或者是老的位置+老的容量。</span></span><br><span class="line"> <span class="comment">// 遍历结束后,lastRun 后面的元素位置都是相同的</span></span><br><span class="line"> <span class="keyword">for</span> (HashEntry<K,V> last = next; last != <span class="keyword">null</span>; last = last.next) {</span><br><span class="line"> <span class="keyword">int</span> k = last.hash & sizeMask;</span><br><span class="line"> <span class="keyword">if</span> (k != lastIdx) {</span><br><span class="line"> lastIdx = k;</span><br><span class="line"> lastRun = last;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// ,lastRun 后面的元素位置都是相同的,直接作为链表赋值到新位置。</span></span><br><span class="line"> newTable[lastIdx] = lastRun;</span><br><span class="line"> <span class="comment">// Clone remaining nodes</span></span><br><span class="line"> <span class="keyword">for</span> (HashEntry<K,V> p = e; p != lastRun; p = p.next) {</span><br><span class="line"> <span class="comment">// 遍历剩余元素,头插法到指定 k 位置。</span></span><br><span class="line"> V v = p.value;</span><br><span class="line"> <span class="keyword">int</span> h = p.hash;</span><br><span class="line"> <span class="keyword">int</span> k = h & sizeMask;</span><br><span class="line"> HashEntry<K,V> n = newTable[k];</span><br><span class="line"> newTable[k] = <span class="keyword">new</span> HashEntry<K,V>(h, p.key, v, n);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 头插法插入新的节点</span></span><br><span class="line"> <span class="keyword">int</span> nodeIndex = node.hash & sizeMask; <span class="comment">// add the new node</span></span><br><span class="line"> node.setNext(newTable[nodeIndex]);</span><br><span class="line"> newTable[nodeIndex] = node;</span><br><span class="line"> table = newTable;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>第一个 for 是为了寻找这样一个节点,这个节点后面的所有 next 节点的新位置都是相同的。然后把这个作为一个链表赋值到新位置。</p>
<p>第二个 for 循环是为了把剩余的元素通过头插法插入到指定位置链表。这样实现的原因可能是基于概率统计,有深入研究的同学可以发表下意见。</p>
<h3 id="get-方法"><a href="#get-方法" class="headerlink" title="get 方法"></a>get 方法</h3><p>get 逻辑比较简单:</p>
<p>只需要将 Key 通过 Hash 之后定位到具体的 <code>Segment</code> ,再通过一次 Hash 定位到具体的元素上。</p>
<p>由于 <code>HashEntry</code> 中的 value 属性是用 <code>volatile</code> 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。</p>
<p><strong><code>ConcurrentHashMap</code> 的 get 方法是非常高效的,因为整个过程都不需要加锁。</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>{</span><br><span class="line"> Segment<K,V> s; <span class="comment">// manually integrate access methods to reduce overhead</span></span><br><span class="line"> HashEntry<K,V>[] tab;</span><br><span class="line"> <span class="keyword">int</span> h = hash(key);</span><br><span class="line"> <span class="keyword">long</span> u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;</span><br><span class="line"> <span class="comment">// 计算得到 key 的存放位置</span></span><br><span class="line"> <span class="keyword">if</span> ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != <span class="keyword">null</span> &&</span><br><span class="line"> (tab = s.table) != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">for</span> (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile</span><br><span class="line"> (tab, ((<span class="keyword">long</span>)(((tab.length - <span class="number">1</span>) & h)) << TSHIFT) + TBASE);</span><br><span class="line"> e != <span class="keyword">null</span>; e = e.next) {</span><br><span class="line"> <span class="comment">// 如果是链表,遍历查找到相同 key 的 value。</span></span><br><span class="line"> K k;</span><br><span class="line"> <span class="keyword">if</span> ((k = e.key) == key || (e.hash == h && key.equals(k)))</span><br><span class="line"> <span class="keyword">return</span> e.value;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="ConcurrentHashMap-1-8"><a href="#ConcurrentHashMap-1-8" class="headerlink" title="ConcurrentHashMap 1.8"></a>ConcurrentHashMap 1.8</h2><h3 id="存储结构"><a href="#存储结构" class="headerlink" title="存储结构"></a>存储结构</h3><p><img src="ConcurrentHashMap-1.8.jpg" alt="ConcurrentHashMap-1.8"></p>
<p>可以发现 Java8 的 <code>ConcurrentHashMap</code> 相对于 Java7 来说变化比较大,不再是之前的 <strong>Segment 数组 + HashEntry 数组 + 链表</strong>,而是 <strong>Node 数组 + 链表 / 红黑树</strong>。当冲突链表达到一定长度时,链表会转换成红黑树。</p>
<p>其中抛弃了原有的 <code>Segment</code> 分段锁,而采用了 <code>CAS + synchronized</code> 来保证并发安全性。</p>
<h3 id="初始化-initTable"><a href="#初始化-initTable" class="headerlink" title="初始化 initTable"></a>初始化 initTable</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Initializes table, using the size recorded in sizeCtl.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Node<K,V>[] initTable() {</span><br><span class="line"> Node<K,V>[] tab; <span class="keyword">int</span> sc;</span><br><span class="line"> <span class="keyword">while</span> ((tab = table) == <span class="keyword">null</span> || tab.length == <span class="number">0</span>) {</span><br><span class="line"> // 如果 sizeCtl < <span class="number">0</span> ,说明另外的线程执行CAS 成功,正在进行初始化。</span><br><span class="line"> <span class="keyword">if</span> ((sc = sizeCtl) < <span class="number">0</span>)</span><br><span class="line"> <span class="comment">// 让出 CPU 使用权</span></span><br><span class="line"> Thread.yield(); <span class="comment">// lost initialization race; just spin</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc, -<span class="number">1</span>)) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> ((tab = table) == <span class="keyword">null</span> || tab.length == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">int</span> n = (sc > <span class="number">0</span>) ? sc : DEFAULT_CAPACITY;</span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> Node<K,V>[] nt = (Node<K,V>[])<span class="keyword">new</span> Node<?,?>[n];</span><br><span class="line"> table = tab = nt;</span><br><span class="line"> sc = n - (n >>> <span class="number">2</span>);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> sizeCtl = sc;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> tab;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>从源码中可以发现 <code>ConcurrentHashMap</code> 的初始化是通过<strong>自旋和 CAS</strong> 操作完成的。里面需要注意的是变量 <code>sizeCtl</code> ,它的值决定着当前的初始化状态。</p>
<ul>
<li>-1 说明正在初始化</li>
<li>-N 说明有N-1个线程正在进行扩容</li>
<li>表示 table 初始化大小,如果 table 没有初始化</li>
<li>表示 table 容量,如果 table 已经初始化。</li>
</ul>
<h3 id="put方法-1"><a href="#put方法-1" class="headerlink" title="put方法"></a>put方法</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> putVal(key, value, <span class="keyword">false</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** Implementation for put and putIfAbsent */</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> V <span class="title">putVal</span><span class="params">(K key, V value, <span class="keyword">boolean</span> onlyIfAbsent)</span> </span>{</span><br><span class="line"> <span class="comment">// key 和 value 不能为空</span></span><br><span class="line"> <span class="keyword">if</span> (key == <span class="keyword">null</span> || value == <span class="keyword">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line"> <span class="keyword">int</span> hash = spread(key.hashCode());</span><br><span class="line"> <span class="keyword">int</span> binCount = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (Node<K,V>[] tab = table;;) {</span><br><span class="line"> <span class="comment">// f = 目标位置元素</span></span><br><span class="line"> Node<K,V> f; <span class="keyword">int</span> n, i, fh;<span class="comment">// fh 后面存放目标位置的元素 hash 值</span></span><br><span class="line"> <span class="keyword">if</span> (tab == <span class="keyword">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line"> <span class="comment">// 数组桶为空,初始化数组桶(自旋+CAS)</span></span><br><span class="line"> tab = initTable();</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((f = tabAt(tab, i = (n - <span class="number">1</span>) & hash)) == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 桶内为空,CAS 放入,不加锁,成功了就直接 break 跳出</span></span><br><span class="line"> <span class="keyword">if</span> (casTabAt(tab, i, <span class="keyword">null</span>,<span class="keyword">new</span> Node<K,V>(hash, key, value, <span class="keyword">null</span>)))</span><br><span class="line"> <span class="keyword">break</span>; <span class="comment">// no lock when adding to empty bin</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((fh = f.hash) == MOVED)</span><br><span class="line"> tab = helpTransfer(tab, f);</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> V oldVal = <span class="keyword">null</span>;</span><br><span class="line"> <span class="comment">// 使用 synchronized 加锁加入节点</span></span><br><span class="line"> <span class="keyword">synchronized</span> (f) {</span><br><span class="line"> <span class="keyword">if</span> (tabAt(tab, i) == f) {</span><br><span class="line"> <span class="comment">// 说明是链表</span></span><br><span class="line"> <span class="keyword">if</span> (fh >= <span class="number">0</span>) {</span><br><span class="line"> binCount = <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 循环加入新的或者覆盖节点</span></span><br><span class="line"> <span class="keyword">for</span> (Node<K,V> e = f;; ++binCount) {</span><br><span class="line"> K ek;</span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash &&</span><br><span class="line"> ((ek = e.key) == key ||</span><br><span class="line"> (ek != <span class="keyword">null</span> && key.equals(ek)))) {</span><br><span class="line"> oldVal = e.val;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line"> e.val = value;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> Node<K,V> pred = e;</span><br><span class="line"> <span class="keyword">if</span> ((e = e.next) == <span class="keyword">null</span>) {</span><br><span class="line"> pred.next = <span class="keyword">new</span> Node<K,V>(hash, key,</span><br><span class="line"> value, <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (f <span class="keyword">instanceof</span> TreeBin) {</span><br><span class="line"> <span class="comment">// 红黑树</span></span><br><span class="line"> Node<K,V> p;</span><br><span class="line"> binCount = <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">if</span> ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,</span><br><span class="line"> value)) != <span class="keyword">null</span>) {</span><br><span class="line"> oldVal = p.val;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line"> p.val = value;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (binCount != <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (binCount >= TREEIFY_THRESHOLD)</span><br><span class="line"> treeifyBin(tab, i);</span><br><span class="line"> <span class="keyword">if</span> (oldVal != <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">return</span> oldVal;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> addCount(<span class="number">1L</span>, binCount);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>根据 key 计算出 <code>hashcode</code> 。</li>
<li>判断是否需要进行初始化。</li>
<li>即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。</li>
<li>如果当前位置的 <code>hashcode == MOVED == -1</code>,则需要进行扩容。</li>
<li>如果都不满足,则利用 synchronized 锁写入数据。</li>
<li>如果数量大于 <code>TREEIFY_THRESHOLD</code> 则要转换为红黑树。</li>
</ul>
<h3 id="get方法"><a href="#get方法" class="headerlink" title="get方法"></a>get方法</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>{</span><br><span class="line"> Node<K,V>[] tab; Node<K,V> e, p; <span class="keyword">int</span> n, eh; K ek;</span><br><span class="line"> <span class="comment">// key 所在的 hash 位置</span></span><br><span class="line"> <span class="keyword">int</span> h = spread(key.hashCode());</span><br><span class="line"> <span class="keyword">if</span> ((tab = table) != <span class="keyword">null</span> && (n = tab.length) > <span class="number">0</span> &&</span><br><span class="line"> (e = tabAt(tab, (n - <span class="number">1</span>) & h)) != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 如果指定位置元素存在,头结点hash值相同</span></span><br><span class="line"> <span class="keyword">if</span> ((eh = e.hash) == h) {</span><br><span class="line"> <span class="keyword">if</span> ((ek = e.key) == key || (ek != <span class="keyword">null</span> && key.equals(ek)))</span><br><span class="line"> <span class="comment">// key hash 值相等,key值相同,直接返回元素 value</span></span><br><span class="line"> <span class="keyword">return</span> e.val;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (eh < <span class="number">0</span>)</span><br><span class="line"> <span class="comment">// 头结点hash值小于0,说明正在扩容或者是红黑树,find查找</span></span><br><span class="line"> <span class="keyword">return</span> (p = e.find(h, key)) != <span class="keyword">null</span> ? p.val : <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">while</span> ((e = e.next) != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 是链表,遍历查找</span></span><br><span class="line"> <span class="keyword">if</span> (e.hash == h &&</span><br><span class="line"> ((ek = e.key) == key || (ek != <span class="keyword">null</span> && key.equals(ek))))</span><br><span class="line"> <span class="keyword">return</span> e.val;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>总结一下 get 过程:</p>
<ul>
<li>根据 hash 值计算位置。</li>
<li>查找到指定位置,如果头节点就是要找的,直接返回它的 value.</li>
<li>如果头节点 hash 值小于 0 ,说明正在扩容或者是红黑树,查找之。</li>
<li>如果是链表,遍历查找之。</li>
</ul>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Java7 中 <code>ConcurrentHashMap</code> 使用的分段锁,也就是每一个 Segment 上同时只有一个线程可以操作,每一个 <code>Segment</code> 都是一个类似 <code>HashMap</code> 数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变。</p>
<p>Java8 中的 <code>ConcurrentHashMap</code> 使用的 <code>Synchronized</code> 锁加 <code>CAS</code> 的机制。结构也由 Java7 中的 <strong>Segment 数组 + HashEntry 数组 + 链表</strong> 进化成了 <strong>Node 数组 + 链表 / 红黑树</strong>,Node 是类似于一个 <code>HashEntry</code> 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。</p>
]]></content>
<categories>
<category>Java</category>
<category>源码分析</category>
</categories>
<tags>
<tag>源码分析</tag>
</tags>
</entry>
<entry>
<title>HashMap 源码分析</title>
<url>/2021/02/26/HashMap%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/</url>
<content><![CDATA[<p>这一篇文章是关于ConcurrentHashMap 源码分析 <a href="/2021/03/10/ConcurrentHashMap%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/" title="ConcurrentHashMap 源码分析">ConcurrentHashMap 源码分析</a></p>
<h2 id="HashMap简介"><a href="#HashMap简介" class="headerlink" title="HashMap简介"></a>HashMap简介</h2><p>JDK1.8 之前 <code>HashMap</code> 由 数组+链表 组成的, 数组是 <code>HashMap</code> 的主体, 链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。</p>
<p>JDK1.8 之后 <code>HashMap</code> 的组成多了红黑树, 在满足下面两个条件之后, 会执行链表转红黑树操作, 以此来加快搜索速度。</p>
<ul>
<li>链表长度大于阈值(默认为 8)</li>
<li><code>HashMap</code> 数组长度超过 64</li>
</ul>
<h2 id="HashMap底层数据结构分析"><a href="#HashMap底层数据结构分析" class="headerlink" title="HashMap底层数据结构分析"></a>HashMap底层数据结构分析</h2><h3 id="JDK1-8-之前"><a href="#JDK1-8-之前" class="headerlink" title="JDK1.8 之前"></a>JDK1.8 之前</h3><a id="more"></a>
<p><strong>JDK1.8之前数据结构图</strong></p>
<p><img src="JDK1.8%E4%B9%8B%E5%89%8D%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.jpg" alt="JDK1.8之前数据结构.jpg"></p>
<p>JDK1.8 之前 <code>HashMap</code> 底层是 <strong>数组和链表</strong> 结合在一起使用也就是 <strong>链表散列</strong>。</p>
<p><code>HashMap</code> 通过 key 的 <code>hashCode</code> 经过扰动函数处理过后得到 hash 值, 然后通过 <code>(n - 1) & hash</code> 判断当前元素存放的位置(这里的 n 指的是数组的长度), 如果当前位置存在元素的话, 就判断该元素与要存入的元素的 hash 值以及 key 是否相同, 如果相同的话, 直接覆盖, 不相同就通过拉链法解决冲突。</p>
<p>所谓扰动函数指的就是 <code>HashMap</code> 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 <code>hashCode()</code> 方法 换句话说使用扰动函数之后可以减少碰撞。</p>
<p><strong>JDK 1.8 HashMap 的 hash 方法源码:</strong></p>
<p>JDK 1.8 的 hash 方法 相比于 JDK 1.7 hash 方法更加简化, 但是原理不变。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">hash</span><span class="params">(Object key)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> h;</span><br><span class="line"> <span class="comment">// key.hashCode():返回散列值也就是hashcode</span></span><br><span class="line"> <span class="comment">// ^ :按位异或</span></span><br><span class="line"> <span class="comment">// >>>:无符号右移, 忽略符号位, 空位都以0补齐</span></span><br><span class="line"> <span class="keyword">return</span> (key == <span class="keyword">null</span>) ? <span class="number">0</span> : (h = key.hashCode()) ^ (h >>> <span class="number">16</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>对比一下 JDK1.7 的 HashMap 的 hash 方法源码.</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">hash</span><span class="params">(<span class="keyword">int</span> h)</span> </span>{</span><br><span class="line"> <span class="comment">// This function ensures that hashCodes that differ only by</span></span><br><span class="line"> <span class="comment">// constant multiples at each bit position have a bounded</span></span><br><span class="line"> <span class="comment">// number of collisions (approximately 8 at default load factor).</span></span><br><span class="line"></span><br><span class="line"> h ^= (h >>> <span class="number">20</span>) ^ (h >>> <span class="number">12</span>);</span><br><span class="line"> <span class="keyword">return</span> h ^ (h >>> <span class="number">7</span>) ^ (h >>> <span class="number">4</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>相比于 JDK1.8 的 hash 方法 , JDK 1.7 的 hash 方法的性能会稍差一点点, 因为毕竟扰动了 4 次。</p>
<p>所谓 <strong>“拉链法”</strong> 就是:将链表和数组相结合。也就是说创建一个链表数组, 数组中每一格就是一个链表。若遇到哈希冲突, 则将冲突的值加到链表中即可。</p>
<h3 id="JDK1-8-之后"><a href="#JDK1-8-之后" class="headerlink" title="JDK1.8 之后"></a>JDK1.8 之后</h3><p><img src="JDK1.8%E4%B9%8B%E5%90%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%9B%BE.jpg" alt="JDK1.8 之后数据结构图"></p>
<p><strong>HashMap类</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HashMap</span><<span class="title">K</span>, <span class="title">V</span>> <span class="keyword">extends</span> <span class="title">AbstractMap</span><<span class="title">K</span>, <span class="title">V</span>> <span class="keyword">implements</span> <span class="title">Map</span><<span class="title">K</span>, <span class="title">V</span>>, <span class="title">Cloneable</span>, <span class="title">Serializable</span> </span>{</span><br><span class="line"> <span class="comment">// 序列号</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">362498820763181265L</span>;</span><br><span class="line"> <span class="comment">// 默认的初始桶容量是16</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DEFAULT_INITIAL_CAPACITY = <span class="number">1</span> << <span class="number">4</span>;</span><br><span class="line"> <span class="comment">// 桶最大值</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> MAXIMUM_CAPACITY = <span class="number">1</span> << <span class="number">30</span>;</span><br><span class="line"> <span class="comment">// 默认的负载因子(0.75)</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">float</span> DEFAULT_LOAD_FACTOR = <span class="number">0.75f</span>;</span><br><span class="line"> <span class="comment">// 当桶(bucket)上的结点数大于这个值时会转成红黑树</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> TREEIFY_THRESHOLD = <span class="number">8</span>;</span><br><span class="line"> <span class="comment">// 当桶(bucket)上的结点数小于这个值时树转链表</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> UNTREEIFY_THRESHOLD = <span class="number">6</span>;</span><br><span class="line"> <span class="comment">// 桶中结构转化为红黑树对应的table的最小大小</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> MIN_TREEIFY_CAPACITY = <span class="number">64</span>;</span><br><span class="line"> <span class="comment">// table 真正存放数据的数组, 总是2的幂次倍</span></span><br><span class="line"> <span class="keyword">transient</span> Node<k, v>[] table;</span><br><span class="line"> <span class="comment">// 存放具体元素的集</span></span><br><span class="line"> <span class="keyword">transient</span> Set<map.entry<k, v>> entrySet;</span><br><span class="line"> <span class="comment">// 存放元素的个数, 注意这个不等于数组的长度</span></span><br><span class="line"> <span class="keyword">transient</span> <span class="keyword">int</span> size;</span><br><span class="line"> <span class="comment">// 每次扩容和更改map结构的计数器</span></span><br><span class="line"> <span class="keyword">transient</span> <span class="keyword">int</span> modCount;</span><br><span class="line"> <span class="comment">// 临界值 当实际大小(容量*填充因子)超过临界值时, 会进行扩容, 可在初始化时显式指定</span></span><br><span class="line"> <span class="keyword">int</span> threshold;</span><br><span class="line"> <span class="comment">// 负载因子, 可在初始化时显式指定</span></span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">float</span> loadFactor;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li><p><strong>loadFactor 加载因子</strong></p>
<p><code>loadFactor</code> 加载因子是控制数组存放数据的疏密程度, <code>loadFactor</code> 越趋近于 1, 那么 数组中存放的数据(entry)也就越多, 也就越密, 也就是会让链表的长度增加, <code>loadFactor</code> 越小, 也就是趋近于 0, 数组中存放的数据(entry)也就越少, 也就越稀疏。</p>
<p><strong>loadFactor 太大导致查找元素效率低, 太小导致数组的利用率低, 存放的数据会很分散。loadFactor 的默认值为 0.75f 是官方给出的一个比较好的临界值</strong>。</p>
<p>给定的默认容量为 16, 负载因子为 0.75。Map 在使用过程中不断的往里面存放数据, 当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容, 而扩容这个过程涉及到 rehash、复制数据等操作, 所以非常消耗性能。</p>
</li>
<li><p><strong>threshold</strong></p>
<p><strong><code>threshold = capacity \* loadFactor</code></strong>, <strong>当 <code>Size>=threshold</code>**的时候, 那么就要考虑对数组的扩增了, 也就是说, 这个的意思就是 **衡量数组是否需要扩增的一个标准</strong>。</p>
</li>
</ul>
<h2 id="HashMap-源码分析"><a href="#HashMap-源码分析" class="headerlink" title="HashMap 源码分析"></a>HashMap 源码分析</h2><h3 id="构造方法"><a href="#构造方法" class="headerlink" title="构造方法"></a>构造方法</h3><p><code>HashMap</code> 中有四个构造方法, 它们分别如下:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 默认构造函数。</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">HashMap</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.loadFactor = DEFAULT_LOAD_FACTOR; <span class="comment">// all other fields defaulted</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 包含另一个“Map”的构造函数</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">HashMap</span><span class="params">(Map<? extends K, ? extends V> m)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.loadFactor = DEFAULT_LOAD_FACTOR;</span><br><span class="line"> putMapEntries(m, <span class="keyword">false</span>);<span class="comment">//下面会分析到这个方法</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 指定“容量大小”的构造函数</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">HashMap</span><span class="params">(<span class="keyword">int</span> initialCapacity)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>(initialCapacity, DEFAULT_LOAD_FACTOR);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 指定“容量大小”和“加载因子”的构造函数</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">HashMap</span><span class="params">(<span class="keyword">int</span> initialCapacity, <span class="keyword">float</span> loadFactor)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (initialCapacity < <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"Illegal initial capacity: "</span> + initialCapacity);</span><br><span class="line"> <span class="keyword">if</span> (initialCapacity > MAXIMUM_CAPACITY)</span><br><span class="line"> initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line"> <span class="keyword">if</span> (loadFactor <= <span class="number">0</span> || Float.isNaN(loadFactor))</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"Illegal load factor: "</span> + loadFactor);</span><br><span class="line"> <span class="keyword">this</span>.loadFactor = loadFactor;</span><br><span class="line"> <span class="keyword">this</span>.threshold = tableSizeFor(initialCapacity);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p><strong><code>putMapEntries</code> 方法:</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">putMapEntries</span><span class="params">(Map<? extends K, ? extends V> m, <span class="keyword">boolean</span> evict)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> s = m.size();</span><br><span class="line"> <span class="keyword">if</span> (s > <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 判断table是否已经初始化</span></span><br><span class="line"> <span class="keyword">if</span> (table == <span class="keyword">null</span>) { <span class="comment">// pre-size</span></span><br><span class="line"> <span class="comment">// 未初始化, s为m的实际元素个数</span></span><br><span class="line"> <span class="keyword">float</span> ft = ((<span class="keyword">float</span>)s / loadFactor) + <span class="number">1.0F</span>;</span><br><span class="line"> <span class="keyword">int</span> t = ((ft < (<span class="keyword">float</span>)MAXIMUM_CAPACITY) ?</span><br><span class="line"> (<span class="keyword">int</span>)ft : MAXIMUM_CAPACITY);</span><br><span class="line"> <span class="comment">// 计算得到的t大于阈值, 则初始化阈值</span></span><br><span class="line"> <span class="keyword">if</span> (t > threshold)</span><br><span class="line"> threshold = tableSizeFor(t);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 已初始化, 并且m元素个数大于阈值, 进行扩容处理</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (s > threshold)</span><br><span class="line"> resize();</span><br><span class="line"> <span class="comment">// 将m中的所有元素添加至HashMap中</span></span><br><span class="line"> <span class="keyword">for</span> (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {</span><br><span class="line"> K key = e.getKey();</span><br><span class="line"> V value = e.getValue();</span><br><span class="line"> putVal(hash(key), key, value, <span class="keyword">false</span>, evict);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="put方法"><a href="#put方法" class="headerlink" title="put方法"></a>put方法</h3><p>HashMap 只提供了 put 用于添加元素, putVal 方法只是给 put 方法调用的一个方法, 并没有提供给用户使用。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> putVal(hash(key), key, value, <span class="keyword">false</span>, <span class="keyword">true</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">final</span> V <span class="title">putVal</span><span class="params">(<span class="keyword">int</span> hash, K key, V value, <span class="keyword">boolean</span> onlyIfAbsent, </span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">boolean</span> evict)</span> </span>{</span><br><span class="line"> Node<K, V>[] tab; Node<K, V> p; <span class="keyword">int</span> n, i;</span><br><span class="line"> <span class="comment">// 如果table尚未初始化, 则此处进行初始化数组, 并赋值初始容量, 重新计算阈值</span></span><br><span class="line"> <span class="keyword">if</span> ((tab = table) == <span class="keyword">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line"> n = (tab = resize()).length;</span><br><span class="line"> <span class="comment">// (n - 1) & hash 确定元素存放在哪个桶中, 桶为空, 新生成结点放入桶中(此时, 这个结点是放在数组中)</span></span><br><span class="line"> <span class="keyword">if</span> ((p = tab[i = (n - <span class="number">1</span>) & hash]) == <span class="keyword">null</span>)</span><br><span class="line"> <span class="comment">// 通过hash找到下标, 如果hash值指定的位置数据为空, 则直接将数据存放进去</span></span><br><span class="line"> tab[i] = newNode(hash, key, value, <span class="keyword">null</span>);</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//如果通过hash找到的位置有数据, 发生碰撞</span></span><br><span class="line"> Node<K, V> e; K k;</span><br><span class="line"> <span class="comment">// 比较桶中第一个元素(数组中的结点)的hash值相等, key相等</span></span><br><span class="line"> <span class="keyword">if</span> (p.hash == hash &&</span><br><span class="line"> ((k = p.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</span><br><span class="line"> <span class="comment">// 将第一个元素赋值给e, 用e来记录</span></span><br><span class="line"> e = p;</span><br><span class="line"> <span class="comment">// hash值不相等, 即key不相等;为红黑树结点</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (p <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line"> <span class="comment">// 如果此时桶中数据类型为 treeNode, 使用红黑树进行插入</span></span><br><span class="line"> e = ((TreeNode<K, V>)p).putTreeVal(<span class="keyword">this</span>, tab, hash, key, value);</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 此时桶中数据类型为链表</span></span><br><span class="line"> <span class="comment">// 进行循环</span></span><br><span class="line"> <span class="comment">// 在链表最末插入结点</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> binCount = <span class="number">0</span>; ; ++binCount) {</span><br><span class="line"> <span class="comment">// 到达链表的尾部</span></span><br><span class="line"> <span class="keyword">if</span> ((e = p.next) == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 如果链表中没有最新插入的节点, 将新放入的数据放到链表的末尾</span></span><br><span class="line"> p.next = newNode(hash, key, value, <span class="keyword">null</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 结点数量达到阈值(默认为 8 ), 执行 treeifyBin 方法</span></span><br><span class="line"> <span class="comment">// 这个方法会根据 HashMap 数组来决定是否转换为红黑树。</span></span><br><span class="line"> <span class="comment">// 只有当数组长度大于或者等于 64 的情况下, 才会执行转换红黑树操作, 以减少搜索时间。否则, 就是只是对数组扩容。</span></span><br><span class="line"> <span class="keyword">if</span> (binCount >= TREEIFY_THRESHOLD - <span class="number">1</span>) <span class="comment">// -1 for 1st</span></span><br><span class="line"> treeifyBin(tab, hash);</span><br><span class="line"> <span class="comment">// 跳出循环</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 判断链表中结点的key值与插入的元素的key值是否相等</span></span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash &&</span><br><span class="line"> ((k = e.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</span><br><span class="line"> <span class="comment">// 相等, 跳出循环</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="comment">// 用于遍历桶中的链表, 与前面的e = p.next组合, 可以遍历链表</span></span><br><span class="line"> p = e;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 经过上面的循环后, 如果e不为空, 则说明上面插入的值已经存在于当前的hashMap中, 那么更新指定位置的键值对</span></span><br><span class="line"> <span class="comment">// 表示在桶中找到key值、hash值与插入元素相等的结点</span></span><br><span class="line"> <span class="keyword">if</span> (e != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 记录e的value</span></span><br><span class="line"> V oldValue = e.value;</span><br><span class="line"> <span class="comment">// onlyIfAbsent为false或者旧值为null</span></span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent || oldValue == <span class="keyword">null</span>)</span><br><span class="line"> <span class="comment">//用新值替换旧值</span></span><br><span class="line"> e.value = value;</span><br><span class="line"> <span class="comment">// 访问后回调</span></span><br><span class="line"> afterNodeAccess(e);</span><br><span class="line"> <span class="comment">// 返回旧值</span></span><br><span class="line"> <span class="keyword">return</span> oldValue;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 结构性修改</span></span><br><span class="line"> ++modCount;</span><br><span class="line"> <span class="comment">// 如果此时hashMap size大于阈值, 则进行扩容</span></span><br><span class="line"> <span class="keyword">if</span> (++size > threshold)</span><br><span class="line"> resize();</span><br><span class="line"> <span class="comment">// 插入后回调</span></span><br><span class="line"> afterNodeInsertion(evict);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>从代码看, put方法分为三种情况:</p>
<ul>
<li><p><strong>table尚未初始化</strong>, 对数据进行初始化</p>
</li>
<li><p><strong>table已经初始化</strong>, 且通过hash算法找到下标所在的位置数据为空, 直接将数据存放到指定位置</p>
</li>
<li><p><strong>table已经初始化</strong>, 且通过hash算法找到下标所在的位置数据不为空, 发生hash冲突(碰撞), 发生碰撞后, 会执行以下操作:</p>
<ul>
<li>判断插入的key如果等于当前位置的key的话, 将 e 指向该键值对</li>
<li>如果此时桶中数据类型为 <code>treeNode</code>, 使用红黑树进行插入</li>
<li>如果是链表, 则进行循环判断, 如果链表中包含该节点, 跳出循环, 如果链表中不包含该节点, 则把该节点插入到链表末尾, 同时, 如果链表长度超过树化阈值(<code>TREEIFY_THRESHOLD</code>)且table容量超过最小树化容量(<code>MIN_TREEIFY_CAPACITY</code>), 则进行链表转红黑树(由于table容量越小, 越容易发生hash冲突, 因此在table容量<<code>MIN_TREEIFY_CAPACITY</code> 的时候, 如果链表长度><code>TREEIFY_THRESHOLD</code>, 会优先选择扩容, 否则会进行链表转红黑树操作)</li>
</ul>
</li>
</ul>
<h3 id="get方法"><a href="#get方法" class="headerlink" title="get方法"></a>get方法</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>{</span><br><span class="line"> Node<K, V> e;</span><br><span class="line"> <span class="keyword">return</span> (e = getNode(hash(key), key)) == <span class="keyword">null</span> ? <span class="keyword">null</span> : e.value;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">final</span> Node<K, V> <span class="title">getNode</span><span class="params">(<span class="keyword">int</span> hash, Object key)</span> </span>{</span><br><span class="line"> Node<K, V>[] tab; Node<K, V> first, e; <span class="keyword">int</span> n; K k;</span><br><span class="line"> <span class="keyword">if</span> ((tab = table) != <span class="keyword">null</span> && (n = tab.length) > <span class="number">0</span> &&</span><br><span class="line"> (first = tab[(n - <span class="number">1</span>) & hash]) != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 根据hash算法找到对应位置的第一个数据, 如果是指定的key, 则直接返回</span></span><br><span class="line"> <span class="keyword">if</span> (first.hash == hash && <span class="comment">// always check first node</span></span><br><span class="line"> ((k = first.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</span><br><span class="line"> <span class="keyword">return</span> first;</span><br><span class="line"> <span class="comment">// 桶中不止一个节点</span></span><br><span class="line"> <span class="keyword">if</span> ((e = first.next) != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 如果该节点为红黑树, 则通过树进行查找</span></span><br><span class="line"> <span class="keyword">if</span> (first <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line"> <span class="keyword">return</span> ((TreeNode<K, V>)first).getTreeNode(hash, key);</span><br><span class="line"> <span class="comment">// 如果该节点是链表, 则遍历查找到数据</span></span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> <span class="keyword">if</span> (e.hash == hash &&</span><br><span class="line"> ((k = e.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</span><br><span class="line"> <span class="keyword">return</span> e;</span><br><span class="line"> } <span class="keyword">while</span> ((e = e.next) != <span class="keyword">null</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>get方法相对于put来说, 逻辑实在是简单太多了</p>
<ul>
<li>根据hash值查找到指定位置的数据</li>
<li>校验指定位置第一个节点的数据是key是否为传入的key, 如果是直接返回第一个节点, 否则继续查找第二个节点</li>
<li>如果数据是<code>TreeNode</code>(红黑树结构), 直接通过红黑树查找节点数据并返回</li>
<li>如果是链表结构, 循环查找所有节点, 返回数据</li>
<li>如果没有找到符合要求的节点, 返回 <code>null</code></li>
</ul>
<p><strong>在这个方法里面, 需要注意的有两个地方:<code>hash(key)</code>和<code>hash</code>的取模运算 <code>(n - 1) & hash</code></strong></p>
<h3 id="resize-方法"><a href="#resize-方法" class="headerlink" title="resize 方法"></a>resize 方法</h3><p><code>resize()</code>进行扩容,会伴随着一次重新 hash 分配,并且会遍历 hash 表中所有的元素,是非常耗时的。在编写程序中,要尽量避免 resize。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">final</span> Node<K,V>[] resize() {</span><br><span class="line"> Node<K,V>[] oldTab = table;</span><br><span class="line"> <span class="keyword">int</span> oldCap = (oldTab == <span class="keyword">null</span>) ? <span class="number">0</span> : oldTab.length;</span><br><span class="line"> <span class="keyword">int</span> oldThr = threshold;</span><br><span class="line"> <span class="keyword">int</span> newCap, newThr = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">if</span> (oldCap > <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 超过最大值就不再扩充了,就只好随你碰撞去吧</span></span><br><span class="line"> <span class="keyword">if</span> (oldCap >= MAXIMUM_CAPACITY) {</span><br><span class="line"> threshold = Integer.MAX_VALUE;</span><br><span class="line"> <span class="keyword">return</span> oldTab;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 没超过最大值,就扩充为原来的2倍</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((newCap = oldCap << <span class="number">1</span>) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)</span><br><span class="line"> newThr = oldThr << <span class="number">1</span>; <span class="comment">// double threshold</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (oldThr > <span class="number">0</span>) <span class="comment">// initial capacity was placed in threshold</span></span><br><span class="line"> newCap = oldThr;</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// signifies using defaults</span></span><br><span class="line"> newCap = DEFAULT_INITIAL_CAPACITY;</span><br><span class="line"> newThr = (<span class="keyword">int</span>)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 计算新的resize上限</span></span><br><span class="line"> <span class="keyword">if</span> (newThr == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">float</span> ft = (<span class="keyword">float</span>)newCap * loadFactor;</span><br><span class="line"> newThr = (newCap < MAXIMUM_CAPACITY && ft < (<span class="keyword">float</span>)MAXIMUM_CAPACITY ? (<span class="keyword">int</span>)ft : Integer.MAX_VALUE);</span><br><span class="line"> }</span><br><span class="line"> threshold = newThr;</span><br><span class="line"> <span class="meta">@SuppressWarnings({"rawtypes","unchecked"})</span></span><br><span class="line"> Node<K,V>[] newTab = (Node<K,V>[])<span class="keyword">new</span> Node[newCap];</span><br><span class="line"> table = newTab;</span><br><span class="line"> <span class="keyword">if</span> (oldTab != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 把每个bucket都移动到新的buckets中</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < oldCap; ++j) {</span><br><span class="line"> Node<K,V> e;</span><br><span class="line"> <span class="keyword">if</span> ((e = oldTab[j]) != <span class="keyword">null</span>) {</span><br><span class="line"> oldTab[j] = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (e.next == <span class="keyword">null</span>)</span><br><span class="line"> newTab[e.hash & (newCap - <span class="number">1</span>)] = e;</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line"> ((TreeNode<K,V>)e).split(<span class="keyword">this</span>, newTab, j, oldCap);</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> Node<K,V> loHead = <span class="keyword">null</span>, loTail = <span class="keyword">null</span>;</span><br><span class="line"> Node<K,V> hiHead = <span class="keyword">null</span>, hiTail = <span class="keyword">null</span>;</span><br><span class="line"> Node<K,V> next;</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> next = e.next;</span><br><span class="line"> <span class="comment">// 原索引</span></span><br><span class="line"> <span class="keyword">if</span> ((e.hash & oldCap) == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (loTail == <span class="keyword">null</span>)</span><br><span class="line"> loHead = e;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> loTail.next = e;</span><br><span class="line"> loTail = e;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 原索引+oldCap</span></span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (hiTail == <span class="keyword">null</span>)</span><br><span class="line"> hiHead = e;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> hiTail.next = e;</span><br><span class="line"> hiTail = e;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">while</span> ((e = next) != <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">// 原索引放到bucket里</span></span><br><span class="line"> <span class="keyword">if</span> (loTail != <span class="keyword">null</span>) {</span><br><span class="line"> loTail.next = <span class="keyword">null</span>;</span><br><span class="line"> newTab[j] = loHead;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 原索引+oldCap放到bucket里</span></span><br><span class="line"> <span class="keyword">if</span> (hiTail != <span class="keyword">null</span>) {</span><br><span class="line"> hiTail.next = <span class="keyword">null</span>;</span><br><span class="line"> newTab[j + oldCap] = hiHead;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> newTab;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="HashMap-常用遍历"><a href="#HashMap-常用遍历" class="headerlink" title="HashMap 常用遍历"></a>HashMap 常用遍历</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 第一种</span></span><br><span class="line">Iterator<Map.Entry<String, Integer>> entryIterator = map.entrySet().iterator();</span><br><span class="line"><span class="keyword">while</span> (entryIterator.hasNext()) {</span><br><span class="line"> Map.Entry<String, Integer> next = entryIterator.next();</span><br><span class="line"> System.out.println(<span class="string">"key="</span> + next.getKey() + <span class="string">" value="</span> + next.getValue());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 第二种</span></span><br><span class="line">Iterator<String> iterator = map.keySet().iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) {</span><br><span class="line"> String key = iterator.next();</span><br><span class="line"> System.out.println(<span class="string">"key="</span> + key + <span class="string">" value="</span> + map.get(key));</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>建议使用第一种 EntrySet 进行遍历。第一种可以把 key value 同时取出,第二种还得需要通过 key 取一次 value,效率较低</strong></p>
]]></content>
<categories>
<category>Java</category>
<category>源码分析</category>
</categories>
<tags>
<tag>源码分析</tag>
</tags>
</entry>
<entry>
<title>ThreadLocal 内存泄露</title>
<url>/2021/04/24/ThreadLocal%20%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2/</url>
<content><![CDATA[<h2 id="ThreadLocal-简介"><a href="#ThreadLocal-简介" class="headerlink" title="ThreadLocal 简介"></a>ThreadLocal 简介</h2><p><code>ThreadLocal</code>提供了线程的局部变量,每个线程都可以通过<code>set()</code>和<code>get()</code>来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,从而避免了线程安全问题。</p>
<p>简而言之:<code>ThreadLocal</code>保证了各个线程的数据互不干扰。</p>
<a id="more"></a>
<h2 id="ThreadLocal-原理"><a href="#ThreadLocal-原理" class="headerlink" title="ThreadLocal 原理"></a>ThreadLocal 原理</h2><p>从 <code>Thread</code>类源代码入手。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Thread</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>{</span><br><span class="line"> ......</span><br><span class="line"><span class="comment">//与此线程有关的ThreadLocal值。由ThreadLocal类维护</span></span><br><span class="line">ThreadLocal.ThreadLocalMap threadLocals = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护</span></span><br><span class="line">ThreadLocal.ThreadLocalMap inheritableThreadLocals = <span class="keyword">null</span>;</span><br><span class="line"> ......</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>从上面<code>Thread</code>类 源代码可以看出<code>Thread</code> 类中有一个 <code>threadLocals</code> 和 一个 <code>inheritableThreadLocals</code> 变量,它们都是 <code>ThreadLocalMap</code> 类型的变量,我们可以把 <code>ThreadLocalMap</code> 理解为<code>ThreadLocal</code> 类实现的定制化的 <code>HashMap</code>。默认情况下这两个变量都是 null,只有当前线程调用 <code>ThreadLocal</code> 类的 <code>set</code>或<code>get</code>方法时才创建它们,实际上调用这两个方法的时候,我们调用的是<code>ThreadLocalMap</code>类对应的 <code>get()</code>、<code>set()</code>方法。</p>
<p><code>ThreadLocal</code>类的<code>set()</code>方法</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(T value)</span> </span>{</span><br><span class="line"> Thread t = Thread.currentThread();</span><br><span class="line"> ThreadLocalMap map = getMap(t);</span><br><span class="line"> <span class="keyword">if</span> (map != <span class="keyword">null</span>)</span><br><span class="line"> map.set(<span class="keyword">this</span>, value);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> createMap(t, value);</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function">ThreadLocalMap <span class="title">getMap</span><span class="params">(Thread t)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> t.threadLocals;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>最终的变量是放在了当前线程的 <code>ThreadLocalMap</code> 中,并不是存在 <code>ThreadLocal</code> 上,<code>ThreadLocal</code> 可以理解为只是<code>ThreadLocalMap</code>的封装,传递了变量值。 <code>ThrealLocal</code> 类中可以通过<code>Thread.currentThread()</code>获取到当前线程对象后,直接通过<code>getMap(Thread t)</code>可以访问到该线程的<code>ThreadLocalMap</code>对象。</p>
<p><strong>每个<code>Thread</code>中都有一个<code>ThreadLocalMap</code>,而<code>ThreadLocalMap</code>可以存储以<code>ThreadLocal</code>为 key ,Object 对象为 value 的键值对;采用类似hashmap机制</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {</span><br><span class="line"> ......</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="ThreadLocal-内存泄露原因"><a href="#ThreadLocal-内存泄露原因" class="headerlink" title="ThreadLocal 内存泄露原因"></a>ThreadLocal 内存泄露原因</h2><p>ThreadLocal可能导致内存泄漏,为什么?</p>
<p>先看看Entry的实现:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Entry</span> <span class="keyword">extends</span> <span class="title">WeakReference</span><<span class="title">ThreadLocal</span><?>> </span>{</span><br><span class="line"> <span class="comment">/** The value associated with this ThreadLocal. */</span></span><br><span class="line"> Object value;</span><br><span class="line"></span><br><span class="line"> Entry(ThreadLocal<?> k, Object v) {</span><br><span class="line"> <span class="keyword">super</span>(k);</span><br><span class="line"> value = v;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong><code>ThreadLocalMap</code> 中使用的 key 为 <code>ThreadLocal</code> 的弱引用,而 value 是强引用。所以,如果 <code>ThreadLocal</code> 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,<code>ThreadLocalMap</code> 中就会出现 key 为 null 的 <code>Entry</code>。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露</strong></p>
<h2 id="如何避免-ThreadLocal-内存泄露"><a href="#如何避免-ThreadLocal-内存泄露" class="headerlink" title="如何避免 ThreadLocal 内存泄露"></a>如何避免 ThreadLocal 内存泄露</h2><p>如果使用ThreadLocal的set方法之后,记得调用<code>remove()</code>方法</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ThreadLocal<String> localName = <span class="keyword">new</span> ThreadLocal();</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> localName.set(<span class="string">"xxx"</span>);</span><br><span class="line"> <span class="comment">// 业务逻辑</span></span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">//调用remove()</span></span><br><span class="line"> localName.remove();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category>Java</category>
<category>并发</category>
</categories>
<tags>
<tag>并发</tag>
</tags>
</entry>
<entry>
<title>SpringBoot 自动装配原理</title>
<url>/2021/04/25/SpringBoot%20%E8%87%AA%E5%8A%A8%E8%A3%85%E9%85%8D%E5%8E%9F%E7%90%86/</url>
<content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>使用过 <code>Spring</code> 的小伙伴,一定有被 XML 配置统治的恐惧。即使 Spring 后面引入了基于注解的配置,我们在开启某些 <code>Spring</code> 特性或者引入第三方依赖的时候,还是需要用 XML 或 Java 进行显式配置。</p>
<p>举个例子。没有 <code>Spring Boot</code> 的时候,我们写一个 <code>RestFul Web</code> 服务,还首先需要进行如下配置。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RESTConfiguration</span></span></span><br><span class="line"><span class="class"></span>{</span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> View <span class="title">jsonTemplate</span><span class="params">()</span> </span>{</span><br><span class="line"> MappingJackson2JsonView view = <span class="keyword">new</span> MappingJackson2JsonView();</span><br><span class="line"> view.setPrettyPrint(<span class="keyword">true</span>);</span><br><span class="line"> <span class="keyword">return</span> view;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ViewResolver <span class="title">viewResolver</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> BeanNameViewResolver();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<a id="more"></a>
<p><code>spring-servlet.xml</code></p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xmlns:context</span>=<span class="string">"http://www.springframework.org/schema/context"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xmlns:mvc</span>=<span class="string">"http://www.springframework.org/schema/mvc"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd</span></span></span><br><span class="line"><span class="tag"><span class="string"> http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context.xsd</span></span></span><br><span class="line"><span class="tag"><span class="string"> http://www.springframework.org/schema/mvc/ http://www.springframework.org/schema/mvc/spring-mvc.xsd"</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">context:component-scan</span> <span class="attr">base-package</span>=<span class="string">"com.howtodoinjava.demo"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">mvc:annotation-driven</span> /></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"><!-- JSON Support --></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">name</span>=<span class="string">"viewResolver"</span> <span class="attr">class</span>=<span class="string">"org.springframework.web.servlet.view.BeanNameViewResolver"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">name</span>=<span class="string">"jsonTemplate"</span> <span class="attr">class</span>=<span class="string">"org.springframework.web.servlet.view.json.MappingJackson2JsonView"</span>/></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></table></figure>
<p>但是,<code>Spring Boot</code> 项目,我们只需要添加相关依赖,无需配置,通过启动下面的 <code>main()</code> 方法即可。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoApplication</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(DemoApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>并且,我们通过 <code>Spring Boot</code> 的全局配置文件 <code>application.properties</code> 或 <code>application.yml</code>即可对项目进行设置比如更换端口号,配置 JPA 属性等等。</p>
<p><strong>为什么 Spring Boot 使用起来这么酸爽呢?</strong> </p>
<p>这得益于其自动装配。</p>
<p><strong>自动装配可以说是 Spring Boot 的核心,那究竟什么是自动装配呢?</strong></p>
<h2 id="什么是-SpringBoot-自动装配?"><a href="#什么是-SpringBoot-自动装配?" class="headerlink" title="什么是 SpringBoot 自动装配?"></a>什么是 SpringBoot 自动装配?</h2><p>我们现在提到自动装配的时候,一般会和 <code>Spring Boot</code> 联系在一起。但是,实际上 <code>Spring Framework</code> 早就实现了这个功能。`Spring Boot 只是在其基础上,通过 SPI 的方式,做了进一步优化。</p>
<blockquote>
<p><code>SpringBoot</code> 定义了一套接口规范,这套规范规定:<code>SpringBoot</code> 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 <code>SpringBoot</code> 定义的标准,就能将自己的功能装置进 <code>SpringBoot</code>。</p>
</blockquote>
<p>没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Spring Boot 中,我们直接引入一个 starter 即可。比如你想要在项目中使用 redis 的话,直接在项目中引入对应的 starter 即可。</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-data-redis<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<p>引入 starter 之后,我们通过少量注解和一些简单的配置就能使用第三方组件提供的功能了。</p>
<p>在我看来,自动装配可以简单理解为:<strong>通过注解或者一些简单的配置就能在 <code>Spring Boot</code> 的帮助下实现某块功能。</strong></p>
<h2 id="SpringBoot-是如何实现自动装配的?"><a href="#SpringBoot-是如何实现自动装配的?" class="headerlink" title="SpringBoot 是如何实现自动装配的?"></a>SpringBoot 是如何实现自动装配的?</h2><p>我们先看一下 <code>SpringBoot</code> 的核心注解 <code>SpringBootApplication</code> 。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Target({ElementType.TYPE})</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Inherited</span></span><br><span class="line"><<span class="number">1.</span>><span class="meta">@SpringBootConfiguration</span></span><br><span class="line"><<span class="number">2.</span>><span class="meta">@ComponentScan</span></span><br><span class="line"><<span class="number">3.</span>><span class="meta">@EnableAutoConfiguration</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> SpringBootApplication {</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//@SpringBootConfiguration 注解</span></span><br><span class="line"><span class="meta">@Target({ElementType.TYPE})</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Configuration</span> <span class="comment">//实际上它也是一个配置类</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> SpringBootConfiguration {</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>大概可以把 <code>@SpringBootApplication</code>看作是 <code>@Configuration</code>、<code>@EnableAutoConfiguration</code>、<code>@ComponentScan</code> 注解的集合。根据 <code>SpringBoot</code> 官网,这三个注解的作用分别是:</p>
<p><strong><code>@EnableAutoConfiguration</code>:</strong> 启用 <code>SpringBoot</code> 的自动配置机制</p>
<p><strong><code>@Configuration</code>:</strong> 允许在上下文中注册额外的 bean 或导入其他配置类</p>
<p><strong><code>@ComponentScan</code>:</strong> 扫描被<code>@Component (@Service,@Controller)</code>注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如下图所示,容器中将排除 <code>TypeExcludeFilter</code> 和 <code>AutoConfigurationExcludeFilter</code>。</p>
<p><img src="SpringBootApplication%E6%B3%A8%E8%A7%A3.jpeg" alt="SpringBootApplication注解"></p>
<p><code>@EnableAutoConfiguration</code> 是实现自动装配的重要注解,我们以这个注解入手。</p>
<p><strong><code>@EnableAutoConfiguration</code>: 实现自动装配的核心注解</strong></p>
<p><code>EnableAutoConfiguration</code> 只是一个简单地注解,自动装配核心功能的实现实际是通过 <code>AutoConfigurationImportSelector</code> 类。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Target({ElementType.TYPE})</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Inherited</span></span><br><span class="line"><span class="meta">@AutoConfigurationPackage</span> <span class="comment">//作用:将main包下的所欲组件注册到容器中</span></span><br><span class="line"><span class="meta">@Import({AutoConfigurationImportSelector.class})</span> <span class="comment">//加载自动装配类 xxxAutoconfiguration</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> EnableAutoConfiguration {</span><br><span class="line"> String ENABLED_OVERRIDE_PROPERTY = <span class="string">"spring.boot.enableautoconfiguration"</span>;</span><br><span class="line"></span><br><span class="line"> Class<?>[] exclude() <span class="keyword">default</span> {};</span><br><span class="line"></span><br><span class="line"> String[] excludeName() <span class="keyword">default</span> {};</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们现在重点分析下 <code>AutoConfigurationImportSelector</code> 类到底做了什么?</p>
<p><strong><code>AutoConfigurationImportSelector</code>: 加载自动装配类</strong></p>
<p><code>AutoConfigurationImportSelector</code>类的继承体系如下:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AutoConfigurationImportSelector</span> <span class="keyword">implements</span> <span class="title">DeferredImportSelector</span>, <span class="title">BeanClassLoaderAware</span>, <span class="title">ResourceLoaderAware</span>, <span class="title">BeanFactoryAware</span>, <span class="title">EnvironmentAware</span>, <span class="title">Ordered</span> </span>{</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">DeferredImportSelector</span> <span class="keyword">extends</span> <span class="title">ImportSelector</span> </span>{</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ImportSelector</span> </span>{</span><br><span class="line"> String[] selectImports(AnnotationMetadata var1);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>可以看出,<code>AutoConfigurationImportSelector</code> 类实现了 <code>ImportSelector</code> 接口,也就实现了这个接口中的 selectImports方法,<strong>该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String[] NO_IMPORTS = <span class="keyword">new</span> String[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> String[] selectImports(AnnotationMetadata annotationMetadata) {</span><br><span class="line"> <span class="comment">// <1>.判断自动装配开关是否打开</span></span><br><span class="line"> <span class="keyword">if</span> (!<span class="keyword">this</span>.isEnabled(annotationMetadata)) {</span><br><span class="line"> <span class="keyword">return</span> NO_IMPORTS;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//<2>.获取所有需要装配的bean</span></span><br><span class="line"> AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(<span class="keyword">this</span>.beanClassLoader);</span><br><span class="line"> AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = <span class="keyword">this</span>.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);</span><br><span class="line"> <span class="keyword">return</span> StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>这里我们需要重点关注一下<code>getAutoConfigurationEntry()</code>方法,这个方法主要负责加载自动配置类的。</p>
<p>该方法调用链如下:</p>
<p><img src="getAutoConfigurationEntry%E8%B0%83%E7%94%A8%E9%93%BE.jpeg" alt="getAutoConfigurationEntry调用链"></p>
<p>现在我们结合<code>getAutoConfigurationEntry()</code>的源码来详细分析一下:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> AutoConfigurationEntry EMPTY_ENTRY = <span class="keyword">new</span> AutoConfigurationEntry();</span><br><span class="line"></span><br><span class="line"><span class="function">AutoConfigurationEntry <span class="title">getAutoConfigurationEntry</span><span class="params">(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata)</span> </span>{</span><br><span class="line"> <span class="comment">//<1>.</span></span><br><span class="line"> <span class="keyword">if</span> (!<span class="keyword">this</span>.isEnabled(annotationMetadata)) {</span><br><span class="line"> <span class="keyword">return</span> EMPTY_ENTRY;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//<2>.</span></span><br><span class="line"> AnnotationAttributes attributes = <span class="keyword">this</span>.getAttributes(annotationMetadata);</span><br><span class="line"> <span class="comment">//<3>.</span></span><br><span class="line"> List<String> configurations = <span class="keyword">this</span>.getCandidateConfigurations(annotationMetadata, attributes);</span><br><span class="line"> <span class="comment">//<4>.</span></span><br><span class="line"> configurations = <span class="keyword">this</span>.removeDuplicates(configurations);</span><br><span class="line"> Set<String> exclusions = <span class="keyword">this</span>.getExclusions(annotationMetadata, attributes);</span><br><span class="line"> <span class="keyword">this</span>.checkExcludedClasses(configurations, exclusions);</span><br><span class="line"> configurations.removeAll(exclusions);</span><br><span class="line"> configurations = <span class="keyword">this</span>.filter(configurations, autoConfigurationMetadata);</span><br><span class="line"> <span class="keyword">this</span>.fireAutoConfigurationImportEvents(configurations, exclusions);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p><strong>第 1 步:</strong></p>
<p>判断自动装配开关是否打开。默认<code>spring.boot.enableautoconfiguration=true</code>,可在 <code>application.properties</code> 或 <code>application.yml</code> 中设置</p>
<p><img src="%E8%87%AA%E5%8A%A8%E8%A3%85%E9%85%8D%E5%BC%80%E5%85%B3.jpeg" alt="自动装配开关"></p>
<p><strong>第 2 步 :</strong></p>
<p>用于获取<code>EnableAutoConfiguration</code>注解中的 <code>exclude</code> 和 <code>excludeName。</code></p>
<p><img src="EnableAutoConfiguration%E8%8E%B7%E5%8F%96exclude%E5%92%8CexcludeName.jpeg" alt="EnableAutoConfiguration获取exclude和excludeName"></p>
<p><strong>第 3 步</strong></p>
<p>获取需要自动装配的所有配置类,读取<code>META-INF/spring.factories</code></p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories</span><br></pre></td></tr></table></figure>
<p><img src="%E8%8E%B7%E5%8F%96%E8%87%AA%E5%8A%A8%E8%A3%85%E9%85%8D%E7%9A%84%E6%89%80%E6%9C%89%E9%85%8D%E7%BD%AE%E7%B1%BB.jpeg" alt="获取自动装配的所有配置类"></p>
<p>从下图可以看到这个文件的配置内容都被我们读取到了。<code>XXXAutoConfiguration</code>的作用就是按需加载组件。</p>
<p><img src="spring.factories.jpeg" alt="spring.factories.jpeg"></p>
<p>不光是这个依赖下的<code>META-INF/spring.factories</code>被读取到,所有 <code>Spring Boot Starter</code> 下的<code>META-INF/spring.factories</code>都会被读取到。</p>
<p>所以,你可以清楚滴看到, druid 数据库连接池的 <code>Spring Boot Starter</code> 就创建了<code>META-INF/spring.factories</code>文件。</p>
<p>如果,我们自己要创建一个 <code>Spring Boot Starter</code>,这一步是必不可少的。</p>
<p><img src="ceshi.jpeg" alt="ceshi"></p>
<p><strong>第 4 步 :</strong></p>
<p>到这里可能面试官会问你:“<code>spring.factories</code>中这么多配置,每次启动都要全部加载么?”。</p>
<p>很明显,这是不现实的。我们 debug 到后面你会发现,<code>configurations</code> 的值变小了。</p>
<p><img src="ConditionalOnXXX%E7%AD%9B%E9%80%89.jpeg" alt="ConditionalOnXXX筛选"></p>
<p>因为,这一步有经历了一遍筛选,<code>@ConditionalOnXXX</code> 中的所有条件都满足,该类才会生效。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="comment">// 检查相关的类:RabbitTemplate 和 Channel是否存在</span></span><br><span class="line"><span class="comment">// 存在才会加载</span></span><br><span class="line"><span class="meta">@ConditionalOnClass({ RabbitTemplate.class, Channel.class })</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties(RabbitProperties.class)</span></span><br><span class="line"><span class="meta">@Import(RabbitAnnotationDrivenConfiguration.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RabbitAutoConfiguration</span> </span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>有兴趣的童鞋可以详细了解下 Spring Boot 提供的条件注解</p>
<ul>
<li><code>@ConditionalOnBean</code>:当容器里有指定 Bean 的条件下</li>
<li><code>@ConditionalOnMissingBean</code>:当容器里没有指定 Bean 的情况下</li>
<li><code>@ConditionalOnSingleCandidate</code>:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean</li>
<li><code>@ConditionalOnClass</code>:当类路径下有指定类的条件下</li>
<li><code>@ConditionalOnMissingClass</code>:当类路径下没有指定类的条件下</li>
<li><code>@ConditionalOnProperty</code>:指定的属性是否有指定的值</li>
<li><code>@ConditionalOnResource</code>:类路径是否有指定的值</li>
<li><code>@ConditionalOnExpression</code>:基于 SpEL 表达式作为判断条件</li>
<li><code>@ConditionalOnJava</code>:基于 Java 版本作为判断条件</li>
<li><code>@ConditionalOnJndi</code>:在 JNDI 存在的条件下差在指定的位置</li>
<li><code>@ConditionalOnNotWebApplication</code>:当前项目不是 Web 项目的条件下</li>
<li><code>@ConditionalOnWebApplication</code>:当前项目是 Web 项 目的条件下</li>
</ul>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><strong><code>Spring Boot</code> 通过<code>@EnableAutoConfiguration</code>开启自动装配,通过 <code>SpringFactoriesLoader</code> 最终加载<code>META-INF/spring.factories</code>中的自动配置类实现自动装配,自动配置类其实就是通过<code>@Conditional</code>按需加载的配置类,想要其生效必须引入<code>spring-boot-starter-xxx</code>包实现起步依赖</strong></p>
<p>本文转载自 <a href="https://www.cnblogs.com/javaguide/p/springboot-auto-config.html">淘宝一面:“说一下 Spring Boot 自动装配原理呗?”</a></p>
]]></content>
<categories>
<category>Java</category>
<category>SpringBoot</category>
</categories>
<tags>
<tag>源码分析</tag>
</tags>
</entry>
<entry>
<title>flask 统一异常处理以及400 500页面</title>
<url>/2020/10/13/Flask%20%E7%BB%9F%E4%B8%80%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E4%BB%A5%E5%8F%8A400-500%E9%A1%B5%E9%9D%A2/</url>
<content><![CDATA[<h3 id="在flask中如何统一处理异常以及400-500页面"><a href="#在flask中如何统一处理异常以及400-500页面" class="headerlink" title="在flask中如何统一处理异常以及400 500页面"></a>在flask中如何统一处理异常以及400 500页面</h3><h4 id="异常类"><a href="#异常类" class="headerlink" title="异常类"></a>异常类</h4><a id="more"></a>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="comment"># Author : Yang Hao</span></span><br><span class="line"><span class="comment"># File : exception_common.py</span></span><br><span class="line"><span class="comment"># Software: PyCharm</span></span><br><span class="line"><span class="comment"># Time : 2020/3/30 17:35</span></span><br><span class="line"><span class="comment"># Description:</span></span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> jsonify, render_template</span><br><span class="line"><span class="keyword">from</span> app <span class="keyword">import</span> app</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ExceptionCommon</span>(<span class="params">Exception</span>):</span></span><br><span class="line"> <span class="comment"># 默认的返回码</span></span><br><span class="line"> status_code = <span class="number">400</span></span><br><span class="line"> <span class="comment"># 可以自己定义return_code</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, message=<span class="literal">None</span>, status_code=<span class="literal">None</span>, payload=<span class="literal">None</span></span>):</span></span><br><span class="line"> Exception.__init__(self)</span><br><span class="line"> self.message = message</span><br><span class="line"> <span class="keyword">if</span> status_code <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line"> self.status_code = status_code</span><br><span class="line"> self.payload = payload</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 构造要返回的错误代码和错误信息的 dict</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">to_dict</span>(<span class="params">self</span>):</span></span><br><span class="line"> rv = <span class="built_in">dict</span>(self.payload <span class="keyword">or</span> ())</span><br><span class="line"> rv[<span class="string">'status_code'</span>] = self.status_code</span><br><span class="line"> rv[<span class="string">'message'</span>] = self.message</span><br><span class="line"> <span class="keyword">return</span> rv</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.errorhandler(ExceptionCommon)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">handle_invalid_usage</span>(<span class="params">error</span>):</span></span><br><span class="line"> response = jsonify(error.to_dict())</span><br><span class="line"> response.status_code = error.status_code</span><br><span class="line"> <span class="keyword">return</span> response</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.errorhandler(500)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">handle_500</span>(<span class="params">e</span>):</span></span><br><span class="line"> <span class="keyword">return</span> render_template(<span class="string">'500.html'</span>), <span class="number">404</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.errorhandler(404)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">page_not_found</span>(<span class="params">e</span>):</span></span><br><span class="line"> <span class="keyword">return</span> render_template(<span class="string">'404.html'</span>), <span class="number">404</span></span><br></pre></td></tr></table></figure>
<h4 id="如何使用"><a href="#如何使用" class="headerlink" title="如何使用"></a>如何使用</h4><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@auth.route('/logout', methods=('GET', 'POST'))</span></span><br><span class="line"><span class="meta">@login_required</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">logout</span>():</span></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> 注销登录,解除会话 logout_user()</span></span><br><span class="line"><span class="string"> :return:</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> xxx</span><br><span class="line"> <span class="keyword">raise</span> ExceptionCommon(<span class="string">"错误原因"</span>)</span><br><span class="line"> <span class="keyword">return</span> redirect(url_for(<span class="string">"auth.login"</span>))</span><br></pre></td></tr></table></figure>
<h4 id="在整个web项目中使用"><a href="#在整个web项目中使用" class="headerlink" title="在整个web项目中使用"></a>在整个web项目中使用</h4><p>上述<code>@app.errorhandler</code> 这个errorhandler处理器注册在app上,只能对app使用</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">@app.errorhandler(ExceptionCommon)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">handle_invalid_usage</span>(<span class="params">error</span>):</span></span><br><span class="line"> response = jsonify(error.to_dict())</span><br><span class="line"> response.status_code = error.status_code</span><br><span class="line"> <span class="keyword">return</span> response</span><br></pre></td></tr></table></figure>
<p>修改为以下内容<code>@comm.app_errorhandler</code>需要注册comm的蓝本</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">@comm.app_errorhandler(ExceptionCommon)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">handle_invalid_usage</span>(<span class="params">error</span>):</span></span><br><span class="line"> response = jsonify(error.to_dict())</span><br><span class="line"> response.status_code = error.status_code</span><br><span class="line"> <span class="keyword">return</span> response</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category>Python</category>
<category>Flask</category>
</categories>
</entry>
<entry>
<title>SpringBoot Websocket 注入bean失败</title>
<url>/2020/09/19/SpringBoot%20Websocket%20%E6%B3%A8%E5%85%A5bean%E5%A4%B1%E8%B4%A5/</url>
<content><![CDATA[<p>踩坑记录</p>
<p>第一种方式:将messageService 注入</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">@ServerEndpoint(value = "/websocket")</span><br><span class="line">@Component</span><br><span class="line">public class WebSocketServer {</span><br><span class="line"></span><br><span class="line"> //这里使用静态,让 service 属于类</span><br><span class="line"> public static MessageServiceImpl messageService;</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * 给类的service 注入</span><br><span class="line"> * spring管理的都是单例(singleton)和 websocket (多对象)相冲突</span><br><span class="line"> *</span><br><span class="line"> * @param messageService</span><br><span class="line"> */</span><br><span class="line"> @Autowired</span><br><span class="line"> public void setMessageService(MessageServiceImpl messageService) {</span><br><span class="line"> WebSocketServer.messageService = messageService;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<a id="more"></a>
<p>第二种方式<br>或者将</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">@Autowired</span><br><span class="line"> public void setMessageService(MessageServiceImpl messageService) {</span><br><span class="line"> WebSocketServer.messageService = messageService;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>写在<strong>websocketConfig</strong>中</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">@Configuration</span><br><span class="line">public class WebSocketConfig {</span><br><span class="line"></span><br><span class="line"> @Bean</span><br><span class="line"> public ServerEndpointExporter serverEndpointExporter() {</span><br><span class="line"> return new ServerEndpointExporter();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> *手动注入一个全局变量</span><br><span class="line"> * @param messageService</span><br><span class="line"> */</span><br><span class="line"> @Autowired</span><br><span class="line"> public void setMessageService(MessageServiceImpl messageService) {</span><br><span class="line"> WebSocketServer.messageService = messageService;</span><br><span class="line"> }</span><br><span class="line">} </span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>Java</category>
<category>SpringBoot</category>
</categories>
<tags>
<tag>踩坑</tag>
</tags>
</entry>
<entry>
<title>MySQL 5.7 返回json数据 中文乱码问题</title>
<url>/2021/01/11/MySQL%205.7%20%E8%BF%94%E5%9B%9Ejson%E6%95%B0%E6%8D%AE-%E4%B8%AD%E6%96%87%E4%B9%B1%E7%A0%81%E9%97%AE%E9%A2%98/</url>
<content><![CDATA[<p>mysql版本为5.7,通过navicat查看json格式的数据是正常显示,但是查询出来是乱码, 读取其他字段都是正常的,也没有特殊设置json格式的字符集</p>
<a id="more"></a>
<p>mysql驱动jdbc是这个版本</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"><dependency></span><br><span class="line"> <groupId>mysql</groupId></span><br><span class="line"> <artifactId>mysql-connector-java</artifactId></span><br><span class="line"> <version>5.1.36</version></span><br><span class="line"></dependency></span><br></pre></td></tr></table></figure>
<p>查资料才发现要jdbc 5.1.40以上,<br>更新依赖解决</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"><dependency></span><br><span class="line"> <groupId>mysql</groupId></span><br><span class="line"> <artifactId>mysql-connector-java</artifactId></span><br><span class="line"> <version>5.1.40</version></span><br><span class="line"></dependency></span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>Java</category>
<category>Mysql</category>
</categories>
<tags>
<tag>踩坑</tag>
</tags>
</entry>
<entry>
<title>Ubuntu 16.04 pptp搭建服务端和客户端</title>
<url>/2020/11/19/Ubuntu%2016.04%20PPTP%E6%90%AD%E5%BB%BA%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%92%8C%E5%AE%A2%E6%88%B7%E7%AB%AF/</url>
<content><![CDATA[<p>本文主要是记录ubuntu 16.04安装pptp 服务端以及客户端的过程</p>
<a id="more"></a>
<h2 id="服务端配置"><a href="#服务端配置" class="headerlink" title="服务端配置"></a>服务端配置</h2><h3 id="安装pptp"><a href="#安装pptp" class="headerlink" title="安装pptp"></a>安装pptp</h3><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">sudo apt<span class="literal">-get</span> install pptpd</span><br></pre></td></tr></table></figure>
<h3 id="修改pptpd-conf中的配置信息"><a href="#修改pptpd-conf中的配置信息" class="headerlink" title="修改pptpd.conf中的配置信息"></a>修改pptpd.conf中的配置信息</h3><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">sudo vim /etc/pptpd.conf</span><br></pre></td></tr></table></figure>
<p>在末尾增加下面两行,或者打开的内容里面找到这两行,取消掉注释</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">localip <span class="number">192.168</span>.<span class="number">0.1</span></span><br><span class="line">remoteip <span class="number">192.168</span>.<span class="number">0.234</span><span class="literal">-238</span>,<span class="number">192.168</span>.<span class="number">0.245</span></span><br></pre></td></tr></table></figure>
<p>分别为创建pptp时的主机ip和连接pptp的其他主机使用的ip段,可以自行修改。<br>注意,这里的ip并不是指外网ip或者当前局域网ip,而是指创建(虚拟专用网络)会分配的ip地址。一般这个可以不用修改。</p>
<h3 id="修改chap-secrets配置"><a href="#修改chap-secrets配置" class="headerlink" title="修改chap-secrets配置"></a>修改chap-secrets配置</h3><p>连接pptp 所需要的账号和密码,修改配置文件<code>/etc/ppp/chap-secrets</code></p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">sudo vim /etc/ppp/chap<span class="literal">-secrets</span></span><br></pre></td></tr></table></figure>
<p>在末尾添加以下内容</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="comment">#用户名 pptpd 密码 *</span></span><br><span class="line">neo pptpd <span class="number">123456</span> *</span><br></pre></td></tr></table></figure>
<p>末尾的<em>表示可以使用任意IP连入,如果你要设置指定IP才能连接,可以将</em>替换成对应的IP。支持添加多个账号。</p>
<h3 id="设置ms-dns"><a href="#设置ms-dns" class="headerlink" title="设置ms-dns"></a>设置ms-dns</h3><p>配置使用的dns,修改配置文件</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">sudo vim /etc/ppp/pptpd<span class="literal">-options</span></span><br></pre></td></tr></table></figure>
<p>在末尾增加下面两行,或者打开的内容里面找到这两行,取消掉注释</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 这是谷歌的DNS 可以根据实际填写</span></span><br><span class="line">ms<span class="literal">-dns</span> <span class="number">8.8</span>.<span class="number">8.8</span></span><br><span class="line">ms<span class="literal">-dns</span> <span class="number">8.8</span>.<span class="number">4.4</span></span><br></pre></td></tr></table></figure>
<h3 id="开启转发"><a href="#开启转发" class="headerlink" title="开启转发"></a>开启转发</h3><p>修改配置文件</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">sudo vim /etc/sysctl.conf</span><br></pre></td></tr></table></figure>
<p>在末尾增加下面内容,或者打开的内容里面找到这一行,取消掉注释</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">net.ipv4.ip_forward=<span class="number">1</span></span><br></pre></td></tr></table></figure>
<p>保存之后执行</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">sudo sysctl <span class="literal">-p</span></span><br></pre></td></tr></table></figure>
<h3 id="配置iptables"><a href="#配置iptables" class="headerlink" title="配置iptables"></a>配置iptables</h3><p>若未安装iptables 执行脚本安装</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">sudo apt<span class="literal">-get</span> install iptables</span><br></pre></td></tr></table></figure>
<blockquote>
<p><strong>tips:若之前安装pptp失败的。执行以下脚本;如果是第一次安装可忽略以下内容(目的为了清除iptables里旧的规则)</strong></p>
</blockquote>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="number">1</span>. sudo iptables <span class="operator">-F</span></span><br><span class="line"><span class="number">2</span>. sudo iptables <span class="literal">-X</span></span><br><span class="line"><span class="number">3</span>. sudo iptables <span class="literal">-t</span> nat <span class="operator">-F</span></span><br><span class="line"><span class="number">4</span>. sudo iptables <span class="literal">-t</span> nat <span class="literal">-X</span></span><br></pre></td></tr></table></figure>
<p>然后,允许GRE协议以及1723端口、47端口:</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">sudo iptables <span class="literal">-A</span> INPUT <span class="literal">-p</span> gre <span class="literal">-j</span> ACCEPT </span><br><span class="line">sudo iptables <span class="literal">-A</span> INPUT <span class="literal">-p</span> tcp -<span class="literal">-dport</span> <span class="number">1723</span> <span class="literal">-j</span> ACCEPT </span><br><span class="line">sudo iptables <span class="literal">-A</span> INPUT <span class="literal">-p</span> tcp -<span class="literal">-dport</span> <span class="number">47</span> <span class="literal">-j</span> ACCEPT</span><br></pre></td></tr></table></figure>
<p>下一步,开启NAT转发:</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">sudo iptables <span class="literal">-t</span> nat <span class="literal">-A</span> POSTROUTING <span class="literal">-s</span> <span class="number">192.168</span>.<span class="number">0.0</span>/<span class="number">24</span> <span class="literal">-o</span> eno33 <span class="literal">-j</span> MASQUERADE</span><br></pre></td></tr></table></figure>
<p>注意,上面的<code>eno33</code>是连接网络的网卡的名称,不同机器这个可能是不一样的。可以在终端输入ifconfig来查看。例如</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">neo@ubuntu:~<span class="variable">$</span> ifconfig</span><br><span class="line">ens33 Link encap:Ethernet HWaddr <span class="number">00</span>:<span class="number">0</span>c:<span class="number">29</span>:<span class="number">37</span>:<span class="number">79</span>:<span class="number">85</span> </span><br><span class="line"> inet addr:xxx.xxx.xxx.xxx Bcast:xxx.xxx.xxx.xxx Mask:<span class="number">255.255</span>.<span class="number">255.0</span></span><br><span class="line"> inet6 addr: xxxx::<span class="number">20</span>c:<span class="number">29</span>ff:fe37:xxxx/<span class="number">64</span> Scope:Link</span><br><span class="line"> UP BROADCAST RUNNING MULTICAST MTU:<span class="number">1500</span> Metric:<span class="number">1</span></span><br><span class="line"> RX packets:<span class="number">293</span> errors:<span class="number">0</span> dropped:<span class="number">0</span> overruns:<span class="number">0</span> frame:<span class="number">0</span></span><br><span class="line"> TX packets:<span class="number">211</span> errors:<span class="number">0</span> dropped:<span class="number">0</span> overruns:<span class="number">0</span> carrier:<span class="number">0</span></span><br><span class="line"> collisions:<span class="number">0</span> txqueuelen:<span class="number">1000</span> </span><br><span class="line"> RX bytes:<span class="number">26801</span> (<span class="number">26.8</span> KB) TX bytes:<span class="number">41763</span> (<span class="number">41.7</span> KB)</span><br><span class="line"></span><br><span class="line">lo Link encap:Local Loopback </span><br><span class="line"> inet addr:<span class="number">127.0</span>.<span class="number">0.1</span> Mask:<span class="number">255.0</span>.<span class="number">0.0</span></span><br><span class="line"> inet6 addr: ::<span class="number">1</span>/<span class="number">128</span> Scope:Host</span><br><span class="line"> UP LOOPBACK RUNNING MTU:<span class="number">65536</span> Metric:<span class="number">1</span></span><br><span class="line"> RX packets:<span class="number">160</span> errors:<span class="number">0</span> dropped:<span class="number">0</span> overruns:<span class="number">0</span> frame:<span class="number">0</span></span><br><span class="line"> TX packets:<span class="number">160</span> errors:<span class="number">0</span> dropped:<span class="number">0</span> overruns:<span class="number">0</span> carrier:<span class="number">0</span></span><br><span class="line"> collisions:<span class="number">0</span> txqueuelen:<span class="number">1</span> </span><br><span class="line"> RX bytes:<span class="number">11840</span> (<span class="number">11.8</span> KB) TX bytes:<span class="number">11840</span> (<span class="number">11.8</span> KB)</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<h3 id="重启pptp服务"><a href="#重启pptp服务" class="headerlink" title="重启pptp服务"></a>重启pptp服务</h3><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">sudo service pptpd restart</span><br></pre></td></tr></table></figure>
<h2 id="客户端配置"><a href="#客户端配置" class="headerlink" title="客户端配置"></a>客户端配置</h2><p>在另一个服务器(本文是用的是两个虚拟机)中执行以下内容</p>
<h3 id="安装pptp客户端"><a href="#安装pptp客户端" class="headerlink" title="安装pptp客户端"></a>安装pptp客户端</h3><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">sudo apt<span class="literal">-get</span> install pptp<span class="literal">-linux</span></span><br></pre></td></tr></table></figure>
<h3 id="初始化一个连接通道:mypptp"><a href="#初始化一个连接通道:mypptp" class="headerlink" title="初始化一个连接通道:mypptp"></a>初始化一个连接通道:mypptp</h3><p>使用服务端设置的账号密码<code>neo/6yhn^YHN</code></p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">sudo pptpsetup -<span class="literal">-create</span> mypptp -<span class="literal">-server</span> xxx.xxx.xxx.xxx -<span class="literal">-username</span> neo -<span class="literal">-password</span> <span class="number">6</span>yhn^YHN -<span class="literal">-encrypt</span> -<span class="literal">-start</span></span><br></pre></td></tr></table></figure>
<p><strong>xxx.xxx.xxx.xxx</strong> 是pptp mypptp服务端的ip地址 根据实际情况填写<br>(以下示例)</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">root@ubuntu:~# sudo pptpsetup --create mypptp --server 172.31.1.112 --username neo --password 6yhn^YHN --encrypt --start</span><br><span class="line">Using interface ppp0</span><br><span class="line">Connect: ppp0 <--> /dev/pts/2</span><br><span class="line"></span><br><span class="line">CHAP authentication succeeded</span><br><span class="line">MPPE 128-bit stateless compression enabled</span><br><span class="line">local IP address 192.168.0.234</span><br><span class="line">remote IP address 192.168.0.1</span><br><span class="line">root@ubuntu:~# </span><br></pre></td></tr></table></figure>
<h3 id="查看连接是否成功"><a href="#查看连接是否成功" class="headerlink" title="查看连接是否成功"></a>查看连接是否成功</h3><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">root@ubuntu:~<span class="comment"># ip addr show</span></span><br><span class="line"><span class="number">1</span>: lo: <LOOPBACK,UP,LOWER_UP> mtu <span class="number">65536</span> qdisc noqueue state UNKNOWN <span class="built_in">group</span> default qlen <span class="number">1</span></span><br><span class="line"> link/loopback <span class="number">00</span>:<span class="number">00</span>:<span class="number">00</span>:<span class="number">00</span>:<span class="number">00</span>:<span class="number">00</span> brd <span class="number">00</span>:<span class="number">00</span>:<span class="number">00</span>:<span class="number">00</span>:<span class="number">00</span>:<span class="number">00</span></span><br><span class="line"> inet <span class="number">127.0</span>.<span class="number">0.1</span>/<span class="number">8</span> scope host lo</span><br><span class="line"> valid_lft forever preferred_lft forever</span><br><span class="line"> inet6 ::<span class="number">1</span>/<span class="number">128</span> scope host </span><br><span class="line"> valid_lft forever preferred_lft forever</span><br><span class="line"><span class="number">2</span>: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu <span class="number">1500</span> qdisc pfifo_fast state UP <span class="built_in">group</span> default qlen <span class="number">1000</span></span><br><span class="line"> link/ether <span class="number">00</span>:<span class="number">0</span>c:<span class="number">29</span>:xx:<span class="number">86</span>:<span class="number">5</span>f brd ff:ff:ff:ff:ff:ff</span><br><span class="line"> inet .<span class="number">31.1</span>.<span class="number">113</span>/<span class="number">24</span> brd xxx.<span class="number">31.1</span>.<span class="number">2</span>xxx5 scope global ens33</span><br><span class="line"> valid_lft forever preferred_lft forever</span><br><span class="line"> inet6 fxx0::<span class="number">20</span>c:<span class="number">29</span>ff:fxx3e:<span class="number">8</span>xxf/<span class="number">64</span> scope link </span><br><span class="line"> valid_lft forever preferred_lft forever</span><br><span class="line"><span class="number">8</span>: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu <span class="number">1496</span> qdisc pfifo_fast state UNKNOWN <span class="built_in">group</span> default qlen <span class="number">3</span></span><br><span class="line"> link/ppp </span><br><span class="line"> inet <span class="number">192.168</span>.<span class="number">0.234</span> peer <span class="number">192.168</span>.<span class="number">0.1</span>/<span class="number">32</span> scope global ppp0</span><br><span class="line"> valid_lft forever preferred_lft forever</span><br><span class="line">root@ubuntu:~<span class="comment"># </span></span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>也可以在pptp 服务端查看</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">neo@ubuntu:~<span class="variable">$</span> route <span class="literal">-n</span></span><br><span class="line">Kernel IP routing table</span><br><span class="line">Destination Gateway Genmask Flags Metric Ref Use Iface</span><br><span class="line"><span class="number">0.0</span>.<span class="number">0.0</span> xxx.xxx.<span class="number">1.1</span> <span class="number">0.0</span>.<span class="number">0.0</span> UG <span class="number">0</span> <span class="number">0</span> <span class="number">0</span> ens33</span><br><span class="line">xxx.xxx.<span class="number">1.0</span> <span class="number">0.0</span>.<span class="number">0.0</span> <span class="number">255.255</span>.<span class="number">255.0</span> U <span class="number">0</span> <span class="number">0</span> <span class="number">0</span> ens33</span><br><span class="line"><span class="number">192.168</span>.<span class="number">0.234</span> <span class="number">0.0</span>.<span class="number">0.0</span> <span class="number">255.255</span>.<span class="number">255.255</span> UH <span class="number">0</span> <span class="number">0</span> <span class="number">0</span> ppp0</span><br><span class="line">neo@ubuntu:~<span class="variable">$</span> </span><br></pre></td></tr></table></figure>
<h3 id="断开重启pptp客户端"><a href="#断开重启pptp客户端" class="headerlink" title="断开重启pptp客户端"></a>断开重启pptp客户端</h3><p>断开连接</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">poff mypptp</span><br></pre></td></tr></table></figure>
<p> 重接mypptp</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">pon mypptp</span><br></pre></td></tr></table></figure>
<h3 id="处理错误"><a href="#处理错误" class="headerlink" title="处理错误"></a>处理错误</h3><p>LCP: timeout sending Config-Requests</p>
<p>执行:<code>sudo modprobe nf_conntrack_pptp</code></p>
]]></content>
<categories>
<category>Ubuntu</category>
<category>PPTP</category>
</categories>
<tags>
<tag>Ubuntu</tag>
<tag>PPTP</tag>
</tags>
</entry>
<entry>
<title>Ubuntu 16.04 使用root ssh登录</title>
<url>/2020/09/28/Ubuntu%2016.04%20%E4%BD%BF%E7%94%A8root%20ssh%E7%99%BB%E5%BD%95/</url>
<content><![CDATA[<p><strong>如果你只是想登陆别的机器的SSH只需要安装openssh-client(ubuntu有默认安装,如果没有则sudoapt-get install openssh-client),如果要使本机开放SSH服务就需要安装openssh-server。</strong></p>
<a id="more"></a>
<ol>
<li><p>安装openssh-server</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">sudo apt-get install openssh-server</span><br></pre></td></tr></table></figure></li>
<li><p>修改 root 密码</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">sudo passwd root</span><br></pre></td></tr></table></figure></li>
<li><p>修改配置文件</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">sudo vi /etc/ssh/sshd_config</span><br></pre></td></tr></table></figure>
<p> 找到下面相关配置:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"># Authentication:</span><br><span class="line">LoginGraceTime 120</span><br><span class="line">PermitRootLogin prohibit-password</span><br><span class="line">StrictModes yes</span><br></pre></td></tr></table></figure>
<p> 更改为:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"># Authentication:</span><br><span class="line">LoginGraceTime 120</span><br><span class="line">#PermitRootLogin prohibit-password</span><br><span class="line">PermitRootLogin yes</span><br><span class="line">StrictModes yes</span><br></pre></td></tr></table></figure></li>
<li><p>重启ssh</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">sudo service ssh restart</span><br></pre></td></tr></table></figure>
</li>
</ol>
]]></content>
<categories>
<category>Ubuntu</category>
</categories>
<tags>
<tag>Ubuntu</tag>
</tags>
</entry>
<entry>
<title>Ubuntu server 如何升级内核</title>
<url>/2020/12/13/Ubuntu%20server%20%E5%A6%82%E4%BD%95%E5%8D%87%E7%BA%A7%E5%86%85%E6%A0%B8/</url>
<content><![CDATA[<h3 id="查看目前内核"><a href="#查看目前内核" class="headerlink" title="查看目前内核"></a>查看目前内核</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">root@ubuntu:~# uname -sr</span><br><span class="line">Linux 4.4.0-142-generic</span><br></pre></td></tr></table></figure>
<h3 id="开始升级内核"><a href="#开始升级内核" class="headerlink" title="开始升级内核"></a>开始升级内核</h3> <a id="more"></a>
<p><a href="http://kernel.ubuntu.com/~kernel-ppa/mainline/">官网镜像</a><br>选择所需要的版本下载(<em>命令下载很慢,建议直接复制连接到浏览器中直接下载</em>);随便找个目录下载下列文件 </p>
<p><strong>64位版本</strong></p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.10.1/linux-headers-4.10.1-041001_4.10.1-041001.201702260735_all.deb </span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.10.1/linux-headers-4.10.1-041001-generic_4.10.1-041001.201702260735_amd64.deb </span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.10.1/linux-image-4.10.1-041001-generic_4.10.1-041001.201702260735_amd64.deb </span><br></pre></td></tr></table></figure>
<p><strong>32位版本</strong></p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.10.1/linux-headers-4.10.1-041001_4.10.1-041001.201702260735_all.deb </span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.10.1/linux-headers-4.10.1-041001-generic_4.10.1-041001.201702260735_i386.deb </span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.10.1/linux-image-4.10.1-041001-generic_4.10.1-041001.201702260735_i386.deb</span><br></pre></td></tr></table></figure>
<h3 id="开始解压安装"><a href="#开始解压安装" class="headerlink" title="开始解压安装"></a>开始解压安装</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ sudo dpkg -i *.deb</span><br></pre></td></tr></table></figure>
<p>如下图</p>
<p><img src="https://res.cloudinary.com/dkptw3xiz/image/upload/v1603874217/ubuntu-server_hyrt7u.png"></p>
<p>安装完成后,重启服务器并验证是否更新新的内核</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">root@ubuntu:~# uname -sr</span><br><span class="line">Linux 4.10.1-041001-generic</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category>Ubuntu</category>
</categories>
<tags>
<tag>Ubuntu</tag>
</tags>
</entry>
<entry>
<title>Ubuntu 更换apt源、pip3源</title>
<url>/2020/11/28/Ubuntu%20%E6%9B%B4%E6%8D%A2apt%E6%BA%90%E3%80%81pip3%E6%BA%90/</url>
<content><![CDATA[<p>Ubuntu终端下载速度过慢问题</p>
<a id="more"></a>
<h2 id="ubuntu-更换apt源"><a href="#ubuntu-更换apt源" class="headerlink" title="ubuntu 更换apt源"></a>ubuntu 更换apt源</h2><ol>
<li><p>切换root </p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">sudo su -</span><br></pre></td></tr></table></figure></li>
<li><p>备份源文件</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">cd /etc/apt</span><br><span class="line">mv sources.list sources.list_bak</span><br></pre></td></tr></table></figure></li>
<li><p>新建sources.list </p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">vim sources.list </span><br></pre></td></tr></table></figure>
<p>拷贝以下内容</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"># deb cdrom:[Ubuntu 16.04 LTS _Xenial Xerus_ - Release amd64 (20160420.1)]/ xenial main restricted</span><br><span class="line">deb-src http://archive.ubuntu.com/ubuntu xenial main restricted #Added by software-properties</span><br><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted</span><br><span class="line">deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted multiverse universe #Added by software-properties</span><br><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted</span><br><span class="line">deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted multiverse universe #Added by software-properties</span><br><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial universe</span><br><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial-updates universe</span><br><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial multiverse</span><br><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial-updates multiverse</span><br><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse</span><br><span class="line">deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse #Added by software-properties</span><br><span class="line">deb http://archive.canonical.com/ubuntu xenial partner</span><br><span class="line">deb-src http://archive.canonical.com/ubuntu xenial partner</span><br><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted</span><br><span class="line">deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted multiverse universe #Added by software-properties</span><br><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial-security universe</span><br><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial-security multiverse</span><br></pre></td></tr></table></figure>
</li>
</ol>
<ol start="4">
<li>更新配置 <figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">sudo apt-get update</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h2 id="ubuntu-更换pip3源"><a href="#ubuntu-更换pip3源" class="headerlink" title="ubuntu 更换pip3源"></a>ubuntu 更换pip3源</h2><ol>
<li><p>切换root</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">sudo su -</span><br></pre></td></tr></table></figure>
</li>
<li><p>安装pip3</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">apt install python3-pip</span><br></pre></td></tr></table></figure></li>
<li><p>创建文件</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">mkdir ~/.pip</span><br><span class="line"></span><br><span class="line">vim ~/.pip/pip.conf</span><br></pre></td></tr></table></figure></li>
<li><p>拷贝以下内容</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">[global]</span><br><span class="line">index-url = https://mirrors.aliyun.com/pypi/simple</span><br></pre></td></tr></table></figure></li>
</ol>
]]></content>
<categories>
<category>Ubuntu</category>
</categories>
<tags>
<tag>Ubuntu</tag>
</tags>
</entry>
<entry>
<title>macOS brew安装mongo遇见的一些坑和报错</title>
<url>/2020/11/29/macOS%20brew%E5%AE%89%E8%A3%85mongo%E9%81%87%E8%A7%81%E7%9A%84%E4%B8%80%E4%BA%9B%E5%9D%91%E5%92%8C%E6%8A%A5%E9%94%99/</url>
<content><![CDATA[<h3 id="brew-安装mongo"><a href="#brew-安装mongo" class="headerlink" title="brew 安装mongo"></a>brew 安装mongo</h3><ul>
<li><p>brew 安装mongo 遇见的一些坑和报错</p>
<p> <strong>1. WiredTiger Permission denied</strong></p>
<p> <strong>2. Found an invalid featureCompatibilityVersion document</strong></p>
<p> <strong>3. Data directory /data/db not found., terminating</strong></p>
</li>
</ul>
<a id="more"></a>
<ul>
<li>从2019年9月2日开始 ,HomeBrew 也从核心仓库 (<a href="https://github.com/Homebrew/homebrew-core/pull/43770">#43770</a>) 当中移除了mongodb 模块 ,MongoDB 已经宣布不再开源;</li>
<li>若果想继续使用 <code>brew install mongodb</code>,MongoDB 官方提供了一个单独的 HomeBrew 的社区版本安装:<a href="https://github.com/mongodb/homebrew-brew">https://github.com/mongodb/homebrew-brew</a></li>
</ul>
<h3 id="开始安装"><a href="#开始安装" class="headerlink" title="开始安装"></a>开始安装</h3><ul>
<li><p><code>brew tap mongodb/brew</code><br>很遗憾brew tap 的源无法加速,是从 mongodb.org 官方下载的安装包,等待一会吧</p>
</li>
<li><p><code>brew install mongodb-community@4.2</code></p>
</li>
<li><p><code>brew tap mongodb/brew</code></p>
</li>
<li><p><code>配置文件:/usr/local/etc/mongod.conf</code></p>
</li>
<li><p><code>日志目录路径:/usr/local/var/log/mongodb</code></p>
</li>
<li><p><code>数据目录路径:/usr/local/var/mongodb</code></p>
</li>
</ul>
<h3 id="启动mongo"><a href="#启动mongo" class="headerlink" title="启动mongo"></a>启动mongo</h3><ul>
<li><p><code>brew services start mongodb-community@4.2</code></p>
</li>
<li><p><code>brew services stop mongodb-community@4.2</code></p>
</li>
<li><p><code>brew services restart mongodb-community@4.2</code></p>
</li>
</ul>
<h3 id="报错-1"><a href="#报错-1" class="headerlink" title="报错 1"></a>报错 1</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">wiredtiger_open: __posix_open_file, 667: /usr/local/var/mongodb/WiredTiger.turtle: handle-open: open: Permission denied</span><br></pre></td></tr></table></figure>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">2020-04-13T14:20:17.647+0800 I STORAGE [initandlisten] Detected data files <span class="keyword">in</span> /usr/<span class="built_in">local</span>/var/mongodb created by the <span class="string">'wiredTiger'</span> storage engine, so setting the active storage engine to <span class="string">'wiredTiger'</span>.</span><br><span class="line">2020-04-13T14:20:17.647+0800 I STORAGE [initandlisten] wiredtiger_open config: create,cache_size=7680M,cache_overflow=(file_max=0M),session_max=33000,eviction=(threads_min=4,threads_max=4),config_base=<span class="literal">false</span>,statistics=(fast),<span class="built_in">log</span>=(enabled=<span class="literal">true</span>,archive=<span class="literal">true</span>,path=journal,compressor=snappy),file_manager=(close_idle_time=100000,close_scan_interval=10,close_handle_minimum=250),statistics_log=(<span class="built_in">wait</span>=0),verbose=[recovery_progress,checkpoint_progress],</span><br><span class="line">2020-04-13T14:20:18.200+0800 E STORAGE [initandlisten] WiredTiger error (13) [1586758818:200112][27741:0x109869dc0], wiredtiger_open: __posix_open_file, 667: /usr/<span class="built_in">local</span>/var/mongodb/WiredTiger.turtle: handle-open: open: Permission denied Raw: [1586758818:200112][27741:0x109869dc0], wiredtiger_open: __posix_open_file, 667: /usr/<span class="built_in">local</span>/var/mongodb/WiredTiger.turtle: handle-open: open: Permission denied</span><br><span class="line">2020-04-13T14:20:18.200+0800 E STORAGE [initandlisten] WiredTiger error (13) [1586758818:200395][27741:0x109869dc0], wiredtiger_open: __posix_open_file, 667: /usr/<span class="built_in">local</span>/var/mongodb/WiredTiger.turtle: handle-open: open: Permission denied Raw: [1586758818:200395][27741:0x109869dc0], wiredtiger_open: __posix_open_file, 667: /usr/<span class="built_in">local</span>/var/mongodb/WiredTiger.turtle: handle-open: open: Permission denied</span><br><span class="line">2020-04-13T14:20:18.200+0800 E STORAGE [initandlisten] WiredTiger error (13) [1586758818:200599][27741:0x109869dc0], wiredtiger_open: __posix_open_file, 667: /usr/<span class="built_in">local</span>/var/mongodb/WiredTiger.turtle: handle-open: open: Permission denied Raw: [1586758818:200599][27741:0x109869dc0], wiredtiger_open: __posix_open_file, 667: /usr/<span class="built_in">local</span>/var/mongodb/WiredTiger.turtle: handle-open: open: Permission denied</span><br><span class="line">2020-04-13T14:20:18.200+0800 W STORAGE [initandlisten] Failed to start up WiredTiger under any compatibility version.</span><br><span class="line">2020-04-13T14:20:18.200+0800 F STORAGE [initandlisten] Reason: 13: Permission denied</span><br><span class="line">2020-04-13T14:20:18.200+0800 F - [initandlisten] Fatal Assertion 28595 at src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp 860</span><br><span class="line">2020-04-13T14:20:18.200+0800 F - [initandlisten]</span><br><span class="line"> </span><br><span class="line"> ***aborting after fassert() failure</span><br></pre></td></tr></table></figure>
<p>将数据目录路径<code>/usr/local/var/mongodb</code>目录赋值权限777<code>sudo chmod -R 777 /usr/local/var/mongodb</code></p>
<p> <img src="https://img-blog.csdnimg.cn/20200413155504590.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lhbmdoYW85MzcxNzA=,size_16,color_FFFFFF,t_70" alt="ddd"></p>
<h3 id="报错-2"><a href="#报错-2" class="headerlink" title="报错 2"></a>报错 2</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Found an invalid featureCompatibilityVersion document If the current featureCompatibilityVersion is below 4.0, see the documentation on upgrading at http://dochub.mongodb.org/core/4.0-upgrade-fcv.</span><br></pre></td></tr></table></figure>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">2020-04-13T15:16:24.375+0800 F CONTROL [initandlisten] ** IMPORTANT: UPGRADE PROBLEM: Found an invalid featureCompatibilityVersion document (ERROR: BadValue: Invalid value <span class="keyword">for</span> version, found 3.6, expected <span class="string">'4.2'</span> or <span class="string">'4.0'</span>. Contents of featureCompatibilityVersion document <span class="keyword">in</span> admin.system.version: { _id: <span class="string">"featureCompatibilityVersion"</span>, version: <span class="string">"3.6"</span> }. See http://dochub.mongodb.org/core/4.0-feature-compatibility.). If the current featureCompatibilityVersion is below 4.0, see the documentation on upgrading at http://dochub.mongodb.org/core/4.0-upgrade-fcv.</span><br><span class="line">2020-04-13T15:16:24.375+0800 I NETWORK [initandlisten] shutdown: going to close listening sockets...</span><br></pre></td></tr></table></figure>
<p><img src="https://img-blog.csdnimg.cn/20200413160717917.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lhbmdoYW85MzcxNzA=,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p><strong>只需将数据目录路径<code>/usr/local/var/mongodb</code>里面内容清空在重新启动mongo即可</strong></p>
<h3 id="其他错误"><a href="#其他错误" class="headerlink" title="其他错误"></a>其他错误</h3><ol>
<li><p>以mongod 启动,报错<code>/data/db</code>文件夹不存在</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">2020-04-13T15:16:24.375+0800 F STORAGE [initandlisten] exception <span class="keyword">in</span> initAndListen: 29 Data directory /data/db not found., terminating</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p> 原因是:macOS 10.15版本不能创建<code>/data/db</code></p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">sudo mkdir -p /data/db</span><br><span class="line">mkdir: /data/db: Read-only file system</span><br></pre></td></tr></table></figure></li>
<li><p>可以mongod指定数据目录启动<code>mongod --dbpath '/Users/neo/data/db'</code></p>
</li>
<li><p>可以编辑配置文件<code>vim ~/.zshrc</code><br>在末尾添加以下内容,以后使用mongod启动</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">alias</span> mongod=<span class="string">"mongod --dbpath '/Users/neo/data/db'"</span></span><br></pre></td></tr></table></figure>
</li>
</ol>
]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>踩坑</tag>
</tags>
</entry>
<entry>
<title>BeyondCompare 无限试用</title>
<url>/2020/08/06/BeyondCompare%20%E6%97%A0%E9%99%90%E8%AF%95%E7%94%A8/</url>
<content><![CDATA[<h2 id="beyondCompare-无限试用"><a href="#beyondCompare-无限试用" class="headerlink" title="beyondCompare 无限试用"></a>beyondCompare 无限试用</h2><p>一次设置,一直有效,以后不需要修改,每次打开都是30天</p>
<h3 id="找到安装的目录"><a href="#找到安装的目录" class="headerlink" title="找到安装的目录"></a>找到安装的目录</h3><p>我的安装目录是在这个下面</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> /Applications/Beyond\ Compare.app/Contents/MacOS/</span><br></pre></td></tr></table></figure>
<a id="more"></a>
<h3 id="备份-BCompare"><a href="#备份-BCompare" class="headerlink" title="备份 BCompare"></a>备份 <code>BCompare</code></h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">mv BCompare BCompare.real </span><br></pre></td></tr></table></figure>
<h3 id="创建并编辑-BCompare"><a href="#创建并编辑-BCompare" class="headerlink" title="创建并编辑 BCompare"></a>创建并编辑 <code>BCompare</code></h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">touch BCompare</span><br></pre></td></tr></table></figure>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">vim BCompare </span><br></pre></td></tr></table></figure>
<h3 id="增加以下内容-无限期试用"><a href="#增加以下内容-无限期试用" class="headerlink" title="增加以下内容 (无限期试用)"></a>增加以下内容 (无限期试用)</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">#!/bin/bash</span><br><span class="line">rm "/Users/neo/Library/Application Support/Beyond Compare/registry.dat"</span><br><span class="line">"`dirname "$0"`"/BCompare.real $@</span><br></pre></td></tr></table></figure>
<h3 id="赋予权限"><a href="#赋予权限" class="headerlink" title="赋予权限"></a>赋予权限</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">chmod a+x BCompare</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category>工具</category>
</categories>
</entry>
<entry>
<title>SpringBoot @Valid 注解不生效</title>
<url>/2021/08/21/Springboot-Valid-%E6%B3%A8%E8%A7%A3%E4%B8%8D%E7%94%9F%E6%95%88/</url>
<content><![CDATA[<h3 id="踩坑记录"><a href="#踩坑记录" class="headerlink" title="踩坑记录"></a>踩坑记录</h3><p><code>SpringBoot</code> 使用<code>@Valid</code>注解 参数校验不生效的问题</p>
<p>代码省略, 直接看解决方案</p>
<a id="more"></a>
<h3 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h3><p><code>springboot-2.3.x</code>之前的版本只需要引入 web 依赖就可以</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"><dependency></span><br><span class="line"> <groupId>org.springframework.boot</groupId></span><br><span class="line"> <artifactId>spring-boot-starter-web</artifactId></span><br><span class="line"></dependency></span><br></pre></td></tr></table></figure>
<p>但是从<code>springboot-2.3.x</code>版本开始,校验包被独立成了一个<code>starter</code>组件,所以需要引入如下<code>validation</code>依赖( <strong><em>参见官方文档</em></strong> <a href="https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#validation-starter-no-longer-included-in-web-starters">Validation Starter no longer included in web starters</a> )</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"><dependency></span><br><span class="line"> <groupId>org.springframework.boot</groupId></span><br><span class="line"> <artifactId>spring-boot-starter-validation</artifactId></span><br><span class="line"></dependency></span><br><span class="line"><dependency></span><br><span class="line"> <groupId>org.springframework.boot</groupId></span><br><span class="line"> <artifactId>spring-boot-starter-web</artifactId></span><br><span class="line"></dependency></span><br></pre></td></tr></table></figure>
<p>完事收工…</p>
]]></content>
<categories>
<category>Java</category>
<category>SpringBoot</category>
</categories>
<tags>
<tag>踩坑</tag>
</tags>
</entry>
</search>