-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
1492 lines (699 loc) · 921 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
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>《The Dataflow Model》论文翻译</title>
<link href="/2020/01/07/%E3%80%8AThe-Dataflow-Model%E3%80%8B%E8%AE%BA%E6%96%87%E7%BF%BB%E8%AF%91/"/>
<url>/2020/01/07/%E3%80%8AThe-Dataflow-Model%E3%80%8B%E8%AE%BA%E6%96%87%E7%BF%BB%E8%AF%91/</url>
<content type="html"><![CDATA[<blockquote><p><strong>The Dataflow Model 是 Google Research 于2015年发表的一篇流式处理领域的具有指导性意义的论文,它对数据集特征和相应的计算方式进行了归纳总结,并针对大规模/无边界/乱序数据集,提出一种可以平衡准确性/延迟/处理成本的数据模型。这篇论文的目的不在于解决目前流计算引擎无法解决的问题,而是提供一个灵活的通用数据模型,可以无缝地切合不同的应用场景。</strong>(来源于:<a href="http://www.whitewood.me/2018/05/07/The-Dataflow-Model-论文总结/" target="_blank" rel="noopener">时间与精神小屋的论文总结</a>)</p><p>本论文是通过机翻+人翻结合一起的,里面包含大量的长句,如果纯人翻的话,完全啃下来有点难!</p></blockquote><h2 id="ABSTRACT"><a href="#ABSTRACT" class="headerlink" title="ABSTRACT"></a>ABSTRACT</h2><p>Unbounded, unordered, global-scale datasets are increasingly common in day-to-day business (e.g. Web logs, mobileusage statistics, and sensor networks). At the same time,consumers of these datasets have evolved sophisticated requirements, such as event-time ordering and windowing by features of the data themselves, in addition to an insatiable hunger for faster answers. Meanwhile, practicality dictates that one can never fully optimize along all dimensions of correctness, latency, and cost for these types of input. As a result, data processing practitioners are left with the quandary of how to reconcile the tensions between these seemingly competing propositions, often resulting in disparate implementations and systems.</p><blockquote><p>无边界的、无序的、全球范围的数据集在日常业务中越来越普遍(例如,Web日志,移动设备使用情况统计信息和传感器网络)。 同时,这些数据集的消费者已经提出了更加复杂的需求,例如基于event-time(事件时间)的排序和数据特征本身的窗口聚合,以满足消费者对于快速消费数据的庞大需求。与此同时,从实用性的角度出发,对于以上提到的数据集,我们永远无法在准确(correctness),延迟(latency)和成本(cost)等所有维度上进行全面优化。 最后,数据处理人员需要在这些看似冲突的方面之间做出妥协与调和,而这些做法往往会产生不同的实现与框架。</p></blockquote><p>We propose that a fundamental shift of approach is necessary to deal with these evolved requirements in modern data processing. We as a field must stop trying to groom unbounded datasets into finite pools of information that eventually become complete, and instead live and breathe under the assumption that we will never know if or when we have seen all of our data, only that new data will arrive, old data may be retracted, and the only way to make this problem tractable is via principled abstractions that allow the practitioner the choice of appropriate tradeoffs along the axes of interest: correctness, latency, and cost.</p><blockquote><p>我们认为有关于数据处理的方法必须得到根本性的改变,以应对现代数据处理中这些不断发展的需求。作为流式处理的领域中,我们必须停止尝试将无边界的数据集归整成完整的、有限的信息池,因为在一般的情况下,我们永远不知道是否或者何时能看到所有的数据。使得该问题变得易于解决的唯一方法就是通过一些规则上的抽象,使得数据处理人员能够从准确(correctness),延迟(latency)和成本(cost)几个维度做出妥协。</p></blockquote><p>In this paper, we present one such approach, the Dataflow Model, along with a detailed examination of the semantics it enables, an overview of the core principles that guided its design, and a validation of the model itself via the real-world experiences that led to its development.</p><blockquote><p>在本文中,我们提出了一种这样的方法,The Dataflow Model,并对其支持的语义进行了详细的审视,概述其设计指导的核心原则,并通过实际的开发经验验证了模型本身的可行性。</p></blockquote><h2 id="1-INTRODUCTION"><a href="#1-INTRODUCTION" class="headerlink" title="1. INTRODUCTION"></a>1. INTRODUCTION</h2><p>Modern data processing is a complex and exciting field. From the scale enabled by MapReduce and its successors(e.g Hadoop, Pig, Hive, Spark), to the vast body of work on streaming within the SQL community (e.g.query systems, windowing, data streams,time domains, semantic models), to the more recent forays in low-latency processing such as Spark Streaming, MillWheel, and Storm, modern consumers of data wield remarkable amounts of power in shaping and taming massive-scale disorder into organized structures with far greater value. Yet, existing models and systems still fall short in a number of common use cases.</p><blockquote><p>现代数据处理是一个复杂且令人兴奋的领域。从MapReduce及其继承者(e.g. Hadoop,Pig,Hive,Spark)实现的大规模运算,到SQL社区对流式处理做出的巨大工作(e.g. 查询系统(query system),窗口(windowing),数据流(data streams),时间域(time domains),语义模型(semantic model)),再到近期如Spark Streaming,MillWheel和Storm对于低延迟数据处理的初步尝试,现代数据的消费者挥舞着庞大的力量,尝试将大规模的、无序的海量数据规整为具有巨大价值的、易于管理的结构当中。然而,现有的模型和系统在许多常见的用例仍然存在不足的地方。</p></blockquote><p>Consider an initial example: a streaming video provider wants to monetize their content by displaying video ads and billing advertisers for the amount of advertising watched. The platform supports online and offline views for content and ads. The video provider wants to know how much to bill each advertiser each day, as well as aggregate statistics about the videos and ads. In addition, they want to efficiently run offline experiments over large swaths of historical data.</p><blockquote><p>考虑一个比较简单的例子:流视频提供者希望通过展示视频广告来使其视频内容能够盈利,并且通过广告的观看量对广告商收取一定的费用。该平台同时支持在线和离线观看视频和广告。视频提供者想要知道每天应向每个广告商收取多少费用,以及所有视频和广告的统计情况。此外,他们还希望能够有效率地对大量的历史数据进行离线实验。</p></blockquote><p>Advertisers/content providers want to know how often and for how long their videos are being watched, with which content/ads, and by which demographic groups. They also want to know how much they are being charged/paid. They want all of this information as quickly as possible, so that they can adjust budgets and bids, change targeting, tweak campaigns, and plan future directions in as close to realtime as possible. Since money is involved, correctness is paramount.</p><blockquote><p>而广告商/内容提供商想要知道他们的视频被观看的频率和时长,观看的内容/广告是什么,观看的人群是什么。他们也想知道他们需要为此要付出多少费用。他们希望尽可能快地获得所有这些信息,这样他们就可以调整预算和投标,改变目标,调整活动,并尽可能实时地规划未来的方向。因为涉及到钱,所以系统上设计时需要首要重点考虑其准确性。</p></blockquote><p>Though data processing systems are complex by nature,the video provider wants a programming model that is simple and flexible. And finally, since the Internet has so greatly expanded the reach of any business that can be parceled along its backbone, they also require a system that can handle the diaspora of global scale data.</p><blockquote><p>虽然数据处理系统本质上是复杂的,但是视频提供商却想要一个简单而灵活的编程模型。最后,由于互联网极大地扩展了任何可以沿着其主干分布的业务的范围,他们还需要一个能够处理全球范围内所有分散数据的系统。</p></blockquote><p>The information that must be calculated for such a usecase is essentially the time and length of each video viewing,who viewed it, and with which ad or content it was paired(i.e. per-user, per-video viewing sessions). Conceptually this is straightforward, yet existing models and systems all fall short of meeting the stated requirements.</p><blockquote><p>对于这样的一个用例,必须计算的信息本质上等同于每个视频观看的时长、谁观看了它,以及它与哪个广告或内容配对(e.g. 每个用户,每个视频观看会话)。从概念上讲,这很简单,但是现有的模型和系统都不能满足上述提到的需求。</p></blockquote><p>Batch systems such as MapReduce (and its Hadoop vari-ants, including Pig and Hive), FlumeJava, and Spark suffer from the latency problems inherent with collecting all input data into a batch before processing it. For many streaming systems, it is unclear how they would remain fault-tolerantat scale (Aurora, TelegraphCQ, Niagara, Esper). Those that provide scalability and fault-tolerance fall short on expressiveness or correctness vectors. </p><blockquote><p>诸如MapReduce(及其Hadoop变体,包括Pig和Hive),FlumeJava和Spark之类的批处理系统都碰到了在批处理之前需要将所有输入数据导入系统时所带来的延迟问题。对于许多流系统,我们无法清晰地知道他们是如何构建大规模的容错机制(Aurora,TelegraphCQ,Niagara,Esper),而那些提供可伸缩性和容错性的系统则在表达性或准确性方面上表现不足。</p></blockquote><p>Many lack the ability to provide exactly-once semantics (Storm, Samza, Pulsar), impacting correctness. Others simply lack the temporal primitives necessary for windowing(Tigon), or provide windowing semantics that are limited to tuple- or processing-time-based windows (Spark Streaming, Sonora, Trident). </p><blockquote><p>许多框架都缺乏提供exactly-once语义的能力(Storm,Samza,Pulsar),从而影响了数据的准确性。 而其他框架则缺少窗口所必需的时间原语(Tigon),或提供仅限于以元组(tuple-)或处理时间(processing-time)为基础的窗口语义(Spark Streaming,Sonora,Trident)。 </p></blockquote><p>Most that provide event-time-based windowing either rely on ordering (SQLStream),or have limited window triggering semantics in event-time mode (Stratosphere/Flink). CEDR and Trill are note worthy in that they not only provide useful triggering semantics via punctuations, but also provide an overall incremental model that is quite similar to the one we propose here; however, their windowing semantics are insufficient to express sessions, and their periodic punctuations are insufficient for some of the use cases in Section3.3. MillWheel and Spark Streaming are both sufficiently scalable, fault-tolerant, and low-latency to act as reasonable substrates, but lack high-level programming models that make calculating event-time sessions straightforward.</p><blockquote><p>大多数框架提供的基于 event-time 的窗口机制要么依赖于排序(SQLStream),要么在event-time 模式下提供有限的窗口触发语义(Stratosphere / Flink)。值得一提的是,CEDR 和 Trill不仅可以通过标点符号(punctuations)提供有效的窗口触发语义,而且还提供了一个整体增量(overall incremental)的模型,该模型与我们此处提到的模型非常相似。然而,它们的窗口语义并不足以表达会话(sessions),并且它们的周期性标点符号不足以满足3.3节中的某些用例。MillWhell 和 Spark Streaming 都具有伸缩性,容错性和低延迟性,作为流框架合理的基础架构,但是其缺少能够让基于 event-time 的会话计算变得通俗易懂的高级编程模型。</p></blockquote><p>The only scalable system we are aware of that supports a high-level notion of unaligned windows such as sessions is Pulsar, but that system fails to provide correctness, as noted above. Lambda Architecture systems can achieve many of the desired requirements, but fail on the simplicity axis on account of having to build and maintain two systems. Summingbird ameliorates this implementation complexity by abstracting the underlying batch and streaming systems behind a single interface, but in doing so imposes limitations on the types of computation that can be performed, and still requires double the operational complexity.</p><blockquote><p>我们观察到唯一具有伸缩性,并且支持未对齐窗口(例如会话)这种高级概念的流数据系统是Pulsar,但是如上所述,该系统无法提供准确性。Lambda架构体系可以满足许多我们期望的要求,但是由于必须构建和维护两套系统,因此其在简单性这一维度上就注定失败。Summingbird通过在单一接口背后抽象底层的批系统和流系统,来改善其实现的复杂性,但是这样做会限制其可以执行的计算类型,并且仍会有两倍的操作复杂性。</p></blockquote><p>None of these short comings are intractable, and systems in active development will likely overcome them in due time. But we believe a major shortcoming of all the models and systems mentioned above (with exception given to CEDR and Trill), is that they focus on input data (unbounded orotherwise) as something which will at some point become complete. We believe this approach is fundamentally flawed when the realities of today’s enormous, highly disordered datasets clash with the semantics and timeliness demanded by consumers. We also believe that any approach that is to have broad practical value across such a diverse and variedset of use cases as those that exist today (not to mention those lingering on the horizon) must provide simple, but powerful, tools for balancing the amount of correctness, latency, and cost appropriate for the specific use case at hand. </p><blockquote><p>这些缺点都不是很难解决的,积极开发中的系统很可能会在适当的时候攻克它们。 但是我们认为,上述所有模型和系统(CEDR和Trill除外)的一个主要缺点是,它们只专注于那些最终在某些时刻达到完整的输入数据(无界或其他)。 我们认为,当现今庞大且高度混乱的数据集与消费者要求的语义和及时性发生冲突时,这种方法从根本上是有缺陷的。 我们还认为,任何在如今多样的用例中都具有广泛实用价值的方法(更不用说那些长期存在的用例)必须提供简单但强大的工具来平衡准确性,低延迟性和适合于特定用例的成本。</p></blockquote><p>Lastly, we believe it is time to move beyond the prevailing mindset of an execution engine dictating system semantics; properly designed and built batch, micro-batch, and streaming systems can all provide equal levels of correctness, and all three see widespread use in unbounded data processing today. Abstracted away beneath a model of sufficient generality and flexibility, we believe the choice of execution engine can become one based solely on the practical underlying differences between them: those of latency and resource cost. </p><blockquote><p>最后,我们认为是时候超越执行引擎决定系统语义的主流思维了。 经过正确设计和构建的批处理,微批处理和流传输系统都可以提供同等程度的准确性,并且这三者在当今的无边界数据处理中都可以得到了广泛使用。 在具有足够通用性和灵活性的模型下进行抽象,我们认为执行引擎的选择可以仅基于它们之间的实际潜在差异(即延迟和资源成本)进行选择。</p></blockquote><p>Taken from that perspective, the conceptual contribution of this paper is a single unified model which:</p><ul><li>Allows for the calculation of event-time ordered results, windowed by features of the data themselves, over an unbounded, unordered data source, with correctness, latency, and cost tunable across a broad spectrum of combinations.</li><li>Decomposes pipeline implementation across four related dimensions, providing clarity, composability, andflexibility:<ul><li>– <strong>What</strong> results are being computed.</li><li>– <strong>Where</strong> in event time they are being computed.</li><li>– <strong>When</strong> in processing time they are materialized.</li><li>– <strong>How</strong> earlier results relate to later refinements.</li></ul></li><li>Separates the logical notion of data processing from the underlying physical implementation, allowing the choice of batch, micro-batch, or streaming engine to become one of simply correctness, latency, and cost.</li></ul><blockquote><p>从这个角度来看,本文提出了一个单一且统一的模型概念,即:</p><ul><li>允许计算event-time排序的结果,并根据数据本身的特征在无边界,无序的数据源上进行窗口化,其准确性,延迟和成本可在多种组合中调整。</li><li>分解四个跨维度相关的管道实现,以提供清晰性,可组合性和灵活性:<ul><li>– What 正在计算<strong>什么</strong>结果。</li><li>– Where 在事件发生时,它们被计算<strong>在哪里</strong>。</li><li>– When <strong>何时</strong>在prcoessing-time内实现。</li><li>– How 早期的结果<strong>如何</strong>与后来的改进相联系。</li></ul></li><li>将数据处理的逻辑概念与底层物理实现分开,允许批处理,微批处理或流引擎的选择成为准确性,延迟和成本中的一种。</li></ul></blockquote><p>Concretely, this contribution is enabled by the following:</p><ul><li><strong>A windowing model</strong> which supports unaligned event-time windows, and a simple API for their creation and use (Section 2.2).</li><li><strong>A triggering model</strong> that binds the output times of results to runtime characteristics of the pipeline, with a powerful and flexible declarative API for describing desired triggering semantics (Section 2.3).</li><li>An <strong>incremental processing model</strong> that integrates retractions and updates into the windowing and triggering models described above (Section 2.3).</li><li><strong>Scalable implementations</strong> of the above atop the MillWheel streaming engine and the FlumeJava batch engine, with an external reimplementation for GoogleCloud Dataflow, including an open-source SDK that is runtime-agnostic (Section 3.1).</li><li>A set of <strong>core principles</strong> that guided the design of this model (Section 3.2).</li><li>Brief discussions of our <strong>real-world experiences</strong> with massive-scale, unbounded, out-of-order data processing at Google that motivated development of this model(Section 3.3).</li></ul><blockquote><p>具体来说,这一模型可由下面几个概念形成:</p><ul><li><strong>窗口模型(A windowing model)</strong>。支持未对齐的event-time窗口,以及提供易于创建和使用窗口 API 的模型(章节2.2)。</li><li><strong>触发模型(A triggering model )</strong>。将输出的时间结果与具有运行特性的管道进行绑定,并提供功能强大且灵活的声明性 API,用于描述所需的触发语义(章节2.3)。</li><li><strong>增量处理模型(incremental processing model)</strong>。将数据回撤功能和数据更新功能集成到上述窗口和触发模型中(章节2.3)。</li><li><strong>可扩展的实现(Scalable implementations)</strong>。在MillWheel流引擎和FlumeJava批处理引擎之上的可扩展实现以及对GoogleCloud Dataflow的外部重新实现,包括与运行时无关的开源SDK(章节3.1)。</li><li><strong>核心原则(core principles)</strong>。用于指导该模型设计的一组核心原则(章节3.2)。</li><li><strong>真实经验( real-world experiences )</strong>。简要讨论了我们在Google上使用大规模,无边界,无序数据处理的真实经验,这些经验推动了该模型的发展(章节3.3)。</li></ul></blockquote><p>It is lastly worth noting that there is nothing magical about this model. Things which are computationally impractical in existing strongly-consistent batch, micro-batch, streaming, or Lambda Architecture systems remain so, with the inherent constraints of CPU, RAM, and disk left steadfastly in place. What it does provide is a common framework that allows for the relatively simple expression of parallel computation in a way that is independent of the underlying execution engine, while also providing the ability to dial in precisely the amount of latency and correctness for any specific problem domain given the realities of the data and resources at hand. In that sense, it is a model aimed at ease of use in building practical, massive-scale data processing pipelines.</p><blockquote><p>最后值得注意的是,这个模型没有什么神奇之处。在现有的强一致批处理、微批处理、流处理或Lambda体系结构系统中,那些不现实的东西依旧存在,CPU、RAM和 Disk的固有约束仍然稳定存在。它所提供的是一个通用的框架,该框架允许以独立于底层执行引擎的方式对并行计算进行相对简单的表达,同时还提供了在现有数据和资源下,为任何特定问题精确计算延迟和准确性的能力。从某种意义上说,它是一种旨在易于使用的模型,可用于构建实用的大规模数据处理管道。</p></blockquote><h3 id="1-1-Unbounded-Bounded-vs-Streaming-Batch"><a href="#1-1-Unbounded-Bounded-vs-Streaming-Batch" class="headerlink" title="1.1 Unbounded/Bounded vs Streaming/Batch"></a>1.1 Unbounded/Bounded vs Streaming/Batch</h3><p>When describing infinite/finite data sets, we prefer the terms unbounded/bounded over streaming/batch, because the latter terms carry with them an implication of the use of a specific type of execution engine. In reality, unbounded datasets have been processed using repeated runs of batch systems since their conception, and well-designed streaming systems are perfectly capable of processing bounded data. From the perspective of the model, the distinction of streaming or batch is largely irrelevant, and we thus reserve those terms exclusively for describing runtime execution engines.</p><blockquote><p>当描述无限/有限数据集时,我们首选“无界/有界”这一术语而不是“流/批处理”,因为后者会带来使用特定类型执行引擎的隐含含义。 实际上,自从无边界数据集的概念诞生以来,就已经使用批处理系统的重复运行对其进行了处理,而精心设计的流系统则完全能够处理有边界的数据。 从模型的角度来看,流或批处理的区别在很大程度上是无关紧要的,因此,我们保留了那些专门用于描述运行时执行引擎的术语。</p></blockquote><h3 id="1-2-Windowing"><a href="#1-2-Windowing" class="headerlink" title="1.2 Windowing"></a>1.2 Windowing</h3><p>Windowing slices up a dataset into finite chunks for processing as a group. When dealing with unbounded data, windowing is required for some operations (to delineate finite boundaries in most forms of grouping: aggregation,outer joins, time-bounded operations, etc.), and unnecessary for others (filtering, mapping, inner joins, etc.). For bounded data, windowing is essentially optional, though still a semantically useful concept in many situations (e.g. back-filling large scale updates to portions of a previously computed unbounded data source). </p><blockquote><p>窗口化(Windowing)将数据集切成有限的数据块,以作为一组进行处理。 处理无边界数据时,某些操作(在大多数分组形式中描绘有限边界:聚合,外部联接,有时间限制的操作等)需要窗口化,而其他操作(过滤,映射,内部联接等)则不需要。 对于有界数据,窗口化在本质上是可选的,尽管在许多情况下仍然是语义上十分有用的概念(例如,回填大规模数据更新到先前计算的无界数据源的某些部分中)。</p></blockquote><p>Windowing is effectively always time based, while many systems support tuple-based windowing, this is essentially time-based windowing over a logical time domain where elements in order have successively increasing logical timestamps. Windows may be either aligned, i.e. applied across all the data for the window of time in question, or unaligned, i.e. applied across only specific subsets of the data (e.g. per key) for the given window of time. Figure 1 highlights three of the major types ofwindows encountered when dealing with unbounded data.</p><blockquote><p>实际上,窗口化总是基于时间的,虽然许多系统支持基于元组的窗口,但这本质上还是基于时间的窗口,并在逻辑时间域上,元素按顺序依次增加逻辑时间戳。窗口可以是对齐的,即在时间窗口中应用所有数据,也可以是未对齐的,即在给定时间窗口中只应用数据的特定子集(例如,每个键值)。图1突出显示了在处理无界数据时遇到的三种主要windows类型。</p></blockquote><p><img src="/2020/01/07/《The-Dataflow-Model》论文翻译/image-20200107205851307.png" alt="Figure 1: Common Windowing Patterns" style="zoom:50%;"></p><p><strong>Fixed</strong> windows (sometimes called tumbling windows) are defined by a static window size, e.g. hourly windows or daily windows. They are generally aligned, i.e. every window applies across all of the data for the corresponding period of time. For the sake of spreading window completion load evenly across time, they are sometimes unaligned by phase shifting the windows for each key by some random value.</p><blockquote><p><strong>固定窗口(有时称为翻滚窗口)</strong>。固定窗口由静态窗口大小定义,例如每小时一次或每天一次。 它们通常是对齐的,即每个窗口都在相应的时间段内应用于所有数据。为了使窗口完成时间均匀地分布在整个时间上,有时通过将每个键的窗口位移某个随机值来使它们不对齐。</p></blockquote><p><strong>Sliding</strong> windows are defined by a window size and slide period, e.g. hourly windows starting every minute. The period may be less than the size, which means the windows may overlap. Sliding windows are also typically aligned; even though the diagram is drawn to give a sense of sliding motion, all five windows would be applied to all three keys inthe diagram, not just Window 3. Fixed windows are really a special case of sliding windows where size equals period.</p><blockquote><p><strong>滑动窗口</strong>。滑动窗口由窗口大小和滑动周期定义,例如每分钟启动一次统计每小时的窗口。周期可能会小于窗口大小,这意味着窗口之间可能会发生重叠。 滑动窗口通常也会对齐,即使绘制该图给人提供一种滑动的感觉,所有五个窗口也将应用于该图中的所有三个键,而不仅仅是窗口3。固定窗口实际上是窗口大小等于滑动周期大小的滑动窗口的一种特殊情况。</p></blockquote><p><strong>Sessions</strong> are windows that capture some period of activity over a subset of the data, in this case per key. Typically they are defined by a timeout gap. Any events that occur within a span of time less than the timeout are grouped together as a session. Sessions are unaligned windows. For example, Window 2 applies to Key 1 only, Window 3 to Key2 only, and Windows 1 and 4 to Key 3 only.</p><blockquote><p><strong>会话窗口。</strong>会话是捕获数据子集(在此情况下为每个键值)的一段时间活动的窗口。 通常,它们由超时时间间隔定义的。 在小于超时的时间间隔范围内发生的任何事件都被归为一个会话。 会话是未对齐的窗口。 例如,窗口2仅适用于键1,窗口3仅适用于键2,窗口1和4仅适用于键3。</p></blockquote><h3 id="1-3-Time-Domains"><a href="#1-3-Time-Domains" class="headerlink" title="1.3 Time Domains"></a>1.3 Time Domains</h3><p>When processing data which relate to events in time, there are two inherent domains of time to consider. Though captured in various places across the literature (particularly time management and semantic models, but also windowing, out-of-order processing, punctuations, heartbeats, watermarks, frames), the detailed examples in section 2.3 will be easier to follow with the concepts clearly in mind. The two domains of interest are:</p><ul><li><strong>Event Time</strong>, which is the time at which the event itself actually occurred, i.e. a record of system clock time (for whatever system generated the event) at the time of occurrence.</li><li><strong>Processing Time</strong>, which is the time at which an event is observed at any given point during processing within the pipeline, i.e. the current time according to the system clock. Note that we make no assumptions about clock synchronization within a distributed system.</li></ul><blockquote><p>在处理与时间事件相关的数据时,需要考虑两个固有的时间域。虽然在文献的不同地方都已经提到过(特别是时间管理和语义模型,但也有窗口,无序处理,标点(punctuations),心跳,水印(watermarks),帧(frame)),详细的例子在章节2.3中展示,其将有助于帮助我们在脑海中更加清晰地掌握它。以下两个时间领域我们所关心的是:</p><ul><li><strong>事件时间(Event Time)。</strong>即事件本身实际发生的时间,即系统时钟时间(对于生成事件的任何系统)在事件发生时的记录。</li><li><strong>处理时间 (Processing Time)。</strong>这是在流水线内处理期间在任何给定点观察到事件的时间,即根据系统时钟的当前时间。 注意,我们不对分布式系统中的时钟同步做任何假设。</li></ul></blockquote><p>Event time for a given event essentially never changes,but processing time changes constantly for each event as it flows through the pipeline and time marches ever forward. This is an important distinction when it comes to robustly analyzing events in the context of when they occurred.</p><blockquote><p>给定事件的事件时间在本质上是不会改变,但是处理时间会随着事件在管道中的流动而不断变化,时间会不断前进。这是一个重要的区别,当它在事件发生的背景下进行清晰地分析时。</p></blockquote><p>During processing, the realities of the systems in use (communication delays, scheduling algorithms, time spent processing, pipeline serialization, etc.) result in an inherent and dynamically changing amount of skew between the two domains. Global progress metrics, such as punctuations or watermarks, provide a good way to visualize this skew. For our purposes, we’ll consider something like MillWheel’swa-termark, which is a lower bound (often heuristically established) on event times that have been processed by the pipeline. As we’ve made very clear above, notions of completeness are generally incompatible with correctness, so we won’t rely on watermarks as such. They do, however, provide a useful notion of when the system thinks it likely that all data up to a given point in event time have been observed,and thus find application in not only visualizing skew, but in monitoring overall system health and progress, as well as making decisions around progress that do not require complete accuracy, such as basic garbage collection policies.</p><blockquote><p>在处理过程中,市面上所有系统都会因为某些原因(通信延迟,调度算法,处理所花费的时间,流水线序列化等)导致两个时间域之间存在固有的,动态变化的偏移量。 诸如标点(punctuations)或水印(watermarks)之类的全局进度指标提供了一种可视化这种偏移量的好方法。为了我们的目的,我们将考虑使用MillWheel的水印,这是管道已处理的事件时间的下限(通常是启发式确定的)。 正如我们在上面非常清楚地指出的那样,完整性的概念通常与准确性是不兼容,因此我们不会像这样依赖水印。 但是,它们确实提供了一个有用的概念,即系统可在所有的数据中,观察那些给定的事件时间节点上的数据,因此不仅可以用于可视化其偏移量,而且可以用于监视整个系统的运行状况和进度, 以及围绕整体进度做出不要求准确性的决策,例如基本的垃圾回收策略。</p></blockquote><p>In an ideal world, time domain skew would always bezero; we would always be processing all events immediately as they happen. Reality is not so favorable, however, and often what we end up with looks more like Figure 2. Starting around 12:00, the watermark starts to skew more away from real time as the pipeline lags, diving back close to real time around 12:02, then lagging behind again noticeably by the time 12:03 rolls around. This dynamic variance in skew is very common in distributed data processing systems, and will play a big role in defining what functionality is necessary for providing correct, repeatable results.</p><p><img src="/2020/01/07/《The-Dataflow-Model》论文翻译/image-20200107215424077.png" alt="Figure 2: Time Domain Skew" style="zoom: 50%;"></p><blockquote><p>在理想的世界中,时间域的偏移量将始终为零,即我们将始终在事件发生时立即处理所有事件。但是,现实情况并非如此,通常,我们最终得到的结果看起来更像图2。从12:00开始,随着管线的滞后,水印开始偏离实时更多,然后回到接近实时12:02,然后到12:03时,又明显落后了。 时间偏移量的动态差异在分布式数据处理系统中非常常见,并且在定义提供准确,可重复的结果所需的功能方面将发挥重要作用。</p></blockquote><h2 id="2-DATAFLOW-MODEL"><a href="#2-DATAFLOW-MODEL" class="headerlink" title="2. DATAFLOW MODEL"></a>2. DATAFLOW MODEL</h2><p>In this section, we will define the formal model for the system and explain why its semantics are general enough to subsume the standard batch, micro-batch, and streaming models, as well as the hybrid streaming and batch semantics of the Lambda Architecture. For code examples, we will usea simplified variant of the Dataflow Java SDK, which itself is an evolution of the FlumeJava API.</p><blockquote><p>在本节中,我们将定义系统的正式模型,并解释为什么它的语义足够通用到可以包含标准批处理、微批处理和流模型,以及Lambda架构的混合流处理和批处理语义。对于代码示例,我们将使用Dataflow Java SDK的简化变体,它本身是FlumeJava API的演化。</p></blockquote><h3 id="2-1-Core-Primitives"><a href="#2-1-Core-Primitives" class="headerlink" title="2.1 Core Primitives"></a>2.1 Core Primitives</h3><p>To begin with, let us consider primitives from the classic batch model. The Dataflow SDK has two core transforms that operate on the (key, value) pairs flowing through the system:</p><ul><li><p><strong><em>ParDo</em></strong> for generic parallel processing. Each input element to be processed (which itself may be a finite collection) is provided to a user-defined function (called a <em>DoFn</em> in Dataflow), which can yield zero or more out-put elements per input. For example, consider an operation which expands all prefixes of the input key, duplicating the value across them:</p><p><img src="/2020/01/07/《The-Dataflow-Model》论文翻译/image-20200107220503810.png" alt="image-20200107220503810" style="zoom:50%;"></p></li><li><p><strong><em>GroupByKey</em></strong> for key-grouping (key, value) pairs.</p></li></ul><p><img src="/2020/01/07/《The-Dataflow-Model》论文翻译/image-20200107220520181.png" alt="image-20200107220520181" style="zoom:50%;"></p><blockquote><p>首先,让我们考虑经典批处理模型中的原语。Dataflow SDK有两个核心转换(transforms),它们对流经系统的(key、value)对进行操作:</p><ul><li><strong><em>ParDo</em></strong>。<em>ParDo</em>用于通用并行处理。每个输入元素(它本身可能是一个有限的集合)均会被用户自定义的函数(在数据流中称为<em>DoFn</em>)所处理,该函数可以为每个输入生成零个或多个输出元素。例如,考虑这样一个操作,它展开输入key的所有前缀,在它们之间复制所有的value</li><li><strong><em>GroupByKey</em></strong>。<em>GroupByKey</em>用来基于 key键将数据进行聚合</li></ul></blockquote><p>The <em>ParDo</em> operation operates element-wise on each input element, and thus translates naturally to unbounded data.The <em>GroupByKey</em> operation, on the other hand, collects all data for a given key before sending them downstream for reduction. If the input source is unbounded, we have no way of knowing when it will end. The common solution to this problem is to window the data.</p><blockquote><p><em>ParDo</em>操作是在每个输入元素上逐个操作元素,从而能够很自然地将其转换为无界数据。而在另一方面,<em>GroupByKey</em>操作收集给定key键的所有数据,然后将它们发送到下游进行缩减。如果输入源是无界的,我们无法知道它何时结束。这个问题的常见解决方案是将数据窗口化。</p></blockquote><h3 id="2-2-Windowing"><a href="#2-2-Windowing" class="headerlink" title="2.2 Windowing"></a>2.2 Windowing</h3><p>Systems which support grouping typically redefine their <em>GroupByKey</em> operation to essentially be <em>GroupByKeyAndWindow</em>. Our primary contribution here is support for un-aligned windows, for which there are two key insights. The first is that it is simpler to treat all windowing strategies as unaligned from the perspective of the model, and allow underlying implementations to apply optimizations relevant to the aligned cases where applicable. The second is that windowing can be broken apart into two related operations:</p><ul><li><p><code>Set<Window> AssignWindows(T datum)</code>, which assigns the element to zero or more windows. This is essentially the Bucket Operator from Li.</p></li><li><p><code>Set<Window> MergeWindows(Set<Window> windows)</code>, which merges windows at grouping time. This allows data-driven windows to be constructed over time as data arrive and are grouped together.</p></li></ul><blockquote><p>支持分组的系统通常将<em>GroupByKey</em>操作重新定义为<em>GroupByKeyAndWindow</em>。我们在这里的主要贡献是支持未对齐的窗口,对此有两个关键的见解。首先,从模型的角度来看,将所有的窗口策略视为未对齐的比较简单,并允许底层实现对对齐的情况应用相关的优化。第二,窗口可以分解为两个相关的操作:</p><ul><li><p><code>Set<Window> AssignWindows(T datum)</code>,它将元素赋值给零个或多个窗口。</p></li><li><p><code>Set<Window> MergeWindows(Set<Window> windows)</code>,它允许按时间分组时合并窗口。这允许在数据到达并分组在一起时,随时间构建数据驱动窗口。</p></li></ul></blockquote><p>For any given windowing strategy, the two operations are intimately related; sliding window assignment requires slid-ing window merging, sessions window assignment requires sessions window merging, etc.</p><blockquote><p>对于任何给定的窗口策略,这两个操作都是密切相关的,如滑动窗口分配需要滑动窗口合并,会话窗口分配需要会话窗口合并,等等。</p></blockquote><p>Note that, to support event-time windowing natively, instead of passing (key, value) pairs through the system, we now pass (key, value, eventtime, window) 4-tuples. Elements are provided to the system with event-time timestamps (which may also be modified at any point in the pipeline), and are initially assigned to a default global window, covering all of event time, providing semantics that match the defaults in the standard batch model.</p><blockquote><p>注意,为了在本地支持事件时间的窗口,我们现在传递(key, value, eventtime, window) 4元组,而不是传递(key, value)到系统中。元素以基于事件时间的时间戳(也可以在管道中的任何位置修改)提供给系统,并在最初时分配给一个默认的全局窗口,覆盖所有事件时间,提供与标准批处理模型中的默认值匹配的语义。</p></blockquote><h4 id="2-2-1-Window-Assignment"><a href="#2-2-1-Window-Assignment" class="headerlink" title="2.2.1 Window Assignment"></a>2.2.1 Window Assignment</h4><p>From the model’s perspective, window assignment creates a new copy of the element in each of the windows to which it has been assigned. For example, consider windowing a dataset by sliding windows of two-minute width and one-minute period, as shown in Figure 3 (for brevity, timestamps are given in HH:MM format).</p><p><img src="/2020/01/07/《The-Dataflow-Model》论文翻译/image-20200107222734127.png" alt="Figure 3: Window Assignment" style="zoom:50%;"></p><blockquote><p>从模型的的角度来看,窗口赋值是在每个已赋值给它的窗口中创建元素的新副本。例如,考虑使用两分钟时间长度和以一分钟为时间周期的滑动窗口来窗口化一个数据集,如图3所示。</p></blockquote><p>In this case, each of the two (key, value) pairs is duplicated to exist in both of the windows that overlapped the element’s timestamp. Since windows are associated directly with the elements to which they belong, this means window assignment can happen any where in the pipeline before grouping is applied. This is important, as the grouping operation may be buried somewhere downstream inside a composite transformation (e.g.<code>Sum.integersPerKey()</code>).</p><blockquote><p>在本例中,这两个(key, value)对中的每一个都被复制到重叠元素时间戳的两个窗口中。由于窗口直接与它们所属的元素相关联,这意味着在应用分组之前,可以在管道中的任何位置进行窗口分配。这很重要,因为分组操作可能隐藏在复合转换(例如<code>Sum.integersPerKey()</code>)下游中的某个地方。</p></blockquote><h4 id="2-2-2-Window-Merging"><a href="#2-2-2-Window-Merging" class="headerlink" title="2.2.2 Window Merging"></a>2.2.2 Window Merging</h4><p>Window merging occurs as part of the <em>GroupByKeyAndWindow</em> operation, and is best explained in the context of an example. We will use session windowing since it is our motivating use case. Figure 4 shows four example data, three for <em>k1</em> and one for <em>k2</em>, as they are windowed by session, with a 30-minute session timeout. All are initially placed in a default global window by the system. The sessions implementation of <em>AssignWindows</em> puts each element into a single window that extends 30 minutes beyond its own timestamp; this window denotes the range of time into which later events can fall if they are to be considered part of the same session. We then begin the <em>GroupByKeyAndWindow</em> operation, which is really a five-part composite operation:</p><ul><li><strong><em>DropTimestamps</em></strong> - Drops element timestamps, as only the window is relevant from here on out.</li><li><strong><em>GroupByKey</em></strong> - Groups (value, window) tuples by key.</li><li><strong><em>MergeWindows</em></strong> - Merges the set of currently buffered windows for a key. The actual merge logic is defined by the windowing strategy. In this case, the windows for <em>v1</em> and <em>v4</em> overlap, so the sessions windowing strategy merges them into a single new, larger session, as indicated in bold.</li><li><strong><em>GroupAlsoByWindow</em></strong> - For each key, groups values by window. After merging in the prior step,<em>v1</em> and <em>v4</em> are now in identical windows, and thus are grouped together at this step. </li><li><strong><em>ExpandToElements</em></strong> - Expands per-key, per-window groups of values into (key, value, eventtime, window)tuples, with new per-window timestamps. In this example, we set the timestamp to the end of the window, but any timestamp greater than or equal to the timestamp of the earliest event in the window is valid with respect to watermark correctness.</li></ul><p><img src="/2020/01/07/《The-Dataflow-Model》论文翻译/image-20200107224002473.png" alt="Figure 4: Window Merging" style="zoom:50%;"></p><blockquote><p>窗口合并是<em>GroupByKeyAndWindow</em>操作的一部分,这将会在后面的示例中进行解释。我们因其常见性,决定在本例中使用会话窗口。图4显示了四个示例数据,其中三个用于k1,一个用于k2,因为它们是按会话窗口显示的,并且有30分钟的会话超时。它们最初都被系统放置在一个默认的全局窗口中。<em>AssignWindows</em>的会话实现将每个元素放入一个单独的窗口中,这个窗口比它自身的时间戳延长了30分钟。此窗口表示如果迟到的事件被认为是同一会话的一部分的话,它们可能落入的时间范围。然后我们开始<em>GroupByKeyAndWindow</em>操作,这实际上是一个由五部分组成的复合操作:</p><ul><li><p><strong><em>DropTimestamps</em></strong> -丢弃元素时间戳,因为从这里开始,只有窗口相关的部分。</p></li><li><p><strong><em>GroupByKey</em></strong> -按key分组成(value、window)元组。</p></li><li><p><strong><em>MergeWindows</em></strong> -合并key的当前缓冲窗口集。实际的合并逻辑是由窗口策略定义的。在这种情况下,v1和v4对应的窗口重叠,所以会话窗口将它们合并成一个新的、更大的会话。</p></li><li><p><strong><em>GroupAlsoByWindow</em></strong> -对于每个key,通过窗口聚合所有的value。在前一步合并之后,v1和v4现在位于相同的窗口中,因此在这一步将它们组合在一起。</p></li><li><p><strong><em>ExpandToElements</em></strong> -将每个key、每个窗口的value组扩展为(key、value、eventtime、window)元组,并使用新的窗口时间戳。在本例中,我们将时间戳设置在窗口的末端,但任何大于或等于窗口中最早事件的时间戳的事件时间戳在水印准确性方面都被认为是有效的。</p></li></ul></blockquote><h4 id="2-2-3-API"><a href="#2-2-3-API" class="headerlink" title="2.2.3 API"></a>2.2.3 API</h4><p>As a brief example of the use of windowing in practice,consider the following Cloud Dataflow SDK code to calculate keyed integer sums:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">PCollection<KV<String, Integer>> input = IO.read(...);</span><br><span class="line">PCollection<KV<String, Integer>> output = input.apply(Sum.integersPerKey());</span><br></pre></td></tr></table></figure><blockquote><p>作为实际使用窗口的简要示例,请考虑以下Cloud Dataflow SDK代码以计算key 对应的整数和:</p></blockquote><p>To do the same thing, but windowed into sessions with a 30-minute timeout as in Figure 4, one would add a single <code>Window.into</code> call before initiating the summation:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">PCollection<KV<String, Integer>> input = IO.read(...);</span><br><span class="line">PCollection<KV<String, Integer>> output = input</span><br><span class="line">.apply(Window.into(Sessions.withGapDuration(Duration.standardMinutes(<span class="number">30</span>))))</span><br><span class="line">.apply(Sum.integersPerKey());</span><br></pre></td></tr></table></figure><blockquote><p>要执行相同的操作,但是要像图4那样以30分钟的超时时间窗口化到会话中,则需要在启动求和之前添加单个Window.into调用</p></blockquote><h3 id="2-3-Triggers-amp-Incremental-Processing"><a href="#2-3-Triggers-amp-Incremental-Processing" class="headerlink" title="2.3 Triggers & Incremental Processing"></a>2.3 Triggers & Incremental Processing</h3><p>The ability to build un-aligned, event-time windows is an improvement, but now we have two more shortcomings to address:</p><ul><li>We need some way of providing support for tuple- and processing-time-based windows, otherwise we have regressed our windowing semantics relative to other systems in existence.</li><li>We need some way of knowing when to emit the results for a window. Since the data are unordered with respect to event time, we require some other signal to tell us when the window is done.</li></ul><blockquote><p>拥有构建未对齐(un-aligned)的事件时间(event-time)窗口的能力是一种改进,但现在我们还有两个缺点需要解决:</p><ul><li><p>我们需要某种方式来提供对基于元组和基于处理时间的窗口的支持,否则我们已经倒退了与现有的其他系统相关的窗口语义了。</p></li><li><p>我们需要一些方法知道什么时候发出窗口的结果。由于数据对于事件时间是无序的,我们需要一些其他信号来告诉我们什么时候窗口完成数据处理了。</p></li></ul></blockquote><p>The problem of tuple- and processing-time-based windows we will address in Section 2.4, once we have built up a solution to the window completeness problem. As to window completeness, an initial inclination for solving it might be to use some sort of global event-time progress metric, such as watermarks. However, watermarks themselves have two major shortcomings with respect to correctness:</p><ul><li>They are sometimes <strong>too fast</strong>, meaning there may be late data that arrives behind the watermark. For many distributed data sources, it is intractable to derive a completely perfect event time watermark, and thus impossible to rely on it solely if we want 100% correctness in our output data.</li><li>They are sometimes <strong>too slow</strong>. Because they are a global progress metric, the watermark can be heldback for the entire pipeline by a single slow datum. And even for healthy pipelines with little variability in event-time skew, the baseline level of skew may still be multiple minutes or more, depending upon the input source. As a result, using watermarks as the sole signal for emitting window results is likely to yield higher latency of overall results than, for example, a comparable Lambda Architecture pipeline.</li></ul><blockquote><p>一旦我们建立了一个窗口完整性问题的解决方案,我们将在章节2.4中讨论基于元组和处理时间的窗口的问题。至于窗口完整性,解决它的最初倾向可能是使用某种全局的事件时间进度度量工具,例如水印(watermark)。但是,就准确性而言,水印(watermark)本身有两大缺点:</p><ul><li><p>他们有时<strong>太快</strong>了,这意味着可能有迟来的数据可能会到达在水印后面。对于许多分布式数据源而言,它们很难获得十分完美的事件时间水印,因此如果我们想要输出数据100%正确,就不可能完全依赖于它。</p></li><li><p>他们有时<strong>太慢</strong>了。因为它们是一个全局进度度量,所以水印或许会被一个缓慢的数据来阻止整个管道。即使是在正常的管道中,即使在事件时间偏移量变化不大,偏移量的基线水平仍然可能是几分钟甚至更多,这取决于输入源。因此,使用水印作为唯一的信号来发送窗口结果可能会产生比类似的Lambda架构管道更高的延迟。</p></li></ul></blockquote><p>For these reasons, we postulate that watermarks alone are insufficient. A useful insight in addressing the completeness problem is that the Lambda Architecture effectively sidesteps the issue: it does not solve the completeness problem by somehow providing correct answers faster; it simply provides the best low-latency estimate of a result that the streaming pipeline can provide, with the promise of eventual consistency and correctness once the batch pipeline runs. If we want to do the same thing from within a single pipeline (regardless of execution engine), then we will need a way to provide multiple answers (or panes) for any given window.We call this feature triggers, since they allow the specification of when to trigger the output results for a given window.</p><blockquote><p>由于这些原因,我们假定仅有水印(watermark)是不够的。解决窗口完整性问题的一个有用的方式(也是Lambda架构提出的一种有效回避该问题的方式):它并没有更快地通过某种方式提供正确的解决方法来处理完整性问题,而只是提供了流管道所能提供的结果的最佳低延迟估计值,并承诺一旦批处理管道运行起来,将在最终保持一致性和正确性。如果我们希望在单个管道中执行相同的操作(与执行引擎无关),那么我们将需要为任何给定窗口提供多个解决方法(或窗格)的方法。我们将此功能称为触发器(triggers),因为它们允许指定何时触发给定窗口的输出结果。</p></blockquote><p>In a nutshell, triggers are a mechanism for stimulating the production of <em>GroupByKeyAndWindow</em> results in response to internal or external signals. They are complementary to the windowing model, in that they each affect system behaviour along a different axis of time:</p><ul><li><p><strong>Windowing</strong> determines <em>where</em> in <strong>event time</strong> data are grouped together for processing.</p></li><li><p><strong>Triggering</strong> determines <em>when</em> in <strong>processing time</strong> the results of groupings are emitted as panes.</p></li></ul><blockquote><p>简而言之,触发器是一种机制,用于触发<em>GroupByKeyAndWindow</em>结果的生成,以响应内部或外部信号。它们是窗口模型的补充,因为它们都影响系统在不同时间轴上的行为:</p><ul><li><p><strong>窗口</strong>确定<strong>事件时间</strong>数据<strong>在哪里</strong>分组,并进行处理。</p></li><li><p><strong>触发器</strong>决定在<strong>处理时间</strong>内分组的结果<strong>在什么时候</strong>以窗格的形式发出。</p></li></ul></blockquote><p>Our systems provide predefined trigger implementations for triggering at completion estimates (e.g. watermarks, including percentile watermarks, which provide useful semantics for dealing with stragglers in both batch and streaming execution engines when you care more about processing a minimum percentage of the input data quickly than processing every last piece of it), at points in processing time, and in response to data arriving (counts, bytes, data punctuations, pattern matching, etc.). We also support composing triggers into logical combinations (and, or, etc.), loops, sequences,and other such constructions. In addition, users may define their own triggers utilizing both the underlying primitives of the execution runtime (e.g. watermark timers, processing-time timers, data arrival, composition support) and any other relevant external signals (data injection requests, external progress metrics, RPC completion callbacks, etc.).We will look more closely at examples in Section 2.4.</p><blockquote><p>我们的系统提供了用于在完成估算时触发的预定义触发器实现(例如,水印,包括百分位数水印,当您更关心快速处理最小百分比的输入数据而不是处理数据时,它们提供了有用的语义来处理批处理和流执行引擎中的散乱消息数据的最后一部分),当位于在处理时间点或者需要对数据到达(计数,字节,数据标点,模式匹配等)的响应时。 我们还支持将触发器组合成逻辑组合(and,or等),循环,序列和其他类似的构造。 另外,用户可以利用执行运行时的基本原语(例如水印计时器,处理时间计时器,数据到达,合成支持)和任何其他相关的外部信号(数据注入请求,外部进度指标,RPC回调等)来定义自己的触发器。。我们将在章节2.4中更详细地研究示例。</p></blockquote><p>In addition to controlling when results are emitted, the triggers system provides a way to control how multiple panes for the same window relate to each other, via three different refinement modes:</p><ul><li><p><strong>Discarding</strong>: Upon triggering, window contents are discarded, and later results bear no relation to previous results. This mode is useful in cases where the downstream consumer of the data (either internal or external to the pipeline) expects the values from various trigger fires to be independent (e.g. when injecting into a system that generates a sum of the values injected). It is also the most efficient in terms of amount of data buffered, though for associative and commutative operations which can be modeled as a Dataflow Combiner, the efficiency delta will often be minimal. For our video sessions use case, this is not sufficient, since it is impractical to require downstream consumers of our data to stitch together partial sessions.</p></li><li><p><strong>Accumulating</strong>: Upon triggering, window contents are left intact in persistent state, and later results become a refinement of previous results. This is useful when the downstream consumer expects to overwrite old values with new ones when receiving multiple results for the same window, and is effectively the mode used in Lambda Architecture systems, where the streaming pipeline produces low-latency results, which are then overwritten in the future by the results from the batch pipeline. For video sessions, this might be sufficient if we are simply calculating sessions and then immediately writing them to some output source that supports updates (e.g. a database or key/value store).</p></li><li><p><strong>Accumulating & Retracting</strong>: Upon triggering, inaddition to the <em>Accumulating</em> semantics, a copy of the emitted value is also stored in persistent state. When the window triggers again in the future, a retraction for the previous value will be emitted first, followed by the new value as a normal datum. Retractions are necessary in pipelines with multiple serial <em>GroupByKeyAndWindow</em> operations, since the multiple results generated by a single window over subsequent trigger fires may end up on separate keys when grouped downstream. In that case, the second grouping operation will generate incorrect results for those keys unless it is informed via a retraction that the effects of the original output should be reversed. Dataflow <em>Combiner</em> operations that are also reversible can support retractions efficiently via an <em>uncombine</em> method. For video sessions,this mode is the ideal. If we are performing aggregations downstream from session creation that depend on properties of the sessions themselves, for example detecting unpopular ads (such as those which are viewed for less than five seconds in a majority of sessions), initial results may be invalidated as inputs evolve overtime, e.g. as a significant number of offline mobile viewers come back online and upload session data. Retractions provide a way for us to adapt to these types of changes in complex pipelines with multiple serial grouping stages.</p></li></ul><blockquote><p>除了控制何时发出结果,触发器系统还提供了一种方法,可通过三种不同的优化模式来控制同一窗口的多个窗格之间的相互关系:</p><ul><li><strong>丢弃(Discarding)</strong>:触发器触发时,窗口内容将会被丢弃,并且以后的结果将与以前的结果无关。 倘若数据的下游使用者(管道内部或外部)期望来自各种触发器触发的值是独立的情况下(例如,注入到生成注入值之和的系统中),此模式很有用。 就缓冲的数据量而言,它也是最有效的,尽管对于可以为数据流组合器建模的关联和交换操作,增量效率通常会很小。 对于我们的视频会话用例,这是不够的,因为要求数据的下游使用者将部分会话缝合在一起是不切实际的。</li><li><strong>累加(Accumulating)</strong>:触发器触发时,窗口内容将保持不变,以后的结果是以以前结果为基础,进行数据增量操作。这是十分有用的方法,当下游使用者希望在同一窗口中接收到多个结果时希望用新值覆盖旧值,并且系统能够有效地作用于Lambda架构系统。而在这其中,流管道产生低延迟的结果,这些结果随后将被来自批处理管道的结果覆盖。对于视频会话,如果我们只是简单地计算会话,然后立即将其写入支持更新的某个输出源中(例如数据库或key/value存储),这可能就足够了。</li><li><strong>累积和回退(Accumulating & Retracting)</strong>:触发器触发时,除了<em>累积</em>语义外,输出值的副本也以持久状态存储。 当窗口在未来再次触发时,将首先会对先前值的回退,然后是输出作为正常基准的新值。 在具有多个串行<em>GroupByKeyAndWindow</em>操作的管道中,回退操作是必要的,因为在下游分组时,单个窗口在后续触发器触发上生成的多个结果可能会在单独的键上结束。 在那种情况下,第二次分组操作将为那些键生成不正确的结果,除非通过回退通知其原始输出进行回退。 数据流<em>Combiner</em>操作也可以通过取消组合方法有效地支持回退。 对于视频会话,此模式是理想的。 如果我们在会话创建的下游执行依赖于会话本身属性的聚合,例如检测不受欢迎的广告(例如在大多数会话中观看时间少于五秒钟的广告),则随着输入的发展,初始结果可能会是无效的,例如因为大量的离线移动设备恢复了在线状态并上传了会话数据。 回退为我们提供了一种方法,使我们可以通过多个串行分组阶段来适应复杂管道中的这些类型的更改。</li></ul></blockquote><h3 id="2-4-Examples"><a href="#2-4-Examples" class="headerlink" title="2.4 Examples"></a>2.4 Examples</h3><blockquote><p>举例部分比较简单,就是结合上面提到的所有概念,进行综合举例,有空再挖坑回填。</p></blockquote><h2 id="3-IMPLEMENTATION-amp-DESING"><a href="#3-IMPLEMENTATION-amp-DESING" class="headerlink" title="3. IMPLEMENTATION & DESING"></a>3. IMPLEMENTATION & DESING</h2><blockquote><p>实现部分是作者自身在 Google 内部的实践与经验,对于流系统开发者而言能够了解到他们在实现时碰到的坑。因为是了解背后原理就不进行详细翻译了。</p></blockquote><h2 id="4-CONCLUSIONS"><a href="#4-CONCLUSIONS" class="headerlink" title="4. CONCLUSIONS"></a>4. CONCLUSIONS</h2><p>The future of data processing is unbounded data. Though bounded data will always have an important and useful place, it is semantically subsumed by its unbounded counterpart. Furthermore, the proliferation of unbounded datasets across modern business is staggering. At the same time, consumers of processed data grow savvier by the day, demanding powerful constructs like event-time ordering and unaligned windows. The models and systems that exist today serve as an excellent foundation on which to build the data processing tools of tomorrow, but we firmly believe that a shift in overall mindset is necessary to enable those tools to comprehensively address the needs of consumers of unbounded data.</p><blockquote><p>无边界(无限)的数据是数据处理的未来。 尽管有边界(有限)的数据将始终具有重要和有用的位置,但从语义上讲,它由无边界的对应部分所包含。 此外,无限数据集在整个跨现代业务中的扩散令人震惊。 同时,处理数据的消费者一天比一天更加精明,因此需要强大的架构,例如事件时间顺序和未对齐的窗口等。 当今存在的模型和系统为构建未来的数据处理工具奠定了良好的基础,但是我们坚信,必须转变整体的观念,以使这些工具能够全面满足数据消费者的需求。 </p></blockquote><p>Based on our many years of experience with real-world,massive-scale, unbounded data processing within Google, we believe the model presented here is a good step in that direction. It supports the un-aligned, event-time-ordered windows modern data consumers require. It provides flexible triggering and integrated accumulation and retraction, refocusing the approach from one of finding completeness in data to one of adapting to the ever present changes manifest in real-world datasets. It abstracts away the distinction of batch vs.micro-batch vs. streaming, allowing pipeline builders a more fluid choice between them, while shielding them from the system-specific constructs that inevitably creep into models targeted at a single underlying system. Its overall flexibility allows pipeline builders to appropriately balance the dimensions of correctness, latency, and cost to fit their use case, which is critical given the diversity of needs in existence. And lastly, it clarifies pipeline implementations by separating the notions of what results are being computed, where in event time they are being computed, when in processing time they are materialized, and how earlier results relate to later refinements. We hope others will find this model useful as we all continue to push forward the state of the art in this fascinating, remarkably complex field.</p><blockquote><p>根据我们多年在Google中真实,大规模,无边界数据处理的经验,我们相信此处介绍的模型是朝这个方向迈出的重要一步。 它支持消费者需要的未对齐,事件时间顺序的窗口现代数据。 它提供了灵活的触发方式以及集成的累积和回退功能,将寻找数据完整性的方法重新定位为适应现实数据集中不断变化的方法。 它抽象化了批处理、微型批处理和流式处理三者的区别,使管道构建器可以在它们之间进行更多的选择,同时使它们免受系统特定的构造的影响,这些构造不可避免地会渗入针对单个基础系统的模型。 它的整体灵活性使流水线构建者可以适当地平衡正确性,延迟和成本这三个维度,以适应其用例,考虑到现有需求的多样性,这一点至关重要。最后,它通过分离以下概念来澄清流水线实现:正在计算哪些结果,其中计算它们的事件时间,在处理时间何时实现它们,以及较早的结果与以后的改进有何关系。我们希望其他人会发现此模型有用,因为我们所有人都将继续在这个引人入胜,非常复杂的领域中发展最先进的技术。</p></blockquote>]]></content>
<tags>
<tag> flink </tag>
</tags>
</entry>
<entry>
<title>八日漫游大西环线</title>
<link href="/2018/12/06/%E5%85%AB%E6%97%A5%E6%BC%AB%E6%B8%B8%E5%A4%A7%E8%A5%BF%E7%8E%AF%E7%BA%BF/"/>
<url>/2018/12/06/%E5%85%AB%E6%97%A5%E6%BC%AB%E6%B8%B8%E5%A4%A7%E8%A5%BF%E7%8E%AF%E7%BA%BF/</url>
<content type="html"><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>这次旅行可以认为是一场说走就走的旅行,缘起于朋友的一次不经意的漫谈,到最终构思出大致的计划不过两日,我俩就踏上了旅程,途中边走边规划,要去哪吃,要去哪玩。</p><blockquote><p>这篇记录主要以风景照为主,美食的话没有拍,味道全留在肚子里了。</p></blockquote><h2 id="第一日:成都-兰州"><a href="#第一日:成都-兰州" class="headerlink" title="第一日:成都-兰州"></a>第一日:成都-兰州</h2><p>出发前一天,看到凌晨的机票十分便宜便立马下手,本以为捡到了大便宜,但是成都突如其来的大雾天气导致我们的航班延误了整整5个小时。在等待期间,别的航空公司的飞机由于机型缘故可以在较恶劣的条件下起飞,所以我们只能在同一个登机口眼巴巴地看着他们欢快的登机。</p><blockquote><p>切勿贪小便宜乘坐廉价航空或者机型较小的飞机!</p></blockquote><p><img src="/2018/12/06/八日漫游大西环线/IMG_8018.JPG" alt=""></p><p>到达兰州的时候已经中午12点半了,我们拿着行李就跑去乘坐机场大巴赶去下榻酒店。中川机场到市中心的距离长达68公里之远,所以一般都不会考虑打车去市中心,而是选择两条路线:到隔壁的中川机场高铁站乘坐高铁或者乘坐机场大巴,两条路线的价格和花费时间都相差不多。</p><p>匆忙放完行李后,我们早已肚子饿的不得了,便到楼下的兰州拉面点上了两碗心心念念的牛肉面。</p><p><img src="/2018/12/06/八日漫游大西环线/IMG_8036.JPG" alt=""></p><p>在兰州安排的第一个必游的景点是甘肃省博物馆,主要目的还是奔着镇馆之宝——马踏飞燕走的。但是不得不说,逛完博物馆后整个人都虚脱了,只能回酒店暂作休息。迷迷糊糊睡了会儿,便起身去看看兰州夜景。</p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_17.jpg" alt=""></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_26.jpg" alt=""></p><h2 id="第二日:兰州-西宁"><a href="#第二日:兰州-西宁" class="headerlink" title="第二日:兰州-西宁"></a>第二日:兰州-西宁</h2><p>由于今日的我们没有安排过多的行程,便睡了个回笼觉,睡醒便已经10点钟了。早上我们只安排了一个景点——白塔山公园,虽说是公园,其实就有点像深圳的莲花山公园,还是有点山路的,况且我们还是拿着全副行李,但是想到能够俯瞰兰州全貌,便咬咬牙爬了上去。</p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_2d.jpg" alt=""></p><p>从公园下来已经接近中午,吃了最后一顿美味的牛肉面后又马不停蹄的赶往兰州西站,乘坐高铁前往西宁。在西宁游玩时,给我印象最深的便是较近晚上时前去的东关清真大寺,印象深的并不是里面的建筑,而是里面的穆斯林老人,他们见到你时,会先递给你一张小纸片,上面记录着伊斯兰教中最重要的几句话,然后会十分热情地跟你述说了伊斯兰教的由来、信仰伊斯兰教与其他宗教的不同等等。从我的直觉上看,倘若我们不刻意打断他们(虽然很不礼貌),他们能讲上一整天。</p><h2 id="第三日:西宁-塔尔寺-青海湖"><a href="#第三日:西宁-塔尔寺-青海湖" class="headerlink" title="第三日:西宁-塔尔寺-青海湖"></a>第三日:西宁-塔尔寺-青海湖</h2><p>尽管昨日有过短暂的休息,但是还是忍不住今早早起的哈欠。我们与昨日联系好的小马哥(本次旅行的司机)约好九点半在酒店楼下集合,这一次同行的包括司机在内总共有七人(四男三女),出乎意料的是主要都来自广东。</p><p>后面的文字记录,我就不过多描述旅途中的辛酸了,主要还是以风景为重点进行记录。</p><h3 id="路途风景"><a href="#路途风景" class="headerlink" title="路途风景"></a>路途风景</h3><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_52.jpg" alt="UNADJUSTEDNONRAW_thumb_52"></p><h3 id="塔尔寺"><a href="#塔尔寺" class="headerlink" title="塔尔寺"></a>塔尔寺</h3><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_40.jpg" alt="UNADJUSTEDNONRAW_thumb_40"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_43.jpg" alt="UNADJUSTEDNONRAW_thumb_43"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_46.jpg" alt="UNADJUSTEDNONRAW_thumb_46"></p><h3 id="青海湖"><a href="#青海湖" class="headerlink" title="青海湖"></a>青海湖</h3><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_5a.jpg" alt="UNADJUSTEDNONRAW_thumb_5a"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_5f.jpg" alt="UNADJUSTEDNONRAW_thumb_5f"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_6a.jpg" alt="UNADJUSTEDNONRAW_thumb_6a"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_57.jpg" alt="UNADJUSTEDNONRAW_thumb_57"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_63.jpg" alt="UNADJUSTEDNONRAW_thumb_63"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_65.jpg" alt="UNADJUSTEDNONRAW_thumb_65"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_68.jpg" alt="UNADJUSTEDNONRAW_thumb_68"></p><h2 id="第四日:茶卡盐湖-翡翠湖-柴达木盆地"><a href="#第四日:茶卡盐湖-翡翠湖-柴达木盆地" class="headerlink" title="第四日:茶卡盐湖-翡翠湖-柴达木盆地"></a>第四日:茶卡盐湖-翡翠湖-柴达木盆地</h2><h3 id="清晨的茶卡镇"><a href="#清晨的茶卡镇" class="headerlink" title="清晨的茶卡镇"></a>清晨的茶卡镇</h3><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_6c.jpg" alt="UNADJUSTEDNONRAW_thumb_6c"></p><h3 id="茶卡盐湖"><a href="#茶卡盐湖" class="headerlink" title="茶卡盐湖"></a>茶卡盐湖</h3><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_7e.jpg" alt="UNADJUSTEDNONRAW_thumb_7e"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_75.jpg" alt="UNADJUSTEDNONRAW_thumb_75"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_79.jpg" alt="UNADJUSTEDNONRAW_thumb_79"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_86.jpg" alt="UNADJUSTEDNONRAW_thumb_86"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_7f.jpg" alt="UNADJUSTEDNONRAW_thumb_7f"></p><h3 id="翡翠湖"><a href="#翡翠湖" class="headerlink" title="翡翠湖"></a>翡翠湖</h3><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_93.jpg" alt="UNADJUSTEDNONRAW_thumb_93"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_96.jpg" alt="UNADJUSTEDNONRAW_thumb_96"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_a0.jpg" alt="UNADJUSTEDNONRAW_thumb_a0"></p><h2 id="第五日:雅丹魔鬼城-敦煌"><a href="#第五日:雅丹魔鬼城-敦煌" class="headerlink" title="第五日:雅丹魔鬼城-敦煌"></a>第五日:雅丹魔鬼城-敦煌</h2><h3 id="雅丹魔鬼城"><a href="#雅丹魔鬼城" class="headerlink" title="雅丹魔鬼城"></a>雅丹魔鬼城</h3><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_ac.jpg" alt="UNADJUSTEDNONRAW_thumb_ac"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_ae.jpg" alt="UNADJUSTEDNONRAW_thumb_ae"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_b1.jpg" alt="UNADJUSTEDNONRAW_thumb_b1"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_b2.jpg" alt="UNADJUSTEDNONRAW_thumb_b2"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_b4.jpg" alt="UNADJUSTEDNONRAW_thumb_b4"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_b5.jpg" alt="UNADJUSTEDNONRAW_thumb_b5"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_b6.jpg" alt="UNADJUSTEDNONRAW_thumb_b6"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_bb.jpg" alt="UNADJUSTEDNONRAW_thumb_bb"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_be.jpg" alt="UNADJUSTEDNONRAW_thumb_be"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_c1.jpg" alt="UNADJUSTEDNONRAW_thumb_c1"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_cc.jpg" alt="UNADJUSTEDNONRAW_thumb_cc"></p><h2 id="第六日:敦煌-莫高窟-鸣沙山月牙泉"><a href="#第六日:敦煌-莫高窟-鸣沙山月牙泉" class="headerlink" title="第六日:敦煌-莫高窟-鸣沙山月牙泉"></a>第六日:敦煌-莫高窟-鸣沙山月牙泉</h2><h3 id="莫高窟"><a href="#莫高窟" class="headerlink" title="莫高窟"></a>莫高窟</h3><p>由于景区规定了洞窟内不能摄影,所以就拍了一张外景图。</p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_e2.jpg" alt="UNADJUSTEDNONRAW_thumb_e2"></p><h3 id="鸣沙山月牙泉"><a href="#鸣沙山月牙泉" class="headerlink" title="鸣沙山月牙泉"></a>鸣沙山月牙泉</h3><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_e9.jpg" alt="UNADJUSTEDNONRAW_thumb_e9"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_ec.jpg" alt="UNADJUSTEDNONRAW_thumb_ec"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_ef.jpg" alt="UNADJUSTEDNONRAW_thumb_ef"></p><h2 id="第七日:敦煌-七彩丹霞-张掖"><a href="#第七日:敦煌-七彩丹霞-张掖" class="headerlink" title="第七日:敦煌-七彩丹霞-张掖"></a>第七日:敦煌-七彩丹霞-张掖</h2><h3 id="七彩丹霞"><a href="#七彩丹霞" class="headerlink" title="七彩丹霞"></a>七彩丹霞</h3><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_fa.jpg" alt="UNADJUSTEDNONRAW_thumb_fa"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_fc.jpg" alt="UNADJUSTEDNONRAW_thumb_fc"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_105.jpg" alt="UNADJUSTEDNONRAW_thumb_105"></p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_110.jpg" alt="UNADJUSTEDNONRAW_thumb_110"></p><h3 id="张掖"><a href="#张掖" class="headerlink" title="张掖"></a>张掖</h3><p>在张掖这块地方,不得不提的就是羊肉了,真的可以说得上又便宜又好吃,60块一斤的羊肉肥而不腻,分量十足,加上大蒜以及泡菜,简单美味。</p><h2 id="第八日:张掖-兰州-成都"><a href="#第八日:张掖-兰州-成都" class="headerlink" title="第八日:张掖-兰州-成都"></a>第八日:张掖-兰州-成都</h2><p>为什么第八日的行程看上去那么复杂?主要考虑到从张掖开车返回西宁的话,由于冬天的缘故,预计行程中的油菜花田是没有的,外加雪山封路会导致时间加长,所以我们就打算以高铁的行程直接返回兰州,再从兰州乘飞机返回成都,这样下来所花费的金钱只和西宁到达成都相差无几,但节省了不少时间。</p><p><img src="/2018/12/06/八日漫游大西环线/IMG_8637.JPG" alt="IMG_8637"></p><h2 id="结尾"><a href="#结尾" class="headerlink" title="结尾"></a>结尾</h2><p>最后就附上我们这次两人这八天下来所预估的花费清单,淡季出行+学生半价(甚至免票)是一个很棒的结合!</p><p><img src="/2018/12/06/八日漫游大西环线/UNADJUSTEDNONRAW_thumb_111.jpg" alt="UNADJUSTEDNONRAW_thumb_111"></p>]]></content>
</entry>
<entry>
<title>交友?</title>
<link href="/2018/11/19/%E4%BA%A4%E5%8F%8B%EF%BC%9F/"/>
<url>/2018/11/19/%E4%BA%A4%E5%8F%8B%EF%BC%9F/</url>
<content type="html"><![CDATA[<blockquote><p>这篇文章纯属自己的有感而发写的。</p></blockquote><p>这段时间也不知怎么回事,做起事来都充满了无力感</p><p>朋友的冷漠 敏感 怀疑自己</p>]]></content>
</entry>
<entry>
<title>出门走走-贵州岑巩县</title>
<link href="/2018/11/12/%E5%87%BA%E9%97%A8%E8%B5%B0%E8%B5%B0-%E8%B4%B5%E5%B7%9E%E5%B2%91%E5%B7%A9%E5%8E%BF/"/>
<url>/2018/11/12/%E5%87%BA%E9%97%A8%E8%B5%B0%E8%B5%B0-%E8%B4%B5%E5%B7%9E%E5%B2%91%E5%B7%A9%E5%8E%BF/</url>
<content type="html"><![CDATA[<blockquote><p>上周参加了学校组织的扶贫活动,地点位于贵州岑巩县。并不是因为偷懒没写技术博客才去的呀:)</p></blockquote><h2 id="做了什么"><a href="#做了什么" class="headerlink" title="做了什么"></a>做了什么</h2><p>这次扶贫活动的主要是帮助各乡镇进行贫困户的信息录入工作,减少一些他们的工作量,以我前去的平庄镇而言,乡镇的人口基数相较于其他乡镇而言还算比较大的,外加上镇上的干部数量比较少,所以信息录入工作基本就是由一人负责,工作量大而繁琐。</p><p>额外说下的是,因为需要在2021年要达到“两个一百年”中第一个一百年的目标,不知从几年前开始,我所在的镇上所有干部就基本上过着加班的日子,隔三差五就有一个会议要开。值班的日子是以两周为间隔,也就是说至少需要值班两周才能够回家休息这样的一个状态。在平常的时候还需要经常下乡对每家每户进行调研统计,方便后期对各贫困户实施不同的扶贫措施。</p><p><strong>真的感谢你们的辛苦工作!</strong></p><h2 id="一些感想"><a href="#一些感想" class="headerlink" title="一些感想"></a>一些感想</h2><p>跟同学一起进行贫困户信息录入的这段时间中,我接触了不少致贫原因各不相同的家庭,总结下自己的一些感触吧。</p><h3 id="补助"><a href="#补助" class="headerlink" title="补助"></a>补助</h3><p>在个人收入中,可分为工资性收入、生产性经营收入和各项补助。若家庭中有患有重病或者残疾的人的话,前两项收入往往是较少的,更主要是通过补助的方式维持生活,而各项补助总和的金额却是较少的(1k-10k 浮动)。</p><p>虽然较偏远地区的生活水平较低,但我真的不清楚这些补助金额是否能够维持这些特殊人群的正常生活。</p><h3 id="教育"><a href="#教育" class="headerlink" title="教育"></a>教育</h3><p>不知是否受限于九年义务教育的原因,有不少的人选择了初中毕业后就直接去外地工作,或独闯天下,或与父母一起,家庭总体收入较低且不具有稳定性(即数据相较于去年而言变化较大)。至于为什么选择直接工作也有各种各样的理由,有的人是因为家庭原因,有的人却是因为不想读了(原话)。与上述情况不同的,有些家庭的家长虽然身处外地打工,却依然支持自己的子女上高中上大学。在一些已有大学生的家庭中,我能够感受他们家庭自身的收入在一个中等偏上(相较于全村而言)的水平。</p><p>由于自己只能够通过纸张上的对比表,从家庭各成员学历、工作地、收入来分析他们的情况,所以我没法真真正正了解到他们每一个人的想法与感受。但是有一点我能感受到的是,接受了高等教育的人,能够为自己的家庭贡献出更多的力量。</p><h2 id="美丽岑巩"><a href="#美丽岑巩" class="headerlink" title="美丽岑巩"></a>美丽岑巩</h2><p>身处于城市过久,来乡村的一周时间中,觉得乡村真是一个很不错的地方,虽然在生活设施方面远不及城市,但无论是自然风景,还是饮食,乡村还是有其独特之处。(再次特别感谢每日饭堂的好饭菜!)</p><p><img src="/2018/11/12/出门走走-贵州岑巩县/IMG_7851.JPG" alt=""></p><p><img src="/2018/11/12/出门走走-贵州岑巩县/IMG_7852.JPG" alt=""></p><p><img src="/2018/11/12/出门走走-贵州岑巩县/IMG_7854.JPG" alt=""></p><p><img src="/2018/11/12/出门走走-贵州岑巩县/IMG_7864.JPG" alt=""></p>]]></content>
<tags>
<tag> 旅行 </tag>
</tags>
</entry>
<entry>
<title>在小米实习的180天</title>
<link href="/2018/07/20/%E5%9C%A8%E5%B0%8F%E7%B1%B3%E5%AE%9E%E4%B9%A0%E7%9A%84180%E5%A4%A9/"/>
<url>/2018/07/20/%E5%9C%A8%E5%B0%8F%E7%B1%B3%E5%AE%9E%E4%B9%A0%E7%9A%84180%E5%A4%A9/</url>
<content type="html"><![CDATA[<p>感恩在小米的这段实习经历,感谢小米身边的每个人。</p><p><img src="http://on83riher.bkt.clouddn.com/WechatIMG17218.png" alt=""></p>]]></content>
</entry>
<entry>
<title>TCP 协议中 Keep-Alive 特性</title>
<link href="/2018/05/27/TCP%20%E5%8D%8F%E8%AE%AE%E4%B8%AD%20Keep-Alive%20%E7%89%B9%E6%80%A7/"/>
<url>/2018/05/27/TCP%20%E5%8D%8F%E8%AE%AE%E4%B8%AD%20Keep-Alive%20%E7%89%B9%E6%80%A7/</url>
<content type="html"><![CDATA[<blockquote><p>在腾讯面试的时候问过我基于这个特性的问题,可惜我没答出来:(,以下为原题部分。</p><p>在 TCP 连接中,我们都知道客户端要与服务器端断开连接时需要经过”四次分手”。但如果客户端在未知因素的情况下宕机了,那服务器端会在什么时候认为客户端已掉线,从而服务器端”主动”断开连接呢?</p></blockquote><h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>抛弃上面的描述,我们知道在 TCP 协议中,如果客户端不主动断开与服务器端的连接时,服务器端便会一直持有对这个客户端的连接。如果不引入某些有效机制的话,这将会大大地消耗服务器端的资源。</p><p>keep-alive 机制确保了服务器端能够在客户端无消息发送的一段时间后,自主地断开与客户端的连接。</p><h4 id="RFC-中-Keep-Alive-机制"><a href="#RFC-中-Keep-Alive-机制" class="headerlink" title="RFC 中 Keep-Alive 机制"></a>RFC 中 Keep-Alive 机制</h4><p>keep-alive 是 TCP 协议的可选特性(optional feature)。如果操作系统实现了这一特性,就必须保证应用程序能够为每个 TCP 连接打开或关闭该特性,且这一特性必须是默认关闭的。</p><p>keep-alive 的心跳包只能够在从最后一次接收到 ACK 包的时间起,经过一个固定的时间间隔后才能发送。这个时间间隔必须能够被配置,且默认值不能够低于2小时。</p><p>keep-alive 应当在服务器端启用,而客户端不做任何修改。倘若客户端开启了这一特性,当客户端异常崩溃或者出现连接故障的话,将会导致该连接无限期挂起和消耗不必要的资源。</p><p>在 TCP 规范中并不包含 keep-alive 机制的主要原因有三:(1)在短暂的网络故障期间,可能会导致一个良好正常的连接(perfectly good connections)断开。(2)消耗不必要的带宽资源(”if no one is using the connection, who cares if it is still good?”)。(3)在以数据包计费的互联网网络中(额外)花费金钱。</p><h4 id="Linux-内核下-Keep-Alive-的重要参数"><a href="#Linux-内核下-Keep-Alive-的重要参数" class="headerlink" title="Linux 内核下 Keep-Alive 的重要参数"></a>Linux 内核下 Keep-Alive 的重要参数</h4><p>在 Linux 内核中,keep-alive 机制涉及到三个重要的参数:</p><ol><li>tcp_keepalive_time。该参数是指最后一次数据包(不包含数据的 ACK 包)发送的时间到第一次发送的心跳包之间的时间间隔。默认值为7200s(2小时)。</li><li>tcp_keepalive_intvl。该参数是指连续两个心跳包之间的时间间隔。默认值为75s。</li><li>tcp_keepalive_probes。该参数是指在服务器端认为该连接失效(dead)并通知用户前,未确认的探测器(unacknowledged probes)发送的数量。默认值为9(次)。</li></ol><p>Linux 的文档还特别声明了即使 keep-alive 这一机制在内核中被配置了,这一行为也不是 Linux 的默认行为。</p><h4 id="面试题的一种合适的解释"><a href="#面试题的一种合适的解释" class="headerlink" title="面试题的一种合适的解释"></a>面试题的一种合适的解释</h4><p>了解了这一特性背后的含义时,我们可以对面试官说到。在 Linux 环境下,如果该连接中 keep-alive 机制已开启时,服务器端会在 7200s + 75s * 9time 后断开与客户端的连接(即在底层清除失效的文件描述符)。</p><h4 id="与-HTTP-中-Keep-Alive-的对比"><a href="#与-HTTP-中-Keep-Alive-的对比" class="headerlink" title="与 HTTP 中 Keep-Alive 的对比"></a>与 HTTP 中 Keep-Alive 的对比</h4><p>HTTP 协议中的 keep-alive 机制是为了通信双方的连接复用,避免消耗太多资源。而 TCP 协议中 keep-alive 机制是为了检验通信双方的是否活着(alive),保证通信能够正常进行。</p><hr><p>参考资料:</p><ol><li><a href="https://tools.ietf.org/html/rfc1122#page-101" target="_blank" rel="noopener">https://tools.ietf.org/html/rfc1122#page-101</a></li><li><a href="http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html" target="_blank" rel="noopener">http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html</a></li><li><a href="http://www.importnew.com/27624.html" target="_blank" rel="noopener">http://www.importnew.com/27624.html</a></li><li><a href="http://www.cnblogs.com/liuyong/archive/2011/07/01/2095487.html" target="_blank" rel="noopener">http://www.cnblogs.com/liuyong/archive/2011/07/01/2095487.html</a></li></ol>]]></content>
</entry>
<entry>
<title>Scala - NonLocalReturnControl</title>
<link href="/2018/05/22/Scala%20-%20NonLocalReturnControl/"/>
<url>/2018/05/22/Scala%20-%20NonLocalReturnControl/</url>
<content type="html"><![CDATA[<h4 id="状态说明"><a href="#状态说明" class="headerlink" title="状态说明"></a>状态说明</h4><p>今天跑 Spark 作业的时候,刚进入 RUNNING 状态没多久就直接抛出了下面这种异常。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">User class threw exception: org.apache.spark.SparkException: Task not serializable</span><br><span class="line"> at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:298)</span><br><span class="line"> at org.apache.spark.util.ClosureCleaner$.org$apache$spark$util$ClosureCleaner$$clean(ClosureCleaner.scala:288)</span><br><span class="line"> at org.apache.spark.util.ClosureCleaner$.clean(ClosureCleaner.scala:108)</span><br><span class="line"> at org.apache.spark.SparkContext.clean(SparkContext.scala:2100)</span><br><span class="line">.....</span><br><span class="line">Caused by: java.io.NotSerializableException: java.lang.Object</span><br><span class="line">Serialization stack:</span><br><span class="line"> - object not serializable (class: java.lang.Object, value: java.lang.Object@65c9e3ee)</span><br><span class="line"> - field (class: com.xiaomi.search.websearch.hbase.SegTitlePick$$anonfun$1, name: nonLocalReturnKey1$1, type: class java.lang.Object)</span><br><span class="line"> - object (class com.xiaomi.search.websearch.hbase.SegTitlePick$$anonfun$1, <function1>)</span><br><span class="line"> at org.apache.spark.serializer.SerializationDebugger$.improveException(SerializationDebugger.scala:40)</span><br><span class="line"> at org.apache.spark.serializer.JavaSerializationStream.writeObject(JavaSerializer.scala:46)</span><br><span class="line"> at org.apache.spark.serializer.JavaSerializerInstance.serialize(JavaSerializer.scala:100)</span><br><span class="line"> at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:295)</span><br></pre></td></tr></table></figure><p>上网一查发现时某个匿名函数里面使用了 return 导致的。</p><h4 id="报错理由是什么呢"><a href="#报错理由是什么呢" class="headerlink" title="报错理由是什么呢"></a>报错理由是什么呢</h4><p>源代码就不贴出来了,我们以一个简单的例子来说明这个问题吧。</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">object</span> <span class="title">Test</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">main</span></span>(args: <span class="type">Array</span>[<span class="type">String</span>]): <span class="type">Unit</span> = {</span><br><span class="line"> <span class="keyword">val</span> datas = <span class="type">List</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>)</span><br><span class="line"> datas.foreach(t => {</span><br><span class="line"> <span class="keyword">if</span> (t % <span class="number">2</span> == <span class="number">0</span>) <span class="keyword">return</span> <span class="comment">// 运行符合条件时便立刻返回</span></span><br><span class="line"> })</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 本例的目标想在遍历完 datas 后便输出该语句,但在实际情况下,return 语句会直接返回并退出当前函数(即 main 函数),所以以下语句并不会输出结果</span></span><br><span class="line"> println(<span class="string">"finished!"</span>) </span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>让我们查看编译后这段遍历的代码有什么不一样的地方吧?</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// scalac -Xprint:explicitouter Test.scala</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">main</span></span>(args: <span class="type">Array</span>[<span class="type">String</span>]): <span class="type">Unit</span> = {</span><br><span class="line"> <synthetic> <span class="keyword">val</span> nonLocalReturnKey1: <span class="type">Object</span> = <span class="keyword">new</span> <span class="type">Object</span>();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">val</span> datas: <span class="type">List</span>[<span class="type">Int</span>] = scala.collection.immutable.<span class="type">List</span>.apply[<span class="type">Int</span>] (scala.<span class="type">Predef</span>.wrapIntArray(<span class="type">Array</span>[<span class="type">Int</span>]{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>}));</span><br><span class="line"> datas.foreach[<span class="type">Unit</span>]({</span><br><span class="line"> <span class="keyword">final</span> <artifact> <span class="function"><span class="keyword">def</span> <span class="title">$anonfun$main</span></span>(t: <span class="type">Int</span>): <span class="type">Unit</span> = <span class="keyword">if</span> (t.%(<span class="number">2</span>).==(<span class="number">0</span>))</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> scala.runtime.<span class="type">NonLocalReturnControl</span>$mcV$sp(nonLocalReturnKey1, ())</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> ();</span><br><span class="line"> ((t: <span class="type">Int</span>) => $anonfun$main(t))</span><br><span class="line"> });</span><br><span class="line"> scala.<span class="type">Predef</span>.println(<span class="string">"finished!"</span>)</span><br><span class="line"> } <span class="keyword">catch</span> {</span><br><span class="line"> <span class="keyword">case</span> (ex @ (_: scala.runtime.<span class="type">NonLocalReturnControl</span>[<span class="type">Unit</span> <span class="meta">@unchecked</span>])) => <span class="keyword">if</span> (ex.key().eq(nonLocalReturnKey1))</span><br><span class="line"> ex.value$mcV$sp()</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="keyword">throw</span> ex</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>编译后我们可以看到原先匿名函数中的 return 语句被替换成抛出一个<code>NonLocalReturnControl</code>运行时异常,而<code>try-catch</code>环绕着整个 main 函数内部的代码块来尝试捕获这个异常。</p><p>而观察<code>NonLocalReturnControl</code>异常,我们发现这个异常是无法被序列化的,这就解释了之前的作业抛出异常的意思了。</p><h4 id="为什么-return-语句要这么设计呢"><a href="#为什么-return-语句要这么设计呢" class="headerlink" title="为什么 return 语句要这么设计呢"></a>为什么 return 语句要这么设计呢</h4><p>为什么 Scala 要这么做呢?这里有几篇不错的文章来说明,我就偷懒不去翻译了(建议从上往下看)</p><ol><li>介绍什么是 non-local return - <a href="https://www.zhihu.com/question/22240354/answer/64673094" target="_blank" rel="noopener">https://www.zhihu.com/question/22240354/answer/64673094</a></li><li>前半段介绍 return 语句该什么时候出现,后半段推测出这么做的两个原因 - <a href="https://stackoverflow.com/questions/17754976/scala-return-statements-in-anonymous-functions" target="_blank" rel="noopener">https://stackoverflow.com/questions/17754976/scala-return-statements-in-anonymous-functions</a></li><li>讨论在 Scala 中 function 和 method 两者概念上的区别 - <a href="https://link.jianshu.com/?t=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F2529184%2Fdifference-between-method-and-function-in-scala" target="_blank" rel="noopener">https://link.jianshu.com/?t=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F2529184%2Fdifference-between-method-and-function-in-scala</a></li></ol><p>但其实翻阅了网上的资料,并没有真正地说明为什么这么设计。结合上面的几篇文章,我个人认为在 Scala 这一门函数式编程语言里,其更加讲究的是程序执行的结果,而并非执行过程。return 语句影响程序的顺序执行,从而可能会使代码变得复杂,也可能会发生若干次程序执行的结果不一致的情况,那么这将在很大程度上影响了我们对于代码的理解与认识。这也是 Scala 为什么不倡导我们使用 return。 </p>]]></content>
</entry>
<entry>
<title>Scala - Iterator vs Stream vs View</title>
<link href="/2018/05/19/Scala%20-%20Iterator%20vs%20Stream%20vs%20View/"/>
<url>/2018/05/19/Scala%20-%20Iterator%20vs%20Stream%20vs%20View/</url>
<content type="html"><![CDATA[<h4 id="问题来源"><a href="#问题来源" class="headerlink" title="问题来源"></a>问题来源</h4><p><a href="https://stackoverflow.com/questions/5159000/stream-vs-views-vs-iterators" target="_blank" rel="noopener">https://stackoverflow.com/questions/5159000/stream-vs-views-vs-iterators</a></p><h4 id="优秀回答"><a href="#优秀回答" class="headerlink" title="优秀回答"></a>优秀回答</h4><blockquote><p>该篇回答被收录到 Scala 文档中的 F&Q 部分。我尝试跟着这篇回答并对照源码部分去翻译,翻译不好多多谅解。</p></blockquote><p>First, they are all <em>non-strict</em>. That has a particular mathematical meaning related to functions, but, basically, means they are computed on-demand instead of in advance.</p><p>首先,它们都是非严格(即惰性的)的。每个函数都有其特定的数学含义,但是基本上,其数学含义通常都意味着它们是按需计算而非提前计算。</p><p><code>Stream</code> is a lazy list indeed. In fact, in Scala, a <code>Stream</code> is a <code>List</code> whose <code>tail</code> is a <code>lazy val</code>. Once computed, a value stays computed and is reused. Or, as you say, the values are cached.</p><p><code>Stream</code>确实是一个惰性列表。事实上,在 Scala 中,<code>Stream</code>是<code>tail</code>变量为惰性值的列表。一旦开始计算,<code>Stream</code>中的值便保持计算后的状态并被能够被重复使用。或者按照你的说法是,<code>Stream</code>中的值能够被缓存下来。</p><blockquote><p>一篇比较不错的、科普<code>Stream</code>的文章:<a href="http://cuipengfei.me/blog/2014/10/23/scala-stream-application-scenario-and-how-its-implemented/" target="_blank" rel="noopener">http://cuipengfei.me/blog/2014/10/23/scala-stream-application-scenario-and-how-its-implemented/</a></p></blockquote><p>An <code>Iterator</code> can only be used once because it is a <em>traversal pointer</em> into a collection, and not a collection in itself. What makes it special in Scala is the fact that you can apply transformation such as <code>map</code> and <code>filter</code> and simply get a new <code>Iterator</code> which will only apply these transformations when you ask for the next element.</p><p><code>Iterator</code>只能够被使用一次,因为其是一个<em>可遍历</em>的指针存在于集合当中,而非集合本身存在于<code>Iterator</code>中。让其在 Scala 如此特殊的原因在于你能够使用 transformation 算子,如<code>map</code>或者<code>filter</code>,并且很容易地获得一个新的<code>Iterator</code>。需要注意的是,新的<code>Iterator</code>只有通过获取元素的时候才会应用那些 transformation 算子。</p><p>Scala used to provide iterators which could be reset, but that is very hard to support in a general manner, and they didn’t make version 2.8.0.</p><p>Scala 曾尝试过给那些 iterator 一个可复位的功能,但这很难以一个通用的方式去支持。</p><p>Views are meant to be viewed much like a database view. It is a series of transformation which one applies to a collection to produce a “virtual” collection. As you said, all transformations are re-applied each time you need to fetch elements from it.</p><p>Views 通常意味着元素需要被观察,类似于数据库中的 view。它是原集合通过一系列的 transformation 算子生成的一个”虚构”的集合。如你所说,每当你需要从原集合中获取数据时,都能够重复应用这些 transformation 算子。</p><p>Both <code>Iterator</code> and views have excellent memory characteristics. <code>Stream</code> is nice, but, in Scala, its main benefit is writing infinite sequences (particularly sequences recursively defined). One <em>can</em> avoid keeping all of the <code>Stream</code> in memory, though, by making sure you don’t keep a reference to its <code>head</code> (for example, by using <code>def</code> instead of <code>val</code> to define the <code>Stream</code>).</p><p><code>Iterator</code>和 views 两者都有不错内存(记忆?)特性。<code>Stream</code>也可以,但是在 Scala 中,其主要的好处在于能够保留无限长的序列(特别是那些序列是通过递归定义的[这一点需要通过 Stream 本身特性才能够理解])当中。不过,你可以避免将所有Stream保留在内存中,其方法是确保不保留那些对 <code>Stream</code>中<code>head</code>的引用。</p><blockquote><p>针对最后提到的例子,<a href="https://stackoverflow.com/questions/13217222/should-i-use-val-or-def-when-defining-a-stream这篇回答有比较好的解释" target="_blank" rel="noopener">https://stackoverflow.com/questions/13217222/should-i-use-val-or-def-when-defining-a-stream这篇回答有比较好的解释</a></p></blockquote><p>Because of the penalties incurred by views, one should usually <code>force</code> it after applying the transformations, or keep it as a view if only few elements are expected to ever be fetched, compared to the total size of the view.</p><p>由于 views 所带来不良影响(个人认为是这么翻译的),我们通常需要在应用 transformations 后调用<code>force</code>进行计算,或者说如果相比于原 view 中大量元素,新 view 只有少量的元素需要去获取时,可以将其当做新的 view 对待。</p>]]></content>
</entry>
<entry>
<title>Scala - 有关变量赋值的问题</title>
<link href="/2018/05/18/Scala%20-%20%E6%9C%89%E5%85%B3%E5%8F%98%E9%87%8F%E8%B5%8B%E5%80%BC%E7%9A%84%E9%97%AE%E9%A2%98/"/>
<url>/2018/05/18/Scala%20-%20%E6%9C%89%E5%85%B3%E5%8F%98%E9%87%8F%E8%B5%8B%E5%80%BC%E7%9A%84%E9%97%AE%E9%A2%98/</url>
<content type="html"><, <span class="type">Person</span>(<span class="string">"marry"</span>), <span class="literal">null</span>).iterator</span><br><span class="line"><span class="keyword">var</span> person: <span class="type">Person</span> = <span class="literal">null</span></span><br><span class="line"><span class="keyword">while</span> ((person = persons.next()) != <span class="literal">null</span>) {</span><br><span class="line"> println(<span class="string">"obj name: "</span> + person.name)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果你的答案是<code>这段代码运行不会出任何问题</code>的话,那么你对于 Scala 的变量赋值还是了解太少。</p><hr><h4 id="为什么呢"><a href="#为什么呢" class="headerlink" title="为什么呢"></a>为什么呢</h4><p>在我们一般的认知中,在 Java 和 C++ 中对变量赋值后,其会返回相对应该变量的值,而在 Scala 中,如果对变量赋值后,获取到的返回值却统一是 Unit。</p><blockquote><p>Unit 是表示为无值,其作用与其他语言中的 void 作用相同,用作不返回任何结果的方法的结果类型。</p></blockquote><p>回到刚才那段代码,根据以上说明,如果我们在赋值对<code>person</code>变量的话,那就会导致在每一次循环当中,其实我们一直都是拿 Unit 这个值去与 null 比较,那么就可以换做一个恒等式为<code>Unit != null</code>,这样做的结果就是这个循环不会中断。</p><blockquote><p>在 IDEA 中,如果我们仔细查看代码,发现 IDE 已经提醒我们这个问题的存在了,这这也仅仅只是 Warning 而已。</p><p>若通过编译的方法查看源代码的话,会在编译的过程中,获得这样一句警告(并非错误!):</p><p><img src="http://on83riher.bkt.clouddn.com/[email protected]" alt=""></p></blockquote><p>有个简单的例子可以检验自己是否明白懂了这个”bug”:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a: <span class="type">Int</span> = <span class="number">0</span></span><br><span class="line"><span class="keyword">var</span> b: <span class="type">Int</span> = <span class="number">0</span></span><br><span class="line">a = b = <span class="number">1</span> <span class="comment">// 这行代码能够跑通,在其他语言呢?</span></span><br></pre></td></tr></table></figure><hr><h4 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h4><p>在给出常见的解决方案前,先给出为什么 Scala 要这样设计的理由(Scala 之父亲自解释):</p><p><a href="https://stackoverflow.com/questions/1998724/what-is-the-motivation-for-scala-assignment-evaluating-to-unit-rather-than-the-v" target="_blank" rel="noopener">https://stackoverflow.com/questions/1998724/what-is-the-motivation-for-scala-assignment-evaluating-to-unit-rather-than-the-v</a></p><p>常见的解决方案会有以下几种:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// solution 1 - 封装成代码块返回最终值,直观但麻烦</span></span><br><span class="line"><span class="keyword">var</span> person = <span class="literal">null</span></span><br><span class="line"><span class="keyword">while</span> ({person = persons.next; person != <span class="literal">null</span>}) {</span><br><span class="line"> println(<span class="string">"obj name: "</span> + person.name)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// solution 2 (推荐)- 通过 Scala 的语法特性,使用它的奇淫技巧</span></span><br><span class="line"><span class="type">Iterator</span>.continually(persons.next())</span><br><span class="line"> .takeWhile(_ != <span class="literal">null</span>)</span><br><span class="line"> .foreach(t => {println(<span class="string">"obj name: "</span> + t.name)})</span><br><span class="line"></span><br><span class="line"><span class="comment">// solution 3 - 这个与 Solution2 的区别仅仅在于使用的类不同,但使用的类不同便意味着这两者之间存在着不同的遍历方式。两者的区别会在博客中更新。</span></span><br><span class="line"><span class="type">Stream</span>.continually(persons.next())</span><br><span class="line"> .takeWhile(_ != <span class="literal">null</span>)</span><br><span class="line"> .foreach(t => {println(<span class="string">"obj name: "</span> + t.name)})</span><br></pre></td></tr></table></figure><p>参考资料:</p><ol><li><a href="https://stackoverflow.com/questions/6881384/why-do-i-get-a-will-always-yield-true-warning-when-translating-the-following-f" target="_blank" rel="noopener">https://stackoverflow.com/questions/6881384/why-do-i-get-a-will-always-yield-true-warning-when-translating-the-following-f</a></li><li><a href="https://stackoverflow.com/questions/3062804/scala-unit-type" target="_blank" rel="noopener">https://stackoverflow.com/questions/3062804/scala-unit-type</a></li><li><a href="https://stackoverflow.com/questions/2442318/how-would-i-express-a-chained-assignment-in-scala" target="_blank" rel="noopener">https://stackoverflow.com/questions/2442318/how-would-i-express-a-chained-assignment-in-scala</a></li></ol>]]></content>
</entry>
<entry>
<title>Scala - 类构造器</title>
<link href="/2018/05/14/Scala%20-%20%E7%B1%BB%E6%9E%84%E9%80%A0%E5%99%A8/"/>
<url>/2018/05/14/Scala%20-%20%E7%B1%BB%E6%9E%84%E9%80%A0%E5%99%A8/</url>
<content type="html"><![CDATA[<p>Scala 构造器可分为两种,主构造器和辅助构造器。</p><h4 id="主构造器"><a href="#主构造器" class="headerlink" title="主构造器"></a>主构造器</h4><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 无参主构造器</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Demo</span> </span>{</span><br><span class="line"> <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="class"><span class="keyword">class</span> <span class="title">Demo2</span>(<span class="params">name: <span class="type">String</span>, age: <span class="type">Int</span></span>) </span>{</span><br><span class="line"> <span class="comment">// 主构造器的构成部分</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从类的定义开始,花括号的部分为主构造器的构成部分。主构造器在执行时,会执行类中所有的语句。</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// example</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Demo</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">val</span> name = <span class="string">"tom"</span></span><br><span class="line"> <span class="keyword">val</span> age = <span class="number">18</span></span><br><span class="line"> </span><br><span class="line"> doSomething() <span class="comment">// 初始化对象时,会打印 name: tome, age: 18</span></span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">doSomething</span></span>() = {</span><br><span class="line"> println(<span class="string">"name: "</span> + name + <span class="string">", age: "</span> + age)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="辅助构造器"><a href="#辅助构造器" class="headerlink" title="辅助构造器"></a>辅助构造器</h4><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Demo</span> </span>{</span><br><span class="line"> <span class="keyword">var</span> name = <span class="string">""</span></span><br><span class="line"> <span class="keyword">var</span> age = <span class="number">0</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 错误定义!!</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">this</span></span>() {</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">this</span></span>(name: <span class="type">String</span>) {</span><br><span class="line"> <span class="keyword">this</span>()</span><br><span class="line"> <span class="keyword">this</span>.name = name</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">this</span></span>(name: <span class="type">String</span>, age: <span class="type">Int</span>) {</span><br><span class="line"> <span class="keyword">this</span>(name)</span><br><span class="line"> <span class="keyword">this</span>.age = age</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>辅助构造器的名称为<code>this</code>,与 Java 的构造器名称不同(Java 构造器名称是以类名定义的),其代码大致结构为<code>def this(...) {}</code>。若一个类如果没有显式定义主构造器,则编译器会自动生成一个无参的主构造器。</p><p>必须注意的是,每个辅助构造器都必须以一个对先前已定义的其他辅助构造器或者主构造器的调用开始。</p>]]></content>
</entry>
<entry>
<title>寻找一种更快更高效的方法</title>
<link href="/2018/05/07/%E5%AF%BB%E6%89%BE%E4%B8%80%E7%A7%8D%E6%9B%B4%E5%BF%AB%E6%9B%B4%E9%AB%98%E6%95%88%E7%9A%84%E6%96%B9%E6%B3%95/"/>
<url>/2018/05/07/%E5%AF%BB%E6%89%BE%E4%B8%80%E7%A7%8D%E6%9B%B4%E5%BF%AB%E6%9B%B4%E9%AB%98%E6%95%88%E7%9A%84%E6%96%B9%E6%B3%95/</url>
<content type="html"><![CDATA[<p>这两天在对我们开发的模块进行最后的收尾,收尾的工作一般来说都是添加测试用例,测试模块调用时是否有 BUG 等。果不其然,老大还是叫我去做模块的测试。其实还是自己对于 C++了解太少,刚入门一个星期才勉强能够看懂之前的部分源码,而且原有工程十分庞大,还有自己封装好的又或自己开发的工具库。想去调用还得自己上网看看 example 熟悉下,没有 example 的那就苦逼自己慢慢摸索了</p><blockquote><p><strong>做测试没关系,毕竟怎么样都能够学到不一样的知识。</strong></p></blockquote><p>先说下这次测试的内容,就是将之前标注好的数据,利用我们的模块重新跑一遍,检验是否有错漏的地方。这上面说的简单,但其中含杂了大量的人工,这我可不干,所以才有了这一篇文章。</p><h4 id="材料准备"><a href="#材料准备" class="headerlink" title="材料准备"></a>材料准备</h4><p>404页面错误检验模块(基于 URL 和 Content 两部分),编写爬虫将标注好的数据中 URL 所对应的页面存储于本地(csv文件)</p><h4 id="人工方法"><a href="#人工方法" class="headerlink" title="人工方法"></a>人工方法</h4><p>如果按照人工方法走,就是针对于一个 URL 创建一个 HTML 文件,然后撰写一个测试用例,跑通了我们就往下走,没跑通那就回头重新梳理逻辑。这种方式如果针对于一两个文件还好说,那如果针对于上百个文件那怎么办?如果这还人工一个个弄,那算你厉害</p><h4 id="自动化方法"><a href="#自动化方法" class="headerlink" title="自动化方法"></a>自动化方法</h4><p>自动化方法是否能够运用在于在这过程当中是否存在一定的规律,相信读到这里的我们,可以明白自动化的方法就是在若干个循环当中,重复操作人工的方法,只是在这个过程当中,你需要用代码来证明你的想法,而非你的汗水</p><p>在材料准备中,我们已经有了包含测试数据的 csv 文件,可能读者会理所当然的认为这个自动化测试不就两行代码妥妥的就搞定吗?其实并不然,c++ 中并没有什么第三方库处理 csv 这样的文件(反正我是没找到),如果利用简单的<code>split</code>函数的话,那就会导致原有数据(HTML)的丢失。</p><p>这个时候,我们需要转向文件流,即将若干个 HTML 文件存储下来,并创建一个索引表,记录 URL 与其对应的文件名,如下所示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">~/htmls/</span><br><span class="line">0.html 1.html 2.html index.txt</span><br><span class="line"></span><br><span class="line">~/htmls/index.txt</span><br><span class="line">https://www.baidu.com 0.html</span><br><span class="line">https://www.taobao.com 1.html</span><br></pre></td></tr></table></figure><p>然后在实际编写代码过程中,先读取索引表,再利用索引表的信息,读取 HTML 文件然后运行模块,记录运行结果,当所有测试用例结束时,统计最终结果,并根据最终结果,调整内部的策略。</p>]]></content>
</entry>
<entry>
<title>有多少人工就有多少智能</title>
<link href="/2018/04/19/%E6%9C%89%E5%A4%9A%E5%B0%91%E4%BA%BA%E5%B7%A5%E5%B0%B1%E6%9C%89%E5%A4%9A%E5%B0%91%E6%99%BA%E8%83%BD/"/>
<url>/2018/04/19/%E6%9C%89%E5%A4%9A%E5%B0%91%E4%BA%BA%E5%B7%A5%E5%B0%B1%E6%9C%89%E5%A4%9A%E5%B0%91%E6%99%BA%E8%83%BD/</url>
<content type="html"><![CDATA[<p>这个标题其实是来自于<a href="https://blog.csdn.net/dQCFKyQDXYm3F8rB0/article/details/79933707" target="_blank" rel="noopener">小米小爱团队负责人王刚:语音交互背后,有多少人工就有多少智能</a>这篇文章,虽然我现在的工作与人工智能没关系,但是与我现在的经历息息相关的。</p><p>最近在跟着老大去做页面分析的模块,现阶段有个问题在于怎么去解决网页软404问题。可行的解决方案当然有很多,HTTP 请求码、URL 的正则匹配、内容关键字匹配等。但是这么多的解决方案都需要的一个判断标准,判断跑出来的数据可不可靠,如果不可靠的话那么这个方案可能就行不通。</p><p>那么比较尴尬的部分来了,这个判断的过程是由人工来的,那这个活自然就落在我和其他同事身上啦。虽然知道这个是必然的过程,但是心还是不甘的,不甘于自己要去做人工筛选工作。</p><p><img src="http://on83riher.bkt.clouddn.com/WechatIMG128.jpeg" alt="工作成果"></p><p>其实单单抛弃人工智能这个前提,<strong>“有多少人工就有多少智能”</strong>这句话适用于互联网的各个领域,只要能够投入了足够的人力,那么系统的未来也会有很大的改善,以上是我现阶段的看法。</p><p>经历了这次人工筛选的活后,我还从这句话体会到了一点,<strong>努力提升自己的技术,别让自己成为可取代的人工。</strong>加深自己的技术栈吧,再经历多点磨难,或许能够看见更多未来。</p>]]></content>
</entry>
<entry>
<title>Spark - take() 算子</title>
<link href="/2018/04/14/Spark%20-%20take()%20%E7%AE%97%E5%AD%90/"/>
<url>/2018/04/14/Spark%20-%20take()%20%E7%AE%97%E5%AD%90/</url>
<content type="html"><![CDATA[<blockquote><p>以后遇到不懂的 Spark 算子的话,我都尽可能以笔记的方式去记录它</p></blockquote><h3 id="遇到的情况"><a href="#遇到的情况" class="headerlink" title="遇到的情况"></a>遇到的情况</h3><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">rdd.map(...) <span class="comment">// 重要前提:数据量在 TB 级别</span></span><br><span class="line"> .filter(...) <span class="comment">// 根据某些条件筛选数据</span></span><br><span class="line"> .take(<span class="number">100000</span>) <span class="comment">// 取当前数据的前十万条</span></span><br></pre></td></tr></table></figure><p>当时的程序大致就是这样,我的想法是根据<code>filter()</code>之后的数据直接利用<code>take()</code>拿前十万的数据,感觉方便又省事,但是实际的运行情况却是作业的运行时间很长,让人怀疑人生。而且<code>take()</code>一开始默认的分区是1,而后如果当前任务失败的话,会适当的扩增分区数来读取更多的数据。</p><p><img src="http://on83riher.bkt.clouddn.com/take%20%E7%AE%97%E5%AD%90%E8%BF%90%E8%A1%8C%E6%83%85%E5%86%B5.png" alt=""></p><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><p>废话不多,先贴源码</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Take the first num elements of the RDD. It works by first scanning one partition, and use the results from that partition to estimate the number of additional partitions needed to satisfy the limit.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * @note This method should only be used if the resulting array is expected to be small, as all the data is loaded into the driver's memory.</span></span><br><span class="line"><span class="comment"> * 此方法被使用时期望目标数组的大小比较小,即其数组中所有数据都能够存储在 driver 的内存当中。这里的函数解释当中提及到了处理的数据量应当较小,但是没说如果处理了比较大的数据时会怎么样,还得看看继续往下看</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * @note Due to complications in the internal implementation, this method will raise</span></span><br><span class="line"><span class="comment"> * an exception if called on an RDD of `Nothing` or `Null`.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">take</span></span>(num: <span class="type">Int</span>): <span class="type">Array</span>[<span class="type">T</span>] = withScope {</span><br><span class="line"> <span class="comment">// scaleUpFactor 字面意思是扩增因子,看到这里我们可以结合上图的例子,不难看出分区的扩增是按照一定的倍数增长的</span></span><br><span class="line"> <span class="keyword">val</span> scaleUpFactor = <span class="type">Math</span>.max(conf.getInt(<span class="string">"spark.rdd.limit.scaleUpFactor"</span>, <span class="number">4</span>), <span class="number">2</span>)</span><br><span class="line"> <span class="keyword">if</span> (num == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">new</span> <span class="type">Array</span>[<span class="type">T</span>](<span class="number">0</span>)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">val</span> buf = <span class="keyword">new</span> <span class="type">ArrayBuffer</span>[<span class="type">T</span>]</span><br><span class="line"> <span class="keyword">val</span> totalParts = <span class="keyword">this</span>.partitions.length</span><br><span class="line"> <span class="keyword">var</span> partsScanned = <span class="number">0</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 这个循环是为什么 take 失败会进行重试的关键</span></span><br><span class="line"> <span class="keyword">while</span> (buf.size < num && partsScanned < totalParts) {</span><br><span class="line"> <span class="comment">// The number of partitions to try in this iteration. It is ok for this number to be</span></span><br><span class="line"> <span class="comment">// greater than totalParts because we actually cap it at totalParts in runJob.</span></span><br><span class="line"> <span class="comment">// numPartsToTry - 此次循环迭代的分区个数,默认为1。</span></span><br><span class="line"> <span class="keyword">var</span> numPartsToTry = <span class="number">1</span>L</span><br><span class="line"> <span class="keyword">val</span> left = num - buf.size</span><br><span class="line"> <span class="keyword">if</span> (partsScanned > <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// If we didn't find any rows after the previous iteration, quadruple and retry.</span></span><br><span class="line"> <span class="comment">// Otherwise, interpolate the number of partitions we need to try, but overestimate</span></span><br><span class="line"> <span class="comment">// it by 50%. We also cap the estimation in the end.</span></span><br><span class="line"> <span class="comment">// 重点!当在上一次迭代当中,我们没有找到任何满足条件的 row 时(至少是不满足指定数量时),有规律的重试(quadruple and retry,翻译水平有限)</span></span><br><span class="line"> <span class="keyword">if</span> (buf.isEmpty) {</span><br><span class="line"> numPartsToTry = partsScanned * scaleUpFactor</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// As left > 0, numPartsToTry is always >= 1</span></span><br><span class="line"> numPartsToTry = <span class="type">Math</span>.ceil(<span class="number">1.5</span> * left * partsScanned / buf.size).toInt</span><br><span class="line"> numPartsToTry = <span class="type">Math</span>.min(numPartsToTry, partsScanned * scaleUpFactor)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> p = partsScanned.until(math.min(partsScanned + numPartsToTry, totalParts).toInt)</span><br><span class="line"> <span class="keyword">val</span> res = sc.runJob(<span class="keyword">this</span>, (it: <span class="type">Iterator</span>[<span class="type">T</span>]) => it.take(left).toArray, p)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 每一次循环迭代都会获取新的数据加到 buf 当中,所以并不是每一次重试都是从头对数据进行遍历,那这样会没完没了</span></span><br><span class="line"> res.foreach(buf ++= _.take(num - buf.size))</span><br><span class="line"> partsScanned += p.size</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这里才是我们最终的结果</span></span><br><span class="line"> buf.toArray</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p><code>take()</code>算子使用的场景是当数据量规模较小的情况,亦或者说搭配<code>filter()</code>时,<code>filter()</code>能够较快的筛选出数据来。</p>]]></content>
</entry>
<entry>
<title>Spark - 由 foreach 引发的思考</title>
<link href="/2018/04/01/Spark%20-%20%E7%94%B1%20foreach%20%E5%BC%95%E5%8F%91%E7%9A%84%E6%80%9D%E8%80%83/"/>
<url>/2018/04/01/Spark%20-%20%E7%94%B1%20foreach%20%E5%BC%95%E5%8F%91%E7%9A%84%E6%80%9D%E8%80%83/</url>
<content type="html"><![CDATA[<p>废话不说,先贴代码</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> numbers = sc.parallelize(<span class="type">Array</span>(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>,<span class="number">7</span>,<span class="number">8</span>,<span class="number">9</span>))</span><br><span class="line"><span class="keyword">val</span> map = scala.collection.mutable.<span class="type">Map</span>[<span class="type">Int</span>, <span class="type">Int</span>]()</span><br><span class="line"></span><br><span class="line">numbers.foreach(l => {map.put(l,l)})</span><br><span class="line">println(map.size) <span class="comment">// 此时 map 的存储了几个键值对</span></span><br></pre></td></tr></table></figure><hr><p>首先我们先说个概念 —— <strong>闭包</strong></p><p>闭包是 Scala 中的特性,用通俗易懂的话讲就是函数内部的运算或者说函数返回值可由外部的变量所控制,用个例子解释就是:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> factor = <span class="number">10</span></span><br><span class="line"><span class="comment">// multiplier 函数的返回值有有两个决定因素,输入参数变量 i 以及外部变量 factor。输入参数变量 i 是由我们调用该函数时决定的,相较于 factor 是可控的,而 factor 则是外部变量所定义,相较于 i 是不可控的</span></span><br><span class="line"><span class="keyword">val</span> multiplier = (i: <span class="type">Int</span>) => i * factor </span><br><span class="line">println(multiplier(<span class="number">1</span>)) <span class="comment">// 10</span></span><br><span class="line"></span><br><span class="line">factor = <span class="number">20</span></span><br><span class="line">println(multiplier(<span class="number">1</span>)) <span class="comment">// 20</span></span><br></pre></td></tr></table></figure><p>根据上述提及的闭包可知,刚才所写的代码中<code>l => {map.put(1,1)}</code>其所定义的函数就是一个闭包</p><hr><p>既然标题中提到了 Spark,那就要说明闭包与 Spark 的关系了</p><p>在 Spark 中,用户自定义闭包函数并传递给相应的 RDD 所定义好的方法(如<code>foreach</code>、<code>map</code>)。<strong>Spark 在运行作业时会检查 DAG 中每个 RDD 所涉及的闭包,如是否可序列化、是否引用外部变量等。若存在引用外部变量的情况,则会将它们的副本复制到相应的工作节点上,保证程序运行的一致性</strong></p><blockquote><p>下面是 Spark 文档中解释的:</p><h3 id="Shared-Variables"><a href="#Shared-Variables" class="headerlink" title="Shared Variables"></a>Shared Variables</h3><p>Normally, when a function passed to a Spark operation (such as <code>map</code> or <code>reduce</code>) is executed on a remote cluster node, it works on separate copies of all the variables used in the function. These variables are copied to each machine, and no updates to the variables on the remote machine are propagated back to the driver program. Supporting general, read-write shared variables across tasks would be inefficient. However, Spark does provide two limited types of <em>shared variables</em> for two common usage patterns: broadcast variables and accumulators.</p><h3 id="共享变量"><a href="#共享变量" class="headerlink" title="共享变量"></a>共享变量</h3><p>通常情况下,当有函数传递给在远端集群节点上执行的 Spark 的算子(如<code>map</code>或<code>reduce</code>)时,Spark 会将所有在该函数内部所需要的用到的变量分别复制到相应的节点上。这些副本变量会被复制到每个节点上,且在算子执行结束后这些变量并不会回传给驱动程序(driver program)。</p><p>Normally, when a function passed to a Spark operation (such as <code>map</code> or <code>reduce</code>) is executed on a remote cluster node, it works on separate copies of all the variables used in the function. These variables are copied to each machine, and no updates to the variables on the remote machine are propagated back to the driver program. </p></blockquote><hr><p>总结,如果直接运行一开始所提及的程序时,那么所获得的答案是0,因为我们知道<code>map</code>变量会被拷贝多份至不同的工作节点上,而我们操作的也仅仅只是副本罢了</p><p>从编译器的角度来说,这段代码是一个闭包函数,而其调用了外部变量,代码上没问题。但是从运行结果中,这是错误操作方式,因为 Spark 会将其所调用的外部变量进行拷贝,并复制到相应的工作节点中,而不会对真正的变量产生任何影响</p><p>相应的解决方案有</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> numbers = sc.parallelize(<span class="type">Array</span>(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>,<span class="number">7</span>,<span class="number">8</span>,<span class="number">9</span>))</span><br><span class="line"><span class="keyword">val</span> map = scala.collection.mutable.<span class="type">Map</span>[<span class="type">Int</span>, <span class="type">Int</span>]()</span><br><span class="line"></span><br><span class="line">numbers.collect().foreach(l => {map.put(l,l)})</span><br><span class="line">println(map.size)</span><br></pre></td></tr></table></figure><hr><p>参考资料:</p><ol><li><a href="http://spark.apache.org/docs/2.1.0/programming-guide.html#shared-variables" target="_blank" rel="noopener">http://spark.apache.org/docs/2.1.0/programming-guide.html#shared-variables</a></li></ol>]]></content>
</entry>
<entry>
<title>列式存储 - HBase vs Parquet</title>
<link href="/2018/03/24/%E5%88%97%E5%BC%8F%E5%AD%98%E5%82%A8%20-%20HBase%20vs%20Parquet/"/>
<url>/2018/03/24/%E5%88%97%E5%BC%8F%E5%AD%98%E5%82%A8%20-%20HBase%20vs%20Parquet/</url>
<content type="html"><![CDATA[<blockquote><p>虽然两者在使用场景上没有可比性,HBase 是非关系型数据库,而 Parquet 是数据存储格式,但是两者却存在相似的概念——列式存储。我在了解 Parquet 的时候,因为列式存储这个概念与 HBase 混淆时,所以特意坐下笔记,记录两者的区别</p></blockquote><p>让我们直入正题,什么是列式存储?相比行式存储又有什么优势呢?</p><p><img src="http://on83riher.bkt.clouddn.com/%E5%88%97%E5%BC%8F%E5%AD%98%E5%82%A8%E4%B8%8E%E8%A1%8C%E5%BC%8F%E5%AD%98%E5%82%A8%20%E5%AF%B9%E6%AF%94%E5%9B%BE.png" alt=""></p><blockquote><p>图源来自 <a href="http://zhuanlan.51cto.com/art/201703/535729.htm" target="_blank" rel="noopener">http://zhuanlan.51cto.com/art/201703/535729.htm</a></p></blockquote><hr><p>首选先从 HBase 开始讲述。HBase是一个分布式的、面向列的非关系型数据库。它的架构设计如下:</p><p><img src="http://on83riher.bkt.clouddn.com/HBase%20%E6%9E%B6%E6%9E%84%E5%9B%BE.png" alt=""></p><p>简单说明一下:</p><ul><li>HMaster:HBase 主/从架构的主节点。通常在一个 HBase 集群中允许存在多个 HMaster 节点,其中一个节点被选举为 Active Master,而剩余节点为 Backup Master。其主要作用在于:<ul><li>管理和分配 HRegionServer 中的 Region</li><li>管理 HRegionServer 的负载均衡</li></ul></li><li>HRegionServer:HBase 主/从架构的从节点。主要负责响应 Client 端的 I/O 请求,并向底层文件存储系统 HDFS 中读写数据</li><li>HRegion:HBase 通过表中的 RowKey 将表进行水平切割后,会生成多个 HRegion。每个 HRegion 都会被安排到 HRegionServer 中</li><li>Store:每一个 HRegion 有一个或多个 Store 组成,Store 相对应表中的 Column Family(列族)。每个 Store 都由一个 MemStore 以及多个 StoreFile 组成</li><li>MemStore:MemStore 是一块内存区域,其将 Client 对 Store 的所有操作进行存储,并到达一定的阈值时会进行 flush 操作</li><li>StoreFile:MemStore 中的数据写入文件后就成为了 StoreFile,而 StoreFile 底层是以 HFile 为存储格式进行保存的</li><li>HFile:HBase 中 Key-Value 数据的存储格式,是 Hadoop 的二进制文件。其中 Key-Value 的格式为(Table, RowKey, Family, Qualifier, Timestamp)- Value</li></ul><p>HBase 的主要读写方式可以通过以下流程进行:</p><p><img src="http://on83riher.bkt.clouddn.com/HBase%20%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F.png" alt=""></p><p>可以从上述的架构讲述看出,HBase 并非严格意义上的列式存储,而是基于“列族”存储的,所以其是列族的角度进行列式存储。</p><hr><p>Parquet 是面向分析型业务的列式存储格式,其不与某一特定语言绑定,也不与任何一种数据处理框架绑定在一起,其性质类似于 JSON。</p><p>Parquet 相较于 HBase 对数据的处理方式,其将数据当做成一种嵌套数据的模型,并将其结构定义为 schema。每一个数据模型的 schema 包含多个字段,而每个字段又可以包含多个字段。每一字段都有三个属性:repetition、type 和 name,其中 repetition 可以是以下三种:required(出现1次)、optional(出现0次或1次)、repeated(出现0次或多次),而 type 可以是 group(嵌套类型)或者是 primitive(原生类型)。</p><p>举一个典型的例子:</p><p><img src="http://on83riher.bkt.clouddn.com/Parquet%20%E4%BE%8B%E5%AD%90.png" alt=""></p><p>在 Parquet 格式的存储当中,一个 schema 的树结构有几个叶子节点,在实际存储中就有多少个 column。例如上面 schema 的数据存储实际上有四个 column,如下所示:</p><p><img src="http://on83riher.bkt.clouddn.com/Parquet%20%E4%BE%8B%E5%AD%902.png" alt=""></p><p>从上面的图看来,与 HBase 好像没有什么区别,但这只是为了让用户更好的了解数据才这样表示,其内部实现的机制与 HBase 完全不同,而且 Parquet 是真正的基于列式存储。其能够进行列式存储归功于 Striping/Assembly 算法。</p><p>算法我就不详细说了,<a href="http://www.infoq.com/cn/articles/in-depth-analysis-of-parquet-column-storage-format" target="_blank" rel="noopener">这篇文章</a>讲的很详细,我就不献丑了。</p><hr><p>参考资料:</p><ol><li>HBase 权威指南</li><li><a href="http://blog.javachen.com/2013/06/15/hbase-note-about-data-structure.html" target="_blank" rel="noopener">HBase笔记:存储结构</a></li><li><a href="http://www.infoq.com/cn/articles/in-depth-analysis-of-parquet-column-storage-format" target="_blank" rel="noopener">深入分析Parquet列式存储格式</a></li></ol>]]></content>
</entry>
<entry>
<title>Scala - identity() 函数</title>
<link href="/2018/03/19/Scala%20-%20identity()%20%E5%87%BD%E6%95%B0/"/>
<url>/2018/03/19/Scala%20-%20identity()%20%E5%87%BD%E6%95%B0/</url>
<content type="html"><![CDATA[<p>最近在写 Spark 作业的时候,使用到了 <code>groupBy</code>和<code>sortBy</code>,在查找文档的时候,发现有的文档中的代码有着<code>groupBy(identity)</code>这样奇怪的写法。</p><p>在 Scala 文档中,<a href="http://www.scala-lang.org/api/current/scala/Predef$.html#identityA:A" target="_blank" rel="noopener">identity 函数</a>的作用就是将传入的参数“直接”当做返回值回传给调用者,这在正常使用中,可以说是毫无作用,但他在<code>groupBy</code>和<code>sortBy</code>等函数中的作用,在于避免程序员书写相同且容易出错的逻辑,原因如下:</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 前提条件:</span></span><br><span class="line"><span class="keyword">val</span> array = <span class="type">Array</span>(<span class="number">9</span>,<span class="number">2</span>,<span class="number">1</span>,<span class="number">3</span>,<span class="number">1</span>,<span class="number">5</span>,<span class="number">9</span>,<span class="number">4</span>,<span class="number">6</span>,<span class="number">7</span>,<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 统计 array 中每个元素出现的次数</span></span><br><span class="line"><span class="comment">// 正常逻辑:</span></span><br><span class="line">array.groupBy(n => n)</span><br><span class="line"><span class="comment">// scala.collection.immutable.Map[Int,Array[Int]] = Map(5 -> Array(5), 1 -> Array(1, 1), 6 -> Array(6), 9 -> Array(9, 9), 2 -> Array(2, 2), 7 -> Array(7), 3 -> Array(3), 4 -> Array(4))</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 identity</span></span><br><span class="line">array.groupBy(identity)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将 array 进行排序(升序)</span></span><br><span class="line"><span class="comment">// 正常逻辑:</span></span><br><span class="line">array.sortBy(n => n)</span><br><span class="line"><span class="comment">// Array[Int] = Array(1, 1, 2, 2, 3, 4, 5, 6, 7, 9, 9)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 identity 或者简化版本</span></span><br><span class="line">array.sortBy(identity)</span><br><span class="line">array.sorted</span><br></pre></td></tr></table></figure>]]></content>
</entry>
<entry>
<title>分布式爬虫架构</title>
<link href="/2018/03/06/%E5%88%86%E5%B8%83%E5%BC%8F%E7%88%AC%E8%99%AB%E6%9E%B6%E6%9E%84/"/>
<url>/2018/03/06/%E5%88%86%E5%B8%83%E5%BC%8F%E7%88%AC%E8%99%AB%E6%9E%B6%E6%9E%84/</url>
<content type="html"><![CDATA[<p>最近突然对爬虫框架很感兴趣,但一直无奈于没有服务器能让我捣鼓捣鼓,所以脑子就一直想如何去设计这个框架。翻了很多篇资料,总结了挺多经验,然后就画了下面这张架构图。个人认为很不成熟,但毕竟也是一种想法。希望能力提升后能够想到更加全面完善的架构。</p><p><img src="http://on83riher.bkt.clouddn.com/%E5%88%86%E5%B8%83%E5%BC%8F%E7%88%AC%E8%99%AB%E6%9E%B6%E6%9E%84.png" alt="分布式爬虫架构"></p>]]></content>
</entry>
<entry>
<title>大三上学期总结</title>
<link href="/2018/01/22/%E5%A4%A7%E4%B8%89%E4%B8%8A%E5%AD%A6%E6%9C%9F%E6%80%BB%E7%BB%93/"/>
<url>/2018/01/22/%E5%A4%A7%E4%B8%89%E4%B8%8A%E5%AD%A6%E6%9C%9F%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<p><strong>今天是周一(2018/01/22),是我正式作为小米实习生的第一天,也是我第一次远离熟悉的地方来到北京闯荡。</strong>经历过这学期磨人的课程后,经历过让人背书背的头大的毛概后,经历过曾经一度让人绝望的面试后,经历过令人心寒的租房后,终于可以安下心来好好写我的学期总结。</p><p>下面我就这学期的比较重要的方向进行总结吧。</p><ul><li><strong>学习</strong></li></ul><p>这学期课程真的比以往的多,几乎每天都要上至少两节课,甚至还得上整天,真让人疲惫不堪,但是真正觉得心累的,还是宿舍的氛围,还是像大二那样过一天是一天,不到找工作/临近考试的时候不会去努力。这学期我就尝试着每晚都去图书馆,但是就算是十点半回到宿舍还是无法得到一片宁静,因为十点半的时候,有个宿友准点吃鸡,而且很吵,吵到连看美剧都没心情。当时也怪自己没肯直说吧,暂时不说了,不想开始就写一长篇的抱怨。</p><p>虽然这学期很累,但是过得也算充实,毕竟我认清自己的学习方向了,之前在大二中我接触的是Web开发,偏向于云计算/微服务方面,但是每次接触的工程都只是学习工具,学习如何使用,然后反复造轮子,跟着规整的MVC架构来搭建项目,我对这一过程心生厌恶,觉得自己不能这样。于是乎我寻找了另一个兴趣点——大数据进行学习。大数据既是现在的热点,也是我最感兴趣的地方,每次都能借好多书走,学习到很多新的内容,新的架构。</p><p>这也是为什么我在找岗位的时候,想要寻找大数据方面的职位,一是充实自己,提高技能;二是在实际开发工程中,切身体会到如何真正的运用大数据来进行对数据分析。</p><ul><li><strong>简历</strong></li></ul><p>当初写简历的时候觉得还很自信,秉持着简约的风格的简历外加上整齐规格的排版,一定能够在一月份前拿到一份心满意足的offer。经历过整整三个星期都没有一通面试电话时,我真的很绝望,发自心底的绝望,认为这三年学的东西是不是白学了。后来经过很多同学的指点过后,<strong>我才发现一份简约的简历,要遵循以下几个点:</strong></p><ul><li>只能是一页纸,不能够再多</li><li>只写有用的话(姓名,联系电话,工作/校园经历)</li><li>排版要规整,粗细得体</li></ul><p>在这里真的要讲一句真心话,在正式修改了简历后,过没两天实习僧上的公司就真的给我面试电话通知了,而且后面陆陆续续也来了不少电话。</p><ul><li><strong>租房(注意粗体部分)</strong></li></ul><p>租房是个出来漂的首要大事啊,自从拿到offer后我就投入了租房这件事了,但是租房并不像想象中那么容易(除非你运气真的超级超级棒)。<strong>既要小心租房中遇到的中介/二房东/代理,还要小心合同中会不会收取额外的费用(中介费/物业管理费/燃气费/服务费),更要小心同住的人是否有良好的习惯。</strong>我几乎每晚回到宿舍都要花上二十分钟到半个小时,<strong>途径有豆瓣/自如(等各大互联网租房平台)/闲鱼/暖房(自动爬虫机制的网站,感觉还行)</strong>,其中遇到了有让人觉得恶心的中介,也遇到了聊得上天的转租大哥,但是由于自己不在北京的缘故,无法确切的看到实际房子的状况,所以一直犹豫着要不要直接租房(实际原因是没看到让人一眼看中的房子,或者碍于价格太高了)。</p><p>出于以上原因,我决定了考完试后联系好之前找好的中介/转租房东一一探寻房子。当时我是提前购买了凌晨到北京的机票,打算在机场中睡一觉就赶过去,所以我就在前一天晚上急忙去联系人预约看房,等到凌晨6点时就赶了过去,从西二旗地铁口出发后,暴走3公里后到达下榻酒店(暴走的原因是因为<strong>要亲自熟悉周边的环境,才好对房子进行更加深刻的评估</strong>),放下行李就跟着中介出去跑了。</p><p>真的是比较幸运,在早上十点钟时,中介带我找到了一个不错的房子,房子空间很大,内部装饰还行,价格中等偏上(相当于拿出一半的实习工资还多)。自己当时就想下定决心去签合同,不过出于谨慎,还是与家里人详细沟通了一下。在得到家里人的赞成后,我当时就和中介签的合同了(还是很<strong>比较谨慎的,看了好多回合同才肯签字,生怕有什么坑自己没注意</strong>)。</p>]]></content>
</entry>
<entry>
<title>聊聊log4j</title>
<link href="/2017/11/27/%E8%81%8A%E8%81%8Alog4j/"/>
<url>/2017/11/27/%E8%81%8A%E8%81%8Alog4j/</url>
<content type="html"><![CDATA[<h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><blockquote><p>最近在学习 Zookeeper 的时候,遇到了不少问题,想要在控制台中查看日志但是记录却死活不显示,于是找到了 /etc/zookeeper/log4j.properties 文件,但发现配置选项看不懂,想到之前在写 Web 应用的时候也是拿来就用,都没涉及到日志配置文件这一层面,所以打算整理一番。</p></blockquote><p>log4j 是一个用 Java 编写的可靠,快速和灵活的日志框架(API),它在 Apache 软件许可下发布。log4j 是高度可配置的,并可通过在运行时的外部文件配置。它根据记录的优先级别,并提供机制,以指示记录信息到许多的目的地,诸如:数据库,文件,控制台,UNIX 系统日志等。</p><h2 id="与-slf4j-的关系"><a href="#与-slf4j-的关系" class="headerlink" title="与 slf4j 的关系"></a>与 slf4j 的关系</h2><p>在实际开发当中,常常有人提醒我们,要使用 slf4j 来记录日志,为什么呢?</p><p>下面是 sl4fj 官网的介绍。</p><blockquote><p>The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging framework at deployment time.</p></blockquote><p>slf4j(Simple Logging Facade For Java,Java 简易日志门面)是一套封装 Logging 框架的抽象层,而 log4j 是 slf4j 下一个具体实现的日志框架,其中还有许许多多的成熟的日志框架,如 logback 等,也是从属于 slf4j。</p><p>使用 slf4j 可以在应用层中屏蔽底层的日志框架,而不需理会过多的日志配置、管理等操作。</p><h2 id="如何配置"><a href="#如何配置" class="headerlink" title="如何配置"></a>如何配置</h2><p>log4j 配置文件的基本格式如下所示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">#配置根Logger</span><br><span class="line">log4j.rootLogger = [level], appenderName1, appenderName2, ...</span><br><span class="line"></span><br><span class="line">#配置日志信息输出目的地 Appender</span><br><span class="line">log4j.appender.appenderName = fully.qualified.name.of.appender.class </span><br><span class="line">log4j.appender.appenderName.option1 = value1 </span><br><span class="line">log4j.appender.appenderName.optionN = valueN </span><br><span class="line"></span><br><span class="line">#配置日志信息的格式(布局)</span><br><span class="line">log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class</span><br><span class="line">log4j.appender.appenderName.layout.option1 = value1 </span><br><span class="line">log4j.appender.appenderName.layout.optionN = valueN</span><br></pre></td></tr></table></figure><p>其中:</p><ul><li>[level] - 日志输出级别,可分为以下级别(级别程度从上到下递增):</li></ul><table><thead><tr><th>级别</th><th>描述</th></tr></thead><tbody><tr><td><strong>ALL</strong></td><td>所有级别,包括定制级别。</td></tr><tr><td><strong>TRACE</strong></td><td>比 DEBUG 级别的粒度更细。</td></tr><tr><td><strong>DEBUG</strong></td><td>指明细致的事件信息,对调试应用最有用。</td></tr><tr><td><strong>INFO</strong></td><td>指明描述信息,从粗粒度上描述了应用运行过程。</td></tr><tr><td><strong>WARN</strong></td><td>指明潜在的有害状况。</td></tr><tr><td><strong>ERROR</strong></td><td>指明错误事件,但应用可能还能继续运行。</td></tr><tr><td><strong>FATAL</strong></td><td>指明非常严重的错误事件,可能会导致应用终止执行。</td></tr><tr><td><strong>OFF</strong></td><td>最高级别,用于关闭日志。</td></tr></tbody></table><ul><li>Appender - 日志输出目的地,常用的 Appender 有以下几种:</li></ul><table><thead><tr><th>Appender</th><th>作用</th></tr></thead><tbody><tr><td><strong>org.apache.log4j.ConsoleAppender</strong></td><td>输出至控制台</td></tr><tr><td><strong>org.apache.log4j.FileAppender</strong></td><td>输出至文件</td></tr><tr><td><strong>org.apache.log4j.DailyRollingFileAppender</strong></td><td>每天产生一个日志文件</td></tr><tr><td><strong>org.apache.log4j.RollingFileAppender</strong></td><td>文件容量到达指定大小时产生一个新的文件</td></tr><tr><td><strong>org.apache.log4j.WriterAppender</strong></td><td>将日志信息以输出流格式发送到任意指定地方</td></tr></tbody></table><ul><li>Layout - 日志输出格式,常用的 Layout 有以下几种:</li></ul><table><thead><tr><th>Layout</th><th>作用</th></tr></thead><tbody><tr><td><strong>org.apache.log4j.HTMLLayout</strong></td><td>以 HTML 表格形式布局</td></tr><tr><td><strong>org.apache.log4j.PatternLauout</strong>(常用)</td><td>以格式化的方式定制布局</td></tr><tr><td><strong>org.apache.log4j.SimpleLayout</strong></td><td>包含日志信息的级别和信息字符串</td></tr><tr><td><strong>org.apache.log4j.TTCCLayout</strong></td><td>包含日志所在线程、产生时间、类名和日志内容等</td></tr></tbody></table><ul><li>打印参数(格式化输出格式,一般对应于 org.apache.log4j.PatternLauout)</li></ul><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><strong>%m</strong></td><td>输出代码中指定的消息</td></tr><tr><td><strong>%p</strong></td><td>输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL</td></tr><tr><td><strong>%r</strong></td><td>输出自应用启动到输出该log信息耗费的毫秒数</td></tr><tr><td><strong>%c</strong></td><td>输出所属的类目,通常就是所在类的全名。%c{1} 可取当前类名称</td></tr><tr><td><strong>%t</strong></td><td>输出产生该日志事件的线程名</td></tr><tr><td><strong>%n </strong></td><td>输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n”</td></tr><tr><td><strong>%d</strong></td><td>输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式。标准格式为 %d{yyyy-MM-dd HH:mm:ss}</td></tr><tr><td><strong>%l </strong></td><td>输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。</td></tr></tbody></table><ul><li>option - 可选配置。一般来说每个 Appender 或者 Layout 都有默认配置,用户使用自定义日志配置,如指定输出地点等。常用的 option 有以下几种:</li></ul><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><strong>file</strong></td><td>日志输出至指定文件</td></tr><tr><td><strong>thresold</strong></td><td>定制日志消息的输出在不同 level 时的行为,</td></tr><tr><td><strong>append</strong></td><td>是否追加至日志文件中</td></tr></tbody></table><hr><p>参考资料:</p><p><a href="http://wiki.jikexueyuan.com/project/log4j/overview.html" target="_blank" rel="noopener">Log4J 教程 - 极客学院</a></p><p><a href="http://www.cnblogs.com/ITEagle/archive/2010/04/23/1718365.html" target="_blank" rel="noopener">log4j.properties配置详解</a></p>]]></content>
<tags>
<tag> log4j </tag>
</tags>
</entry>
<entry>
<title>SpringMVC源码分析 - DispatcherServlet请求处理过程</title>
<link href="/2017/11/26/SpringMVC%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-DispatcherServlet%E8%AF%B7%E6%B1%82%E5%A4%84%E7%90%86%E8%BF%87%E7%A8%8B/"/>
<url>/2017/11/26/SpringMVC%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-DispatcherServlet%E8%AF%B7%E6%B1%82%E5%A4%84%E7%90%86%E8%BF%87%E7%A8%8B/</url>
<content type="html"><![CDATA[<h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><p><img src="http://on83riher.bkt.clouddn.com/DispatcherServlet%E6%A1%86%E6%9E%B6%E6%A6%82%E8%A6%81.png" alt=""></p><blockquote><p>这张图在网上搜到的,但是实际的来源处实在找不到了,如果后面找到一定补上链接。</p></blockquote><p>上图的流程可用以下文字进行描述:</p><ol><li>DispatcherServelt 作为前端控制器,拦截所有的请求。</li><li>DispatcherServlet 接收到 http 请求之后, 根据访问的路由以及 HandlerMapping,获取一个 HandlerExecutionChain 对象。</li><li>DispatcherServlet 将 Handler 对象交由 HandlerAdapter,调用处理器 Controller 对应功能处理方法。</li><li>HandlerAdapter 返回 ModelAndView 对象,DispatcherServlet 将 view 交由 ViewResolver 进行解析,得到相应的视图,并用 Model 对 View 进行渲染。</li><li>返回响应结果。</li></ol><h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><p>源码部分我打算通过流程图的形式来分析,源代码部分还是根据流程图来一步步看会更好,否则会被陌生且复杂的源代码给搞混(欲哭无泪)。</p><p><img src="http://on83riher.bkt.clouddn.com/DispatcherServlet%E5%A4%84%E7%90%86%E8%AF%B7%E6%B1%82.png" alt=""></p><blockquote><p>DEBUG大法是真的好!</p></blockquote>]]></content>
<tags>
<tag> SpringMVC </tag>
</tags>
</entry>
<entry>
<title>SpringMVC源码分析 - DispatcherServlet初始化过程</title>
<link href="/2017/11/26/SpringMVC%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-DispatcherServlet%E5%88%9D%E5%A7%8B%E5%8C%96%E8%BF%87%E7%A8%8B/"/>
<url>/2017/11/26/SpringMVC%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-DispatcherServlet%E5%88%9D%E5%A7%8B%E5%8C%96%E8%BF%87%E7%A8%8B/</url>
<content type="html"><![CDATA[<h2 id="继承体系"><a href="#继承体系" class="headerlink" title="继承体系"></a>继承体系</h2><p><img src="http://on83riher.bkt.clouddn.com/DispatcherServlet%E7%BB%A7%E6%89%BF%E4%BD%93%E7%B3%BB.png" alt=""></p><p>从 DispatcherServlet 继承体系来看(蓝色部分),DispatcherServlet 继承自 FrameworkServlet,而 FrameworkServlet 又继承自 HttpServletBean ,最终 HttpSevletBean 继承了 HttpServlet 。通过这一步步继承封装之后,才构成了如今的 DispatcherSevlet 架构基础。</p><p>下面将自上到下来说明 DispatcherServlet 的初始化过程。</p><h2 id="HttpServlet"><a href="#HttpServlet" class="headerlink" title="HttpServlet"></a>HttpServlet</h2><p>HttpServletBean 继承自 Servlet 架构中的 HttpServlet 类,并重写了<code>init()</code>方法。</p><blockquote><p>Servlet 生命周期从创建到销毁的过程中,有三个重要的方法:</p><ul><li>init() - 负责初始化 Servlet 对象。在 Servlet 生命周期中只会调用一次。</li><li>service() - 负责响应客户的请求。每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service 方法中两个参数,分别是 ServletRequest 和 ServletResponse,用于传递 http 请求和回写。</li><li>destory() - 负责销毁 Servlet 对象。在 Servlet 生命周期中只会调用一次。</li></ul></blockquote><p>从 Servlet 的生命周期可知,在 <code>init()</code>方法中,我们可以进行初始化工作,HttpServletBean 正是也做了这样的工作。源码如下所示:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span> <span class="keyword">throws</span> ServletException </span>{</span><br><span class="line"> <span class="keyword">if</span> (logger.isDebugEnabled()) {</span><br><span class="line"> logger.debug(<span class="string">"Initializing servlet '"</span> + getServletName() + <span class="string">"'"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Set bean properties from init parameters.</span></span><br><span class="line"> <span class="comment">// 加载 Servlet 的配置文件(一般指 web.xml)</span></span><br><span class="line"> PropertyValues pvs = <span class="keyword">new</span> ServletConfigPropertyValues(getServletConfig(), <span class="keyword">this</span>.requiredProperties);</span><br><span class="line"> <span class="keyword">if</span> (!pvs.isEmpty()) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(<span class="keyword">this</span>);</span><br><span class="line"> ResourceLoader resourceLoader = <span class="keyword">new</span> ServletContextResourceLoader(getServletContext());</span><br><span class="line"> bw.registerCustomEditor(Resource.class, <span class="keyword">new</span> ResourceEditor(resourceLoader, getEnvironment()));</span><br><span class="line"> initBeanWrapper(bw); <span class="comment">// 上面做了这么多的工作,到这里却是一个空方法,而它的子类都没有去重写这个方法,个人认为这是想让开发者自定义如何管理 Servlet 配置吧</span></span><br><span class="line"> bw.setPropertyValues(pvs, <span class="keyword">true</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">catch</span> (BeansException ex) {</span><br><span class="line"> <span class="keyword">if</span> (logger.isErrorEnabled()) {</span><br><span class="line"> logger.error(<span class="string">"Failed to set bean properties on servlet '"</span> + getServletName() + <span class="string">"'"</span>, ex);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">throw</span> ex;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Let subclasses do whatever initialization they like.</span></span><br><span class="line"> <span class="comment">// 交由子类(FrameworkServlet)来进行其特有的初始化工作</span></span><br><span class="line"> initServletBean();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (logger.isDebugEnabled()) {</span><br><span class="line"> logger.debug(<span class="string">"Servlet '"</span> + getServletName() + <span class="string">"' configured successfully"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="FrameworkServlet"><a href="#FrameworkServlet" class="headerlink" title="FrameworkServlet"></a>FrameworkServlet</h2><p>FrameworkServlet 继承自 HttpServletBean,实现了<code>initServletBean()</code>方法。FrameworkServlet 在继承体系结构中,在 Servlet 与 SpringMVC 起到了承上启下的作用,它负责初始化 WebApplicationContext,还负责重写了 Servlet 生命周期中另外两个重要方法——<code>service()</code>和<code>destory()</code>,并改写了<code>doGet()</code>、<code>doPost()</code>等 http 方法,统一调用<code>processHandler()</code>方法来处理所有 http 请求。</p><blockquote><p>ApplicationContext 是 Spring 的核心,相当于 Spring 环境中的上下文。而在WebApplicationContext 继承自 ApplicationContext,充当了在 Web 环境中使用 Spring 的上下文。在 Web 环境中,WebApplicationContext 实例需要 ServletContext,即它必须拥有 Web 容器才能够完成启动的工作。</p></blockquote><p>下面重点讲<code>initServletBean()</code>方法,源码如下所示:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">initServletBean</span><span class="params">()</span> <span class="keyword">throws</span> ServletException </span>{</span><br><span class="line"> <span class="comment">// do sth</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 初始化 WebApplicationContext</span></span><br><span class="line"> <span class="keyword">this</span>.webApplicationContext = initWebApplicationContext();</span><br><span class="line"> initFrameworkServlet();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">catch</span> (ServletException ex) {</span><br><span class="line"> <span class="keyword">this</span>.logger.error(<span class="string">"Context initialization failed"</span>, ex);</span><br><span class="line"> <span class="keyword">throw</span> ex;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">catch</span> (RuntimeException ex) {</span><br><span class="line"> <span class="keyword">this</span>.logger.error(<span class="string">"Context initialization failed"</span>, ex);</span><br><span class="line"> <span class="keyword">throw</span> ex;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// initServletBean()转而调用了initWebApplicationContext(),所以重点工作在这里</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> WebApplicationContext <span class="title">initWebApplicationContext</span><span class="params">()</span> </span>{</span><br><span class="line"> WebApplicationContext rootContext =</span><br><span class="line"> WebApplicationContextUtils.getWebApplicationContext(getServletContext());</span><br><span class="line"> WebApplicationContext wac = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.webApplicationContext != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// A context instance was injected at construction time -> use it</span></span><br><span class="line"> wac = <span class="keyword">this</span>.webApplicationContext;</span><br><span class="line"> <span class="keyword">if</span> (wac <span class="keyword">instanceof</span> ConfigurableWebApplicationContext) {</span><br><span class="line"> ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;</span><br><span class="line"> <span class="keyword">if</span> (!cwac.isActive()) {</span><br><span class="line"> <span class="keyword">if</span> (cwac.getParent() == <span class="keyword">null</span>) {</span><br><span class="line"> cwac.setParent(rootContext);</span><br><span class="line"> }</span><br><span class="line"> configureAndRefreshWebApplicationContext(cwac);</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> (wac == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// No context instance was injected at construction time -> see if one</span></span><br><span class="line"> <span class="comment">// has been registered in the servlet context. If one exists, it is assumed</span></span><br><span class="line"> <span class="comment">// that the parent context (if any) has already been set and that the</span></span><br><span class="line"> <span class="comment">// user has performed any initialization such as setting the context id</span></span><br><span class="line"> wac = findWebApplicationContext();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (wac == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// No context instance is defined for this servlet -> create a local one</span></span><br><span class="line"> wac = createWebApplicationContext(rootContext);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!<span class="keyword">this</span>.refreshEventReceived) {</span><br><span class="line"> <span class="comment">// DispatcherSevlet 初始化工作的入口就在这里!</span></span><br><span class="line"> onRefresh(wac);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// do sth</span></span><br><span class="line"> <span class="keyword">return</span> wac;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="DispatcherServlet"><a href="#DispatcherServlet" class="headerlink" title="DispatcherServlet"></a>DispatcherServlet</h2><p>在进行下一步代码分析之前,先看下 DispatcherSevrlet 的静态代码块部分:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DEFAULT_STRATEGIES_PATH = <span class="string">"DispatcherServlet.properties"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> {</span><br><span class="line"> <span class="comment">// Load default strategy implementations from properties file.</span></span><br><span class="line"> <span class="comment">// This is currently strictly internal and not meant to be customized</span></span><br><span class="line"> <span class="comment">// by application developers.</span></span><br><span class="line"> <span class="comment">// 加载所有默认配置,用于后面的初始化工作</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> ClassPathResource resource = <span class="keyword">new</span> ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);</span><br><span class="line"> defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">catch</span> (IOException ex) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">"Could not load '"</span> + DEFAULT_STRATEGIES_PATH + <span class="string">"': "</span> + ex.getMessage());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>DispatcherServlet.properties</code>配置文件中定义了DispatcherServlet各组件中的配置实现形式,如下所示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"># Default implementation classes for DispatcherServlet's strategy interfaces.</span><br><span class="line"># Used as fallback when no matching beans are found in the DispatcherServlet context.</span><br><span class="line"># Not meant to be customized by application developers.</span><br><span class="line"></span><br><span class="line">org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver</span><br><span class="line"></span><br><span class="line">org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver</span><br><span class="line"></span><br><span class="line">org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\</span><br><span class="line"> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping</span><br><span class="line"></span><br><span class="line">org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\</span><br><span class="line"> org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\</span><br><span class="line"> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter</span><br><span class="line"></span><br><span class="line">org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\</span><br><span class="line"> org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\</span><br><span class="line"> org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver</span><br><span class="line"></span><br><span class="line">org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator</span><br><span class="line"></span><br><span class="line">org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver</span><br><span class="line"></span><br><span class="line">org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager</span><br></pre></td></tr></table></figure><p>回到正题,在<code>onRefresh()</code>方法,调用了<code>initStrategies()</code>,所以重点部分就在于<code>initStrategies()</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onRefresh</span><span class="params">(ApplicationContext context)</span> </span>{</span><br><span class="line"> initStrategies(context);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">initStrategies</span><span class="params">(ApplicationContext context)</span> </span>{</span><br><span class="line"> <span class="comment">// 初始化多媒体解析器</span></span><br><span class="line"> initMultipartResolver(context);</span><br><span class="line"> <span class="comment">// 初始化位置解析器</span></span><br><span class="line"> initLocaleResolver(context);</span><br><span class="line"> <span class="comment">// 初始化主题解析器</span></span><br><span class="line"> initThemeResolver(context);</span><br><span class="line"> <span class="comment">// 初始化 HandlerMappings</span></span><br><span class="line"> initHandlerMappings(context);</span><br><span class="line"> <span class="comment">// 初始化 HandlerAdapters</span></span><br><span class="line"> initHandlerAdapters(context);</span><br><span class="line"> <span class="comment">// 初始化异常处理解析器</span></span><br><span class="line"> initHandlerExceptionResolvers(context);</span><br><span class="line"> <span class="comment">// 初始化请求到视图名转换器</span></span><br><span class="line"> initRequestToViewNameTranslator(context);</span><br><span class="line"> <span class="comment">// 初始化视图解析器</span></span><br><span class="line"> initViewResolvers(context);</span><br><span class="line"> <span class="comment">// 初始化 FlashMapManager</span></span><br><span class="line"> initFlashMapManager(context);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>下面以<code>initHandlerMappings()</code>方法为例,分析如何加载 HandlerMapping。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">initHandlerMappings</span><span class="params">(ApplicationContext context)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.handlerMappings = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.detectAllHandlerMappings) {</span><br><span class="line"> <span class="comment">// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.</span></span><br><span class="line"> Map<String, HandlerMapping> matchingBeans =</span><br><span class="line"> BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, <span class="keyword">true</span>, <span class="keyword">false</span>);</span><br><span class="line"> <span class="keyword">if</span> (!matchingBeans.isEmpty()) {</span><br><span class="line"> <span class="keyword">this</span>.handlerMappings = <span class="keyword">new</span> ArrayList<>(matchingBeans.values());</span><br><span class="line"> <span class="comment">// We keep HandlerMappings in sorted order.</span></span><br><span class="line"> AnnotationAwareOrderComparator.sort(<span class="keyword">this</span>.handlerMappings);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);</span><br><span class="line"> <span class="keyword">this</span>.handlerMappings = Collections.singletonList(hm);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">catch</span> (NoSuchBeanDefinitionException ex) {</span><br><span class="line"> <span class="comment">// Ignore, we'll add a default HandlerMapping later.</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Ensure we have at least one HandlerMapping, by registering</span></span><br><span class="line"> <span class="comment">// a default HandlerMapping if no other mappings are found.</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.handlerMappings == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">this</span>.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);</span><br><span class="line"> <span class="keyword">if</span> (logger.isDebugEnabled()) {</span><br><span class="line"> logger.debug(<span class="string">"No HandlerMappings found in servlet '"</span> + getServletName() + <span class="string">"': using default"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面的源码注释解释的十分清楚了,值得注意一点的是,为了确保 DispatcherServlet 中至少有一个 HandlerMapping,它会从上面所述的默认配置项中加载所有默认组件,如 HandlerMapping 默认组件为 BeanNameUrlHandlerMapping、RequestMappingHandlerMapping。</p><hr><p>参考资料:</p><p><a href="http://www.cnblogs.com/klguang/p/4748922.html" target="_blank" rel="noopener">servlet清晰理解</a></p><p><a href="http://blog.csdn.net/zhulinu/article/details/7305247" target="_blank" rel="noopener">WebApplicationContext初始化</a></p><p><a href="http://wujiu.iteye.com/blog/2170778" target="_blank" rel="noopener">Spring MVC之DispatcherServlet初始化</a></p>]]></content>
<tags>
<tag> SpringMVC </tag>
</tags>
</entry>
<entry>
<title>Executor 框架 - 线程池</title>
<link href="/2017/11/14/Executor%20%E6%A1%86%E6%9E%B6%20-%20%E7%BA%BF%E7%A8%8B%E6%B1%A0/"/>
<url>/2017/11/14/Executor%20%E6%A1%86%E6%9E%B6%20-%20%E7%BA%BF%E7%A8%8B%E6%B1%A0/</url>
<content type="html"><![CDATA[<h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>线程池(Thread Pool)是一种线程使用模式。当线程使用不当,创建过多时会带来调度的开销,进而影响缓存局部性和整体性能。而线程池维护着若干个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。</p><h2 id="重要成员"><a href="#重要成员" class="headerlink" title="重要成员"></a>重要成员</h2><p>在 Java 中,线程池一般都是通过 ThreadPoolExecutor 来构建的,下面将介绍 ThreadPoolExecutor 的构造函数部分。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ThreadPoolExecutor</span><span class="params">(<span class="keyword">int</span> corePoolSize,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">int</span> maximumPoolSize,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">long</span> keepAliveTime,</span></span></span><br><span class="line"><span class="function"><span class="params"> TimeUnit unit,</span></span></span><br><span class="line"><span class="function"><span class="params"> BlockingQueue<Runnable> workQueue,</span></span></span><br><span class="line"><span class="function"><span class="params"> ThreadFactory threadFactory,</span></span></span><br><span class="line"><span class="function"><span class="params"> RejectedExecutionHandler handler)</span></span></span><br></pre></td></tr></table></figure><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td>corePoolSize</td><td>线程池中所保存的核心线程数。线程池启动时默认是空的,只有任务来临时才会创建线程以处理请求。在 corePoolSize 范围内已创建的线程即使处于空闲状态,除非设置了 allowCoreThreadTimeOut,否则不会被销毁</td></tr><tr><td>maximumPoolSize</td><td>线程池允许创建的最大线程数。</td></tr><tr><td>keepAliveTime</td><td>当线程池中的线程超过了 corePoolSize 的范围时,终止过多的空闲线程的时间</td></tr><tr><td>unit</td><td>keepAliveTime 的时间单位</td></tr><tr><td>workQueue</td><td>用于容纳所有待执行的任务的工作队列。该工作队列只能够容纳实通过 execute() 方法提交的实现 Runnable 接口的任务</td></tr><tr><td>threadFactory</td><td>用于 executor 如何创建一个线程(一般使用默认线程工厂DefaultThreadFactory)</td></tr><tr><td>handler</td><td>当线程池与工作队列的容量已满时,任务提交被拒绝时所采取的拒绝策略。</td></tr></tbody></table><h2 id="工作流程"><a href="#工作流程" class="headerlink" title="工作流程"></a>工作流程</h2><p>以下流程为向线程池中正常提交任务时的基本流程:</p><ol><li>线程池判断核心线程池(corePoolSize)里的线程是否都在执行任务,如果不是,则创建一个新的工作线程(或复用一个工作线程)来执行任务。如果核心线程池里的线程都在执行任务,则执行第二步。</li><li>线程池判断工作队列(workQueue)是否已经满了。如果工作队列没有满,则将新提交的任务存储到工作队列中,等待被调度执行。如果工作队列已满,则执行第三步。</li><li>线程池判断线程池的线程是否已经满了(maximumPoolSize)。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则根据线程池的拒绝策略来处理该任务。</li></ol><p><img src="http://on83riher.bkt.clouddn.com/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B1.png" alt=""></p><p><img src="http://on83riher.bkt.clouddn.com/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B2.png" alt=""></p><blockquote><p>以上两图均来自于这篇博客<a href="http://blog.csdn.net/fuyuwei2015/article/details/72758179" target="_blank" rel="noopener">Java线程池(ThreadPoolExecutor)原理分析与使用 - 孙_悟__空</a></p></blockquote><h2 id="拒绝策略"><a href="#拒绝策略" class="headerlink" title="拒绝策略"></a>拒绝策略</h2><p>线程池中已经定义了四种任务提交的拒绝策略(以下策略我都贴出源码部分,怕翻译有问题):</p><ul><li>AbortPolicy :不执行任务,并直接抛出一个运行时异常。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">A handler <span class="keyword">for</span> rejected tasks that <span class="keyword">throws</span> a RejectedExecutionException</span><br></pre></td></tr></table></figure><ul><li>DiscardPolicy :(silently)直接抛弃任务,其内部实现是一个空方法。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">A handler <span class="keyword">for</span> rejected tasks that silently discards the rejected task.</span><br></pre></td></tr></table></figure><ul><li>DiscardOldestPolicy : 从工作队列中抛弃最老的未处理的任务,并尝试重新执行该任务。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">A handler <span class="keyword">for</span> rejected tasks that discards the oldest unhandled request </span><br><span class="line">and then retries {<span class="meta">@code</span> execute}, unless the executor is shut down, in </span><br><span class="line">which <span class="keyword">case</span> the task is discarded.</span><br></pre></td></tr></table></figure><ul><li>CallerRunsPolicy : 线程池直接创建一个 calling 线程来执行任务。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">A handler <span class="keyword">for</span> rejected tasks that runs the rejected task directly in the </span><br><span class="line">calling thread of the {<span class="meta">@code</span> execute} method, unless the executor has </span><br><span class="line">been shut down, in which <span class="keyword">case</span> the task is discarded.</span><br></pre></td></tr></table></figure><hr><p>参考资料</p><p><a href="https://zh.wikipedia.org/wiki/%E7%BA%BF%E7%A8%8B%E6%B1%A0" target="_blank" rel="noopener">线程池 - 维基百科,自由的百科全书</a></p><p><a href="http://www.cnblogs.com/nayitian/p/3262031.html" target="_blank" rel="noopener">Java:多线程,线程池,ThreadPoolExecutor详解</a></p><p><a href="http://blog.csdn.net/fuyuwei2015/article/details/72758179" target="_blank" rel="noopener">Java线程池(ThreadPoolExecutor)原理分析与使用</a></p>]]></content>
</entry>
<entry>
<title>Executor 框架 - 概念</title>
<link href="/2017/11/14/Executor%20%E6%A1%86%E6%9E%B6%20-%20%E6%A6%82%E5%BF%B5/"/>
<url>/2017/11/14/Executor%20%E6%A1%86%E6%9E%B6%20-%20%E6%A6%82%E5%BF%B5/</url>
<content type="html"><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Executor 框架是 Java 5 中引入的,位于 java.util.concurrent 包下,其内部使用了线程池机制,可用于启动、调度和管理多个线程。通过 Executor 来启动线程比使用 Thread 的 start 方法的好处不仅在于更易于管理,效率更好,还在于有助于避免 this 逃逸问题。</p><blockquote><p>this 逃逸问题是指在构造函数返回之前其他线程就持有该对象的引用。调用尚未构造完全的对象的方法可能会引发令人疑惑的错误。this 逃逸经常发生在构造函数中启动线程或注册监听器时。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ThisEscape</span> </span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">ThisEscape</span><span class="params">()</span> </span>{ </span><br><span class="line"> <span class="keyword">new</span> Thread(<span class="keyword">new</span> EscapeRunnable()).start(); </span><br><span class="line"> <span class="comment">// ... </span></span><br><span class="line"> } </span><br><span class="line"> </span><br><span class="line"> <span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">EscapeRunnable</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>{ </span><br><span class="line"> <span class="meta">@Override</span> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{ </span><br><span class="line"> <span class="comment">// 通过ThisEscape.this就可以引用外围类对象, 但是此时外围类对象可能还没有构造完成, 即发生了外围类的this引用的逃逸 </span></span><br><span class="line"> } </span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="组成部分"><a href="#组成部分" class="headerlink" title="组成部分"></a>组成部分</h2><p>Executor 框架包括有以下组件:</p><ul><li><strong>任务:</strong>包含被执行任务需要实现的接口:Runnable 接口和 Callable 接口。</li><li><strong>任务的执行:</strong>包括任务任务机制的核心接口 Executor,以及继承自Executor 接口的 ExecutorService 接口与 CompletionService 接口。</li><li><strong>异步计算的结果:</strong>包括接口 Future,以及实现 Future 接口的 FutureTask 类。</li></ul><h2 id="成员介绍"><a href="#成员介绍" class="headerlink" title="成员介绍"></a>成员介绍</h2><h3 id="Executor"><a href="#Executor" class="headerlink" title="Executor"></a>Executor</h3><p><strong>Executor是一个Executor 框架的核心接口</strong>,它内部只定义了一个方法<code>void execute(Runnable command)</code>,该方法接受一个 Runnable 实例,并将其执行。</p><h3 id="ExecutorService"><a href="#ExecutorService" class="headerlink" title="ExecutorService"></a>ExecutorService</h3><p><strong>ExecutorService接口继承自 Executor 接口,它提供了更加丰富的管理多线程的方法</strong>,比如,ExecutorService 接口提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。</p><p>ExecutorService 的生命周期包括三种状态:运行、关闭、终止。</p><ul><li><strong>运行</strong>:当实现 ExecutorService 接口的类的实例被创建后,便进入运行状态。</li><li><strong>关闭</strong>:当调用了 ExecutorService 接口内部提供的 shutdown 方法时,便平滑地进入关闭状态。平滑过渡是指在关闭状态中,ExecutorService 会停止接收任何新的任务,并且会等待所有已经提交的任务执行完成(已经提交的任务分为两类:一类是已经在执行的,另一类是还没有开始执行的)。</li><li><strong>终止</strong>:在所有已提交的任务执行完毕后,便进入了终止状态。</li></ul><h3 id="Executors"><a href="#Executors" class="headerlink" title="Executors"></a>Executors</h3><p><strong>Executors 提供了一系列工厂方法用于用于创建功能不同的线程池,所有返回的线程池都实现了ExecutorService 接口。</strong>以下为四种常见的线程池类型:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><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="keyword">static</span> ExecutorService <span class="title">newFixedThreadPool</span><span class="params">(<span class="keyword">int</span> nThreads)</span></span></span><br><span class="line"><span class="function"> </span></span><br><span class="line"><span class="function"><span class="comment">// 创建一个可缓存的线程池,调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程不可用,则创建一个新线程并添加到线程池中。终止并从缓存中移除那些已有60秒未被使用的线程</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title">newCachedThreadPool</span><span class="params">()</span></span></span><br><span class="line"><span class="function"> </span></span><br><span class="line"><span class="function"><span class="comment">// 创建一个单线程化的线程池</span></span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title">newSingleThreadExecutor</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建一个支持定时以及周期性的任务执行的线程池,多数情况可代替 Timer 类</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title">newScheduledThreadPool</span><span class="params">(<span class="keyword">int</span> corePoolSize)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="Future-FutureTask-Callable-Runnable"><a href="#Future-FutureTask-Callable-Runnable" class="headerlink" title="Future/FutureTask/Callable/Runnable"></a>Future/FutureTask/Callable/Runnable</h3><p>在 JDK5 之后,任务可分为两类:一类是实现了 Runnable 接口的类,另一类是实现了 Callable 接口的类。<strong>两者都能够被 ExecutorService 执行,但两者区别在于,Runnable 任务没有返回值,而 Callable 任务有返回值,且能够抛出检查异常(checked exception)。</strong></p><p><strong>Future 接口对具体提交的任务,封装并提供了获取结果,任务取消等操作。执行结果可通过调用 get() 方法来获取,该方法会阻塞直到任务返回结果。FutureTask 则是 Future 接口的具体实现类。</strong></p><blockquote><p>Future 封装的 Runnable 任务可以调用 get() 方法,但是其返回值为 null。</p></blockquote><h3 id="CompletionService"><a href="#CompletionService" class="headerlink" title="CompletionService"></a>CompletionService</h3><p>若通过向线程池提交了若干个任务,并通过容器保存所有 FutureTask,当需要得到执行结果的时候,可以通过循环遍历 FutureTask 的方式,调用 get() 方法获取,但是如果此时 FutureTask 尚未完成,那么此时线程便会阻塞等待至任务运行结束。由于无法准确知道哪个任务将会优先执行完成,使用循环遍历的方式效率不会很高。</p><p><strong>在 JDK5 中提供了 CompletionService,其内部通过 BlockingQueue 来管理若干线程。ExecutorCompletionService 为 CompletionService 接口的具体实现类。</strong></p><ul><li>take() :获取任务结果。获取并移除下一个已完成任务的 Future。如果任务不存在,则等待。</li><li>poll() : 与 take() 功能相同,不同之点在于任务不存在时,直接返回 null。</li></ul><blockquote><p>以上两种方法特性其实就是利用了 BlockingQueue 接口的特点。</p></blockquote><hr><p>参考资料</p><p><a href="http://wiki.jikexueyuan.com/project/java-concurrency/executor.html" target="_blank" rel="noopener">并发新特性—Executor 框架与线程池</a></p><p><a href="http://coolxing.iteye.com/blog/1464501" target="_blank" rel="noopener">变量可见性和volatile, this逃逸, 不可变对象, 以及安全公开–Java Concurrency In Practice C03读书笔记</a></p><p><a href="http://www.jianshu.com/p/8826a459471f" target="_blank" rel="noopener">Executor框架简介 - 加大装益达</a></p><p><a href="http://www.jianshu.com/p/c4a31f914cc7" target="_blank" rel="noopener">java并发编程之CompletionService - miaoLoveCode</a></p>]]></content>
</entry>
<entry>
<title>《深入理解Java虚拟机》读书笔记 - 线程安全与锁优化</title>
<link href="/2017/11/11/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%20-%20%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E4%B8%8E%E9%94%81%E4%BC%98%E5%8C%96/"/>
<url>/2017/11/11/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%20-%20%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E4%B8%8E%E9%94%81%E4%BC%98%E5%8C%96/</url>
<content type="html"><![CDATA[<blockquote><p>此篇为《深入理解Java虚拟机》第十三章13.2部分的读书笔记</p></blockquote><h2 id="线程安全"><a href="#线程安全" class="headerlink" title="线程安全"></a>线程安全</h2><blockquote><p>对于线程安全较合适的定义为:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。</p></blockquote><h3 id="Java-语言中的线程安全"><a href="#Java-语言中的线程安全" class="headerlink" title="Java 语言中的线程安全"></a>Java 语言中的线程安全</h3><p>按照线程安全的“安全程度”由强至弱来排序,我们可以将 Java 语言中各种操作共享的数据分为以下5类:不可变、线程绝对安全、相对线程安全、线程兼容和线程对立。</p><ul><li>不可变</li></ul><p><strong>在 Java 中不可变(Immutable)的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要再采用任何的线程安全保障措施。</strong>如果共享数据是一个基本数据类型,那么只要在定义时使用 final 关键字修饰它就可以保证它的不可变性;如果共享数据是一个对象,那就需要保证对象的行为不会对其状态产生任何影响,如 String。</p><ul><li>绝对线程安全</li></ul><p>在 Java 中要求一个类如同开头的定义一般,不管运行环境如何,调用者都不需要任何额外的同步措施。<strong>这种做法虽然是安全可用的,但是这往往都会付出很大的、甚至是不切实际的代价。</strong></p><ul><li>相对线程安全</li></ul><p><strong>相对的线程安全就是我们通常意义上的所讲的线程安全,它需要保证对这个对象单独的操作是线程安全的,我们在调用的时候不需要额外的保证措施,但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段保证调用的正确性。</strong>例如 Vector、Hashtable、Collections.synchronizedCollection() 方法包装的集合等。</p><blockquote><p>特别说明,Vector 内部函数都使用 synchronized 关键字修饰,看上去很安全,但如果调用者的操作不当,仍会出现不可避免的错误。即在查询一个元素的时候,某个线程就已经将这个元素删除了,那就会抛出 ArrayIndexOutOfBoundsException 异常。</p></blockquote><ul><li>线程兼容</li></ul><p><strong>线程兼容是指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境下中可以安全使用。</strong>例如 Java API 中大部分的类都属于线程兼容,如与前面 Vector、Hashtable 内部所使用的就是 ArrayList 和 HashMap 等。</p><ul><li>线程对立</li></ul><p><strong>线程对立是指无论调用端是否采用了同步措施,都无法在多线程环境中并发使用的代码。</strong>由于 Java 语言天生具备多线程特性,线程对立这种排斥多线程的代码是很少出现的,而且通常是有害的,应当尽量避免。比如 Thread 类中的 suspend() 和 resume() 方法,suspend() 试图中断线程,resume() 试图恢复线程,如果并发进行的话,会存在很大的死锁风险,所以这两个方法已被抛弃(@Depreacted)使用。</p><h3 id="线程安全的实现方法"><a href="#线程安全的实现方法" class="headerlink" title="线程安全的实现方法"></a>线程安全的实现方法</h3><blockquote><p>P390</p></blockquote><h2 id="锁优化"><a href="#锁优化" class="headerlink" title="锁优化"></a>锁优化</h2><p>自 JDK1.5之后,HotSpot 虚拟机针对多线程并发花了十分的精力,去实现各种锁优化技术,如适应性自选(Adaptive Spinning)、锁清除(Lock Elimination)、锁粗化(Lock Coarsening)、轻量级锁(Lightweight Locking)和偏向锁(Biased Locking)等。</p><h3 id="自旋锁与自适应自旋"><a href="#自旋锁与自适应自旋" class="headerlink" title="自旋锁与自适应自旋"></a>自旋锁与自适应自旋</h3><p>在线程互斥同步的时候,由于需要实现线程互斥,被阻塞线程需要由运行态转入阻塞态,而挂起线程和恢复线程的操作都需要从用户态转入到内核态中完成,这些操作给系统的并发性能带来了很大的压力。而往往线程并发时,线程共享数据的锁定状态只会持续很短的一段时间,为了这段时间选择去挂起和恢复线程是不值得的。</p><p><strong>那么就引出了自旋锁的作用:如果在同一时刻中有两个以上的线程并行执行,我们可以让后面请求锁的线程“稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快地就会释放锁。但持有锁的线程依旧不放弃锁,那么为了最大化降低 CPU 的消耗,将正在自旋等待的线程使用传统的方式进行挂起阻塞等待。上面所述中,为了让线程等待,我们只需要让线程执行一个忙循环(自旋)即可。</strong></p><p>而在 JDK1.6中引入了自适应的自旋锁。自适应意味着自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。</p><h3 id="锁消除"><a href="#锁消除" class="headerlink" title="锁消除"></a>锁消除</h3><p><strong>锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。</strong>锁消除的主要判定依据来源于逃逸分析的数据支持。</p><h3 id="锁粗化"><a href="#锁粗化" class="headerlink" title="锁粗化"></a>锁粗化</h3><p><strong>锁粗化是指虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,那将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。</strong>例如,在 for 循环中进行对字符串拼接的任务进行加锁,那么锁粗化就会将这一操作外提至 for 循环外。</p><h3 id="轻量级锁"><a href="#轻量级锁" class="headerlink" title="轻量级锁"></a>轻量级锁</h3><p><strong>轻量级锁是相对于传统的锁机制操作而言的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁同步代码所带来的性能消耗。</strong>轻量级锁本质上是一种乐观锁的实现。</p><h3 id="偏向锁"><a href="#偏向锁" class="headerlink" title="偏向锁"></a>偏向锁</h3><p><strong>偏向锁是指在无竞争情况下,这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步。</strong>如果轻量级锁是在无竞争情况下使用 CAS 操作去消除同步的互斥量,那么偏向锁就是在无竞争的情况下,把整个同步都消除掉,连 CAS 操作都不需要。</p>]]></content>
</entry>
<entry>
<title>从输入 URL 到页面加载完成的过程中都发生了什么事情?</title>
<link href="/2017/11/06/%E4%BB%8E%E8%BE%93%E5%85%A5%20URL%20%E5%88%B0%E9%A1%B5%E9%9D%A2%E5%8A%A0%E8%BD%BD%E5%AE%8C%E6%88%90%E7%9A%84%E8%BF%87%E7%A8%8B%E4%B8%AD%E9%83%BD%E5%8F%91%E7%94%9F%E4%BA%86%E4%BB%80%E4%B9%88%E4%BA%8B%E6%83%85/"/>
<url>/2017/11/06/%E4%BB%8E%E8%BE%93%E5%85%A5%20URL%20%E5%88%B0%E9%A1%B5%E9%9D%A2%E5%8A%A0%E8%BD%BD%E5%AE%8C%E6%88%90%E7%9A%84%E8%BF%87%E7%A8%8B%E4%B8%AD%E9%83%BD%E5%8F%91%E7%94%9F%E4%BA%86%E4%BB%80%E4%B9%88%E4%BA%8B%E6%83%85/</url>
<content type="html"><![CDATA[<p>最近看到了一道面试题,叫做“从输入 URL 到页面加载完成的过程中都发生了什么事情?”。当看到这道题的时候,就瞬间联想了计网中的五层模型,并想了想大致的流程,但是天真的我看了下网上的回答,发现实在太 naive 了。</p><p>下面总结了几篇文章,看看能不能找到时间梳理一遍。</p><ul><li><p>小白难度</p><p><a href="https://www.zhihu.com/question/34873227" target="_blank" rel="noopener">https://www.zhihu.com/question/34873227</a> - 知乎上评分较高的文章。</p></li><li><p>中等难度</p><p><a href="http://www.cnblogs.com/panxueji/archive/2013/05/12/3073924.html" target="_blank" rel="noopener">http://www.cnblogs.com/panxueji/archive/2013/05/12/3073924.html</a> - 翻译自网上一篇不错的科普文章。</p></li><li><p>地狱难度</p><p><a href="http://fex.baidu.com/blog/2014/05/what-happen/" target="_blank" rel="noopener">http://fex.baidu.com/blog/2014/05/what-happen/</a> - 对不起,这全篇我都没看懂,只想放上来纪念一下。</p></li></ul>]]></content>
</entry>
<entry>
<title>HTTPS 机制原理分析</title>
<link href="/2017/11/06/HTTPS%20%E6%9C%BA%E5%88%B6%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/"/>
<url>/2017/11/06/HTTPS%20%E6%9C%BA%E5%88%B6%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>超文本传输安全协议(Hypertext Transfer Protocol Secure,HTTPS) 是一种通过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。这个协议由网景公司在1994年首次提出,随后扩展至互联网上。</p><p>HTTPS 顺应时代被发展出来的很大原因在于 HTTP 协议本身的不安全性,即 HTTP 协议传输的内容是不加密的,直接由明文的方式传输,在复杂的网络通信容易被黑客截取,比如<a href="https://zh.wikipedia.org/wiki/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB" target="_blank" rel="noopener">中间人攻击</a>等手段。所以在发展 HTTP为前提下,网景公司加入了 SSL(Secure Socket Layer,安全套接字层) ,并在随后的发展过程中,扩展了 TSL(Transport Layer Security,传输层安全),如下所示:</p><div align="center"><br><img src="http://on83riher.bkt.clouddn.com/HTTPS%20%E7%BD%91%E7%BB%9C%E6%9E%B6%E6%9E%84%E5%9B%BE.png" alt=""><br></div><h2 id="相关术语"><a href="#相关术语" class="headerlink" title="相关术语"></a>相关术语</h2><p>在了解 HTTPS 如何在信息传输过程中保证数据的安全性前,需要了解下述的一些术语解释:</p><ul><li>对称加密</li></ul><p><strong>对称加密是指对数据进行加密和解密时使用相同的密钥,或是使用两个可以简单地相互推算的密钥。</strong>对称加密优点在于算法公开,计算量小,加解密效率高,但是其明显的缺点在于若密钥在网络传输过程中被黑客截取,那么黑客就能够正确地解析数据,那么这样就无法保证数据的安全了。</p><ul><li>非对称加密</li></ul><p><strong>非对称加密,与对称加密正好相反,该算法需要两个密钥:公开密钥(public key)和私有密钥(private key)。</strong>公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。其最大的优点在于安全性大大提高,原因在于数据接收方的私钥一般处于不公开的状态,黑客在获取数据的时候,无法通过私钥正确解析数据。那么其缺点,相对于对称加密,在于计算量大,加解密时效率较低。</p><ul><li>哈希算法</li></ul><p>哈希算法是一种单向密码体制,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。同时,哈希函数可以将任意长度的输入经过变化以后得到固定长度的输出。哈希算法在 HTTPS 的应用当中起了数据校验和的作用。</p><ul><li>数字证书</li></ul><p>数字证书是一个经证书授权中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件。它是一种权威性的电子文档,具有极高的安全性和可依赖性。</p><ul><li>数字签名</li></ul><p>数字签名就是在 HTTPS 验证过程中,用指定的哈希算法将信息进行哈希华后,将所得的值附加在信息后面,用于在数据传输后,方便信息接收端对数据进行校验,确保信息没有被恶意篡改。</p><h2 id="过程"><a href="#过程" class="headerlink" title="过程"></a>过程</h2><div align="center"><br><img src="http://on83riher.bkt.clouddn.com/HTTPS%20%E9%80%9A%E4%BF%A1%E8%BF%87%E7%A8%8B.png" alt=""><br></div><blockquote><p>图源来自于<a href="https://www.cnblogs.com/zery/p/5164795.html" target="_blank" rel="noopener">HTTPS 原理解析</a> ,这张画的实在太棒了!</p></blockquote><ol><li><p>客户端首先从发送一个 HTTPS 请求,将自己所支持的加密算法,通知服务器端。</p></li><li><p>服务器端从客户端发来的加密算法列表中,选出一种加密算法和 HASH 算法,并将其自身的数字证书附加选出的算法一并发回给客户端。而证书中一般包含了网站的地址,公钥,证书失效日期以及证书的颁发机构等等。</p></li><li><p>客户端在收到服务器端的响应之后,会做一下几件事:</p><p>1)验证证书的合法性。一般通过证书的颁发机构是否是合法的、证书是否超过失效日期、证书中所包含的网站地址是否与你正在所访问的相同等方面进行验证。若证书合法,则通过验证,否则将提示用户该证书存在风险。</p><p>2)生成随机密码。在证书通过验证,或用户主动信任该证书后,客户端会随机生成一串序列号,并使用服务器端传来的公钥进行加密,并生成握手消息。</p><p>3)HASH 算法加密信息。利用服务器端所回传的 HASH 算法将客户端生成的握手信息进行加密,并将加密后的 HASH 值附加上握手消息中,用于数据校验。</p></li><li><p>服务器端接收到客户端的请求后,同样也会做下面几件事:</p><p>1)使用自己的私钥来解密客户端所传来的握手消息,得到客户端生成的随机序列号。在这一部分过程中就运用了非对称加密的技术。</p><p>2)使用随机序列号,对握手消息进行 HASH 算法加密,并将获得的 HASH 值与从客户单一并传来的 HASH 值进行对比,查看是否一致。</p><p>3)最后,使用该随机序列号,再用公钥加密一段握手消息,并附加上该握手消息的 HASH 值,发回给客户端。</p></li><li><p>客户端接收到服务器端的请求后,用生成的随机序列号对握手消息进行解密,并对比传来的 HASH 值是否一致。倘若 HASH 值一直,则握手过程正式结束,之后的所有通信将由客户端所生成的随机序列号并利用加密算法对消息进行加密处理。</p></li></ol><hr><p>参考资料</p><p><a href="https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8%E5%8D%8F%E8%AE%AE" target="_blank" rel="noopener">超文本传输安全协议</a></p><p><a href="http://blog.csdn.net/zhongzh86/article/details/69389967" target="_blank" rel="noopener">深度解析HTTPS原理</a></p><p><a href="http://www.jianshu.com/p/650ad90bf563" target="_blank" rel="noopener">简单粗暴系列之HTTPS原理</a></p><p><a href="http://www.cnblogs.com/zery/p/5164795.html" target="_blank" rel="noopener">HTTPS 原理解析</a></p>]]></content>
</entry>
<entry>
<title>《深入理解Java虚拟机》读书笔记 - 类加载机制</title>
<link href="/2017/11/05/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%20-%20%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6/"/>
<url>/2017/11/05/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%20-%20%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6/</url>
<content type="html"><![CDATA[<blockquote><p>此篇为《深入理解Java虚拟机》第七章7.2、7.3、7.4部分的读书笔记</p></blockquote><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。</p><h2 id="类加载时机"><a href="#类加载时机" class="headerlink" title="类加载时机"></a>类加载时机</h2><p><strong>类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Loading)、准备(Loading)、解析(Loading)、初始化(Loading)、使用(Loading)、卸载(Loading)7个阶段。</strong>其中验证、准备、解析3个部分统称为连接(Linking),这7个阶段的发生顺序如下所示。</p><div align="center"><br><img src="http://on83riher.bkt.clouddn.com/%E7%B1%BB%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.png" alt=""><br></div><p>注意两点:</p><ul><li>加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类加载过程必须按照这个顺序进行,而解析阶段因为需要支持 Java语言的运行时绑定,可以在初始化阶段之后开始。</li><li>类加载时,这7个阶段虽然必须是要按顺序开始,但是并不要求7个阶段按顺序结束,它们通常以交叉混合式进行的。</li></ul><h2 id="类加载过程"><a href="#类加载过程" class="headerlink" title="类加载过程"></a>类加载过程</h2><h3 id="加载"><a href="#加载" class="headerlink" title="加载"></a>加载</h3><p>“加载”是“类加载”(Class Loading)过程的一个阶段。在加载阶段,虚拟机需要完成以下3件事情:</p><ul><li><strong>通过一个类的全限定名来获取定义此类的二进制字节流。</strong></li><li><strong>将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。</strong></li><li><strong>在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。</strong></li></ul><p>注意这里不一定非要从 Class 文件中获取,在这个阶段中,既可以从以下几个不同地方获取:</p><ul><li>从ZIP 包中读取(JAR、EAR、WAR 格式的包也可以)。</li><li>从网络中获取。一般应用场景为RMI。</li><li>运行时计算生成。一般应用场景为动态代理。</li><li>由其他文件生成。一般应用场景为 JSP 应用。</li><li>从数据库中读取。</li><li>…</li></ul><h3 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h3><p><strong>验证是连接阶段的第一步,这一阶段的目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。</strong></p><p>验证阶段是非常重要的,这个阶段是否严谨,直接决定了 Java 虚拟机是否能承受恶意代码的攻击,从执行性能的角度上讲,验证阶段的工作量是在虚拟机的类加载子系统中又占了相当大的一部分。</p><p>在验证阶段中,大致会完成下面4个阶段的校验工作:</p><ul><li>文件格式验证</li><li>元数据验证</li><li>字节码验证</li><li>符号引用验证</li></ul><h3 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h3><p><strong>准备阶段是正式为类变量分配内存并设置类变量初始化值的阶段,这些变量所使用的内存都将在方法区中进行分配。</strong>这个阶段需要注意以下两点:</p><ul><li><strong>准备阶段进行内存分配的仅包括类变量(被 static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中。</strong></li><li><strong>这个阶段的初始化值,在通常情况下,是数据类型的所对应的零值</strong>,假设一个类变量的定义为:</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> value = <span class="number">123</span>;</span><br></pre></td></tr></table></figure><p>那变量 value 在准备阶段过后的初始值为0而不是123,真正的赋值操作将延迟到初始化阶段进行。但若上述的类变量 value 的定义变为:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> finla value = <span class="number">123</span>;</span><br></pre></td></tr></table></figure><p>那么,在编译时虚拟机将会为 value 生成 ConstantValue 属性,在准备阶段虚拟机就会根据 ConstantValue 的设置将 value 赋值为123。</p><h3 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h3><p>解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用通常以 CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info 等类型的常量出现。下面将解释符号引用和直接引用的关系:</p><ul><li><strong>符号引用(Symbolic References)</strong>:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标不一定已经加载到内存中。各个虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。</li><li><strong>直接引用(Direct References)</strong>:直接引用可以是直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经存在于内存中。</li></ul><h3 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h3><p><strong>类初始化阶段是类加载过程的最后一步,前面的类加载过程之中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其他动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的 Java 程序代码。</strong></p><p><strong>初始化阶段是执行类加载器<code><clinit>()</code>方法的过程。</strong></p><ul><li><code><clinit>()</code>方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值,但是不能访问。</li></ul><blockquote><p>为什么能够赋值,但不能访问呢?个人认为在运行期间的准备阶段时,类变量已经经过了零值的初始化了,所以赋值操作是正常进行的,但是在编译期间,编译器认为这种操作是错误的(非法向前引用)。</p></blockquote><ul><li><code><clinit>()</code>方式与类的构造函数(或者说是实例构造器<code><init>()</code>方法)不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的<code><clinit>()</code>方法执行之前,父类的<code><clinit>()</code>方法已经执行完毕。因此在虚拟机中第一个被执行的<code><clinit>()</code>方法的类肯定是 java.lang.Object。</li><li>由于父类的<code><clinit>()</code>方法优先执行,那么父类中的静态语句块也优先于子类执行。</li><li><code><clinit>()</code>方法对于类和接口并不是必需的,如果一个类中没有静态语句块,也没有对类变量的赋值操作,那么编译器可以不为这个类生成<code><clinit>()</code>方法。</li><li>接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成<code><clinit>()</code>方法。但接口和类不同的是,执行接口的<code><clinit>()</code>方法不需要先执行父接口的<code><clinit>()</code>方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化阶段也一样不会执行接口的<code><clinit>()</code>方法。</li><li>虚拟机会保证一个类的<code><clinit>()</code>方法在多线程环境中被正确地加锁,同步,如果多线程同时去初始化一个类,那么只有一个线程去执行这个类的<code><clinit>()</code>方法,其他线程都需要阻塞等待,直到活动线程执行<code><clinit>()</code>方法完毕。</li></ul><h2 id="类加载器"><a href="#类加载器" class="headerlink" title="类加载器"></a>类加载器</h2><p>虚拟机设计团队把类加载阶段中的加载动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为”类加载器“。</p><h3 id="类与类加载器"><a href="#类与类加载器" class="headerlink" title="类与类加载器"></a>类与类加载器</h3><p>对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在其 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立类名称空间。</p><h3 id="双亲委派模型"><a href="#双亲委派模型" class="headerlink" title="双亲委派模型"></a>双亲委派模型</h3><p>从 Java 虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用 C++语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由 Java 语言实现的,独立于虚拟机外部,并且全部继承自抽象类 java.lang.ClassLoader。</p><p>从 Java 开发人员的角度来看,绝大部分 Java 程序都会使用以下3种系统提供的类加载器:</p><ul><li><strong>启动类加载器(Bootstrap ClassLoader)</strong>:这个类加载器负责将存放在<code><JAVA_HOME>\lib</code>目录中的,或者被<code>-Xbootclasspath</code>参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使存放在 lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接饮用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,那直接使用 null 代替即可。</li><li><strong>扩展类加载器(Extension ClassLoader)</strong>:这个类加载器有<code>sun.misc.Launcher$ExtClassLoader</code>实现,它负责加载<code><JAVA_HOME>\lib\ext</code>,目录中的,或者被<code>java.ext.dirs</code>系统变量所指定的路径中的所有类库。开发者可以直接使用扩展类加载器。</li><li><strong>应用程序类加载器(Application ClassLoader)</strong>:这个类加载器由<code>sun.misc.Launcher$AppClassLoader</code>实现,它负责加载用户类路径(ClassPath)上所指定的类库。开发者可以直接使用应用程序类加载器。</li></ul><p>如下图所示,<strong>以上的类加载器之间的层次关系,称之为类加载器的双亲委派模型(Parents Delegation Model)。双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不会以继承的关系来实现,而都是通过组合关系来复用父类加载器的代码。</strong></p><div align="center"><br><img src="http://on83riher.bkt.clouddn.com/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%A8%A1%E5%9E%8B.png" alt=""><br></div><blockquote><p>类加载的双亲委派模型不是虚拟机中强制性的约束模型,而是 Java 设计者推荐给开发者的一种类加载实现方式。</p></blockquote><p><strong>双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(即他的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。</strong></p><p>采用双亲委派模型的好处在于,Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。<strong>无论是哪一个类加载器要加载某个类,最终都是委派给处于模型最顶端的的启动类加载器进行加载,这样保证了 Java 类在程序的各种类加载器环境中都是同一个类。</strong></p><p>下面为简单解释一下实现双亲委派模型的关键代码<code>loadClass()</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> Class<?> loadClass(String name, <span class="keyword">boolean</span> resolve)</span><br><span class="line"> <span class="keyword">throws</span> ClassNotFoundException {</span><br><span class="line"> <span class="keyword">synchronized</span> (getClassLoadingLock(name)) {</span><br><span class="line"> <span class="comment">// 首先,检查类是否已经被加载到内存中</span></span><br><span class="line"> Class<?> c = findLoadedClass(name);</span><br><span class="line"> <span class="keyword">if</span> (c == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">long</span> t0 = System.nanoTime();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 这一步中运用了双亲委派模型。</span></span><br><span class="line"> <span class="keyword">if</span> (parent != <span class="keyword">null</span>) {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 当该类加载器中存在父类加载器,那么就调用其父类加载器来响应加载类的请求</span></span><br><span class="line"> c = parent.loadClass(name, <span class="keyword">false</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 如果没有父类加载器,那么说明该类加载器为启动类加载器</span></span><br><span class="line"> c = findBootstrapClassOrNull(name);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (ClassNotFoundException e) {</span><br><span class="line"> <span class="comment">// ClassNotFoundException thrown if class not found</span></span><br><span class="line"> <span class="comment">// from the non-null parent class loader</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (c == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// If still not found, then invoke findClass in order</span></span><br><span class="line"> <span class="comment">// to find the class.</span></span><br><span class="line"> <span class="keyword">long</span> t1 = System.nanoTime();</span><br><span class="line"> c = findClass(name);</span><br><span class="line"><span class="comment">// ...其余代码</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (resolve) {</span><br><span class="line"> resolveClass(c);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> c;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="破坏双亲委派模型"><a href="#破坏双亲委派模型" class="headerlink" title="破坏双亲委派模型"></a>破坏双亲委派模型</h3><blockquote><p>具体操作实现在P231。</p></blockquote>]]></content>
</entry>
<entry>
<title>理解悲观锁与乐观锁</title>
<link href="/2017/11/05/%E7%90%86%E8%A7%A3%E6%82%B2%E8%A7%82%E9%94%81%E4%B8%8E%E4%B9%90%E8%A7%82%E9%94%81/"/>
<url>/2017/11/05/%E7%90%86%E8%A7%A3%E6%82%B2%E8%A7%82%E9%94%81%E4%B8%8E%E4%B9%90%E8%A7%82%E9%94%81/</url>
<content type="html"><![CDATA[<h2 id="悲观锁"><a href="#悲观锁" class="headerlink" title="悲观锁"></a>悲观锁</h2><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><p>维基百科这样解释:在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,PCC)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作读某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。</p><p>悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。</p><p>从上述的解释可以知道,悲观锁是从一个悲观的角度看待用户操作,在用户想要操作数据时,悲观锁会“悲观”地认为其他用户也想要同时对同一数据进行操作,从而要求用户先获取锁,再进行操作,保证了用户操作的安全性。</p><h3 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h3><p>优点:</p><ul><li>通过“先取锁后访问”的保守策略,为数据处理的安全提供了保证。</li></ul><p>缺点:</p><ul><li>对数据加锁会让数据库系统产生额外的开销,还增加了死锁的机会。</li><li>在只读型事务处理中由于不会产生冲突,使用悲观锁,只会增加系统负载,降低并行性。</li></ul><h3 id="应用"><a href="#应用" class="headerlink" title="应用"></a>应用</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">select status from goods where id=1 for update; # for update 用于开启排它锁</span><br><span class="line">update goods set status=2;</span><br></pre></td></tr></table></figure><h2 id="乐观锁"><a href="#乐观锁" class="headerlink" title="乐观锁"></a>乐观锁</h2><h3 id="概念-1"><a href="#概念-1" class="headerlink" title="概念"></a>概念</h3><p>维基百科这样解释:在关系数据库管理系统中,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,OCC)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自的那部分数据。在提交数据更新之前,每个事务都会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务更新的话,正在提交的事务会进行回滚。</p><p>乐观并发控制多用于数据争用不大、冲突较少的环境下,这种环境中,偶尔回滚事务的成本会低于读取数据时锁定数据的成本,因为可以获得比其他并发控制方法更高。</p><p>从上述的解释可以知道,相较于悲观锁,乐观锁决定从一个乐观的角度看待用户操作。在用户想要操作数据时,乐观锁会“乐观”地认为其他用户不会同时进行对同一数据进行操作的,而当用户对数据进行再次提交时,乐观锁才会对数据是否被修改进行检测,如果被修改过只能放弃当前操作。</p><h3 id="优缺点-1"><a href="#优缺点-1" class="headerlink" title="优缺点"></a>优缺点</h3><p>优点:</p><ul><li>乐观并发控制相信事务之间的数据竞争的概率比较小,所以在事务处理过程中不会出现任何锁,或产生死锁现象。</li></ul><p>缺点:</p><ul><li>在系统并发量大的情况下,事务发生冲突的概率会大大增加,系统可用性会降低,用户体验也会随着操作不断失败而降低。</li></ul><h3 id="应用-1"><a href="#应用-1" class="headerlink" title="应用"></a>应用</h3><p>在乐观锁中,一般会采用以下两种机制来记录数据的唯一性:</p><ul><li>数据版本(Version)。数据版本,即为数据增加一个版本标识,一般是通过为数据库增加一个数字类型的“version”字段来实现的。当读取数据时,将 version 字段的值一同读出,数据每更新一次,对此 version 的值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的 version 值进行比对,如果数据库当前版本号与第一次取出来的 version 值相等,则予以更新,否则认为是过期数据。</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">select status, version from goods where id=1;</span><br><span class="line">update goods set status=2, version=version+1 where id=1 and version=version;</span><br></pre></td></tr></table></figure><ul><li>时间戳(timestamp)。使用方式与数据版本相同,只是字段类型为时间戳而已。</li></ul><blockquote><p>顺带一提,Java并发中的 CAS 机制也是乐观锁机制。</p></blockquote><hr><p>参考资料</p><p><a href="https://zh.wikipedia.org/wiki/%E6%82%B2%E8%A7%82%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6" target="_blank" rel="noopener">悲观并发控制</a></p><p><a href="https://zh.wikipedia.org/wiki/%E4%B9%90%E8%A7%82%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6" target="_blank" rel="noopener">乐观并发控制</a></p>]]></content>
</entry>
<entry>
<title>《深入理解Java虚拟机》读书笔记 - Java内存模型与线程</title>
<link href="/2017/11/04/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%20-%20Java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B/"/>
<url>/2017/11/04/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%20-%20Java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B/</url>
<content type="html"><![CDATA[<blockquote><p>此篇为《深入理解Java虚拟机》第十二章12.3、12.4部分的读书笔记</p></blockquote><h2 id="Java-内存模型"><a href="#Java-内存模型" class="headerlink" title="Java 内存模型"></a>Java 内存模型</h2><p><strong>Java 虚拟机规范中试图定义一种 Java 内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台上都能达到一致的内存访问效果。</strong></p><p>定义 Java 内存模型并非一件容易的事情:这个模型必须定义得足够严谨,才能让 Java 的并发内存访问操作不会产生歧义;但是,也必须定义得足够宽松,使得虚拟机的实现有足够的自由空间去利用硬件的各种特性(寄存器,高速缓冲和指令集中某些特有的指令)来获得更好的执行速度。</p><h3 id="主内存与工作内存"><a href="#主内存与工作内存" class="headerlink" title="主内存与工作内存"></a>主内存与工作内存</h3><p><strong>Java 内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。</strong>此处的变量(Variables)与 Java 编程所说的变量有所区别,它包括了实例字段、静态字段和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不会被共享,自然就不会存在竞争问题。为了获得较好的执行效率,Java 内存模型并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制即时编译器进行调整代码执行顺序这类优化措施。</p><p><strong>Java 内存模型规定了所有的变量都存储在主内存中(Main Memory)中。每条线程还有自己的工作内存(Working Memory),线程的工作内存中保存了被线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。</strong>线程、主内存、工作内存三者关系图如下所示。</p><p><div align="center"><br><img src="http://on83riher.bkt.clouddn.com/%E7%BA%BF%E7%A8%8B%E3%80%81%E4%B8%BB%E5%86%85%E5%AD%98%E3%80%81%E5%B7%A5%E4%BD%9C%E5%86%85%E5%AD%98%E4%B8%89%E8%80%85%E5%85%B3%E7%B3%BB%E5%9B%BE.png" alt=""><br></div><br>这部分中与前面笔记中所记录的 Java 内存区域并不是同一层次的内存划分,两者基本上没有什么关系。</p><h3 id="内存间互相操作"><a href="#内存间互相操作" class="headerlink" title="内存间互相操作"></a>内存间互相操作</h3><p>这一部分中, Java 内存模型定义了8种操作来完成主内存与工作内存之间的具体变量交互工作:lock(锁定)、unlock(解锁)、 read(读取)、load(载入)、 use(使用)、assign( 赋值)、 store(存储)、 write( 写入),虚拟机实现时必须保证以上所有操作都是原子的、不可再分的。</p><blockquote><p>具体操作实现在P364。</p></blockquote><h3 id="对于-volatile-型变量的特殊规则"><a href="#对于-volatile-型变量的特殊规则" class="headerlink" title="对于 volatile 型变量的特殊规则"></a>对于 volatile 型变量的特殊规则</h3><p>关键字 volatile 是 Java 虚拟机所提供的轻量级同步机制。当一个变量定义为 volatile 之后,它将具备两种特性:</p><ul><li><p><strong>保证此变量对所有线程的可见性。</strong>这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。而普通变量不能做到这一点,普通变量的值在线程间传递需要通过主内存来完成。例如:线程 A 修改一个普通变量的值,然后向主内存进行回写,另外一条线程 B 在线程 A 回写完成了之后再从主内存进行读取操作,新变量值才会对线程 B 可见。</p><blockquote><p>由于 volatile 变量只能保证可见性,而无法保证操作的原子性。在不符合以下两条规则的运算场景下,我们仍然需要加锁机制来保证操作原子性:</p><ul><li>运算结果并不依赖变量的当前值,或者能够确保只有单一的线程来修改变量的值</li><li>变量不需要与其他的状态变量共同参与不变约束。</li></ul></blockquote></li><li><p><strong>禁止指令重排序优化。</strong>普通的变量仅仅会保证在该方法的执行中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作顺序与代码执行顺序一致。</p></li></ul><p>在于 volatile 的性能提升方面,可得出:volatile 变量读操作的性能消耗与普通变量几乎没有什么差别,但是写操作则可能会慢一些,因为它需要在本地代码中插入许多内存屏蔽指令保证处理器不发生乱序执行。不过即使如此,大多数场景下 volatile 的总开销仍然要比锁低,我们在 volatile 与锁之间的选择依据只取决于 volatile 是否能够满足当前场景所需的并发要求。</p><h3 id="先行发生原则"><a href="#先行发生原则" class="headerlink" title="先行发生原则"></a>先行发生原则</h3><p>如果 Java 内存模型中所有的有序性都仅仅依靠 volatile 和 synchronized 来完成,那么有一些操作将会变得很烦琐,但是在实际编程当中,Java 语言内部中存在一种“先行发生”(happens-before)的原则来保证代码在正常情况下的并发处理。</p><p>先行发生是 Java 内存模型中定义的两项操作之间的偏序关系,如果说操作 A 先行于操作 B,其实就是说在发生操作 B 之前,操作 A 产生的影响能被操作 B 观察到,“影响”包括了修改了内存中共享变量的值、发送了消息、调用了方法等。</p><p>下面是 Java 内存模型中一些“天然的”先行发生关系,这些先行发生关系无须任何同步器协助就已经存在,可以在编码中直接使用。如果两个操作之间的关系不在此列,并且无法从下列规则中推导出来,它们就没有顺序性保障,虚拟机可以对它们随意地进行重排序。</p><ul><li><strong>程序次序规则(Program Order Rule)</strong>:在一个线程内,按照程序代码顺序,书写在前面的操作先行于书写在后面的操作。准确地说,应该是控制流顺序而不是程序代码,因为需要考虑分支、循环等结构。</li><li><strong>管程锁定规则(Monitor Lock Rule)</strong>: 一个 unlock 操作先行发生于后面(特指时间顺序)对同一个锁的 lock 操作。</li><li><strong>volatile 变量规则(Volatile Variable Rule)</strong>: 对一个 volatile 变量的写操作先行发生于后面(特指时间顺序)对这个变量的读操作。</li><li><strong>线程启动规则(Thread Start Rule)</strong>:Thread 对象的 start() 方法先行发生于此线程的每一个动作。</li><li><strong>线程终止规则(Thread Termination Rule)</strong>: 线程中的所有操作都先行发生于对此线程的终止检测。</li><li><strong>线程中断规则(Thread Interruption Rule)</strong>: 对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生。</li><li><strong>对象终结规则(Finalizer Rule)</strong>: 一个对象的初始化完成(构造函数的结束)先行发生于它的 finalize() 方法的开始。</li><li><strong>传递性( Transitivity)</strong>: 如果操作 A 先行发生于操作 B,而操作 B 先行发生于操作 C,那就可以推导出操作 A 先行发生于操作 C 的结论。</li></ul><h2 id="Java-与线程"><a href="#Java-与线程" class="headerlink" title="Java 与线程"></a>Java 与线程</h2><h3 id="线程创建-amp-线程调度"><a href="#线程创建-amp-线程调度" class="headerlink" title="线程创建&线程调度"></a>线程创建&线程调度</h3><blockquote><p>P379,与操作系统创建线程相同,因为 Java 线程创建与线程调度的操作就是基于操作系统的。</p></blockquote><h3 id="状态转换"><a href="#状态转换" class="headerlink" title="状态转换"></a>状态转换</h3><ul><li><p><strong>新建(New)</strong>:创建后尚未未启动的线程。</p></li><li><p><strong>运行(Runable)</strong>:线程有可能正在运行,或也有可能正在等待CPU为它分配执行时间。</p></li><li><p><strong>无限期等待(Waiting)</strong>:不会被分配CPU执行时间,要等待被其他线程显式唤醒,以下方法会让线程处于无限期的等待状态:</p><ul><li>没有设置 Timeout 参数的 Object.wait() 方法。</li><li>没有设置 Timeout 参数的 Thread.join() 方法。</li><li>LockSupport.park() 方法。</li></ul></li><li><p><strong>限期等待(Timed Waiting)</strong>:不会被分配CPU执行时间,不需要等待被其他线程显式唤醒,在一定时间之后它们会由系统自动唤醒,以下方法会让线程处于限期的等待状态:</p><ul><li>Thread.sleep() 方法。</li><li>设置了 Timeout 参数的Object.wait() 方法。</li><li>设置了 Timeout 参数的Thread.join() 方法。</li><li>LockSupport.parkNanos() 方法。</li><li>LockSupport.parkUntil() 方法。</li></ul></li><li><p><strong>阻塞(Blocked)</strong>:线程被阻塞了,在等待获取一个排它锁。例如线程A和B在执行同步方法C时,线程A先拿到排它锁,那么线程B的状态就是阻塞状态,等待线程B释放排它锁。</p></li><li><p><strong>结束(Terminated)</strong>:线程执行完毕。<br><div align="center"><br><img src="http://on83riher.bkt.clouddn.com/%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E5%85%B3%E7%B3%BB.png" alt=""><br></div><br></p></li></ul>]]></content>
</entry>
<entry>
<title>《深入理解Java虚拟机》读书笔记 - 垃圾回收机制</title>
<link href="/2017/11/03/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%20-%20%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/"/>
<url>/2017/11/03/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%20-%20%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/</url>
<content type="html"><![CDATA[<blockquote><p>此篇为《深入理解Java虚拟机》第三章3.1、3.2、3.3部分的读书笔记</p></blockquote><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p><strong>在垃圾收集(Garbage Collection,GC)中,我们需要考虑以下三个问题:</strong></p><ul><li><strong>哪些内存需要回收?</strong></li><li><strong>什么时候回收?</strong></li><li><strong>如何回收?</strong></li></ul><p>经过半个多世纪的发展,目前内存的动态分配与内存回收技术已经相当成熟,一切看起来都进入“自动化”时代,那为什么我们还要去了解 GC 和内存分配呢?答案很简单:当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。</p><h2 id="对象已死吗"><a href="#对象已死吗" class="headerlink" title="对象已死吗"></a>对象已死吗</h2><p>在堆里面存放着 Java 世界中几乎所有的对象实例,垃圾收集器在对堆进行垃圾回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(即不可能再被任何途径使用的对象)。</p><h3 id="引用计数算法"><a href="#引用计数算法" class="headerlink" title="引用计数算法"></a>引用计数算法</h3><p><strong>引用计数算法是指:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。</strong></p><p>客观地说,引用计数算法(Reference Counting)的实现简单,判定效率也很高,在大部分情况下它都是一个不错的算法,但是,至少在主流的 Java 虚拟机里面没有选用引用计数拳算法来管理内存,其中主要的原因是<strong>它很难解决对象之间互相循环引用的问题</strong>。</p><p>举个例子,若 Java 堆上同时存在 objA 和 objB 两个对象,两个对象中都有字段 instance,赋值令 objA.instance = objB 以及 objB.instance = objA,除此之外,这两个对象再无任何引用,实际上这两个对象已经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数不为0,于是引用计数算法无法通知 GC 收集器来回收它们。</p><h3 id="可达性分析算法"><a href="#可达性分析算法" class="headerlink" title="可达性分析算法"></a>可达性分析算法</h3><p><strong>可达性分析算法是指:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连(用图论的话来说,就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的。</strong>如下图所示,对象 object5、object6、object7虽然互相有关联,但是它们到 GC Roots 是不可达的,所以它们将会被判定为是可回收对象。</p><p><div align="center"><br><img src="http://on83riher.bkt.clouddn.com/%E5%8F%AF%E8%BE%BE%E6%80%A7%E5%88%86%E6%9E%90%E7%AE%97%E6%B3%95.png" alt=""><br></div><br>在 Java 语言中,可作为 GC Roots 的对象包括下面几种:</p><ul><li>虚拟机栈(栈帧中的本地变量表)中引用的对象</li><li>方法区中类静态属性引用的对象</li><li>方法区中常量引用的对象</li><li>本地方法栈中 JNI(即一般说的 Native 方法)引用的对象</li></ul><h3 id="再谈引用"><a href="#再谈引用" class="headerlink" title="再谈引用"></a>再谈引用</h3><p>无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是都存活都与“引用”有关。<strong>对于对象“引用”的准确定义,希望通过这种方式来描述:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集之后还是非常紧张,则可以抛弃这些对象。</strong>很多系统的缓存功能都符合这样的应用场景。</p><p>在 JDK1.2之后,Java 对引用的概念进行了补充,将引用以下四类,并且这四种引用强度自上到下依次减弱。</p><ul><li><strong>强引用(Strong Reference)。</strong>强引用就是指在程序之中普遍存在的,类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。</li><li><strong>软引用(Soft Reference)。</strong>软引用是用来描述一些还有用但并非必需的对象。对于软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出的异常。</li><li><strong>弱引用(Weak Reference)。</strong>弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。</li><li><strong>虚引用(Phantom Reference)。</strong>虚引用也称为幽灵引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。</li></ul><h3 id="生存还是死亡"><a href="#生存还是死亡" class="headerlink" title="生存还是死亡"></a>生存还是死亡</h3><p>即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,<strong>要真正宣告一个对象死亡,至少要经历两次标记过程</strong>:如果对象在进行可达性分析之后,发现没有与 GC Roots 相连接的引用链,那么它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两次情况都视为“没有必要执行”。</p><p>如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象将会放置在一个叫做 F-Queue 的队列之中,并在稍后由一个由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。这个所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束,这样做的原因是,如果一个对象在 finalize() 方法中执行缓慢,或者发生了死循环,将很可能会导致 F-Queue 队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。</p><p> finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 会对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this 关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的就被回收了。</p><blockquote><p>任何一个对象的 finalize() 方法都只会被系统自动调用一次,如果面临第二次回收,那么它的 finalize()方法将不会被调用。</p><p>对于 finalize() 方法,应该尽可能避免使用,因为这个 Java 在较早之前对于 C/C++程序员的妥协,最主要的原因是它的运行代价高昂,不确定性大,无法保证各个对象的调用顺序。所以了解它的概念,在实际使用上忽略它的存在即可。</p></blockquote><h2 id="垃圾收集算法"><a href="#垃圾收集算法" class="headerlink" title="垃圾收集算法"></a>垃圾收集算法</h2><h3 id="标记-清除算法"><a href="#标记-清除算法" class="headerlink" title="标记-清除算法"></a>标记-清除算法</h3><p><strong>最基础的收集算法是“标记-清除”(Mark-Sweep)算法,如同它的名字一样,算法分为“标记”和“清除“两个阶段:首先标记处所有需要回收的对象,在标记完成后,统一回收所有被标记的对象(标记过程参考上一小节)。</strong>之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其不足进行改进而得到的。</p><p>它的主要不足有两个:</p><ul><li>一个是效率问题,标记和清除两个过程的效率并不高</li><li>一个是空间问题,标记清楚之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大的对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。<br><div align="center"><br><img src="http://on83riher.bkt.clouddn.com/%E6%A0%87%E8%AE%B0%E6%B8%85%E9%99%A4%E7%AE%97%E6%B3%95.png" alt=""><br></div><h3 id="复制算法"><a href="#复制算法" class="headerlink" title="复制算法"></a>复制算法</h3></li></ul><p><strong>为了解决效率问题,一种称为”复制“(Copying)的收集算法出现了,他将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配即可,实现简单,运行高效。</strong>只是这种算法的代价是将内存缩小为原来的一半,分配可能过高了。</p><p><div align="center"><br><img src="http://on83riher.bkt.clouddn.com/%E5%A4%8D%E5%88%B6%E7%AE%97%E6%B3%95.png" alt=""><br></div><br>按照 IBM 的研究表明,新生代中的对象98%都是”朝生夕死“的。所以内存分配的常见做法是:将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。当回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另外一块 Survivor 空间上,最后清理掉 Eden 和刚才用过的 Survivor 空间。</p><p> 在 HotSpot 虚拟机中,默认 Eden 和 Survivor 的大小比例为8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存会被”浪费“。在 GC 回收过程中,若未使用的那块 Survivor 空间不够时,需要依赖其他内存(老年代)进行分配担保(Handle Promotion)(具体规则取决于垃圾收集器)。</p><h3 id="标记-整理算法"><a href="#标记-整理算法" class="headerlink" title="标记-整理算法"></a>标记-整理算法</h3><p>复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。</p><p><strong>根据老年代的特点,顺应有了”标记-整理“算法,其过程与”标记-清除“算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。</strong></p><p><div align="center"><br><img src="http://on83riher.bkt.clouddn.com/%E6%A0%87%E8%AE%B0%E6%95%B4%E7%90%86%E7%AE%97%E6%B3%95.png" alt=""><br></div></p><h3 id="分代收集算法"><a href="#分代收集算法" class="headerlink" title="分代收集算法"></a>分代收集算法</h3><p>当前商业虚拟机的垃圾收集都采用”分代收集“(Generational Collection)算法,<strong>这种算法是根据对象的存活周期的不同将内存划分为几块,一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。</strong></p><ul><li>在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本</li><li>在老年代中,因为对象存活率高,没有额外的空间对它进行分配担保,就必须使用”标记-清理“或”标记-整理“算法来进行回收</li></ul><h2 id="GC日志中的术语"><a href="#GC日志中的术语" class="headerlink" title="GC日志中的术语"></a>GC日志中的术语</h2><ul><li><strong>Minor GC</strong> 指发生在新生代的垃圾收集动作,非常频繁,速度较快。</li><li><strong>Major GC</strong>(通常与 Full GC 是等价de )指发生发生在整个 GC 堆中的垃圾收集动作,频次较少,一般由多次 Minor GC 后,内存空间仍不满足程序运行时调用。</li></ul>]]></content>
</entry>
<entry>
<title>《深入理解Java虚拟机》读书笔记 - Java对象</title>
<link href="/2017/11/02/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%20-%20Java%E5%AF%B9%E8%B1%A1/"/>
<url>/2017/11/02/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%20-%20Java%E5%AF%B9%E8%B1%A1/</url>
<content type="html"><![CDATA[<blockquote><p>此篇为《深入理解Java虚拟机》第二章2.3部分的读书笔记</p></blockquote><h2 id="对象的创建"><a href="#对象的创建" class="headerlink" title="对象的创建"></a>对象的创建</h2><p>Java 是一门面向对象的编程语言,在 Java 程序运行过程中无时无刻都有对象被创建出来。在语言层面上,创建对象通常仅仅是一个 new 关键字而已,而在虚拟机中,对象(仅限于普通 Java 对象,不包括数组和 Class 对象等)的创建又是怎样一个过程呢?</p><ol><li>虚拟机遇到一条 new 指令时,<strong>首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用</strong>,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。</li><li><strong>在类加载检查通过后,接下来虚拟机将为新生对象分配内存。</strong>对象所需内存的大小在类加载完成后便可以完全确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。具体堆的内存如何划分以及怎么分配堆中的内存者这取决于虚拟机所采用的垃圾收集器是否带有压缩整理功能决定。</li><li>除如何划分可用空间之外,还有另外一个<strong>需要考虑的问题是对象创建在虚拟机中是非常频繁的行为</strong>,即使是仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针分配内存的情况。解决这个问题有两种方案:<ol><li>一种是对分配内存空间的动作进行同步处理——实际上虚拟机采用 CAS + 失败重试的方式来保证更新操作的原子性;</li><li>另一种把内存分配动作按照线程划分在不同的空间中进行,即每个线程在 Java 堆中预先分配一小块内存,称之为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。哪个线程需要分配内存,就在哪个线程的 TLAB 上分配,只有 TLAB 用完并分配新的 TLAB 时,才需要同步锁定。</li></ol></li><li><strong>内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)</strong>,如果使用了 TLAB,这一工作过程也可以提前至 TLAB 分配时进行。这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。</li><li><strong>接下来,虚拟机要对对象进行必要的设置</strong>,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。这些信息都存放在对象的对象头之中。根据虚拟机当前的运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。</li></ol><p>在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚刚开始——<code><init></code>方法还没有执行,所有的字段都还为零。<strong>所以,一般来说,执行 new 指令之后会接着执行<code><init></code>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。</strong></p><h2 id="对象的内存布局"><a href="#对象的内存布局" class="headerlink" title="对象的内存布局"></a>对象的内存布局</h2><p>在 HotSpot 虚拟机中,对象在内存中存储的布局可以分为3个区域:对象头(Header)、实例数据(Instance Data)和对其补充(Padding)。</p><ul><li><p><strong>对象头。</strong>对象头可以分为以下两部分信息:</p><ul><li>用于存储对象自身运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。</li><li>类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。另外,如果对象是一个 Java 数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通 Java 对象的元数据信息来确定 Java 对象的大小,但是从数据的元数据中却无法确定数组的大小。</li></ul><div align="center"><br><img src="http://on83riher.bkt.clouddn.com/%E5%AF%B9%E8%B1%A1%E5%A4%B4.png" alt=""><br></div></li><li><p><strong>实例数据</strong></p><p>实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录下来。</p></li><li><p><strong>对齐补充</strong></p><p>对齐补充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于 HotSpot VM 的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。</p></li></ul><h2 id="对象的访问定位"><a href="#对象的访问定位" class="headerlink" title="对象的访问定位"></a>对象的访问定位</h2><p>建立对象是为了使用对象,我们的 Java 程序需要通过栈上的 renference 数据来操作堆上的具体对象。由于 reference 类型在 Java 虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问的方式也是取决于虚拟机实现而决定的。目前主流的访问方式有使用句柄和直接指针两种。</p><ul><li><strong>如果使用句柄访问的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。</strong>其最大好处在于 reference 中存储的是稳定的句柄地址,在对象移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 refernece 本身不需要修改。<div align="center"><br><img src="http://on83riher.bkt.clouddn.com/%E5%8F%A5%E6%9F%84%E8%AE%BF%E9%97%AE.png" alt=""><br></div></li><li><strong>如果使用直接指针访问(HotSpot 采取方式),那么 Java 堆对象的布局就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象地址。</strong>其最大好处在于速度相较于句柄访问更快,它节省了一次指针定位的时间开销,由于对象的访问在 Java 中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。<div align="center"><br><img src="http://on83riher.bkt.clouddn.com/%E7%9B%B4%E6%8E%A5%E8%AE%BF%E9%97%AE.png" alt=""><br></div></li></ul>]]></content>
</entry>
<entry>
<title>PriorityQueue 源码分析</title>
<link href="/2017/11/01/PriorityQueue-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
<url>/2017/11/01/PriorityQueue-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h2 id="结构体系"><a href="#结构体系" class="headerlink" title="结构体系"></a>结构体系</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PriorityQueue</span><<span class="title">E</span>> <span class="keyword">extends</span> <span class="title">AbstractQueue</span><<span class="title">E</span>></span></span><br><span class="line"><span class="class"> <span class="keyword">implements</span> <span class="title">java</span>.<span class="title">io</span>.<span class="title">Serializable</span> </span>{</span><br></pre></td></tr></table></figure><p><code>PriorityQueue</code>是通过最小堆(?)实现内部元素按一定顺序的队列,也称其为优先队列。从结构体系上看,<code>PriorityQueue</code>是继承自<code>AbstractQueue</code>的,即<code>PriorityQueue</code>实现了基本的队列的操作。但为何<code>PriorityQueue</code>能够实现元素按指定排序存在队列呢,那么我们应该看它的成员变量部分。</p><blockquote><p>若忘了最大/小堆的概念,可以查看这篇文章<a href="/2017/10/31/堆排序(Heap%20Sort)/">堆排序(Heap Sort)</a></p></blockquote><h2 id="常量与重要的成员变量"><a href="#常量与重要的成员变量" class="headerlink" title="常量与重要的成员变量"></a>常量与重要的成员变量</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="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">int</span> DEFAULT_INITIAL_CAPACITY = <span class="number">11</span>;</span><br><span class="line"></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">int</span> MAX_ARRAY_SIZE = Integer.MAX_VALUE - <span class="number">8</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// PriorityQueue 真实操作容器(知道最大/小堆性质的,应该不难明白)</span></span><br><span class="line"><span class="keyword">transient</span> Object[] queue;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 记录容器内部实际元素个数</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">int</span> size = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The comparator, or null if priority queue uses elements'</span></span><br><span class="line"><span class="comment"> * natural ordering.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="comment">// 上述注释表明comparator若为空时,即使用自然递增的顺序存储元素</span></span><br><span class="line"><span class="comment">// 那么既然给出了这个comparator,就说明了该comparator可以由用户给定</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Comparator<? <span class="keyword">super</span> E> comparator;</span><br></pre></td></tr></table></figure><h2 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">PriorityQueue</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>(DEFAULT_INITIAL_CAPACITY, <span class="keyword">null</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">PriorityQueue</span><span class="params">(<span class="keyword">int</span> initialCapacity)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>(initialCapacity, <span class="keyword">null</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">PriorityQueue</span><span class="params">(Comparator<? <span class="keyword">super</span> E> comparator)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>(DEFAULT_INITIAL_CAPACITY, comparator);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 关键构造函数,这一步证实了可以由用户传递自定义的comparator来实现自定义顺序容器</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">PriorityQueue</span><span class="params">(<span class="keyword">int</span> initialCapacity,</span></span></span><br><span class="line"><span class="function"><span class="params"> Comparator<? <span class="keyword">super</span> E> comparator)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (initialCapacity < <span class="number">1</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException();</span><br><span class="line"> <span class="keyword">this</span>.queue = <span class="keyword">new</span> Object[initialCapacity];</span><br><span class="line"> <span class="keyword">this</span>.comparator = comparator;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="增加操作-——-add"><a href="#增加操作-——-add" class="headerlink" title="增加操作 —— add()"></a>增加操作 —— add()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">add</span><span class="params">(E e)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> offer(e);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">offer</span><span class="params">(E e)</span> </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">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line"> modCount++;</span><br><span class="line"> <span class="keyword">int</span> i = size;</span><br><span class="line"> <span class="comment">// 保证容器能够存储所有元素</span></span><br><span class="line"> <span class="keyword">if</span> (i >= queue.length)</span><br><span class="line"> grow(i + <span class="number">1</span>);</span><br><span class="line"> size = i + <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span> (i == <span class="number">0</span>)</span><br><span class="line"> queue[<span class="number">0</span>] = e;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="comment">// 实际操作部分</span></span><br><span class="line"> siftUp(i, e);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>关键部分如下所示:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 这一步就是PriorityQueue的关键部分</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">siftUp</span><span class="params">(<span class="keyword">int</span> k, E x)</span> </span>{</span><br><span class="line"> <span class="comment">// 若comparator不为空,则使用用户给定的comparator,否则则使用元素本身提供的比较器</span></span><br><span class="line"> <span class="keyword">if</span> (comparator != <span class="keyword">null</span>)</span><br><span class="line"> siftUpUsingComparator(k, x);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> siftUpComparable(k, x);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line"><span class="comment">// comparator为空时调用</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">siftUpComparable</span><span class="params">(<span class="keyword">int</span> k, E x)</span> </span>{</span><br><span class="line"> <span class="comment">// 获取元素本身,并转化为可Comparable类型</span></span><br><span class="line"> Comparable<? <span class="keyword">super</span> E> key = (Comparable<? <span class="keyword">super</span> E>) x;</span><br><span class="line"> <span class="comment">// 若k>0,即k未处于根元素位置</span></span><br><span class="line"> <span class="keyword">while</span> (k > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">int</span> parent = (k - <span class="number">1</span>) >>> <span class="number">1</span>;</span><br><span class="line"> Object e = queue[parent];</span><br><span class="line"> <span class="keyword">if</span> (key.compareTo((E) e) >= <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> queue[k] = e;</span><br><span class="line"> k = parent;</span><br><span class="line"> }</span><br><span class="line"> queue[k] = key;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line"><span class="comment">// comparator不为空时调用</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">siftUpUsingComparator</span><span class="params">(<span class="keyword">int</span> k, E x)</span> </span>{</span><br><span class="line"> <span class="keyword">while</span> (k > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">int</span> parent = (k - <span class="number">1</span>) >>> <span class="number">1</span>;</span><br><span class="line"> Object e = queue[parent];</span><br><span class="line"> <span class="comment">// 使用用户给定的comparator</span></span><br><span class="line"> <span class="keyword">if</span> (comparator.compare(x, (E) e) >= <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> queue[k] = e;</span><br><span class="line"> k = parent;</span><br><span class="line"> }</span><br><span class="line"> queue[k] = x;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="删除操作-poll"><a href="#删除操作-poll" class="headerlink" title="删除操作 - poll()"></a>删除操作 - poll()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">poll</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (size == <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">int</span> s = --size;</span><br><span class="line"> modCount++;</span><br><span class="line"> E result = (E) queue[<span class="number">0</span>];</span><br><span class="line"> E x = (E) queue[s];</span><br><span class="line"> queue[s] = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (s != <span class="number">0</span>)</span><br><span class="line"> siftDown(<span class="number">0</span>, x);</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>与add()同理。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">siftDown</span><span class="params">(<span class="keyword">int</span> k, E x)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (comparator != <span class="keyword">null</span>)</span><br><span class="line"> siftDownUsingComparator(k, x);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> siftDownComparable(k, x);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">siftDownComparable</span><span class="params">(<span class="keyword">int</span> k, E x)</span> </span>{</span><br><span class="line"> Comparable<? <span class="keyword">super</span> E> key = (Comparable<? <span class="keyword">super</span> E>)x;</span><br><span class="line"> <span class="keyword">int</span> half = size >>> <span class="number">1</span>; <span class="comment">// loop while a non-leaf</span></span><br><span class="line"> <span class="keyword">while</span> (k < half) {</span><br><span class="line"> <span class="keyword">int</span> child = (k << <span class="number">1</span>) + <span class="number">1</span>; <span class="comment">// assume left child is least</span></span><br><span class="line"> Object c = queue[child];</span><br><span class="line"> <span class="keyword">int</span> right = child + <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span> (right < size &&</span><br><span class="line"> ((Comparable<? <span class="keyword">super</span> E>) c).compareTo((E) queue[right]) > <span class="number">0</span>)</span><br><span class="line"> c = queue[child = right];</span><br><span class="line"> <span class="keyword">if</span> (key.compareTo((E) c) <= <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> queue[k] = c;</span><br><span class="line"> k = child;</span><br><span class="line"> }</span><br><span class="line"> queue[k] = key;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">siftDownUsingComparator</span><span class="params">(<span class="keyword">int</span> k, E x)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> half = size >>> <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">while</span> (k < half) {</span><br><span class="line"> <span class="keyword">int</span> child = (k << <span class="number">1</span>) + <span class="number">1</span>;</span><br><span class="line"> Object c = queue[child];</span><br><span class="line"> <span class="keyword">int</span> right = child + <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span> (right < size &&</span><br><span class="line"> comparator.compare((E) c, (E) queue[right]) > <span class="number">0</span>)</span><br><span class="line"> c = queue[child = right];</span><br><span class="line"> <span class="keyword">if</span> (comparator.compare(x, (E) c) <= <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> queue[k] = c;</span><br><span class="line"> k = child;</span><br><span class="line"> }</span><br><span class="line"> queue[k] = x;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="查找操作-peek"><a href="#查找操作-peek" class="headerlink" title="查找操作 - peek()"></a>查找操作 - peek()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 时间复杂度O(1)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">peek</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> (size == <span class="number">0</span>) ? <span class="keyword">null</span> : (E) queue[<span class="number">0</span>];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
</entry>
<entry>
<title>《深入理解Java虚拟机》读书笔记 - Java内存分配</title>
<link href="/2017/11/01/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%20-%20Java%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D/"/>
<url>/2017/11/01/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%20-%20Java%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D/</url>
<content type="html"><![CDATA[<blockquote><p>此篇为《深入理解Java虚拟机》第二章2.2部分的读书笔记</p></blockquote><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>对于 Java 程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个 new 操作去写配对的 delete/free 代码,不容易出现内存泄漏和内存溢出问题,由于虚拟机管理内存这一切看起来很美好,不过,也正是因为 Java 程序员将内存控制的权利交给了 Java 虚拟机,一旦出现内存泄漏和溢出问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将是一件异常艰难的工作。</p><h2 id="运行时数据区域"><a href="#运行时数据区域" class="headerlink" title="运行时数据区域"></a>运行时数据区域</h2><p>Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间。有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。根据《Java 虚拟机规范》的规定,Java 虚拟机所管理的内存将会包括以下几个运行时数据区域,如下图所示。</p><p><div align="center"><br><img src="http://on83riher.bkt.clouddn.com/Java%20%E5%86%85%E5%AD%98%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BA.png" alt=""><br></div></p><h2 id="程序计数器"><a href="#程序计数器" class="headerlink" title="程序计数器"></a>程序计数器</h2><p><strong>程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程执行的字节码的行号指示器。</strong>在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令(类似于操作系统一般),分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。</p><p>由于 Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。<strong>因此,为了线程切换后能够恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。</strong></p><p>如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器值则为空(Undefined)(因为 Native 所修饰的方法是虚拟机根据当前系统,调用本地应用/库实现的,不归属于任何字节码指令)。<strong>此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。</strong></p><h2 id="Java-虚拟机栈"><a href="#Java-虚拟机栈" class="headerlink" title="Java 虚拟机栈"></a>Java 虚拟机栈</h2><p><strong>与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stack)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行的同时会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。</strong></p><p>局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和 returnAddress 类型(指向了一条字节码指令的地址)。</p><p>其中64位长度的 long 和 doule 类型的数据会占用2个局部变量空间(slot),其余的数据类型只占用1个。<strong>局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。</strong></p><p>在 Java 虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError 异常。</p><h2 id="本地方法栈"><a href="#本地方法栈" class="headerlink" title="本地方法栈"></a>本地方法栈</h2><p><strong>本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。</strong>在虚拟机规范中,对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。与虚拟机栈一样,本地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异常。</p><h2 id="Java-堆"><a href="#Java-堆" class="headerlink" title="Java 堆"></a>Java 堆</h2><p><strong>对于大多数应用来说,Java 堆(Java Heap)是 Java 虚拟机所管理的内存中最大的一块。Java 堆被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。</strong>这一点在 Java 虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。但随着 JIT 编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。</p><p>Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称作“GC 堆”(Garbage Collected Heap)。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以 Java 堆还可以细分为:新生代和老年代;再细致一点的有 Eden 空间、From Survivor 空间、To Survivor 空间等;从内存分配的角度来看,线程共享的 Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。不过无论如何划分,都与存放内容无关,无论哪个区域,存储的仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。</p><p>根据 Java 虚拟机规范的规定,Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。</p><h2 id="方法区"><a href="#方法区" class="headerlink" title="方法区"></a>方法区</h2><p><strong>方法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。</strong>虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。</p><p>Java 虚拟机规范对方法区的限制非常宽松,除了和 Java 堆一样不需要连续的内存和可以选择固定大小或可扩展外,还可以选择不实现垃圾收集。相对来言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如同永生代的名字一样“永远”存在了。</p><p><strong>这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说,这个区域的回收“成绩”比较难令人满意(类似于操作系统中页面的换入换出算法),尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是必要的。</strong></p><p>根据 Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常。</p><h2 id="运行时常量池"><a href="#运行时常量池" class="headerlink" title="运行时常量池"></a>运行时常量池</h2><p><strong>运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法等描述信息外,还有一项信息就是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。</strong></p><p>Java 虚拟机对 Class 文件每一部分(自然也包括常量池)的格式都有严格规定,每一个字节用于存储那种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行,但对于运行时常量池,Java 虚拟机规范没有做任何细节的要求,不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。不过,一般来说,除了保存 Class 文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。</p><p><strong>运行时常量池相对于 Class 文件常量池的另外一个重要特征是具备动态性,Java 语言并不要求常量一定只有编译器才能产生,也就是并非预置入 Class 文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,类似于 String 类的 intern() 方法。</strong></p><h2 id="直接内存"><a href="#直接内存" class="headerlink" title="直接内存"></a>直接内存</h2><p>直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域。但是这部分内存也会被频繁地使用。</p><p>在 JDK1.4中新加入了 NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区( Buffer)的 I/O 方式,<strong>它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 队中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。</strong></p><p>显然,本机直接内存的分配不会受到 Java 堆大小的限制,但是,既然是内存,肯定还是会受本机总内存大小以及处理器寻址空间的限制。</p>]]></content>
</entry>
<entry>
<title>Java抽象类与接口</title>
<link href="/2017/10/31/Java%E6%8A%BD%E8%B1%A1%E7%B1%BB%E4%B8%8E%E6%8E%A5%E5%8F%A3/"/>
<url>/2017/10/31/Java%E6%8A%BD%E8%B1%A1%E7%B1%BB%E4%B8%8E%E6%8E%A5%E5%8F%A3/</url>
<content type="html"><![CDATA[<h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>抽象类是用来捕捉子类的通用特性的。它不能被实例化,只能被用作子类的超类。可以将抽象类当做是被用来创建继承层级里子类的模板。</p><p>接口是抽象方法的集合。如果一个类实现了某个接口,那么它就继承了这个接口的所有抽象方法,并需要确保这些方法全部实现,这就像是契约模式一般。接口只是一种形式,接口自身不能做任何事情。</p><p>在Java中,一个类只能够继承一个抽象类,但是一个类可以同时实现多个接口。</p><h2 id="设计思想"><a href="#设计思想" class="headerlink" title="设计思想"></a>设计思想</h2><p>从概念上,可知抽象类与接口所设计的目的是不一样的 —— <strong>接口是对动作的抽象,而抽象类是对根源的抽象</strong>。</p><p>举个例子而言,在这个世界上存在很多不同的车,跑车、轿车、货车等,那么对于抽象类而言,我们就需要从这些车中提取它们的公共部分,设计出一个更高级别的抽象类——四轮车。</p><p>但轿车与货车同为四轮车,但是普遍轿车都存在着自动驾驶功能,如果货车需要自动驾驶功能的话,那么对于接口而言,可以把自动驾驶功能所需的这些方法抽象成接口——自动驾驶。</p><h2 id="如何选择"><a href="#如何选择" class="headerlink" title="如何选择"></a>如何选择</h2><p>从程序设计中,如何选择抽象类与接口?从我的观点而言,在程序设计中我们需要考虑的是高度抽象化以及程序可扩展性。</p><p>因为在Java中只能够继承一个父类,所以定义抽象类的代价比较高。即在程序设计中,需要从自下至上,综合分析所有子类中的共同点,高度抽象化成父类(这里可以联想Object,即使不是抽象类,但它却由始至终贯穿整个java体系)。</p><p>但相比于抽象类而言,接口所付出的代价相对的低很多,可扩展性也大大提高了,因为类可以实现多个接口,因此每个接口你只需要将特定的动作抽象到这个接口即可。</p><p>举个例子,如<code>LinkedList</code>的继承体系一般。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LinkedList</span><<span class="title">E</span>></span></span><br><span class="line"><span class="class"> <span class="keyword">extends</span> <span class="title">AbstractSequentialList</span><<span class="title">E</span>></span></span><br><span class="line"><span class="class"> <span class="keyword">implements</span> <span class="title">List</span><<span class="title">E</span>>, <span class="title">Deque</span><<span class="title">E</span>>, <span class="title">Cloneable</span>, <span class="title">java</span>.<span class="title">io</span>.<span class="title">Serializable</span></span></span><br></pre></td></tr></table></figure><ul><li>抽象类的角度。<code>LinkedList</code>继承自<code>AbstractSequentialList</code>,这说明了<code>LinkedList</code>属于链表这种数据结构的继承体系中,它继承了所有链表的基本操作。</li><li>接口的角度。<code>LinkedList</code>实现了<code>Deque</code>接口,这表明了即使是链表,也能够实现双端队列的功能,使程序调用者可以将<code>LinkedList</code>同等视为队列使用。</li></ul><hr><p>参考资料</p><p><a href="https://www.zhihu.com/question/20149818/answer/153188511" target="_blank" rel="noopener">接口和抽象类有什么区别?</a></p><p><a href="http://www.importnew.com/12399.html" target="_blank" rel="noopener">Java抽象类与接口的区别</a></p>]]></content>
</entry>
<entry>
<title>Java关键字 - final</title>
<link href="/2017/10/31/Java%E5%85%B3%E9%94%AE%E5%AD%97-final/"/>
<url>/2017/10/31/Java%E5%85%B3%E9%94%AE%E5%AD%97-final/</url>
<content type="html"><![CDATA[<h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>在Java中,我们可以通过<code>final</code>来表示某个变量、某个方法,甚至是某个类是“不变的”或“无法改变的”。</p><h2 id="怎么使用final"><a href="#怎么使用final" class="headerlink" title="怎么使用final"></a>怎么使用final</h2><h3 id="final变量"><a href="#final变量" class="headerlink" title="final变量"></a>final变量</h3><p><code>final</code>修饰的变量称之为常量,其主要应用于以下地方:</p><ul><li>编译器常量,永远不可改变。</li><li>运行其初始化时,我们希望它不会被改变。</li></ul><p>对于编译器常量,它在类加载的过程就已经完成了初始化,所以当类加载完成后是不可改变的。而对于运行时变量,也称为空白<code>final</code>,即代表先声明,后赋值这一过程。</p><p>运行时变量可分为基本数据类型与引用数据类型,其中基本数据类型不可变的是内容,而引用数据类型的不可变的是引用,引用所指的对象内容是可变的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> SIZE;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> String test = <span class="string">"final can't change"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Test</span><span class="params">(<span class="keyword">int</span> size)</span> </span>{</span><br><span class="line"> <span class="comment">// 运行期变量,可以根据传递的值来声明不同的常量</span></span><br><span class="line"> <span class="keyword">this</span>.SIZE = size;</span><br><span class="line"> <span class="comment">// 编译器报错,因为test被final修饰,其在编译期间已确定的值,所以不可改变</span></span><br><span class="line"> <span class="keyword">this</span>.test = <span class="string">"final??"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="final方法"><a href="#final方法" class="headerlink" title="final方法"></a>final方法</h3><p>被<code>final</code>修饰的方法都是不能被继承、更改的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Father</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">hello</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"father can't change"</span>);</span><br><span class="line"> }</span><br><span class="line"></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"> <span class="keyword">new</span> Son().hello();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Son</span> <span class="keyword">extends</span> <span class="title">Father</span> </span>{</span><br><span class="line"> <span class="comment">// 编译器报错,因为Father类中的hello()被final修饰,所以子类是不能够重写的</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">hello</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"hhello"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="final参数"><a href="#final参数" class="headerlink" title="final参数"></a>final参数</h3><p>被<code>final</code>修饰的参数都是不可变的,即在函数作用域内,该参数的值都是不可变的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">hello</span><span class="params">(<span class="keyword">final</span> String str)</span> </span>{</span><br><span class="line"><span class="comment">// 编译出错</span></span><br><span class="line"> str = <span class="string">"world"</span>;</span><br><span class="line"> System.out.println(str);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="final类"><a href="#final类" class="headerlink" title="final类"></a>final类</h3><p>被<code>final</code>修饰的类是不允许被继承的,所以可视该类为最终类。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Father</span> </span>{</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 编译出错,Father类不可被继承</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Son</span> <span class="keyword">extends</span> <span class="title">Father</span> </span>{</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="final能提高性能吗?"><a href="#final能提高性能吗?" class="headerlink" title="final能提高性能吗?"></a>final能提高性能吗?</h2><p>基于<a href="https://www.zhihu.com/question/21762917" target="_blank" rel="noopener">JVM对于声明为final的局部变量(local var)做了哪些性能优化?</a>与<a href="http://codewenda.com/%E5%9C%A8java%E4%B8%AD%E4%BD%BF%E7%94%A8final%E5%85%B3%E9%94%AE%E5%AD%97%E4%BC%9A%E6%8F%90%E9%AB%98%E6%80%A7%E8%83%BD%E5%90%97%EF%BC%9F/" target="_blank" rel="noopener">在Java中使用final关键字会提高性能吗?</a>两文,可以归纳总结出<code>final</code>关键字并不会从性能上有很大的提升,甚至可以说是没有。反而要求设计者在程序设计中,不要过分追求性能,需要注重的是代码的可读性与可维护性。</p><hr><p>参考资料</p><p><a href="http://www.cnblogs.com/chenssy/p/3428180.html" target="_blank" rel="noopener">java提高篇(十五)—–关键字final</a></p>]]></content>
</entry>
<entry>
<title>Java关键字 - static</title>
<link href="/2017/10/31/Java%E5%85%B3%E9%94%AE%E5%AD%97-static/"/>
<url>/2017/10/31/Java%E5%85%B3%E9%94%AE%E5%AD%97-static/</url>
<content type="html"><![CDATA[<h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>在Java中,我们可以通过用<code>static</code>来表示某个字段或者方法为“全局“或者”静态“的意思,当然<code>static</code>也可以修饰代码块。</p><h2 id="怎么使用static"><a href="#怎么使用static" class="headerlink" title="怎么使用static"></a>怎么使用static</h2><p><code>static</code>可以用于修饰成员变量和成员方法,我们将其称为静态变量和静态方法,可以直接通过类名进行访问。如下所示:</p><ul><li>ClassName.propertyName</li><li>ClassName.methodName()</li></ul><p>而<code>static</code>修饰的代码块也称之为静态代码块,当其所属的类被加载时,就会优先先执行这部分代码。</p><h3 id="static变量"><a href="#static变量" class="headerlink" title="static变量"></a>static变量</h3><p><code>static</code>修饰的变量称之为静态变量,而没有<code>static</code>修饰的变量称之为实例变量。两者的区别在于:</p><p>静态变量是随着类加载时被完成初始化的,它在内存中仅有一个,且JVM也只会为它分配一次内存,同时类所有的实例都共享静态变量,可以直接通过类名来访问。</p><p>但是实例变量则不同,他是伴随着实例的创建而创建,也伴随着实例的消亡而消亡。而且实例变量只能够通过对象进行访问。</p><h3 id="static函数"><a href="#static函数" class="headerlink" title="static函数"></a>static函数</h3><p><code>static</code>修饰的方法称之为静态方法,可以直接通过类名对其进行调用。正因为<code>static</code>修饰的函数在类加载的时候就已经存在了,它不依赖于任何实例,所以<code>static</code>方法必须被实现,也就是说它不能够同时与<code>abstract</code>搭配修饰函数。</p><p><code>static</code>方法是类中的一种特殊方法,当我们需要这个类下的某个方法完成某个特定的目的而无须将其实例化时,才将这个方法修饰为<code>static</code>。如Math类下的所有方法都是静态<code>static</code>的。</p><h3 id="static代码块"><a href="#static代码块" class="headerlink" title="static代码块"></a>static代码块</h3><p><code>static</code>修饰的代码块称之为静态代码块,它会随着类加载的时候一块执行。静态代码块可以放置类中任意地方,且在类加载时,静态代码块按从下到上的顺序依次执行。</p><h3 id="使用注意"><a href="#使用注意" class="headerlink" title="使用注意"></a>使用注意</h3><p><code>static</code>修饰的方法不能够调用非<code>static</code>变量或者非<code>static</code>方法</p><p><code>static</code>所修饰的方法是从属于类的,且被由该类实例化出的所有对象所共享。若在<code>static</code>方法中调用了非<code>static</code>变量或非<code>static</code>方法,那么在运行期间,程序则无法确定此时的所调用的非<code>static</code>变量的确切值(因为成员变量的实际值取决于其所属的对象),从而导致程序运行错误。</p><h2 id="静态代码块与非静态代码块的初始化顺序"><a href="#静态代码块与非静态代码块的初始化顺序" class="headerlink" title="静态代码块与非静态代码块的初始化顺序"></a>静态代码块与非静态代码块的初始化顺序</h2><p><strong>优先级结论:静态代码块 == 静态变量初始化 > 实例代码块 == 实例变量初始化 > 实例构造器。</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> util_test;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Main</span> </span>{</span><br><span class="line"> {</span><br><span class="line"> System.out.println(<span class="string">"main instance block"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> {</span><br><span class="line"> System.out.println(<span class="string">"main static block"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Main</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"main instance constructor"</span>);</span><br><span class="line"> }</span><br><span class="line"></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"> <span class="keyword">new</span> Son();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Father</span> </span>{</span><br><span class="line"> {</span><br><span class="line"> System.out.println(<span class="string">"father instance block"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span>{</span><br><span class="line"> System.out.println(<span class="string">"father static block"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Father</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"father instance constructor"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Son</span> <span class="keyword">extends</span> <span class="title">Father</span> </span>{</span><br><span class="line"> {</span><br><span class="line"> System.out.println(<span class="string">"son instance block"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> {</span><br><span class="line"> System.out.println(<span class="string">"son static block"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Son</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"son instance constructor"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>正确的执行顺序如下所示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">main static block</span><br><span class="line">father static block</span><br><span class="line">son static block</span><br><span class="line">father instance block</span><br><span class="line">father instance constructor</span><br><span class="line">son instance block</span><br><span class="line">son instance constructor</span><br></pre></td></tr></table></figure><p>分析上述的代码加载过程。</p><ol><li>因为<code>main</code>方法在Main类中,所以首先加载Main类,因此会执行Main类中的<code>static</code>代码块。接着执行<code>main</code>方法中的<code>new Son()</code>语句,而此时Son类尚未被加载,因此此时需要加载Son类。在加载Son类的时候,发现Son类是继承自Father类,所以转而去优先加载Father类,所以Father类中的<code>static</code>代码块会被执行,然后再执行Son类中的<code>static代码</code>块。</li><li>在所有所需的类加载完毕后,就通过构造器来生成对象。而在生成对象的时候,必须先初始化父类的成员变量以及执行实例代码块。因此先执行Father类中的实例代码块与构造器,最后再执行Son类中实例代码块与构造器。</li></ol><hr><p>参考资料</p><p><a href="http://www.cnblogs.com/dolphin0520/p/3799052.html" target="_blank" rel="noopener">Java中的static关键字解析</a></p><p><a href="http://www.cnblogs.com/chenssy/p/3386721.html" target="_blank" rel="noopener">java提高篇(七)—–关键字static</a></p>]]></content>
</entry>
<entry>
<title>堆排序(Heap Sort)</title>
<link href="/2017/10/31/%E5%A0%86%E6%8E%92%E5%BA%8F%EF%BC%88Heap%20Sort%EF%BC%89/"/>
<url>/2017/10/31/%E5%A0%86%E6%8E%92%E5%BA%8F%EF%BC%88Heap%20Sort%EF%BC%89/</url>
<content type="html"><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>堆排序(Heap Sort)是指利用堆这种数据结构所设计的一种排序算法。</p><h2 id="什么是堆"><a href="#什么是堆" class="headerlink" title="什么是堆"></a>什么是堆</h2><p>堆的实现通过构造二叉堆,实为二叉树的一种分支。这种数据结构具有以下性质:</p><ul><li>任意节点小于(或大于)它的所有后裔,最小元(或最大元)在堆的根上(堆序性)。</li><li>堆总是一个完全二叉树,即除了最底层外,其他层的节点都被元素填满,且最底层尽可能地从左到右填入。</li></ul><p>将根节点最大的堆叫做最大堆,而将根节点最小的堆叫做最小堆。</p><p><img src="http://on83riher.bkt.clouddn.com/%E6%9C%80%E5%A4%A7%E6%9C%80%E5%B0%8F%E5%A0%86.png" alt="图源于维基百科"></p><h2 id="堆排序中如何表示堆"><a href="#堆排序中如何表示堆" class="headerlink" title="堆排序中如何表示堆"></a>堆排序中如何表示堆</h2><p>由于<strong>堆总是一个完全二叉树</strong>这一特性,这使得堆可以利用<strong>数组</strong>来表示,如下图所示。</p><p><img src="http://on83riher.bkt.clouddn.com/%E5%A0%86%E6%8E%92%E5%BA%8F%E5%88%9D%E5%A7%8B.png" alt=""></p><p>对于给定的某个节点的下标i,可以很容易的计算出这个节点的父节点、左右孩子节点的下标:</p><ul><li><strong>Parent(i) = floor((i - 1) / 2)</strong>,i节点的父节点下标</li><li><strong>Left(i) = 2i * 1</strong>,i节点的左子节点下标</li><li><strong>Right(i) = 2 * (i - 1)</strong>,i节点的右子节点下标</li></ul><h2 id="堆排序原理(以最大堆为例)"><a href="#堆排序原理(以最大堆为例)" class="headerlink" title="堆排序原理(以最大堆为例)"></a>堆排序原理(以最大堆为例)</h2><p>堆排序就是把最大堆的堆顶取出,将剩余的堆继续调整为最大堆,再将堆顶的数值取出,不断重复这一过程,直至堆中只剩下一个节点为止。堆中定义以下几种操作:</p><ul><li><strong>最大堆调整(MaxHeapify)</strong>:将堆的末端子节点作调整,使得子节点永远小于父节点</li><li><strong>创建最大堆(BuildMaxHeap)</strong>:将堆所有数据重新排序</li><li><strong>推排序(HeapSort)</strong>:移除在第一个数据的根节点,bin并做最大堆调整的递归运算</li></ul><h4 id="最大堆调整(MaxHeapify)"><a href="#最大堆调整(MaxHeapify)" class="headerlink" title="最大堆调整(MaxHeapify)"></a>最大堆调整(MaxHeapify)</h4><p>最大堆调整的作用是保持最大堆的性质,是整个算法的核心部分。它内部的算法其实决定了堆到底是最大堆还是最小堆。</p><p><img src="http://on83riher.bkt.clouddn.com/MAX_HEAPIFY.png" alt=""></p><p>代码部分如下所示:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// array代表需要排序的数组部分</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">maxHeapAdjust</span><span class="params">(<span class="keyword">int</span> index, <span class="keyword">int</span> heapSize)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> largest = index;</span><br><span class="line"> <span class="comment">// 左右子节点在数组中的位置</span></span><br><span class="line"> <span class="keyword">int</span> left = <span class="number">2</span> * index + <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">int</span> right = <span class="number">2</span> * (index + <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">if</span> (left < heapSize && array[index] < array[left]) {</span><br><span class="line"> largest = left;</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">if</span> (right < heapSize && array[largest] < array[right]) {</span><br><span class="line"> largest = right;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 倘若larget并非指向原节点index时,则证明父节点index小于某个子节点left/right</span></span><br><span class="line"> <span class="keyword">if</span> (largest != index) {</span><br><span class="line"> swap(largest, index);</span><br><span class="line"> <span class="comment">// 因为largest并非index,所以节点largest的堆结构也发生了变化</span></span><br><span class="line"> maxHeapAdjust(largest, heapSize);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">swap</span><span class="params">(<span class="keyword">int</span> i, <span class="keyword">int</span> j)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> tmp = queue[i];</span><br><span class="line"> queue[i] = queue[j];</span><br><span class="line"> queue[j] = tmp;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>非递归实现版本</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">maxHeapAdjustWhile</span><span class="params">(<span class="keyword">int</span> index, <span class="keyword">int</span> heapSize)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> largest, left, right;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"> largest = index;</span><br><span class="line"> left = <span class="number">2</span> * index + <span class="number">1</span>;</span><br><span class="line"> right = <span class="number">2</span> * (index + <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">if</span> (left < heapSize && queue[index] < queue[left]) {</span><br><span class="line"> largest = left;</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">if</span> (right < heapSize && queue[largest] < queue[right]) {</span><br><span class="line"> largest = right;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 倘若larget并非指向原节点index时,则证明父节点index小于某个子节点left/right</span></span><br><span class="line"> <span class="keyword">if</span> (largest != index) {</span><br><span class="line"> swap(largest, index);</span><br><span class="line"> maxHeapAdjustWhile(largest, heapSize);</span><br><span class="line"> } <span class="keyword">else</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></pre></td></tr></table></figure><h4 id="创建最大堆(BuildMaxHeap)"><a href="#创建最大堆(BuildMaxHeap)" class="headerlink" title="创建最大堆(BuildMaxHeap)"></a>创建最大堆(BuildMaxHeap)</h4><p>创建最大堆的作用是将一个数组转换为一个最大堆。倘若堆中有n个元素,那么BuildMaxHeap就从Parent(n)开始(因为Parent(n)的节点刚刚好指向最后一个元素的父节点),从下往上地调用MaxHeapify。</p><p><img src="http://on83riher.bkt.clouddn.com/BUILD_MAX_HEAP.png" alt=""></p><p>代码结构如下所示:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">buildMaxHeap</span><span class="params">(<span class="keyword">int</span> heapSize)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> parent = (heapSize - <span class="number">1</span>) / <span class="number">2</span>;</span><br><span class="line"> <span class="comment">// 可以参考图思考一下,为什么这样循环递减i</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = parent; i >= <span class="number">0</span>; i--) {</span><br><span class="line"> maxHeapAdjust(i, heapSize);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="推排序(HeapSort)"><a href="#推排序(HeapSort)" class="headerlink" title="推排序(HeapSort)"></a>推排序(HeapSort)</h4><p>堆排序是堆排序算法的接口算法部分,HeapSort先调用BuildMaxHeap将传递来的数组转换为最大堆,然后将最大堆堆顶元素与堆底最后一个元素对换,然后再重新调用MaxHeapify来保证最大堆的性质。</p><p><img src="http://on83riher.bkt.clouddn.com/HEAPSORT.png" alt=""></p><p>代码结构如下所示:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">int</span>[] maxHeapSort() {</span><br><span class="line"> <span class="keyword">int</span> heapSize = queue.length;</span><br><span class="line"> buildMaxHeap(heapSize);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = heapSize - <span class="number">1</span>; i > <span class="number">0</span>; i--) {</span><br><span class="line"> <span class="comment">// 交换堆顶的最大值放置数组末尾</span></span><br><span class="line"> swap(<span class="number">0</span>, i);</span><br><span class="line"> <span class="comment">// 重新整理最大堆,范围缩小至除已排序的节点外</span></span><br><span class="line"> maxHeapAdjust(<span class="number">0</span>, i);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> queue;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="时间与空间复杂度"><a href="#时间与空间复杂度" class="headerlink" title="时间与空间复杂度"></a>时间与空间复杂度</h2><p>最优时间复杂度为O(nlogn),最坏时间复杂度O(nlogn)。</p><p>总空间复杂度为O(n),辅助空间复杂度O(1)。</p><hr><p>参考资料</p><p><a href="https://zh.wikipedia.org/wiki/%E5%A0%86%E6%8E%92%E5%BA%8F" target="_blank" rel="noopener">堆排序</a></p><p><a href="https://zh.wikipedia.org/wiki/%E5%A0%86_(%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84" target="_blank" rel="noopener">堆 (数据结构)</a>)</p><p><a href="http://bubkoo.com/2014/01/14/sort-algorithm/heap-sort/" target="_blank" rel="noopener">常见排序算法 - 堆排序 (Heap Sort)</a></p>]]></content>
</entry>
<entry>
<title>Java异常机制分析</title>
<link href="/2017/10/30/Java%E5%BC%82%E5%B8%B8%E6%9C%BA%E5%88%B6%E5%88%86%E6%9E%90/"/>
<url>/2017/10/30/Java%E5%BC%82%E5%B8%B8%E6%9C%BA%E5%88%B6%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>异常是指程序在运行期间所发生的错误,如使用了空指针、栈溢出、非法参数等。在程序编写期间,编译器会自动检查代码是否符合规范,并尽可能地帮助程序员将其纠正。但即使是看似正确的代码,也可能会在运行期间抛出一个意想不到的异常。</p><p>Java为此提供了异常处理机制,即在程序运行期间,倘若抛出了异常,则可以以适当的方式进行捕获处理,使得程序能够正常的运作下去。</p><h2 id="体系结构"><a href="#体系结构" class="headerlink" title="体系结构"></a>体系结构</h2><p><img src="http://on83riher.bkt.clouddn.com/%E5%BC%82%E5%B8%B8%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84.png" alt=""></p><p>在Java中所有的异常类都是从<code>java.lang.Throwable</code>类集成的子类。</p><p>根类<code>Throwable</code>下(仅)有两个重要的子类——<code>Error</code>与<code>Exception</code>。</p><ul><li><code>Error</code>代表运行期间JVM(Java虚拟机)出现的异常,这种异常一般来说是无法处理的。</li><li><code>Exception</code>代表运行期间程序本身的逻辑出现的异常,这种异常一般是程序本身可以处理的。</li></ul><p>其中,<code>Exception</code>可分为两类:运行时异常和检查异常。</p><ul><li>检查异常(CheckedException),是指程序在执行某段代码时,是可以提前知道这段代码是存在潜在异常的,而且<strong>要求程序必须以某种方式来处理</strong>。若不处理这种异常情况时,编译器是不会通过编译的。</li></ul><ul><li>运行时异常(RuntimeException),也称为非检查异常,是指程序在运行期间可能会抛出异常,但不要求程序必须处理该异常。在编译期间,编译器也不会要求用户去处理它。</li></ul><h2 id="TRY-CATCH会不会性能消耗"><a href="#TRY-CATCH会不会性能消耗" class="headerlink" title="TRY-CATCH会不会性能消耗"></a>TRY-CATCH会不会性能消耗</h2><p>当初的自己觉得如果在<code>try-catch</code>块中大量使用循环的话,想当然的认为会消耗大量的性能。但是通过阅读多篇文章后,得出以下结论:</p><ul><li>异常如果没发生,也就不会去查异常表,也就是说你写不写<code>try-catch</code>,也就是有没有这个异常表的问题,如果没有发生异常,写<code>try-catch</code>对性能是木有消耗的,所以不会让程序跑得更慢。</li><li><code>try-catch</code> 的范围大小其实就是异常表中两个值(开始地址和结束地址)的差异而已,也是不会影响性能的。</li></ul><p>具体文章如下所示:</p><p><a href="http://www.cnblogs.com/isline/archive/2010/04/22/1717837.html" target="_blank" rel="noopener">Try-Catch真的会影响程序性能吗</a></p><p><a href="http://blog.csdn.net/tao_zi7890/article/details/17584813" target="_blank" rel="noopener">Java上的try catch并不影响性能(转)</a></p><h2 id="优化建议"><a href="#优化建议" class="headerlink" title="优化建议"></a>优化建议</h2><p>优化建议这一部分是结合了<a href="http://www.cnblogs.com/dolphin0520/p/3769804.html" target="_blank" rel="noopener">Java异常处理和设计</a>和<a href="https://www.oschina.net/question/12_37141" target="_blank" rel="noopener">异常处理的 15 个处理原则</a>两文的精华,小弟只能做个低调的搬运工。</p><ul><li>只在必要使用异常的地方才使用异常,不要用异常去控制程序的流程</li></ul><p>即使在上述的说到异常机制不会怎么消耗性能,但这并不代表能够在程序中随处使用<code>try-catch</code>。要在程序中谨慎地使用异常,倘若异常使用过多仍然会很大程度上影响程序的性能。如果在程序中能够用<code>if</code>语句来进行逻辑判断,自然能更清楚地表明出当某个字段处于某个阶段时要进行的逻辑,也可以减少异常的使用,从而避免不必要的异常捕获和处理。</p><ul><li>切忌使用空<code>catch</code>块</li></ul><p>倘若程序在捕获了异常之后什么都不做,相当于你直接隐藏了这个异常,这可能会导致后面的程序逻辑出现不可控的执行结果,这是一种相当不负责任的行为。倘若有这种情况发生,不如改变程序本身的代码逻辑,使其变得更加健壮,并用日志的方式记录其异常的状态,方便日后的更新和维护。</p><ul><li>检查异常与非检查异常的选择</li></ul><p>当你决定要抛出一个自己新定义的异常,你就要决定以什么形式来处理这个异常。</p><p>当有些检查异常对开发人员来说是无法通过合理的手段处理的,例如<code>SQLException</code>,这样就会导致在代码中经常出现的一种情况:逻辑代码很少几行,但是要进行异常捕获和异常处理的代码却有很多行,这会导致逻辑代码阅读起来晦涩难懂,使得代码难以维护。</p><p>在检查异常与非检查异常的选择上面,如果存在该异常情况的出现很普遍,需要特别提醒调用者注意处理的话,就是用检查异常,否则就使用非检查异常。</p><ul><li>注意<code>catch</code>块的顺序</li></ul><p>切忌将捕获父类异常的<code>catch</code>块放置于捕获子类异常<code>catch</code>块前,否则将永远无法到达程序理想的异常处理逻辑状态中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> FileInputStream inputStream = <span class="keyword">new</span> FileInputStream(<span class="string">"d:/a.txt"</span>);</span><br><span class="line"> <span class="keyword">int</span> ch = inputStream.read();</span><br><span class="line"> System.out.println(<span class="string">"aaa"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"step1"</span>;</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> System.out.println(<span class="string">"io exception"</span>); </span><br><span class="line"> <span class="keyword">return</span> <span class="string">"step2"</span>;</span><br><span class="line">} <span class="keyword">catch</span> (FileNotFoundException e) {</span><br><span class="line"> <span class="comment">// 永远到不了这一步,因为catch块是从上到下优先匹配到符合该异常类及其父类</span></span><br><span class="line"> <span class="comment">// 由于Exception为FileNotFoundException的父类,所以catch块将在第一次匹配中结束</span></span><br><span class="line"> System.out.println(<span class="string">"file not found"</span>); </span><br><span class="line"> <span class="keyword">return</span> <span class="string">"step3"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>避免多次在日志信息中记录同一异常</li></ul><p>很多情况下异常都是层层向上抛出,如果在每次向上抛出异常的时候,都记录到日志中,则会导致冗余的异常重复记录在日志中,不仅大量浪费空间,而且很难查找到异常的根源。</p><p>妥当的做法是只在异常最开始发生的地方进行日志信息记录。</p><ul><li>在<code>finally</code>中释放资源</li></ul><p>如果在程序中存在文件读取、网络操作以及数据库操作等,需要在<code>finally</code>块中释放资源。这样不仅使得程序占用的资源更少,也会避免由于资源未及时释放而导致的异常情况。</p><ul><li>不要在<code>finally</code>中使用<code>return</code>语句</li></ul><p>倘若在正常<code>try</code>块中返回值,又或者是,在捕获异常后打算在<code>catch</code>块中返回值的话,切忌在<code>finally</code>块中再返回值,否则<code>finally</code>的返回值将直接取代<code>catch</code>块中的返回值。这不难想象,因为<code>finally</code>块在<code>try-catch</code>执行完后一定会执行的,所以<code>finally</code>中的操作将会正常执行。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> FileInputStream inputStream = <span class="keyword">new</span> FileInputStream(<span class="string">"d:/a.txt"</span>);</span><br><span class="line"> <span class="keyword">int</span> ch = inputStream.read();</span><br><span class="line"> System.out.println(<span class="string">"aaa"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"step1"</span>;</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> System.out.println(<span class="string">"io exception"</span>); </span><br><span class="line"> <span class="keyword">return</span> <span class="string">"step2"</span>;</span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line"> System.out.println(<span class="string">"finally end"</span>); </span><br><span class="line"> <span class="comment">// 程序执行到这,会导致最终的返回值是"step3",而非"step1",也不会是"step1"</span></span><br><span class="line"> <span class="keyword">return</span> <span class="string">"step3"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>当方法判断出错该返回时应该抛出异常,而不是返回一些错误值</li></ul><p>因为错误值在程序逻辑中可能会出现难以理解的情况,并且错误值在描述异常的情况并不直观。在文件找不到的时候,应当抛出类似 FileNotFoundException 异常,而不是返回 -1 或者 -2 之类的错误值。</p><hr><p>参考资料</p><p><a href="http://www.cnblogs.com/dolphin0520/p/3769804.html" target="_blank" rel="noopener">Java异常处理和设计</a></p><p><a href="https://www.oschina.net/question/12_37141" target="_blank" rel="noopener">异常处理的 15 个处理原则</a></p>]]></content>
</entry>
<entry>
<title>ArrayList与LinkedList的循环效率对比</title>
<link href="/2017/10/29/ArrayList%E4%B8%8ELinkedList%E7%9A%84%E5%BE%AA%E7%8E%AF%E6%95%88%E7%8E%87%E5%AF%B9%E6%AF%94/"/>
<url>/2017/10/29/ArrayList%E4%B8%8ELinkedList%E7%9A%84%E5%BE%AA%E7%8E%AF%E6%95%88%E7%8E%87%E5%AF%B9%E6%AF%94/</url>
<content type="html"><![CDATA[<h2 id="问题来源"><a href="#问题来源" class="headerlink" title="问题来源"></a>问题来源</h2><p><code>for</code>循环遍历存在这两种方式,如下所示:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < objects.length; i++);</span><br><span class="line"><span class="keyword">for</span> (Object object: objects);</span><br></pre></td></tr></table></figure><p><code>ArrayList</code>与<code>LinkedList</code>两种集合在两种方式的遍历存在着较大的性能差距,下面将以以下测试代码作为范例解释:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ForTest</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> NUM_SIZE = <span class="number">2000000</span>;</span><br><span class="line"></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">testList</span><span class="params">(List<Integer> list)</span> </span>{</span><br><span class="line"> <span class="keyword">long</span> start = System.currentTimeMillis();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < list.size(); i++) {</span><br><span class="line"> list.get(i);</span><br><span class="line"> }</span><br><span class="line"> System.out.println(list.getClass().getSimpleName() + <span class="string">"-普通for-花费时间: "</span> + (System.currentTimeMillis() - start) + <span class="string">"ms"</span>);</span><br><span class="line"></span><br><span class="line"> start = System.currentTimeMillis();</span><br><span class="line"> <span class="keyword">for</span> (Integer num: list) {</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> System.out.println(list.getClass().getSimpleName() + <span class="string">"-foreach-花费时间: "</span> + (System.currentTimeMillis() - start) + <span class="string">"ms"</span>);</span><br><span class="line"> }</span><br><span class="line"></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"> List<Integer> arrayNums = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line"> List<Integer> linkNums = <span class="keyword">new</span> LinkedList<>();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < NUM_SIZE; i++) {</span><br><span class="line"> arrayNums.add(i);</span><br><span class="line"> linkNums.add(i);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> testList(arrayNums);</span><br><span class="line"> testList(linkNums);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>测试运行结果可得:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ArrayList-普通for-花费时间: 5ms</span><br><span class="line">ArrayList-foreach-花费时间: 7ms</span><br><span class="line">LinkedList-普通for-花费时间: 32459ms</span><br><span class="line">LinkedList-foreach-花费时间: 9ms</span><br></pre></td></tr></table></figure><p>从上述的结果中,可以看出<code>ArrayList</code>在普通for循环上更胜一筹,而在LinkedList在foreach上效率极高。倘若数据量不断上升,那么这个差距只会不断加大。</p><p>那么为什么会造成上述情况发生呢?</p><h2 id="问题解释"><a href="#问题解释" class="headerlink" title="问题解释"></a>问题解释</h2><p>问题可以从两者的结构体系上分析。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ArrayList</span><<span class="title">E</span>> <span class="keyword">extends</span> <span class="title">AbstractList</span><<span class="title">E</span>></span></span><br><span class="line"><span class="class"> <span class="keyword">implements</span> <span class="title">List</span><<span class="title">E</span>>, <span class="title">RandomAccess</span>, <span class="title">Cloneable</span>, <span class="title">java</span>.<span class="title">io</span>.<span class="title">Serializable</span></span></span><br><span class="line"><span class="class"> </span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">LinkedList</span><<span class="title">E</span>></span></span><br><span class="line"><span class="class"> <span class="keyword">extends</span> <span class="title">AbstractSequentialList</span><<span class="title">E</span>></span></span><br><span class="line"><span class="class"> <span class="keyword">implements</span> <span class="title">List</span><<span class="title">E</span>>, <span class="title">Deque</span><<span class="title">E</span>>, <span class="title">Cloneable</span>, <span class="title">java</span>.<span class="title">io</span>.<span class="title">Serializable</span></span></span><br></pre></td></tr></table></figure><p>在两者对比中,我们可以发现<code>ArrayList</code>中实现了<code>RandomAccess</code>接口,而<code>LinkedList</code>没有实现。仔细查看RandomAccess的注释解释,如果实现该接口的<code>List</code>,在普通for循环遍历的效率上会快于foreach循环。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Marker interface used by <tt>List</tt> implementations to indicate that</span></span><br><span class="line"><span class="comment"> * they support fast (generally constant time) random access. The primary</span></span><br><span class="line"><span class="comment"> * purpose of this interface is to allow generic algorithms to alter their</span></span><br><span class="line"><span class="comment"> * behavior to provide good performance when applied to either random or</span></span><br><span class="line"><span class="comment"> * sequential access lists.</span></span><br><span class="line"><span class="comment"> * List实现所使用的标记接口,用来表明实现了这个接口的list支持快速随机访问(通常在常数时</span></span><br><span class="line"><span class="comment"> * 间内)。这个接口主要目的在于允许一般的算法更改它们的行为,以便在随机或者顺序访问list</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"> * <p>The best algorithms for manipulating random access lists (such as</span></span><br><span class="line"><span class="comment"> * <tt>ArrayList</tt>) can produce quadratic behavior when applied to</span></span><br><span class="line"><span class="comment"> * sequential access lists (such as <tt>LinkedList</tt>). Generic list</span></span><br><span class="line"><span class="comment"> * algorithms are encouraged to check whether the given list is an</span></span><br><span class="line"><span class="comment"> * <tt>instanceof</tt> this interface before applying an algorithm that would</span></span><br><span class="line"><span class="comment"> * provide poor performance if it were applied to a sequential access list,</span></span><br><span class="line"><span class="comment"> * and to alter their behavior if necessary to guarantee acceptable</span></span><br><span class="line"><span class="comment"> * performance.</span></span><br><span class="line"><span class="comment"> * 操作随机访问列表(如ArrayList)的最佳算法在顺序访问列表(如LinkedList)上应用,会产</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="comment"> *</span></span><br><span class="line"><span class="comment"> * <p>It is recognized that the distinction between random and sequential</span></span><br><span class="line"><span class="comment"> * access is often fuzzy. For example, some <tt>List</tt> implementations</span></span><br><span class="line"><span class="comment"> * provide asymptotically linear access times if they get huge, but constant</span></span><br><span class="line"><span class="comment"> * access times in practice. Such a <tt>List</tt> implementation</span></span><br><span class="line"><span class="comment"> * should generally implement this interface. As a rule of thumb, a</span></span><br><span class="line"><span class="comment"> * <tt>List</tt> implementation should implement this interface if,</span></span><br><span class="line"><span class="comment"> * for typical instances of the class, this loop:</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"> * <pre></span></span><br><span class="line"><span class="comment"> * for (int i=0, n=list.size(); i &lt; n; i++)</span></span><br><span class="line"><span class="comment"> * list.get(i);</span></span><br><span class="line"><span class="comment"> * </pre></span></span><br><span class="line"><span class="comment"> * runs faster than this loop:</span></span><br><span class="line"><span class="comment"> * 比下面的循环运行速度更快。</span></span><br><span class="line"><span class="comment"> * <pre></span></span><br><span class="line"><span class="comment"> * for (Iterator i=list.iterator(); i.hasNext(); )</span></span><br><span class="line"><span class="comment"> * i.next();</span></span><br><span class="line"><span class="comment"> * </pre></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">RandomAccess</span> </span>{</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><p>参考资料</p><p><a href="http://www.jianshu.com/p/d5ec2ff72b33" target="_blank" rel="noopener">Java集合干货系列-(二)LinkedList源码解析</a></p>]]></content>
</entry>
<entry>
<title>LinkedList 源码分析</title>
<link href="/2017/10/29/LinkedList%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
<url>/2017/10/29/LinkedList%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h2 id="结构体系"><a href="#结构体系" class="headerlink" title="结构体系"></a>结构体系</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LinkedList</span><<span class="title">E</span>></span></span><br><span class="line"><span class="class"> <span class="keyword">extends</span> <span class="title">AbstractSequentialList</span><<span class="title">E</span>></span></span><br><span class="line"><span class="class"> <span class="keyword">implements</span> <span class="title">List</span><<span class="title">E</span>>, <span class="title">Deque</span><<span class="title">E</span>>, <span class="title">Cloneable</span>, <span class="title">java</span>.<span class="title">io</span>.<span class="title">Serializable</span></span></span><br></pre></td></tr></table></figure><p><code>LinkedList</code>继承自<code>AbstractSequentialList</code>,并同时实现了<code>List</code>、<code>Deque</code>、<code>Cloneable</code>与<code>Serializable</code>接口。从继承体系上与接口上,可以看出<code>LinkedList</code>不仅仅是<strong>双向链表</strong>而已,它可以同时被当做<strong>双向队列</strong>进行操作。</p><p>为什么是双向链表呢?后面一小节将会解释。</p><h2 id="常量与重要成员"><a href="#常量与重要成员" class="headerlink" title="常量与重要成员"></a>常量与重要成员</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 记录链表上实际装载的节点个数</span></span><br><span class="line"><span class="keyword">transient</span> <span class="keyword">int</span> size = <span class="number">0</span>;</span><br><span class="line"><span class="comment">// 指向链表头部的指针</span></span><br><span class="line"><span class="keyword">transient</span> Node<E> first;</span><br><span class="line"><span class="comment">// 指向链表尾部的指针</span></span><br><span class="line"><span class="keyword">transient</span> Node<E> last;</span><br></pre></td></tr></table></figure><p>在成员变量中,我们可以看到链表所承载的节点是<code>Node</code>,下面看看<code>Node</code>是怎么构成的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Node</span><<span class="title">E</span>> </span>{</span><br><span class="line"> E item; <span class="comment">// 节点上承载的值</span></span><br><span class="line"> Node<E> next; <span class="comment">// 指向前一个节点</span></span><br><span class="line"> Node<E> prev; <span class="comment">// 指向后一个节点</span></span><br><span class="line"></span><br><span class="line"> Node(Node<E> prev, E element, Node<E> next) {</span><br><span class="line"> <span class="keyword">this</span>.item = element;</span><br><span class="line"> <span class="keyword">this</span>.next = next;</span><br><span class="line"> <span class="keyword">this</span>.prev = prev;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从<code>Node</code>中,不难看出<code>LinkedList</code>就是一个双向链表的集合,如下所示。</p><p><img src="http://on83riher.bkt.clouddn.com/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.png" alt=""></p><h2 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">LinkedList</span><span class="params">()</span> </span>{</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">LinkedList</span><span class="params">(Collection<? extends E> c)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>();</span><br><span class="line"> addAll(c);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>LinkedList</code>的构造函数可以说是很简约了,毕竟内部不像<code>ArrayList</code>里面还有个数组作支撑,只需头/尾指针即可。</p><h2 id="基本操作-node"><a href="#基本操作-node" class="headerlink" title="基本操作 - node()"></a>基本操作 - node()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在LinkedList中,在某些需要在指定节点中进行操作的时候,是怎么通过索引下标的形式找到指定节点的呢</span></span><br><span class="line"><span class="comment">// LinkedList内部提供了node()方法来实现这一目标。顺便一提node()内部充分使用了头尾节点的好处,将遍历范围缩减到size/2内</span></span><br><span class="line"><span class="function">Node<E> <span class="title">node</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> <span class="comment">// assert isElementIndex(index);</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 若index > size/2</span></span><br><span class="line"> <span class="keyword">if</span> (index < (size >> <span class="number">1</span>)) {</span><br><span class="line"> Node<E> x = first;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < index; i++)</span><br><span class="line"> x = x.next;</span><br><span class="line"> <span class="keyword">return</span> x;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> Node<E> x = last;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = size - <span class="number">1</span>; i > index; i--)</span><br><span class="line"> x = x.prev;</span><br><span class="line"> <span class="keyword">return</span> x;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="添加操作-add"><a href="#添加操作-add" class="headerlink" title="添加操作 - add()"></a>添加操作 - add()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">add</span><span class="params">(E e)</span> </span>{</span><br><span class="line"> linkLast(e);</span><br><span class="line"> <span class="keyword">return</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">public</span> <span class="keyword">void</span> <span class="title">add</span><span class="params">(<span class="keyword">int</span> index, E element)</span> </span>{</span><br><span class="line"> checkPositionIndex(index);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (index == size)</span><br><span class="line"> linkLast(element);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> linkBefore(element, node(index));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面的<code>add()</code>方法均调用了<code>link*()</code>字样的方法,其实这是<code>LinkedList</code>内部用于对链表的中<strong>单个节点</strong>的操作。相对应的<code>remove()</code>方法也会调用<code>unlink*()</code>字样的方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 作为头节点链接到链表中</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">linkFirst</span><span class="params">(E e)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Node<E> f = first;</span><br><span class="line"> <span class="keyword">final</span> Node<E> newNode = <span class="keyword">new</span> Node<>(<span class="keyword">null</span>, e, f);</span><br><span class="line"> first = newNode;</span><br><span class="line"> <span class="comment">// 若f为null, 则表明链表为空</span></span><br><span class="line"> <span class="keyword">if</span> (f == <span class="keyword">null</span>)</span><br><span class="line"> last = newNode;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> f.prev = newNode;</span><br><span class="line"> size++;</span><br><span class="line"> modCount++;</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">void</span> <span class="title">linkLast</span><span class="params">(E e)</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Node<E> l = last;</span><br><span class="line"> <span class="keyword">final</span> Node<E> newNode = <span class="keyword">new</span> Node<>(l, e, <span class="keyword">null</span>);</span><br><span class="line"> last = newNode;</span><br><span class="line"> <span class="comment">// 若l为null, 则表明链表为空</span></span><br><span class="line"> <span class="keyword">if</span> (l == <span class="keyword">null</span>)</span><br><span class="line"> first = newNode;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> l.next = newNode;</span><br><span class="line"> size++;</span><br><span class="line"> modCount++;</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">void</span> <span class="title">linkBefore</span><span class="params">(E e, Node<E> succ)</span> </span>{</span><br><span class="line"> <span class="comment">// assert succ != null;</span></span><br><span class="line"> <span class="keyword">final</span> Node<E> pred = succ.prev;</span><br><span class="line"> <span class="keyword">final</span> Node<E> newNode = <span class="keyword">new</span> Node<>(pred, e, succ);</span><br><span class="line"> succ.prev = newNode;</span><br><span class="line"> <span class="keyword">if</span> (pred == <span class="keyword">null</span>)</span><br><span class="line"> first = newNode;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> pred.next = newNode;</span><br><span class="line"> size++;</span><br><span class="line"> modCount++;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>LinkedList</code>也提供了将一组数据添加入链表中的方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">addAll</span><span class="params">(Collection<? extends E> c)</span> </span>{</span><br><span class="line"> <span class="comment">// 默认从尾节点后插入</span></span><br><span class="line"> <span class="keyword">return</span> addAll(size, c);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">addAll</span><span class="params">(<span class="keyword">int</span> index, Collection<? extends E> c)</span> </span>{</span><br><span class="line"> checkPositionIndex(index);</span><br><span class="line"></span><br><span class="line"> Object[] a = c.toArray();</span><br><span class="line"> <span class="keyword">int</span> numNew = a.length;</span><br><span class="line"> <span class="keyword">if</span> (numNew == <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line"> Node<E> pred, succ;</span><br><span class="line"> <span class="comment">// 若指定位置就是尾节点时</span></span><br><span class="line"> <span class="keyword">if</span> (index == size) {</span><br><span class="line"> succ = <span class="keyword">null</span>;</span><br><span class="line"> pred = last;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> succ = node(index);</span><br><span class="line"> pred = succ.prev;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (Object o : a) {</span><br><span class="line"> <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>) E e = (E) o;</span><br><span class="line"> Node<E> newNode = <span class="keyword">new</span> Node<>(pred, e, <span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">// 确保头节点始终不为空</span></span><br><span class="line"> <span class="keyword">if</span> (pred == <span class="keyword">null</span>)</span><br><span class="line"> first = newNode;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> pred.next = newNode;</span><br><span class="line"> pred = newNode;</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">if</span> (succ == <span class="keyword">null</span>) {</span><br><span class="line"> last = pred;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> pred.next = succ;</span><br><span class="line"> succ.prev = pred;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> size += numNew;</span><br><span class="line"> modCount++;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="删除操作-remove"><a href="#删除操作-remove" class="headerlink" title="删除操作 - remove()"></a>删除操作 - remove()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">remove</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> removeFirst();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">remove</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> checkElementIndex(index);</span><br><span class="line"> <span class="keyword">return</span> unlink(node(index));</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">removeFirst</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Node<E> f = first;</span><br><span class="line"> <span class="keyword">if</span> (f == <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NoSuchElementException();</span><br><span class="line"> <span class="keyword">return</span> unlinkFirst(f);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">removeLast</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">final</span> Node<E> l = last;</span><br><span class="line"><span class="keyword">if</span> (l == <span class="keyword">null</span>)</span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> NoSuchElementException();</span><br><span class="line"><span class="keyword">return</span> unlinkLast(l);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>remove()</code>如同<code>add()</code>一样,使用着功能截然相反的<code>unlink*()</code>方法,但逻辑处理难度上较大与<code>link*()</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 删除非空头节点</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> E <span class="title">unlinkFirst</span><span class="params">(Node<E> f)</span> </span>{</span><br><span class="line"> <span class="comment">// assert f == first && f != null;</span></span><br><span class="line"> <span class="keyword">final</span> E element = f.item;</span><br><span class="line"> <span class="keyword">final</span> Node<E> next = f.next;</span><br><span class="line"> f.item = <span class="keyword">null</span>;</span><br><span class="line"> f.next = <span class="keyword">null</span>; <span class="comment">// 方便GC进行垃圾回收</span></span><br><span class="line"> first = next;</span><br><span class="line"> <span class="comment">// 若链表中只有单个节点</span></span><br><span class="line"> <span class="keyword">if</span> (next == <span class="keyword">null</span>)</span><br><span class="line"> last = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> next.prev = <span class="keyword">null</span>;</span><br><span class="line"> size--;</span><br><span class="line"> modCount++;</span><br><span class="line"> <span class="keyword">return</span> element;</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">private</span> E <span class="title">unlinkLast</span><span class="params">(Node<E> l)</span> </span>{</span><br><span class="line"> <span class="comment">// assert l == last && l != null;</span></span><br><span class="line"> <span class="keyword">final</span> E element = l.item;</span><br><span class="line"> <span class="keyword">final</span> Node<E> prev = l.prev;</span><br><span class="line"> l.item = <span class="keyword">null</span>;</span><br><span class="line"> l.prev = <span class="keyword">null</span>; <span class="comment">// 方便GC进行垃圾回收</span></span><br><span class="line"> last = prev;</span><br><span class="line"> <span class="comment">// 若链表中只有单个节点</span></span><br><span class="line"> <span class="keyword">if</span> (prev == <span class="keyword">null</span>)</span><br><span class="line"> first = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> prev.next = <span class="keyword">null</span>;</span><br><span class="line"> size--;</span><br><span class="line"> modCount++;</span><br><span class="line"> <span class="keyword">return</span> element;</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">E <span class="title">unlink</span><span class="params">(Node<E> x)</span> </span>{</span><br><span class="line"> <span class="comment">// assert x != null;</span></span><br><span class="line"> <span class="keyword">final</span> E element = x.item;</span><br><span class="line"> <span class="keyword">final</span> Node<E> next = x.next;</span><br><span class="line"> <span class="keyword">final</span> Node<E> prev = x.prev;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 若prev指向的上一个节点为空,那么该节点为头节点</span></span><br><span class="line"> <span class="keyword">if</span> (prev == <span class="keyword">null</span>) {</span><br><span class="line"> first = next;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> prev.next = next;</span><br><span class="line"> x.prev = <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 若next指向的下一个节点为空,那么该节点为尾节点</span></span><br><span class="line"> <span class="keyword">if</span> (next == <span class="keyword">null</span>) {</span><br><span class="line"> last = prev;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> next.prev = prev;</span><br><span class="line"> x.next = <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> x.item = <span class="keyword">null</span>;</span><br><span class="line"> size--;</span><br><span class="line"> modCount++;</span><br><span class="line"> <span class="keyword">return</span> element;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>remove()</code>操作中还提供了根据给定值删除指定节点的方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">remove</span><span class="params">(Object o)</span> </span>{</span><br><span class="line"> <span class="comment">// 若给定值为null</span></span><br><span class="line"> <span class="keyword">if</span> (o == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 在这里就不能缩减范围提高效率了,只能老实的进行从头遍历到尾</span></span><br><span class="line"> <span class="keyword">for</span> (Node<E> x = first; x != <span class="keyword">null</span>; x = x.next) {</span><br><span class="line"> <span class="keyword">if</span> (x.item == <span class="keyword">null</span>) {</span><br><span class="line"> unlink(x);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">for</span> (Node<E> x = first; x != <span class="keyword">null</span>; x = x.next) {</span><br><span class="line"> <span class="keyword">if</span> (o.equals(x.item)) {</span><br><span class="line"> unlink(x);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</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> <span class="keyword">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="查询操作-get"><a href="#查询操作-get" class="headerlink" title="查询操作 - get()"></a>查询操作 - get()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">get</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> checkElementIndex(index);</span><br><span class="line"> <span class="keyword">return</span> node(index).item;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">getFirst</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">final</span> Node<E> f = first;</span><br><span class="line"><span class="keyword">if</span> (f == <span class="keyword">null</span>)</span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> NoSuchElementException();</span><br><span class="line"><span class="keyword">return</span> f.item;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">getLast</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">final</span> Node<E> l = last;</span><br><span class="line"><span class="keyword">if</span> (l == <span class="keyword">null</span>)</span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> NoSuchElementException();</span><br><span class="line"><span class="keyword">return</span> l.item;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="修改操作-set"><a href="#修改操作-set" class="headerlink" title="修改操作 - set()"></a>修改操作 - set()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">set</span><span class="params">(<span class="keyword">int</span> index, E element)</span> </span>{</span><br><span class="line"> checkElementIndex(index);</span><br><span class="line"> Node<E> x = node(index);</span><br><span class="line"> E oldVal = x.item;</span><br><span class="line"> x.item = element;</span><br><span class="line"> <span class="keyword">return</span> oldVal;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="实现了Deque接口的相关操作"><a href="#实现了Deque接口的相关操作" class="headerlink" title="实现了Deque接口的相关操作"></a>实现了Deque接口的相关操作</h2><p>别忘了,LinkedList实现了Deque接口,这说明操作LinkedList也可以像操作双向队列一样。如同<code>*first()</code>以及<code>*last()</code>这种操作也属于Deque要求实现的,下面将列出别的方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">peek</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Node<E> f = first;</span><br><span class="line"> <span class="keyword">return</span> (f == <span class="keyword">null</span>) ? <span class="keyword">null</span> : f.item;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">poll</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">final</span> Node<E> f = first;</span><br><span class="line"><span class="keyword">return</span> (f == <span class="keyword">null</span>) ? <span class="keyword">null</span> : unlinkFirst(f);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
</entry>
<entry>
<title>Map.Entry 使用解析</title>
<link href="/2017/10/28/Map-Entry%20%E4%BD%BF%E7%94%A8%E8%A7%A3%E6%9E%90/"/>
<url>/2017/10/28/Map-Entry%20%E4%BD%BF%E7%94%A8%E8%A7%A3%E6%9E%90/</url>
<content type="html"><![CDATA[<h2 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h2><p><code>Map.Entry</code>是<code>Map</code>声明的一个内部接口,此接口为泛型,定义为<code>Entry<K,V></code>。它可用于 表示Map中的一个键值对。</p><p>在<code>Map</code>提供的<code>EntrySet()</code>返回的是<code>Set<Map.Entry<K,V>></code>,是一个<code>Set</code>集合,刺激和类型是<code>Map.Entry</code>。相较于<code>Map</code>所提供的另一个方法<code>keySet()</code>,它所提供的以<code>key</code>值为数据的<code>Set</code>集合。</p><h2 id="使用以及对比"><a href="#使用以及对比" class="headerlink" title="使用以及对比"></a>使用以及对比</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// keySet()循环遍历</span></span><br><span class="line"><span class="keyword">for</span> (Object key: map.keySet()) {</span><br><span class="line"> Object key = key;</span><br><span class="line"> Object value = map.get(key);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// entrySet()循环遍历</span></span><br><span class="line"><span class="keyword">for</span> (Map.Entry<Object, Object> entry: map.entrySet()) {</span><br><span class="line"> Object = entry.getKey();</span><br><span class="line"> Object = entry.getValue();</span><br><span class="line"> <span class="comment">// entry.setValue()</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以上两种方式中,相较于<code>keySet()</code>遍历<code>Map</code>,<code>entrySet()</code>能够更加清晰的显示<code>Map</code>内部的数据结构,同时<code>entrySet()</code>提供了用于修改<code>Map</code>的值。</p><hr><p>参考链接</p><p><a href="http://www.cnblogs.com/ningvsban/archive/2013/05/06/3062217.html" target="_blank" rel="noopener">Map.Entry使用详解</a></p>]]></content>
</entry>
<entry>
<title>ArrayList 源码分析</title>
<link href="/2017/10/27/ArrayList%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
<url>/2017/10/27/ArrayList%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h2 id="常量与重要的成员变量"><a href="#常量与重要的成员变量" class="headerlink" title="常量与重要的成员变量"></a>常量与重要的成员变量</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><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">int</span> DEFAULT_CAPACITY = <span class="number">10</span>;</span><br><span class="line"></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> Object[] EMPTY_ELEMENTDATA = {};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 默认空数组-对应第二个构造函数。这个与上面EMPTY_ELEMENTDATA要进行区别,根据源码解释说到,两个变量决定了当第一次插入数据时容器的扩容机制,在这里相当于起到了标志位的作用。实际操作看扩容与缩容部分</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};</span><br><span class="line"></span><br><span class="line"><span class="comment">// ArrayList内部的真实容器(数组)</span></span><br><span class="line"><span class="keyword">transient</span> Object[] elementData;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ArrayList内部的记录实际装载数据个数</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">int</span> size;</span><br></pre></td></tr></table></figure><h2 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 根据用户自定义容量初始化容器</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ArrayList</span><span class="params">(<span class="keyword">int</span> initialCapacity)</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">this</span>.elementData = <span class="keyword">new</span> Object[initialCapacity];</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (initialCapacity == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">this</span>.elementData = EMPTY_ELEMENTDATA;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"Illegal Capacity: "</span>+</span><br><span class="line"> initialCapacity);</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="function"><span class="keyword">public</span> <span class="title">ArrayList</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 由一组数组进行容器的初始化(前提该数组必须是实现了Collection接口,并继承或来源于<E>)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ArrayList</span><span class="params">(Collection<? extends E> c)</span> </span>{</span><br><span class="line"> elementData = c.toArray();</span><br><span class="line"> <span class="keyword">if</span> ((size = elementData.length) != <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 倘若返回的Class类型并非Object[],需要Arrays.copy()将其类型转化为Object[]</span></span><br><span class="line"> <span class="keyword">if</span> (elementData.getClass() != Object[].class)</span><br><span class="line"> elementData = Arrays.copyOf(elementData, size, Object[].class);</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="keyword">this</span>.elementData = EMPTY_ELEMENTDATA;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在<code>EMPTY_ELEMENTDATA</code>与<code>DEFAULTCAPACITY_EMPTY_ELEMENTDATA</code>两个标志位,个人认为:</p><ul><li>如果以默认的构造函数模式初始化ArrayList,则以ArrayList内部的增长模式扩展,即初始化时容器大小就是DEFAULT_CAPACITY,即为10</li><li>如果</li></ul><h2 id="增加操作-——-add"><a href="#增加操作-——-add" class="headerlink" title="增加操作 —— add()"></a>增加操作 —— add()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><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="keyword">boolean</span> <span class="title">add</span><span class="params">(E e)</span> </span>{</span><br><span class="line"> <span class="comment">// 无论是哪种插入操作,都需要提前进行扩容,防止抛出ArrayIndexOutOfBoundsException</span></span><br><span class="line"> ensureCapacityInternal(size + <span class="number">1</span>);</span><br><span class="line"> elementData[size++] = e;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</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="keyword">void</span> <span class="title">add</span><span class="params">(<span class="keyword">int</span> index, E element)</span> </span>{</span><br><span class="line"> <span class="comment">// if (index<0 || index>size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));</span></span><br><span class="line"> rangeCheckForAdd(index);</span><br><span class="line"></span><br><span class="line"> ensureCapacityInternal(size + <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// System.arraycopy(Object src, int srcPos,</span></span><br><span class="line"> <span class="comment">//Object dest, int destPos,</span></span><br><span class="line"> <span class="comment">//int length)</span></span><br><span class="line"> <span class="comment">// 即从原数组(src)中的指定位置(src),复制一定长度的数据(length),到目标数组(dest)的指定位置上(destPos)</span></span><br><span class="line"> System.arraycopy(elementData, index, elementData, index + <span class="number">1</span>,</span><br><span class="line"> size - index);</span><br><span class="line"> elementData[index] = element;</span><br><span class="line"> size++;</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="keyword">boolean</span> <span class="title">addAll</span><span class="params">(Collection<? extends E> c)</span> </span>{</span><br><span class="line"> Object[] a = c.toArray();</span><br><span class="line"> <span class="keyword">int</span> numNew = a.length;</span><br><span class="line"> ensureCapacityInternal(size + numNew);</span><br><span class="line"> System.arraycopy(a, <span class="number">0</span>, elementData, size, numNew);</span><br><span class="line"> size += numNew;</span><br><span class="line"> <span class="keyword">return</span> numNew != <span class="number">0</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="keyword">boolean</span> <span class="title">addAll</span><span class="params">(<span class="keyword">int</span> index, Collection<? extends E> c)</span> </span>{</span><br><span class="line"> rangeCheckForAdd(index);</span><br><span class="line"></span><br><span class="line"> Object[] a = c.toArray();</span><br><span class="line"> <span class="keyword">int</span> numNew = a.length;</span><br><span class="line"> ensureCapacityInternal(size + numNew);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 原数组中需要移动的个数</span></span><br><span class="line"> <span class="keyword">int</span> numMoved = size - index;</span><br><span class="line"> <span class="keyword">if</span> (numMoved > <span class="number">0</span>)</span><br><span class="line"> System.arraycopy(elementData, index, elementData, index + numNew,</span><br><span class="line"> numMoved);</span><br><span class="line"></span><br><span class="line"> System.arraycopy(a, <span class="number">0</span>, elementData, index, numNew);</span><br><span class="line"> size += numNew;</span><br><span class="line"> <span class="keyword">return</span> numNew != <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在ArrayList内部,数组的移动往往通过<code>System.arraycopy()</code>和<code>Array.copy()</code>进行操作。这种方式增加了提高了内聚性,也避免了大量的重复代码出现在不同地方中。</p><h2 id="扩容与缩容操作-ensureCapacity-与trimToSize"><a href="#扩容与缩容操作-ensureCapacity-与trimToSize" class="headerlink" title="扩容与缩容操作 - ensureCapacity()与trimToSize()"></a>扩容与缩容操作 - ensureCapacity()与trimToSize()</h2><h3 id="扩容"><a href="#扩容" class="headerlink" title="扩容"></a>扩容</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 方法域为public,即该方法是方便用户根据自己的需求直接扩展容器容量</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">ensureCapacity</span><span class="params">(<span class="keyword">int</span> minCapacity)</span> </span>{</span><br><span class="line"> <span class="comment">// 此时出现了DEFAULTCAPACITY_EMPTY_ELEMENTDATA标志,该字段是表明不同的标志位所要求的最低扩展阈值不同</span></span><br><span class="line"> <span class="keyword">int</span> minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)</span><br><span class="line"> ? <span class="number">0</span></span><br><span class="line"> : DEFAULT_CAPACITY;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (minCapacity > minExpand) {</span><br><span class="line"> <span class="comment">// 最终调用内部的ensureCapacityInternal()</span></span><br><span class="line"> ensureExplicitCapacity(minCapacity);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法域为private,内部只存在简单的判断逻辑,用于判断该数组是不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">ensureCapacityInternal</span><span class="params">(<span class="keyword">int</span> minCapacity)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {</span><br><span class="line"> minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ensureExplicitCapacity(minCapacity);</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">private</span> <span class="keyword">void</span> <span class="title">ensureExplicitCapacity</span><span class="params">(<span class="keyword">int</span> minCapacity)</span> </span>{</span><br><span class="line"> modCount++;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (minCapacity - elementData.length > <span class="number">0</span>)</span><br><span class="line"> grow(minCapacity);</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">private</span> <span class="keyword">void</span> <span class="title">grow</span><span class="params">(<span class="keyword">int</span> minCapacity)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> oldCapacity = elementData.length;</span><br><span class="line"> <span class="keyword">int</span> newCapacity = oldCapacity + (oldCapacity >> <span class="number">1</span>); <span class="comment">// 新容器大小为旧容器的1.5倍</span></span><br><span class="line"> <span class="keyword">if</span> (newCapacity - minCapacity < <span class="number">0</span>)</span><br><span class="line"> newCapacity = minCapacity;</span><br><span class="line"> <span class="keyword">if</span> (newCapacity - MAX_ARRAY_SIZE > <span class="number">0</span>)</span><br><span class="line"> newCapacity = hugeCapacity(minCapacity);</span><br><span class="line"> <span class="comment">// minCapacity is usually close to size, so this is a win:</span></span><br><span class="line"> elementData = Arrays.copyOf(elementData, newCapacity);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="缩容"><a href="#缩容" class="headerlink" title="缩容"></a>缩容</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 由于ArrayList本身是不会进行缩容的,于是在进行大量的数据插入删除后,会造成大面积的空间浪费</span></span><br><span class="line"><span class="comment">// 此时用户可以自己通过trimToSize()来缩容</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">trimToSize</span><span class="params">()</span> </span>{</span><br><span class="line"> modCount++;</span><br><span class="line"> <span class="keyword">if</span> (size < elementData.length) {</span><br><span class="line"> elementData = (size == <span class="number">0</span>)</span><br><span class="line"> ? EMPTY_ELEMENTDATA</span><br><span class="line"> : Arrays.copyOf(elementData, size);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="删除操作-remove"><a href="#删除操作-remove" class="headerlink" title="删除操作 - remove()"></a>删除操作 - remove()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在指定位置上删除数据</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">remove</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> rangeCheck(index);</span><br><span class="line"></span><br><span class="line"> modCount++;</span><br><span class="line"> E oldValue = elementData(index);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> numMoved = size - index - <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span> (numMoved > <span class="number">0</span>)</span><br><span class="line"> <span class="comment">// 数组直接向左移动ß</span></span><br><span class="line"> System.arraycopy(elementData, index+<span class="number">1</span>, elementData, index,</span><br><span class="line"> numMoved);</span><br><span class="line"> elementData[--size] = <span class="keyword">null</span>; <span class="comment">// 进行清空操作,方便GC回收</span></span><br><span class="line"></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"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">remove</span><span class="params">(Object o)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (o == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> index = <span class="number">0</span>; index < size; index++)</span><br><span class="line"> <span class="keyword">if</span> (elementData[index] == <span class="keyword">null</span>) {</span><br><span class="line"> fastRemove(index);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> index = <span class="number">0</span>; index < size; index++)</span><br><span class="line"> <span class="keyword">if</span> (o.equals(elementData[index])) {</span><br><span class="line"> fastRemove(index);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">fastRemove</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> modCount++;</span><br><span class="line"> <span class="keyword">int</span> numMoved = size - index - <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span> (numMoved > <span class="number">0</span>)</span><br><span class="line"> System.arraycopy(elementData, index+<span class="number">1</span>, elementData, index,</span><br><span class="line"> numMoved);</span><br><span class="line"> elementData[--size] = <span class="keyword">null</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 还存在其他删除操作,但原理基本相同</span></span><br></pre></td></tr></table></figure><h2 id="查找操作-get"><a href="#查找操作-get" class="headerlink" title="查找操作 - get()"></a>查找操作 - get()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 时间复杂度O(1)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">get</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> rangeCheck(index);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> elementData(index);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line"><span class="function">E <span class="title">elementData</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> (E) elementData[index];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="修改操作-set"><a href="#修改操作-set" class="headerlink" title="修改操作 - set()"></a>修改操作 - set()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 时间复杂度O(1)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">set</span><span class="params">(<span class="keyword">int</span> index, E element)</span> </span>{</span><br><span class="line"> rangeCheck(index);</span><br><span class="line"></span><br><span class="line"> E oldValue = elementData(index);</span><br><span class="line"> elementData[index] = element;</span><br><span class="line"> <span class="keyword">return</span> oldValue;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="三种遍历ArrayList的方式"><a href="#三种遍历ArrayList的方式" class="headerlink" title="三种遍历ArrayList的方式"></a>三种遍历ArrayList的方式</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第一种,通过迭代器遍历</span></span><br><span class="line">Integer value = <span class="keyword">null</span>;</span><br><span class="line">Iterator iter = list.iterator();</span><br><span class="line"><span class="keyword">while</span> (iter.hasNext()) {</span><br><span class="line"> value = (Integer)iter.next();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 第二种,随机访问(RandomAccess),通过索引值去遍历 -> 效率最高</span></span><br><span class="line">Integer value = <span class="keyword">null</span>;</span><br><span class="line"><span class="keyword">int</span> size = list.size();</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < size; i++) {</span><br><span class="line"> value = (Integer) list.get(i); </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 第三种,for循环遍历 -> 效率最低</span></span><br><span class="line">Integer value = <span class="keyword">null</span>;</span><br><span class="line"><span class="keyword">for</span> (Integer integer: list) {</span><br><span class="line"> value = integer;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><p>参考资料:</p><p><a href="http://www.jianshu.com/p/2cd7be850540" target="_blank" rel="noopener">Java集合干货系列-(一)ArrayList源码解析</a></p>]]></content>
</entry>
<entry>
<title>HashMap 源码分析</title>
<link href="/2017/10/26/HashMap%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
<url>/2017/10/26/HashMap%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>下图可从可视化的角度理解 HashMap(其实也是方便自己想起来)。</p><p><img src="http://4.bp.blogspot.com/-unPwpp8AJTA/U0e9S0F5ljI/AAAAAAAAAUo/xMnUVRO5fyY/s1600/how+hashmap+works+internally+in+java+.png" alt=""></p><h2 id="常量与重要的成员变量"><a href="#常量与重要的成员变量" class="headerlink" title="常量与重要的成员变量"></a>常量与重要的成员变量</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><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">1</span> << <span class="number">4</span>; <span class="comment">// aka 16</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">// 默认负载因子</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">// 从链表转变为红黑树的阈值</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">// 从红黑树转变为链表的阈值</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">// 从链表转变为红黑树的最小容量</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><br><span class="line"><span class="comment">// HashMap 实际存储键值对的容器</span></span><br><span class="line"><span class="keyword">transient</span> Node<K,V>[] table;</span><br><span class="line"><span class="comment">// HashMap 实际阈值,其值由 capacity * loadFactor 决定</span></span><br><span class="line"><span class="keyword">int</span> threshold;</span><br></pre></td></tr></table></figure><h2 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><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> +</span><br><span class="line"> 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> +</span><br><span class="line"> loadFactor);</span><br><span class="line"> <span class="keyword">this</span>.loadFactor = loadFactor;</span><br><span class="line"> <span class="comment">// 重点!</span></span><br><span class="line"> <span class="keyword">this</span>.threshold = tableSizeFor(initialCapacity);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 该方法用于返回大于给定容量的最小2的幂次方的数值</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">tableSizeFor</span><span class="params">(<span class="keyword">int</span> cap)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> n = cap - <span class="number">1</span>;</span><br><span class="line"> n |= n >>> <span class="number">1</span>;</span><br><span class="line"> n |= n >>> <span class="number">2</span>;</span><br><span class="line"> n |= n >>> <span class="number">4</span>;</span><br><span class="line"> n |= n >>> <span class="number">8</span>;</span><br><span class="line"> n |= n >>> <span class="number">16</span>;</span><br><span class="line"> <span class="keyword">return</span> (n < <span class="number">0</span>) ? <span class="number">1</span> : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + <span class="number">1</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>为什么 HashMap 的容量数值非要是2的幂次方呢?请看<a href="https://www.zhihu.com/question/20733617" target="_blank" rel="noopener">JDK 源码中 HashMap 的 hash 方法原理是什么?</a> </p><h2 id="hash"><a href="#hash" class="headerlink" title="hash()"></a>hash()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><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="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>HashMap 中的实际 hash 值计算是通过 <code>key.hashCode()</code>所得出来的<code>h</code> ,与<code>h</code>无条件右移16位后,进行按位异或<code>^</code>得出来的。</p><p>但是怎么转化成实际上<code>table</code>数组的所索引值呢?剧透一下,<code>table</code> 的索引值是通过 <code>capacity</code>与<code>hash</code>进行按位与<code>&</code>计算出来的。</p><h2 id="putVal"><a href="#putVal" class="headerlink" title="putVal()"></a>putVal()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><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><br><span class="line"> <span class="comment">// 当HashMap中的数组,即table为空,或者table的长度为0时,调用 resize 方式进行 HashMap 的初始化(HashMap真正的容器初始化阶段是在第一次插入时)</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><br><span class="line"> <span class="comment">// 根据n(capacity)-1与hash值进行按位运算,获得该key值对应的数组中的位置。若该索引(p)上的值为null,则直接创建新的节点</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"> tab[i] = newNode(hash, key, value, <span class="keyword">null</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 该索引上的值不为null,那么需要分以下三种情况分析</span></span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> Node<K,V> e; K k;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 该p的hash值与传入的hash值相等,并且p的key值也与传入的key值相等,或者在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"> e = p;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 若p的key值不等于传入的key值</span></span><br><span class="line"> <span class="comment">// p的类型属于TreeNode,即从属于红黑树,则转由红黑树进行实际节点添加的操作</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"> e = ((TreeNode<K,V>)p).putTreeVal(<span class="keyword">this</span>, tab, hash, key, value);</span><br><span class="line"> <span class="comment">// p的类型属于Node,即从属于链表。这里就是HashMap中怎么处理哈希冲突的办法。</span></span><br><span class="line"> <span class="comment">// 当传入元素的hash值与数组上的元素相同,但key不同时。</span></span><br><span class="line"> <span class="keyword">else</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="keyword">if</span> ((e = p.next) == <span class="keyword">null</span>) {</span><br><span class="line"> p.next = newNode(hash, key, value, <span class="keyword">null</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="keyword">break</span>;</span><br><span class="line"> }</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">break</span>;</span><br><span class="line"> p = e;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 当上述的添加新节点的阶段结束后,若此时的e(即原始节点)不为空时,则进行值的替换。</span></span><br><span class="line"> <span class="keyword">if</span> (e != <span class="keyword">null</span>) {</span><br><span class="line"> V oldValue = e.value;</span><br><span class="line"> <span class="keyword">if</span> (!onlyIfAbsent || oldValue == <span class="keyword">null</span>)</span><br><span class="line"> e.value = value;</span><br><span class="line"> afterNodeAccess(e);</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><br><span class="line"> ++modCount;<span class="comment">// 用于记录修改的操作次数</span></span><br><span class="line"><span class="comment">// 若此时的容器容量大于阈值时,进行resize()扩容容器</span></span><br><span class="line"> <span class="keyword">if</span> (++size > threshold)</span><br><span class="line"> resize();</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><h2 id="get"><a href="#get" class="headerlink" title="get()"></a>get()</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 如果看懂了putVal(),那么get()就是同样的方式分析了</span></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="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="keyword">if</span> ((e = first.next) != <span class="keyword">null</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="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><h2 id="resize"><a href="#resize" class="headerlink" title="resize()"></a>resize()</h2><p><code>resize()</code>实际上的目的在于将原数组中的值均匀地平摊到新数组中,这样无论是插入还是访问的效率也会有一定的提升。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 这一部分分析难度不亚于putVal()</span></span><br><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><br><span class="line"> <span class="comment">// 若老数组为0,那么老容量为0,否则为老数组长度</span></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><br><span class="line"> <span class="comment">// 若老容量大于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">// 若扩容后的新容量小于最大容量阈值且老容量大于默认容量值,则新阈值为老阈值的两倍</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 &&</span><br><span class="line"> 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><br><span class="line"> <span class="comment">// 若老容量等于0且老阈值大于0,那么新容量就等于老阈值</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (oldThr > <span class="number">0</span>)</span><br><span class="line"> newCap = oldThr;</span><br><span class="line"> <span class="comment">// 若老容量等于0且老阈值也为0,这种比较极端了</span></span><br><span class="line"> <span class="comment">// 新容量为默认容量值,而新阈值也为默认阈值(0.75)</span></span><br><span class="line"> <span class="keyword">else</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><br><span class="line"> <span class="comment">// 若新阈值为0,那么则由负载因子与新容量的乘积获得</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><br><span class="line"> (<span class="keyword">int</span>)ft : Integer.MAX_VALUE);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> threshold = newThr;</span><br><span class="line"> <span class="meta">@SuppressWarnings</span>({<span class="string">"rawtypes"</span>,<span class="string">"unchecked"</span>})</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 实际操作部分,初始化新容器!</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><br><span class="line"> <span class="comment">// 其实HashMap的初始化阶段从这里就结束了,以下部分只适用于存有实际节点的容器</span></span><br><span class="line"> <span class="keyword">if</span> (oldTab != <span class="keyword">null</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> 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="comment">// 若该索引上的节点部位不为空,则分以下三种情况分析</span></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="comment">// 单个节点</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="comment">// 红黑树</span></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="comment">// 链表</span></span><br><span class="line"> <span class="keyword">else</span> { <span class="comment">// preserve order</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="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="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="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="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><br><span class="line"> <span class="keyword">return</span> newTab;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>实际可视化操作如下所示:</p><p><img src="http://upload-images.jianshu.io/upload_images/1541350-f1221a46429fffc9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p><h2 id="为什么-HashMap-不是线程安全的?"><a href="#为什么-HashMap-不是线程安全的?" class="headerlink" title="为什么 HashMap 不是线程安全的?"></a>为什么 HashMap 不是线程安全的?</h2><p>根据《Java并发编程的艺术》中写道:</p><blockquote><p>HashMap 在并发执行 put 操作时会引起死循环,导致 CPU 利用率接近100%。因为多线程会导致 HashMap 的 Node 链表形成环形数据结构,一旦形成环形数据结构,Node 的 next 节点永远不为空,就会在获取 Node 时产生死循环。</p></blockquote><p>实际原理可以<a href="https://coolshell.cn/articles/9606.html" target="_blank" rel="noopener">疫苗:JAVA HASHMAP的死循环</a>一文。</p>]]></content>
</entry>
<entry>
<title>《别让我思考》读书笔记</title>
<link href="/2017/10/12/%E3%80%8A%E5%88%AB%E8%AE%A9%E6%88%91%E6%80%9D%E8%80%83%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
<url>/2017/10/12/%E3%80%8A%E5%88%AB%E8%AE%A9%E6%88%91%E6%80%9D%E8%80%83%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h3 id="别让我思考"><a href="#别让我思考" class="headerlink" title="别让我思考"></a>别让我思考</h3><h4 id="Krug-可用性第一定律"><a href="#Krug-可用性第一定律" class="headerlink" title="Krug 可用性第一定律"></a>Krug 可用性第一定律</h4><p><strong>设计者应该尽量做到,当我看一个页面时,他应该是不言而喻、一目了然、自我解释的。</strong>我应该能明白它——它是什么,怎样使用它——而不需要花费精力进行思考。</p><p>网页上每项内容都有可能迫使我们停下来,进行不必要的思考。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">作为一个用户,永远不该让我们花上几微秒去思考某个东西是否能点击。你可能会这么想,“其实,找出某个东西是否能点击并不需要花费多大工夫。如果你将鼠标移过去,它的光标由箭头变成一只小手,就表示可以点击。这会有很大的问题吗?”。</span><br><span class="line"></span><br><span class="line">问题是,当我们访问 Web 的时候,每个问号都会加重我们的认知负担,把我们的注意力从要完成的任务上拉开。这种干扰也许很轻微,但它们会累积起来,有时候这样的干扰不用太多,就足以让我们抓狂。况且,人们通常不喜欢苦苦思索背后的原理。</span><br></pre></td></tr></table></figure><hr><h3 id="我们实际上是如何使用-Web-的"><a href="#我们实际上是如何使用-Web-的" class="headerlink" title="我们实际上是如何使用 Web 的"></a>我们实际上是如何使用 Web 的</h3><h4 id="扫描,满意即可,勉强应付"><a href="#扫描,满意即可,勉强应付" class="headerlink" title="扫描,满意即可,勉强应付"></a>扫描,满意即可,勉强应付</h4><p>如果想设计有效地网页,你必须开始接受关于网络使用情况的三个事实。</p><h5 id="第一个事实:我们不是阅读,二是扫描"><a href="#第一个事实:我们不是阅读,二是扫描" class="headerlink" title="第一个事实:我们不是阅读,二是扫描"></a>第一个事实:我们不是阅读,二是扫描</h5><p><strong>人们会花极少的时间来阅读大部分的页面,其实,我们只是扫描一下(或者匆匆掠过)网页,寻找能够吸引我们注意力的文字或词语。</strong></p><p>我们为什么扫描:</p><ul><li><strong>我们总是处于忙碌之中。</strong>Web 用户的行为更像鲨鱼,即它们不得不一直移动,否则就会死掉。我们没有时间阅读那些不必要的内容。</li><li><strong>我们知道自己不必阅读所有内容。</strong>在绝大多数页面上,我们实际上只对其中一小部分内容感兴趣,剩下的内容我们并不关心。</li><li><strong>我们善于扫描。</strong></li></ul><h5 id="第二个事实:我们不做最佳选择,而是满意即可"><a href="#第二个事实:我们不做最佳选择,而是满意即可" class="headerlink" title="第二个事实:我们不做最佳选择,而是满意即可"></a>第二个事实:我们不做最佳选择,而是满意即可</h5><p>在设计页面时,我们通常假设用户只是扫过整个页面,考虑所有可能的选项,然后选择一个最好的。<strong>然而,事实上,大多数时间里我们不会选择最佳选项,而是选择第一个合理的选项,这就是满意策略。</strong>一旦我们发现一个链接,看起来似乎能够跳转到我们想去的地方,那就是一个我们将会点击它的大好机会。</p><p>我们为什么不寻找最佳选择:</p><ul><li><strong>我们总是处于忙碌之中。</strong></li><li><strong>如果猜错了,也不会产生什么严重的后果。</strong>与救火不同,在网站上做了一次错误选择的后果通常只是点击几次后退按钮。</li><li><strong>对选择进行权衡并不会改善我们的机会。</strong></li><li><strong>猜测更有意思。</strong>猜测不会像仔细衡量那么累,而且如果猜对了,速度会更快。他还会带来一个机会因素——有可能无意中看到某个令人意外但不错的内容,这种可能性让人开心。</li></ul><h5 id="第三个事实:我们不是追根究底,而是勉强应对"><a href="#第三个事实:我们不是追根究底,而是勉强应对" class="headerlink" title="第三个事实:我们不是追根究底,而是勉强应对"></a>第三个事实:我们不是追根究底,而是勉强应对</h5><p>在很大程度上人们一直在使用这些东西,但并不理解它们的运作原理,甚至对它们的工作原理有完全错误的理解。无论面对哪种技术,很少有人会花时间读说明书。<strong>相反,我们贸然前进,勉强应对,编造出我们自己模棱两可的故事,来解释我们的所作所为,以及为什么这样能行得通。</strong></p><p>为什么会这样:</p><ul><li><strong>这对我们来说并不重要。</strong>对于我们中的大多数人来说,是否明白事物背后的工作机制并不重要,只要我们能正常使用它们即可。这并不是智力低下的表现,而是我们并不关心。</li><li><strong>如果发现某个事物能用,我们会一直用它。</strong>我们一旦发现某个事物能够用(不管有多难用),我们也不会去找一种更好的方法(至少不会主动去找)。</li></ul><hr><h3 id="广告牌设计101法则"><a href="#广告牌设计101法则" class="headerlink" title="广告牌设计101法则"></a>广告牌设计101法则</h3><h4 id="为扫描设计,不为阅读设计"><a href="#为扫描设计,不为阅读设计" class="headerlink" title="为扫描设计,不为阅读设计"></a>为扫描设计,不为阅读设计</h4><p>如果用户们都是疾驰而过,那么,你需要注意以下5个重要方面,来保证他们尽可能地看到了并理解了你的网站:</p><h5 id="在每个页面上建立清楚的视觉层次。"><a href="#在每个页面上建立清楚的视觉层次。" class="headerlink" title="在每个页面上建立清楚的视觉层次。"></a>在每个页面上建立清楚的视觉层次。</h5><ul><li>越重要的部分越突出。</li><li>逻辑上相关的部分在视觉上也相关。</li><li>逻辑上包含的部分在视觉上进行嵌套。</li></ul><h5 id="尽可能利用习惯用法。"><a href="#尽可能利用习惯用法。" class="headerlink" title="尽可能利用习惯用法。"></a>尽可能利用习惯用法。</h5><ul><li><strong>(优)它们非常有用。</strong>通常,习惯用法因为有用才会成为习惯用法。适当使用习惯用法会使用户在网站之间的访问更容易,不需要花费额外的努力来得到背后的工作原理。</li><li><strong>(劣)设计师通常不愿意利用他们。</strong>和使用习惯用法相比,设计师们都面临着很大的诱惑,想要重新发明轮子很大程度上是因为他们觉得他们的职业使命感,趋势他们去做一些崭新的,与众不同的设计。</li></ul><h5 id="把页面划分成明确定义的区域。"><a href="#把页面划分成明确定义的区域。" class="headerlink" title="把页面划分成明确定义的区域。"></a>把页面划分成明确定义的区域。</h5><p>把页面划分成明确意义的区域,可以让用户很快决定关注页面的哪些区域,或者放心地跳过哪些区域。</p><h5 id="明显标识可以点击的地方。"><a href="#明显标识可以点击的地方。" class="headerlink" title="明显标识可以点击的地方。"></a>明显标识可以点击的地方。</h5><h5 id="最大限度降低干扰。"><a href="#最大限度降低干扰。" class="headerlink" title="最大限度降低干扰。"></a>最大限度降低干扰。</h5><p>有效降低噪音的方式——在设计页面的收,先假定所有的内容都是视觉噪声,除非得到证明它们不是。</p><hr><h3 id="动物、植物、无机物"><a href="#动物、植物、无机物" class="headerlink" title="动物、植物、无机物"></a>动物、植物、无机物</h3><h4 id="为什么用户喜欢无须思考的选择"><a href="#为什么用户喜欢无须思考的选择" class="headerlink" title="为什么用户喜欢无须思考的选择"></a>为什么用户喜欢无须思考的选择</h4><p><strong>“点击多少次都没关系,只要每次点击都是无须思考、明确无误的选择。”——Krug 可用性第二定律</strong></p><p>如果我们需要一直在网络上进行选择,那么让这些选择变得无须思考是让一个网站容易使用的主要因素。</p><hr><h3 id="省略不必要的文字"><a href="#省略不必要的文字" class="headerlink" title="省略不必要的文字"></a>省略不必要的文字</h3><h4 id="不要在-Web-上写作的艺术"><a href="#不要在-Web-上写作的艺术" class="headerlink" title="不要在 Web 上写作的艺术"></a>不要在 Web 上写作的艺术</h4><p><strong>“去掉每个页面上一半的文字,然后把剩下的文字再去掉一半。”——Krug 可用性第三定律</strong></p><p><strong>省略多余的文字。</strong>有力的文字都很简练。句子里不应该有多余的文字,段落中不应该有多余的句子。同样,画上不应该有多余的线条,机器上不应该有多余的零件。</p><hr><h3 id="街头指示牌和面包屑"><a href="#街头指示牌和面包屑" class="headerlink" title="街头指示牌和面包屑"></a>街头指示牌和面包屑</h3><h4 id="设计导航"><a href="#设计导航" class="headerlink" title="设计导航"></a>设计导航</h4><p><strong>如果在网站上找不到方向,人们不会使用你的网站。</strong></p><p>导航有两个显而易见的用途:<strong>帮助我们找到想要的任何东西</strong>和<strong>告诉我们现身何处</strong>。此外,导航还有以下额外的好处:</p><ul><li><strong>他给了我们一些固定的感觉。</strong></li><li><strong>它告诉我们当前的位置。</strong></li><li><strong>它告诉我们如何使用网站。</strong></li><li><strong>它给了我们对网站建造者的信心。</strong>在网站上的每一刻,我们都会在头脑中保持一个标杆:这些人知道他们在做什么吗?这是我们决定是否离开,或者以后会不会来的主要考虑因素之一。</li></ul><hr><h3 id="首先要承认,主页不由你控制"><a href="#首先要承认,主页不由你控制" class="headerlink" title="首先要承认,主页不由你控制"></a>首先要承认,主页不由你控制</h3><h4 id="设计主页"><a href="#设计主页" class="headerlink" title="设计主页"></a>设计主页</h4><p>主页要完成的任务:</p><ul><li><strong>站点的标识(Logo)和使命。</strong>主页要告诉我这是什么网站,它是做什么的。</li><li><strong>站点层次。</strong>主页要给出网站提供的服务的概貌——既要包括内容(“我能在这里找到什么?”),也要包括功能(“我能做什么?”)——还有这些服务是如何组织的。</li><li><strong>搜索。</strong></li><li><strong>导读。</strong></li><li><strong>内容更新。</strong>时常更新的内容让用户觉得这个网站并不是一成不变的。</li><li><strong>友情链接。</strong>需要在主页上预留空间,用来放置广告,交叉推广,合作品牌的友情链接等。</li><li><strong>快捷方式。</strong></li><li><strong>注册。</strong></li></ul><p>主页需要满足一些抽象的目标:</p><ul><li><strong>让我看到自己正在寻找的东西。</strong></li><li><strong>··· ··· 还有我没有寻找的。</strong></li><li><strong>告诉我从哪里开始。</strong></li><li><strong>建立可信度和信任感。</strong></li></ul><hr><h3 id="农场主和牧牛人应该是朋友"><a href="#农场主和牧牛人应该是朋友" class="headerlink" title="农场主和牧牛人应该是朋友"></a>农场主和牧牛人应该是朋友</h3><h4 id="为什么-Web-设计团队讨论可用性是在浪费时间,如何避免这种情况"><a href="#为什么-Web-设计团队讨论可用性是在浪费时间,如何避免这种情况" class="headerlink" title="为什么 Web 设计团队讨论可用性是在浪费时间,如何避免这种情况"></a>为什么 Web 设计团队讨论可用性是在浪费时间,如何避免这种情况</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">从个人角度来说,我们喜欢 Flash 动画,因为它们很好玩;我们也可能不喜欢它们,因为要花很长时间下载。我们喜欢每个页面左边的菜单,因为它们看起来很熟悉而且容易使用;我们可能不喜欢它们,因为它们很枯燥乏味。我们真的喜欢有____的网站,或者,我们发现____真是让人痛苦极了。</span><br></pre></td></tr></table></figure><p>在网站日常开发当中,项目人员共同讨论关于某些的设计问题时,很难不讲以上例子所阐述的感觉牵涉进来。结果往往就是一堆人待在房间里面,每个人都有自持主见,不肯让步。<strong>而且,由于这些主张的力量——还有人的天性——自然有一种把这些喜欢或者不喜欢投射到整个 Wen 用户身上的倾向,认为绝大多数的 Web 用户喜欢我们所喜欢的。我们通常认为大部分 Web 用户和我们一样。</strong></p><p>争辩人们喜欢什么既浪费时间又消耗团队的精力,而通过<strong>测试</strong>能将讨论对错转移到什么有效、什么无效上,更容易缓和争论,打破僵局。而且,测试会让我们看到用户的动机、理解、反应的不同,从而让我们不会再坚持认为用户的想法和我们的想法一样。</p><hr><h3 id="一天10美分的可用性测试"><a href="#一天10美分的可用性测试" class="headerlink" title="一天10美分的可用性测试"></a>一天10美分的可用性测试</h3><h4 id="让测试简单——这样你能进行充分的测试"><a href="#让测试简单——这样你能进行充分的测试" class="headerlink" title="让测试简单——这样你能进行充分的测试"></a>让测试简单——这样你能进行充分的测试</h4><p>关于测试的几个重要事实:</p><ul><li><strong>如果想建立一个优秀的网站,一定要测试。</strong>测试更像是邀请外地的朋友,不可避免地,当你和他们一起四处游玩时,你会看到平时不会注意到的一些情况,因为你对它们太熟悉了。同时,你也意识到有很多你认为想当然的事情,对别人来说却并非如此。</li><li><strong>测试一个用户比不做测试好一倍。</strong>测试总是有效果的,哪怕是对错误的用户做一次最糟糕的测试,也会让你看到一些改善网站的重要方面。</li><li><strong>在项目中,早点测试一位用户,好过最后测试50位用户。</strong>一旦一个网站投入使用,要改变它就不会那么容易了。有些用户拒绝做出任何变化,因为即使很小的变更也会给他们带来深远的影响,让我们付出无法想象的代价(至少是项目初期所付出的数倍),所以任何在开始时就有助于防止你犯错误的方法都很划算。</li><li><strong>人们对招募用户代表的重要性估计过高。</strong></li><li><strong>测试的关键不是要证明什么或者反驳什么,而是了解你的判断力。</strong>测试能做的就是给你提供有价值的参考,加上你的经验、专业判断和常识能够让你更容易地在 A 和 B 之间做出更明智——也更自信——的选择。</li><li><strong>测试是一个迭代的过程。</strong></li><li><strong>没有什么比现场用户的反应更重要的。</strong></li></ul><h5 id="跳楼大减价的简易可用性测试"><a href="#跳楼大减价的简易可用性测试" class="headerlink" title="跳楼大减价的简易可用性测试"></a>跳楼大减价的简易可用性测试</h5><table><thead><tr><th></th><th>传统可用性测试</th><th>跳楼大减价的建议可用性测试</th></tr></thead><tbody><tr><td>每次测试的用户数量</td><td>通常需要八个或者更多个用户,因为建立测试的花费不菲</td><td>3-4个用户</td></tr><tr><td>招募方式</td><td>仔细选择,尽量靠近目标用户</td><td>随便找一些人,几乎任何会上网的人都可以</td></tr><tr><td>测试地点</td><td>一个可用性实验室,其中包括一个观察室和单向玻璃</td><td>任何办公室或会议室</td></tr><tr><td>主导测试</td><td>一位有经验的可用性专家</td><td>任何相对有耐心的人</td></tr><tr><td>提前计划</td><td>需要提前几个星期制定测试计划,预定可用性实验室,并预留招募时间</td><td>几乎可以在任何时间进行测试,稍微提前一些做计划即可</td></tr><tr><td>准备工作</td><td>起草、讨论并修订测试草案</td><td>决定你要展示什么</td></tr><tr><td>测试目标/时间</td><td>除非你预算充足,否则会把所有的鸡蛋放在一个篮子里,在网站快要完成的时候做一次测试</td><td>在开发过程中持续进行小规模的测试</td></tr><tr><td>成本</td><td>5000-15000美元(或者更多)</td><td>300美元(50-100美元是给每个用户的补贴),或者更少</td></tr><tr><td>后续工作</td><td>一周之后,产生一份20页的报告,然后开发团队朋友来决定怎样修改</td><td>开发团队(还有有兴趣的人员)利用当天的午餐时间进行总结</td></tr></tbody></table><hr><h3 id="可用性是基本礼貌"><a href="#可用性是基本礼貌" class="headerlink" title="可用性是基本礼貌"></a>可用性是基本礼貌</h3><h4 id="为什么你的网站应该让人尊敬"><a href="#为什么你的网站应该让人尊敬" class="headerlink" title="为什么你的网站应该让人尊敬"></a>为什么你的网站应该让人尊敬</h4><p>降低好感的几种方式:</p><ul><li><strong>隐藏我想要的信息。</strong></li><li><strong>因为没有按照你们的方式形式而惩罚我。</strong></li><li><strong>向我询问不必要的信息。</strong></li><li><strong>敷衍我,欺骗我。</strong></li><li><strong>给我设置障碍。</strong></li><li><strong>你的网站看上去不专业。</strong></li></ul><p>提高好感的几种方式</p><ul><li><strong>知道人们在你的网站上想做什么,并让它们明白简易、清晰明了。</strong></li><li><strong>告诉我我想知道的。</strong></li><li><strong>尽量减少步骤。</strong></li><li><strong>花点心思。</strong></li><li><strong>知道我可能有哪些疑问,并且给予解答。</strong></li><li><strong>为我提供协助,例如打印友好页面。</strong></li><li><strong>容易从错误中恢复。</strong></li><li><strong>如有不确定,记得道歉。</strong></li></ul><hr><h3 id="可访问性、级联样式表和你"><a href="#可访问性、级联样式表和你" class="headerlink" title="可访问性、级联样式表和你"></a>可访问性、级联样式表和你</h3><h4 id="正当你觉得已经完成了的时候,一只猫掉了下来,背上捆着涂了奶油的面包"><a href="#正当你觉得已经完成了的时候,一只猫掉了下来,背上捆着涂了奶油的面包" class="headerlink" title="正当你觉得已经完成了的时候,一只猫掉了下来,背上捆着涂了奶油的面包"></a>正当你觉得已经完成了的时候,一只猫掉了下来,背上捆着涂了奶油的面包</h4><p>在页面设计中,可以从下面几个方面有效提高网站的可访问性:</p><ul><li><strong>为每张图片添加 alt 文本。</strong></li><li><strong>让你的表单配合屏幕阅读器。</strong></li><li><strong>在每页的最前面增加一个“跳转到主要内容”的链接。</strong></li><li><strong>让所有的内容都可以通过键盘访问。</strong></li><li><strong>如果没有充分的理由,不要使用 JavaScript。</strong></li><li><strong>使用客户端的影像地图。</strong></li></ul>]]></content>
</entry>
<entry>
<title>BIO/NIO/AIO 三者关系解析</title>
<link href="/2017/09/20/BIO-NIO-AIO%20%E4%B8%89%E8%80%85%E5%85%B3%E7%B3%BB%E8%A7%A3%E6%9E%90/"/>
<url>/2017/09/20/BIO-NIO-AIO%20%E4%B8%89%E8%80%85%E5%85%B3%E7%B3%BB%E8%A7%A3%E6%9E%90/</url>
<content type="html"><![CDATA[<h4 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h4><p>在 Java 的 I/O 体系架构中,存在三种截然不同的 I/O 模型,分别为 BIO(Block I/O,阻塞型 I/O)、NIO(New I/O,非阻塞型 I/O)以及 AIO(Asynchronous I/O,异步 I/O)。</p><p>下面分析将从基本的术语开始讲解,最后归整讲述不同 I/O 模型的区别。</p><hr><h4 id="同步与异步"><a href="#同步与异步" class="headerlink" title="同步与异步"></a>同步与异步</h4><p>同步与异步关注的是<strong>消息通信机制</strong>。</p><ul><li><strong>同步</strong>是指发送方发出一个 I/O 请求时,在没有得到结果之前,该请求不返回结果。但是一旦请求返回时,就得到了相应的返回值。</li><li><strong>异步</strong>是指发送方发出一个 I/O 请求之后,这个请求便立即返回,该请求没有返回结果。直至请求接收方(即被调用者)通过回调的方式来通知发送方,或者发送方主动询问接收方请求结果。</li></ul><p>举个例子:</p><p>晚上我们需要去饭店预定位置,我们会优先打个电话给酒店来预定位置,当我们被告知饭店位置爆满时需要等待时。在同步的通信机制情况下,我们(发送方)只能默默地够保持通话的方式等待饭店(接收方)来通知我们空余位置的结果,不能够做别的事情。</p><p>而在异步的通信机制情况下,饭店(接收方)提供了特殊的服务,让我们(发送方)预留手机号码(回调方式),等有位置了可以主动通知你,我们就能够单方面切断通信,等待饭店通过我们预留的手机号码来通知我们,或者我们来主动询问饭店位置的空余情况。</p><hr><h4 id="阻塞与非阻塞"><a href="#阻塞与非阻塞" class="headerlink" title="阻塞与非阻塞"></a>阻塞与非阻塞</h4><p>阻塞与非阻塞关注的是<strong>程序在等待调用结果时的状态</strong>。</p><ul><li><strong>阻塞</strong>是指请求结果返回之前,当前线程会被挂起。请求线程只有在得到结果之后才会返回。此时的线程处于阻塞状态,相当于卡住不动了。</li><li><strong>非阻塞</strong>是指请求结果返回之前,当前线程不会被阻塞,可以处理别的任务。</li></ul><p>同举以上的例子:</p><p>当我们打电话给饭店,被告知饭店位置爆满时需要等待时。在阻塞线程的请求方式下,我们(发送方)只能够保持通讯(阻塞),直至饭店(接收方)通知我们空余位置的结果。</p><p>而在非阻塞线程的请求方式下,我们(发送方)可以单方面挂掉电话,继续去逛街(非阻塞),直至饭店(接收方)通知我们,亦或者我们主动打电话去询问。</p><hr><h4 id="同步-异步与阻塞-非阻塞的区别"><a href="#同步-异步与阻塞-非阻塞的区别" class="headerlink" title="同步/异步与阻塞/非阻塞的区别"></a>同步/异步与阻塞/非阻塞的区别</h4><p>在以上的解释当中,同步/异步与阻塞/非阻塞两者之间的关系十分相似,但是它们却存在本质上的区别。</p><ul><li>同步/异步注重的是<strong>消息的通信机制</strong>,重点在于<strong>消息</strong>本身。</li><li>阻塞/非阻塞注重的是<strong>程序在等待调用结果时的状态</strong>,重点在于<strong>程序</strong>本身。</li></ul><hr><h4 id="BIO"><a href="#BIO" class="headerlink" title="BIO"></a>BIO</h4><p>BIO(Block I/O)为同步阻塞型 I/O。在服务器端中实现模式为<strong>一个连接一个线程</strong>,即客户端有连接请求时,服务器端就会按需启动一个线程来处理。</p><p><img src="http://on83riher.bkt.clouddn.com/BIO.png" alt="BIO"></p><p>如果这个连接不做任何事情时,就造成不必要的线程开销,此时可以通过线程池机制来对于空线程进行回收,但是对于线程的创建与销毁等操作,系统所消耗的资源依然很大。</p><hr><h4 id="NIO"><a href="#NIO" class="headerlink" title="NIO"></a>NIO</h4><p>NIO(New I/O)为同步非阻塞型 I/O。在服务器端中实现模式为<strong>一个请求一个线程</strong>,即客户端发送的连接请求都会注册到多路复用器上(Selector),多路复用器轮询到连接有 I/O 请求(Channel)才启动一个线程(Handler)来处理。用户进程也需要时不时地询问 I/O 操作是否就绪。</p><p><img src="http://on83riher.bkt.clouddn.com/NIO.png" alt="NIO"></p><p>在 NIO 的 I/O 模型上,可以仅通过单线程的方式来处理高并发问题。</p><hr><h4 id="AIO"><a href="#AIO" class="headerlink" title="AIO"></a>AIO</h4><p>AIO(Asynchronous I/O)为异步非阻塞型 I/O。在此种模式下,用户进程只需要发起一个IO操作然后便立即返回,待 I/O 操作真正的完成以后,应用程序会得到I/O操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的 I/O 读写操作,因为真正的 I/O 读取或者写入操作已经由内核完成了。 </p><hr><p>参考资料:</p><p><a href="https://www.zhihu.com/question/19732473" target="_blank" rel="noopener">怎样理解阻塞非阻塞与同步异步的区别?</a></p><p><a href="http://loveshisong.cn/%E7%BC%96%E7%A8%8B%E6%8A%80%E6%9C%AF/2016-06-25-%E5%8D%81%E5%88%86%E9%92%9F%E4%BA%86%E8%A7%A3BIO-NIO-AIO.html" target="_blank" rel="noopener">十分钟了解BIO、NIO、AIO</a></p><p><a href="http://qindongliang.iteye.com/blog/2018539" target="_blank" rel="noopener">JAVA 中BIO,NIO,AIO的理解</a></p><p><a href="http://blog.csdn.net/liuhaiabc/article/details/64905654" target="_blank" rel="noopener">对Java BIO、NIO、AIO 学习</a></p>]]></content>
</entry>
<entry>
<title>Android 源码分析资料归纳</title>
<link href="/2017/09/07/Android%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E8%B5%84%E6%96%99%E5%BD%92%E7%BA%B3/"/>
<url>/2017/09/07/Android%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E8%B5%84%E6%96%99%E5%BD%92%E7%BA%B3/</url>
<content type="html"><![CDATA[<p>这一篇是最近学习 Android 源码,在碰到不懂的知识点时,上网找到的不错的、通俗易懂的文章,在此进行归纳收藏,在比较深入了解的时候可以去整理笔记。</p><hr><ul><li>Context </li></ul><p>Context 是 Android 应用层架构中最重要类,它是维持 Android 程序中各个组件能够正常工作的核心功能类。下篇文章就阐明了这一论点,以及如何有效使用 Context。</p><p><a href="http://blog.csdn.net/guolin_blog/article/details/47028975" target="_blank" rel="noopener">Android Context完全解析,你所不知道的Context的各种细节</a></p><ul><li>View 事件分发机制</li></ul><p>该文章主要是通过图文结合的方式,并结合少量精炼的代码,详细地叙述了一个事件如何进行传递,又如何被处理。</p><p><a href="http://www.jianshu.com/p/38015afcdb58" target="_blank" rel="noopener">Android事件分发机制详解:史上最全面、最易懂</a></p><ul><li>Actvity 启动及工作流程</li></ul><p>该文章讲述了 Activity 如何启动,以及科普了 Activity 启动时所涉及的重要组件,并根据主要源码讲述了过程,虽然我还没看懂内部机制(有些方法太变态!),但是还是从大局观上有了部分的了解。</p><p><a href="http://www.jianshu.com/p/6037f6fda285" target="_blank" rel="noopener">【凯子哥带你学Framework】Activity启动过程全解析</a></p><ul><li>Handler 消息机制</li></ul><p>该文章从生产者-消费者这一设计模式中,阐明了 Handler 机制中的三大组件 Handler、Looper 以及 MessageQueue,以及组件间是如何相互配合的。</p><p><a href="http://www.woaitqs.cc/android/2016/06/06/android-handler.html" target="_blank" rel="noopener">Android Handler机制全解析</a></p><ul><li>BroadcastProvider 广播机制</li></ul><p>该文章主要从三个角度来入手,广播接受者是如何进行注册,广播如何被发送,广播负载物是什么。</p><p><a href="http://www.jianshu.com/p/48d58a9dcc62" target="_blank" rel="noopener">读源码-五分钟理解不了广播机制</a></p><ul><li>Service 机制</li></ul><p>该文章怎么说,通读下来,虽然能从文章中能够理解他这个意思,但是具体的实现却异常复杂,可能现在我这个水平,还理解不了为什么它们要这样设计,这样设计的好处。</p><p><a href="http://www.woaitqs.cc/android/2016/09/20/android-service-usage.html" target="_blank" rel="noopener">从源码出发深入理解 Android Service</a></p><ul><li>ContentProvider 机制</li></ul><p><a href="http://gityuan.com/2016/07/30/content-provider/" target="_blank" rel="noopener">理解ContentProvider原理</a></p><ul><li>Android 动画机制</li></ul><p>看了两位 csdn 大神所写的 Android 动画教学系列,了解了 Android 动画的大战里程从 View Animation 的卡帧动画到 Drawable Animation 的仅支持少数动画效果动画机制,最后到 Property Animation 的强大。</p><p><a href="http://blog.csdn.net/guolin_blog/article/details/43536355" target="_blank" rel="noopener">Android属性动画完全解析(上),初识属性动画的基本用法</a></p><p><a href="http://blog.csdn.net/guolin_blog/article/details/43816093" target="_blank" rel="noopener">Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法</a></p><p><a href="http://blog.csdn.net/guolin_blog/article/details/44171115" target="_blank" rel="noopener">Android属性动画完全解析(下),Interpolator和ViewPropertyAnimator的用法</a></p><p><a href="http://blog.csdn.net/lmj623565791/article/details/38067475" target="_blank" rel="noopener">Android 属性动画(Property Animation) 完全解析 (上)</a></p><p><a href="http://blog.csdn.net/lmj623565791/article/details/38092093" target="_blank" rel="noopener">Android 属性动画(Property Animation) 完全解析 (下)</a></p>]]></content>
</entry>
<entry>
<title>Android 进行 HTTPS 网络通信</title>
<link href="/2017/09/01/Android%20%E8%BF%9B%E8%A1%8C%20HTTPS%20%E7%BD%91%E7%BB%9C%E9%80%9A%E4%BF%A1/"/>
<url>/2017/09/01/Android%20%E8%BF%9B%E8%A1%8C%20HTTPS%20%E7%BD%91%E7%BB%9C%E9%80%9A%E4%BF%A1/</url>
<content type="html"><![CDATA[<h4 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h4><p>前两天在 Andorid 上与使用自签名证书的服务器进行 https 网络通信遇到了问题,主要的问题出在于服务器端的证书不受客户端信任与认证,服务器端也不认识客户端,双方互不认识(在浏览器好歹也会提示用户添加安全证书)。</p><hr><h4 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h4><p>根据需求分析,Android 平台上需要进行双向验证才能够进行正常的 https 通信。而在之前的代码结构中,由于不熟悉 https 沟通方式,错误使用了服务端证书来进行身份识别与验证。</p><hr><h4 id="解决方式"><a href="#解决方式" class="headerlink" title="解决方式"></a>解决方式</h4><p>进行后续操作的调整,在 centOS 平台上使用 keytool 分别了生成了服务器端证书以及客户端证书,并将客户端证书放置 Android 上,用于连接服务器端时对服务器端的证书进行鉴别与认证。</p><p>具体操作如下所示:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">1、生成服务器证书库</span><br><span class="line"></span><br><span class="line">keytool -validity 365 -genkey -v -alias server -keyalg RSA -keystore server.keystore -dname "CN=commonName,OU=organizationalUnit,O=Organization,L=Locality,ST=state,c=country" -storepass 123456 -keypass 123456 -keysize 2048</span><br><span class="line"></span><br><span class="line">2、生成客户端证书库</span><br><span class="line"></span><br><span class="line">keytool -validity 365 -genkey -v -alias client -keyalg RSA -storetype PKCS12 -keystore client.p12 -dname "CN=client,OU=organizationalUnit,O=Organization,L=Organization,ST=state,c=country" -storepass 123456 -keypass 123456 -keysize 2048</span><br><span class="line"></span><br><span class="line">3、从客户端证书库中导出客户端证书</span><br><span class="line"></span><br><span class="line">keytool -export -v -alias client -keystore client.p12 -storetype PKCS12 -storepass 123456 -rfc -file client.cer</span><br><span class="line"></span><br><span class="line">4、从服务器证书库中导出服务器证书</span><br><span class="line"></span><br><span class="line">keytool -export -v -alias server -keystore server.keystore -storepass 123456 -rfc -file server.cer</span><br><span class="line"></span><br><span class="line">5、生成客户端信任证书库(由服务端证书生成的证书库)</span><br><span class="line"></span><br><span class="line">keytool -import -v -alias server -file server.cer -keystore client.truststore -storepass 123456 -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider</span><br><span class="line"></span><br><span class="line">6、将客户端证书导入到服务器证书库(使得服务器信任客户端证书)</span><br><span class="line"></span><br><span class="line">keytool -import -v -alias client -file client.cer -keystore server.keystore -storepass 123456</span><br></pre></td></tr></table></figure><p>keytool 常用参数说明:</p><table><thead><tr><th>参数</th><th>用途</th></tr></thead><tbody><tr><td>-genkey</td><td>在用户主目录中创建一个默认文件“.keystore”</td></tr><tr><td>-validity</td><td>指定创建的证书有效期为多少天</td></tr><tr><td>-alias</td><td>产生别名,每个 keystore 都关联一个独一无二的 alias</td></tr><tr><td>-keystore</td><td>指定密钥库的名称</td></tr><tr><td>-keyalg</td><td>指定密钥的算法</td></tr><tr><td>-keysize</td><td>指定密钥的长度</td></tr><tr><td>-dname</td><td>指定证书发行者信息,其中: “CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名 称,ST=州或省份名称,C=单位的两字母国家代码”</td></tr><tr><td>-storepass</td><td>指定密钥库的密码(.keystore密码)</td></tr><tr><td>-keypass</td><td>指定别名条目的密码(私钥密码)</td></tr><tr><td>-storetype</td><td>指定密钥库的存储类型</td></tr><tr><td>-export</td><td>将 alias 指定的证书导出到文件</td></tr><tr><td>-import</td><td>将已签名的证书导入密钥库中</td></tr><tr><td>-rfc</td><td>以Base64的编码格式打印证书</td></tr><tr><td>-file</td><td>指定导出到文件的文件名</td></tr><tr><td>-v</td><td>查看密钥库中的证书详细信息</td></tr></tbody></table><hr><p>在代码结构上,在 http 通信代码中添加 SSL 通信机制,如下所示:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String KEY_STORE_TYPE_BKS = <span class="string">"BKS"</span>;<span class="comment">//证书类型</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String KEY_STORE_TYPE_P12 = <span class="string">"PKCS12"</span>;<span class="comment">//证书类型</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String KEY_STORE_CLIENT_PATH = <span class="string">"swaypay.p12"</span>;<span class="comment">//客户端要给服务器端认证的证书</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String KEY_STORE_TRUST_PATH = <span class="string">"swaypay.truststore"</span>;<span class="comment">//客户端验证服务器端的证书库</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String KEY_STORE_PASSWORD = <span class="string">"superssl1"</span>;<span class="comment">// 客户端证书密码</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String KEY_STORE_TRUST_PASSWORD = <span class="string">"superssl1"</span>;<span class="comment">//客户端证书库密码</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String KEY_STORE_TYPE_X509 = <span class="string">"X509"</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String SSL_CONTEXT_PROTOCOL = <span class="string">"TLS"</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setSSLConnection</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 1 - KeyStore - 用于存储各种类型的密钥,方便管理与使用</span></span><br><span class="line"> KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);</span><br><span class="line"> KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2 - 将提前获取的受服务器端信任的客户端证书/用于验证服务器端的证书的证书库进行导入</span></span><br><span class="line"> InputStream ksIn = ContextHolder.getContext().getAssets().open(KEY_STORE_CLIENT_PATH);</span><br><span class="line"> InputStream tsIn = ContextHolder.getContext().getAssets().open(KEY_STORE_TRUST_PATH);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 3 - 初始化 KeyManagerFactory</span></span><br><span class="line"> keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());</span><br><span class="line"> KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KEY_STORE_TYPE_X509);</span><br><span class="line"> keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 4 - 初始化 TrustManagerFactory</span></span><br><span class="line"> trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());</span><br><span class="line"> TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());</span><br><span class="line"> trustManagerFactory.init(trustStore);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 5 - 初始化 SSLContext 并添加通过 KeyManagerFactory 和 TrustManagerFactory 分别生成的 getKeyManagers 和 getTrustManagers</span></span><br><span class="line"> <span class="comment">// 这一步中,通过这种方式,才算 https 的双向验证机制的真正建立</span></span><br><span class="line"> sslContext = SSLContext.getInstance(SSL_CONTEXT_PROTOCOL);</span><br><span class="line"> sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), <span class="keyword">null</span>);</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="function"><span class="keyword">private</span> HttpURLConnection <span class="title">createHttpURLConnection</span><span class="params">(String targetUrl)</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> <span class="comment">// 6</span></span><br><span class="line"> <span class="keyword">if</span> (httpURLConnection <span class="keyword">instanceof</span> HttpsURLConnection) {</span><br><span class="line"> setSSLConnection();</span><br><span class="line"> ((HttpsURLConnection) httpURLConnection).setSSLSocketFactory(sslContext.getSocketFactory());</span><br><span class="line"> <span class="comment">// 暂不验证 host 有效性</span></span><br><span class="line"> ((HttpsURLConnection) httpURLConnection).setHostnameVerifier((String hostname, SSLSession session) -> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><hr><p>参考资料:</p><p>[1] <a href="http://frank-zhu.github.io/android/2014/12/26/android-https-ssl/" target="_blank" rel="noopener">http://frank-zhu.github.io/android/2014/12/26/android-https-ssl/</a></p>]]></content>
</entry>
<entry>
<title>心跳机制</title>
<link href="/2017/08/28/%E5%BF%83%E8%B7%B3%E6%9C%BA%E5%88%B6/"/>
<url>/2017/08/28/%E5%BF%83%E8%B7%B3%E6%9C%BA%E5%88%B6/</url>
<content type="html"><![CDATA[<h1 id="心跳机制及基本实现"><a href="#心跳机制及基本实现" class="headerlink" title="心跳机制及基本实现"></a>心跳机制及基本实现</h1><h3 id="什么是心跳机制"><a href="#什么是心跳机制" class="headerlink" title="什么是心跳机制"></a>什么是心跳机制</h3><p>心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制。这种机制在分布式系统中十分常见,也是确保分布式系统中的主机是否存在的有效机制之一。</p><h3 id="一般的实现机制"><a href="#一般的实现机制" class="headerlink" title="一般的实现机制"></a>一般的实现机制</h3><p>心跳机制一般会有两种,客户端实现和服务器端实现。</p><h4 id="客户端实现"><a href="#客户端实现" class="headerlink" title="客户端实现"></a>客户端实现</h4><p>客户端通过本机与服务器端连接后获得的 Socket 对象,在一定时间间隔中,发送附加有效信息的心跳包给服务器端。服务器端正确接收后,在服务器端上以适当的形式保存该客户端发送心跳包时间,以便用于检测客户端在规定的超时间隔内依然是否存在。</p><h4 id="服务器端实现"><a href="#服务器端实现" class="headerlink" title="服务器端实现"></a>服务器端实现</h4><p>服务器端需要保留所有已连接的客户端 Socket 对象,并在一定时间间隔中,发送空载的心跳包给所有客户端,并在本机中维护一个发送时间计时器。客户端正确接收到心跳包后,回传给服务器端一个附加有效信息的心跳包,用于证明客户端依然存在。若服务器端在计时器超时之后,仍没有收到客户端发送来的心跳包,可视为客户端断开连接。</p><h3 id="尝实现"><a href="#尝实现" class="headerlink" title="尝实现"></a>尝实现</h3><p>自己觉得好玩就尝试去实现,放在了 github,代码比较粗糙</p><p>Github: <a href="https://github.com/jianpeng957/heartbeat" target="_blank" rel="noopener">https://github.com/jianpeng957/heartbeat</a></p>]]></content>
</entry>
<entry>
<title>记得刷牙!</title>
<link href="/2017/08/07/%E8%AE%B0%E5%BE%97%E5%88%B7%E7%89%99%EF%BC%81/"/>
<url>/2017/08/07/%E8%AE%B0%E5%BE%97%E5%88%B7%E7%89%99%EF%BC%81/</url>
<content type="html"><![CDATA[<p>上个星期,小时候为了修复蛀牙而去补牙的填充物不知为何掉了出来,空剩我一颗大蛀牙在里面,虽然不会有很大疼痛,但是心情却异样烦闷。今天特意去医院看了口腔科,做了一系列诊断后,医生说我这颗牙已经烂得不行了,要么做根管治疗,要么就拔了做假牙,需要的费用也是挺贵的,前一个至少3000+,后一个至少1w+。</p><p>听了之后,再看看 X 光片中那颗糜烂的大蛀牙,我整个人都不好了,<strong>很懊恼为什么小时候老是不刷牙,尤其是吃了糖之后不刷牙。爱护牙齿,甚至爱护身体的每一部分都是自己生命的本钱,不好好珍惜,到后来都是要自己买单,甚至是给家里带来负担。</strong></p><p><strong>在爱护自己的道路上,一定要且行且珍惜。尤其是到了工作的部分,我相信肯定比大学时期的生活更加艰难。</strong>附上一张在医院拍的 X 光片。</p><p><img src="http://on83riher.bkt.clouddn.com/CA92AA0605B8DD2FD983C270192AC095.png" alt=""></p>]]></content>
</entry>
<entry>
<title>移位操作符</title>
<link href="/2017/08/05/%E7%A7%BB%E4%BD%8D%E6%93%8D%E4%BD%9C%E7%AC%A6/"/>
<url>/2017/08/05/%E7%A7%BB%E4%BD%8D%E6%93%8D%E4%BD%9C%E7%AC%A6/</url>
<content type="html"><![CDATA[<blockquote><p>移位运算符就是在二进制的基础上对数学进行平移。按照平移的方向和填充数字的规则分为三种:<<(左移)、>>(右移)、>>>(无符号右移)。</p></blockquote><p>Java 中的移位运算符也有三种:</p><ul><li><code><<</code> - 左移运算符</li><li><code>>></code> - 右移运算符</li><li><code>>>></code> - 无符号右移运算符</li></ul><hr><h3 id="代码展示"><a href="#代码展示" class="headerlink" title="代码展示"></a>代码展示</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 左移运算符</span></span><br><span class="line"><span class="keyword">int</span> a = <span class="number">10</span>; <span class="comment">// 二进制 - 1010</span></span><br><span class="line">a << <span class="number">1</span>; <span class="comment">// 二进制 - 10100 - 相当于乘于 2^1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 右移运算符</span></span><br><span class="line"><span class="keyword">int</span> a = <span class="number">10</span>;</span><br><span class="line">a >> <span class="number">1</span>; <span class="comment">// 二进制 - 101 - 相当于除于 2^1</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="keyword">int</span> a = <span class="number">10</span>;</span><br><span class="line">a >> <span class="number">1</span>; <span class="comment">// 二进制 - 101 - 相当于除于 2^1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 负数</span></span><br><span class="line"><span class="keyword">int</span> a = -<span class="number">10</span>; <span class="comment">// 二进制 - 11111111111111111111111111110110 - 负数在高位填补1</span></span><br><span class="line">a >> <span class="number">1</span>; <span class="comment">// 二进制 - 01111111111111111111111111111011 - 十进制 - 2147483643</span></span><br></pre></td></tr></table></figure><p>以上代码中,十进制转换为二进制中,为什么<code>int a = -10</code>的二进制数的总长度为<strong>32</strong>呢。这取决于数据类型,在 java 中,基本数据类型<code>int</code>是4个字节,即32位。如果将数据类型<code>int</code>改变为<code>long</code>,结果则完全不同。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">long</span> a = -<span class="number">10</span>;</span><br><span class="line">a >> <span class="number">1</span>; <span class="comment">// 十进制 - 9223372036854775803</span></span><br></pre></td></tr></table></figure><hr><h3 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h3><ul><li>移位运算是直接基于二进制对数值进行操作,主要目的是节约内存,运算时间比算术运算符更加快。</li></ul><hr><hr><p>参考资料:</p><ol><li><a href="https://baike.baidu.com/item/%E7%A7%BB%E4%BD%8D%E8%BF%90%E7%AE%97%E7%AC%A6" target="_blank" rel="noopener">https://baike.baidu.com/item/%E7%A7%BB%E4%BD%8D%E8%BF%90%E7%AE%97%E7%AC%A6</a></li><li><a href="http://www.cnblogs.com/hongten/p/hongten_java_yiweiyunsuangfu.html" target="_blank" rel="noopener">http://www.cnblogs.com/hongten/p/hongten_java_yiweiyunsuangfu.html</a></li></ol>]]></content>
</entry>
<entry>
<title>joda-time源码简略剖析.md</title>
<link href="/2017/08/04/joda-time%E6%BA%90%E7%A0%81%E7%AE%80%E7%95%A5%E5%89%96%E6%9E%90/"/>
<url>/2017/08/04/joda-time%E6%BA%90%E7%A0%81%E7%AE%80%E7%95%A5%E5%89%96%E6%9E%90/</url>
<content type="html"><![CDATA[<p>最近查看了 Java 中有关于日期/时间处理的 API,发现了 <code>Joda-time</code> 这一款被众人广泛使用的开源库,花了一个下午,研究了内部的架构解析,画了下草图(真草图),内部的有些计算解析涉及到位处理,有些难以消化。</p><ul><li><img src="http://on83riher.bkt.clouddn.com/joda-time%E6%BA%90%E7%A0%81%E5%9B%BE.JPG" alt=""></li></ul>]]></content>
</entry>
<entry>
<title>Linux命令 - mount与umount</title>
<link href="/2017/07/31/Linux%E5%91%BD%E4%BB%A4-mount%E4%B8%8Eumount/"/>
<url>/2017/07/31/Linux%E5%91%BD%E4%BB%A4-mount%E4%B8%8Eumount/</url>
<content type="html"><![CDATA[<h4 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h4><blockquote><p>mount 与 umount 分别用于挂载与卸载文件系统</p></blockquote><h4 id="mount"><a href="#mount" class="headerlink" title="mount"></a>mount</h4><hr><h4 id="基本语法"><a href="#基本语法" class="headerlink" title="基本语法"></a>基本语法</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mount [-l|-h|-V]</span><br><span class="line">mount -a</span><br><span class="line">mount [-fnrsvw] [-t fstype] [-o options] <device> <dir></span><br></pre></td></tr></table></figure><h5 id="基本参数解释:"><a href="#基本参数解释:" class="headerlink" title="基本参数解释:"></a>基本参数解释:</h5><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td>无参数</td><td>显示所有磁盘的挂载情况</td></tr><tr><td>-a</td><td>依照配置文件 /etc/fstab 的数据将所有未挂载的磁盘挂载上去</td></tr><tr><td>-l</td><td>显示时多一列 Label 名称</td></tr><tr><td>-t</td><td>可以加上文件系统种类来指定欲挂载的类型。常见 Linux 支持类型有:ext2, ext3, vfat, reiserfs 等</td></tr><tr><td>-n</td><td>在默认的情况下,系统会将实际挂载的情况实时写入 /etc/mtab 中,以方便其他程序的运行。但某些情况下允许用户不将挂在情况写入</td></tr><tr><td>-o</td><td>可以在挂载时附加一些参数(详情看链接)</td></tr></tbody></table><hr><h4 id="常用基本操作"><a href="#常用基本操作" class="headerlink" title="常用基本操作"></a>常用基本操作</h4><ul><li>显示所有端口</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span> mount</span><br><span class="line">sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)</span><br><span class="line">proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)</span><br><span class="line">devtmpfs on /dev type devtmpfs (rw,nosuid,size=49311808k,nr_inodes=12327952,mode=755)</span><br><span class="line">securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)</span><br><span class="line">tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)</span><br><span class="line">...</span><br><span class="line">nfsd on /proc/fs/nfsd type nfsd (rw,relatime)</span><br><span class="line">/dev/sda2 on /boot type ext4 (rw,relatime,data=ordered)</span><br><span class="line">/dev/sda1 on /boot/efi type vfat (rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=winnt,errors=remount-ro)</span><br><span class="line">/dev/mapper/ze-home on /home type xfs (rw,relatime,attr2,inode64,noquota)</span><br><span class="line">tmpfs on /run/user/0 type tmpfs (rw,nosuid,nodev,relatime,size=9864680k,mode=700)</span><br></pre></td></tr></table></figure><ul><li>挂载硬盘</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span> mount /dev/sdb /home</span><br></pre></td></tr></table></figure><p>上面的命令是指将 <code>/dev/sdb</code>磁盘挂载至<code>/home</code>文件目录下。</p><blockquote><p>其余操作在有空闲服务器时再添加</p></blockquote><hr><hr><h4 id="umount"><a href="#umount" class="headerlink" title="umount"></a>umount</h4><hr><h4 id="基本语法-1"><a href="#基本语法-1" class="headerlink" title="基本语法"></a>基本语法</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">umount -a</span><br><span class="line">umount [dflnrv] [-t fstype] [-O options] <device|dir></span><br></pre></td></tr></table></figure><h5 id="基本参数解释:-1"><a href="#基本参数解释:-1" class="headerlink" title="基本参数解释:"></a>基本参数解释:</h5><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td>-a</td><td>将所有在 /proc/self/moutinfo 有描述记录的文件系统卸载下来,除了 proc, devfs, devpts, sysfs, rpc_pipefs 以及 nfsd 文件系统</td></tr><tr><td>-f</td><td>强制卸载!</td></tr><tr><td>-n</td><td>不将磁盘卸载情况写入 /etc/mtab</td></tr></tbody></table><hr><h4 id="常用基本操作-1"><a href="#常用基本操作-1" class="headerlink" title="常用基本操作"></a>常用基本操作</h4><ul><li>卸载磁盘</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span> umount /dev/sdb</span><br></pre></td></tr></table></figure><p>上面的命令是指将 <code>/home</code>文件目录下的<code>/dev/sdb</code>磁盘卸载下来</p><hr><hr><p>参考资料:</p><p>[1] <a href="http://cn.linux.vbird.org/linux_basic/0230filesystem.php#mount" target="_blank" rel="noopener">http://cn.linux.vbird.org/linux_basic/0230filesystem.php#mount</a></p><p>[2] <a href="http://man7.org/linux/man-pages/man8/umount.8.html" target="_blank" rel="noopener">http://man7.org/linux/man-pages/man8/umount.8.html</a></p><p>[3] <a href="http://man7.org/linux/man-pages/man8/mount.8.html" target="_blank" rel="noopener">http://man7.org/linux/man-pages/man8/mount.8.html</a></p>]]></content>
</entry>
<entry>
<title>《程序员修炼之道》读书笔记</title>
<link href="/2017/07/30/%E3%80%8A%E7%A8%8B%E5%BA%8F%E5%91%98%E4%BF%AE%E7%82%BC%E4%B9%8B%E9%81%93%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
<url>/2017/07/30/%E3%80%8A%E7%A8%8B%E5%BA%8F%E5%91%98%E4%BF%AE%E7%82%BC%E4%B9%8B%E9%81%93%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h1 id="注重实效的哲学"><a href="#注重实效的哲学" class="headerlink" title="注重实效的哲学"></a>注重实效的哲学</h1><p>这一章中,作者从六个相互独立的主题来阐述程序员应该从哪几个方面来注重实效,为注重实效的哲学奠定了基础。</p><hr><h2 id="责任"><a href="#责任" class="headerlink" title="责任"></a>责任</h2><blockquote><p>注重实效的程序员对他/她自己的职业生涯负责,并且不害怕承认无知和错误。</p></blockquote><p><strong>责任是你主动负担的东西。</strong>你承诺确保某件事情正确完成,但你不一定能直接控制事情的每一个方面。除了尽你所能以外,你必须分析风险是否超出了你的控制。对于不可能做到的事情或者风险太大的事情,你有权不去为之负责。你必须基于你自己的道德准则和判断来做出决定。</p><p>如果你确实同意要为某个结果负责,你就应切实负起责任。当你犯错误、或者判断错误时,诚实地承认它,<strong>并设法给出各种选择</strong>,而不是选择去责备别人或别的东西、或者拼凑借口。</p><p>在你走向任何人,告诉他们为何某事做不到、为何耽搁、为何出问题之前,先停下来,听一下你心里的声音。在你的头脑中把谈话预演一遍。其他人可能会说什么?你将怎样回答?在你去告诉他们坏消息之前,是否还有其他你可以再试一试的办法?有时,你其实知道他们会说什么,所以还是不要给他们添加麻烦吧。</p><hr><h2 id="软件的熵"><a href="#软件的熵" class="headerlink" title="软件的熵"></a>软件的熵</h2><p>熵,指的是系统中的“无序”的总量。在热力学定律中,保证了宇宙中的熵倾向于最大化,而在相似的环境中,当软件正在无序、野蛮的增长时,程序员称之为“软件腐烂”。</p><blockquote><p>破窗户理论:一扇破窗户,只要有那么一段时间不去修路,就会渐渐给建筑的居民带来一种废弃感——一种职权部门不关心这座建筑的感觉。于是又一扇窗户破了。人们开始乱扔垃圾。出现了乱涂图画。严重的结构损坏开始了。在相对较短的一段时间里,建筑就被损毁得超出了业主愿意修理的程度,而废弃感就成了现实。</p></blockquote><p>从“破窗户理论”中,我们可以得知,当项目中出现了类似于低劣的设计或者糟糕的代码,倘若程序员不及时去修复时,放着不管,就容易使得负责该项目的人员产生一种随意感,这样做的后果往往会是项目失败的一个较大的原因。</p><p>所以,在项目开始时,不要留着”破窗户”不修。发现一个就修复一个。如果没有足够的时间进行适当的修理,就做一个 to-do-list,亦或者在问题的代码上放入具有警示性的注释,亦或者用虚设的数据(dummy data)加以替代。<strong>采用某种行动防止进一步的损坏,并说明情势仍处于你的控制之下。</strong></p><p>若项目中的每个人员都注重于自身设计的简约,代码的工整,使得项目的结构合理得体,那么身为项目中的一员的你也不愿意去玷污这个工程,即使在项目的最后期限(deadline),也不愿成为第一个弄脏东西的人。</p><hr><h2 id="石头汤与青蛙"><a href="#石头汤与青蛙" class="headerlink" title="石头汤与青蛙"></a>石头汤与青蛙</h2><p>这一章中的故事主要讲述了三个士兵利用人的好奇心,用石头汤换来一顿美味的大餐。</p><h4 id="从士兵的角度思考整个故事"><a href="#从士兵的角度思考整个故事" class="headerlink" title="从士兵的角度思考整个故事"></a>从士兵的角度思考整个故事</h4><p>士兵的角度解析这篇故事有两层寓意。士兵戏弄了村民,他们利用村民的好奇,从他们那里弄到了食物。但更重要的是,士兵充当了催化剂,把村民们团结起来,和他们一起做了他们自己本来做不到的事情。</p><p>在有些情况下,你也许确切地知道需要做什么,以及怎么样去做。整个系统就在你的眼前——你知道它是对的,但请求许可去处理整个事情,你会遇到拖延和漠然。每个人都会护卫他们自己的资源,不轻易去协助,甚至让步。</p><p>这正是拿出“石头”的时候。设计出你可以合理要求的东西,好好开发它。一旦完成雏形之时,可以与人分享,引起他们兴趣,人就自然而然会来帮助你。<strong>人们发现,参与正在发生的成功要更容易。让他们瞥见未来,你就能让他们聚集在你周围。</strong></p><h4 id="从村民的角度思考整个故事"><a href="#从村民的角度思考整个故事" class="headerlink" title="从村民的角度思考整个故事"></a>从村民的角度思考整个故事</h4><p>从故事中,我们知道村民们注意力过度集中,只想着石头,而忘却了自己身处的环境(贫困饥饿)。这种事情就好比温水煮青蛙一般,程序员如果没有观察到项目中的细小变化,而被一些微不足道的事情逐步侵蚀,最终的后果是灾难性的。</p><p><strong>在项目开发中,不要像温水中青蛙一样。要留心大局(big picture)。要持续不断地观察周围发生的事情,而不只是你自己在做的事情。</strong></p><hr><h2 id="足够好的软件"><a href="#足够好的软件" class="headerlink" title="足够好的软件"></a>足够好的软件</h2><blockquote><p>欲求更好,常把好事变糟。 —— 李尔王</p></blockquote><p>在现实世界中,我们无法制作出完美的产品,特别是没有任何差错(bug)的软件。但是我们可以训练自己,编写出足够好的软件——对你的用户(功能)、对未来的维护者(文档)、对你自己内心的安宁(满足)来说足够好。</p><p>在编写软件的过程中,我们应当接纳用户的各种各样的意见,让他们参与权衡之中。无视用户的需求,一味地给程序增加新特性,闭门造车,或是一次又一次润饰代码,这不是有职业素养的做法。</p><p>在项目开发过程中,我们应该将项目的范围与质量作为项目需求的一部分规定下来,使之成为需求问题,一步步满足完善。但是同时,我们也不该过度修饰和过于求精而损毁完好的程序。继续前进,让你的代码凭着自己的质量(高可用)站立。<strong>它也许不完美,但你也不用担心,它不可能完美。</strong></p><hr><h2 id="你的知识资产"><a href="#你的知识资产" class="headerlink" title="你的知识资产"></a>你的知识资产</h2><p>在程序员的职业生涯中,知识和经验是你重要的职业财富。遗憾的是,它们是有时效的资产(expiring asset)。随着新的技术、语言及环境的出现,你的知识可能会变得过时。不断变化的市场驱动也许会使你的经验变得陈旧或无关紧要。<strong>随着你的知识的价值降低,对你的公司或者你的客户来说,你的价值也在降低。</strong></p><p>作为程序员,我们将所知道的关于计算技术和他们所工作的应用领域的全部事实,以及他们的所有经验视为他们的知识资产。管理知识资产与管理金融资产非常相似:</p><ul><li><p><strong>严肃的投资者定期投资。</strong></p><p>就像金融投资一样,你必须定期地为你的知识资产投资。即使投资量很小,习惯自身也和总量一样重要。可以通过几种途径累积自己的资本:</p><ul><li><strong>每年至少学习一种新语言。</strong>不同语言以不同的方式解决相同的问题。通过不同的语言,有助于拓宽思维,并避免墨守成规。</li><li><strong>每季度阅读一本技术书籍。</strong>起码至少一个月读一本书。</li><li><strong>也要阅读非技术书籍。</strong></li><li><strong>上课。</strong></li><li><strong>试验不同的环境。</strong></li><li><strong>跟上潮流。</strong>了解时事,抓紧下一次流量窗口。</li></ul></li><li><p><strong>多元化是长期成功的关键。</strong></p><p>你知道的不同的事情越多,你就越有价值。作为底线,你需要知道你目前所用的特定技术的各种特性。但不要就此止步。计算技术的面貌变化地很快——今天热门技术明天就可能变得近乎无用。</p></li><li><p><strong>聪明的投资者在保守的投资和高风险、高回报的投资之间平衡他们的资产。</strong></p><p>从高风险、可能有高回报,到低风险,低回报,技术存在于这样一条谱带中。把你所有的金钱都投入到可能突然崩盘的高风险股票并不是一个很好的主意;你也不应太过于保守,错过可能的机会。不要将你所有的技术鸡蛋放在一个篮子中。</p></li><li><p><strong>投资者设法低买高卖,以获得最大回报。</strong></p><p>在新型的技术流行之前学习它可能就和找到被低估的股票一样困难,但所得到的就会像那样的股票带来的收益一样。</p></li><li><p><strong>应周期性地重新评估和平衡资产。</strong></p><p>充分评估自身所拥有的资产(技术栈),并不断地丰富自己的技术栈,有助于自身的发展。</p></li><li><p><strong>批判地思考你读到的和听到的。</strong></p><p>所见非所得,你需要对不同途径获取的信息,进行批判性地思考,对接受的内容持有怀疑态度,直至被自己证实。</p></li></ul><hr><h2 id="交流"><a href="#交流" class="headerlink" title="交流"></a>交流</h2><p>作为开发者,我们必须在许多层面上进行交流。我们把许多小时花在开会、倾听和交谈上。我们与最终用户一起工作,设法了解他们的需要。我们编写代码,与机器交流我们的意图;把我们的想法变成文档,留给以后的开发者。我们撰写提案和备忘录,用以申请资源并证明其正当性、报告我们的状态、以及提出各种新的办法。我们每天在团队中工作,宣扬我们的主意、修正现有的做法、并提出新的做法。<strong>我们的时间有很大一部分都花在交流上,所以我们需要把它做好。</strong></p><ul><li><p><strong>知道你想说什么。</strong></p><p>规划你想要说的东西。写出大纲。然后问你自己:”这是否讲清了我要说的所有内容?“提炼它,直到确实如此为止。</p></li><li><p><strong>了解你的听众。</strong></p><p>只有当你是在传达令人能够理解的信息时,你才是在进行交流。为此,你需要了解你的听众的需要、兴趣、能力。要在脑海里形成一幅明确的关于你的听众的画面。</p></li></ul><table><thead><tr><th style="text-align:left">WISDOM</th><th style="text-align:left">译文</th></tr></thead><tbody><tr><td style="text-align:left">What do you want them to learn?</td><td style="text-align:left">你想让他们学到什么?</td></tr><tr><td style="text-align:left">What is their interest in what you’ve got to say?</td><td style="text-align:left">他们对你讲的什么感兴趣?</td></tr><tr><td style="text-align:left">How sophisticated are they?</td><td style="text-align:left">他们有多少经验?</td></tr><tr><td style="text-align:left">How much detail do they want?</td><td style="text-align:left">他们想要多少细节?</td></tr><tr><td style="text-align:left">Whom do you want to own the information?</td><td style="text-align:left">你想要让谁获得这些信息?</td></tr><tr><td style="text-align:left">How canyou motivate them to to listen to you?</td><td style="text-align:left">你如何使他们听你说话?</td></tr></tbody></table><ul><li><p><strong>选择时机。</strong></p><p>为了了解你的听众需要听什么,你需要弄清楚他们的”轻重缓急“是什么。要让你所说的适得其时,在内容上切实相关,满足听众需求。</p></li><li><p><strong>选择风格。</strong></p><p>调整你的交流风格,让其适应你的听众。有人要的是正式的会议简报。另一些人喜欢在进行正题之前高谈阔论一番。</p></li><li><p><strong>让文档美观。</strong></p><p>你的主意固然重要,但是结构良好,思路清晰的文档更容易地让听众接受。</p></li><li><p><strong>让听众参与。</strong></p><p>在项目开发过程中,在尽可能的情况下,让你的读者参与到文档的早起草稿的制作。获取他们的反馈,并汲取他们的智慧。你将建立良好的工作关系,并很可能在此过程中制作出比原先更好的文档。</p></li><li><p><strong>做倾听者。</strong></p><p>如果你想要大家听你说话,你必须使用一种方法:听他们说话。在正式会议中,即使你掌握着全部信息,倘若你只管你自己讲话,是没有任何听众愿意听你讲话的。</p><p>鼓励大家通过提问来交谈,或者让他们总结你告诉他们的东西。把会议变成对话,你将能更有效地阐述你的观点。</p></li><li><p><strong>回复他人。</strong></p><p>及时、随时回复他人,会让他们更容易原谅你偶然的疏忽,并有助于维持一段较为良好的关系。</p></li></ul>]]></content>
<categories>
<category> 技术笔记 </category>
</categories>
</entry>
<entry>
<title>Linux LVM磁盘管理</title>
<link href="/2017/07/29/Linux-LVM%E7%A3%81%E7%9B%98%E7%AE%A1%E7%90%86/"/>
<url>/2017/07/29/Linux-LVM%E7%A3%81%E7%9B%98%E7%AE%A1%E7%90%86/</url>
<content type="html"><![CDATA[<h1 id="Linux-LVM磁盘管理"><a href="#Linux-LVM磁盘管理" class="headerlink" title="Linux LVM磁盘管理"></a>Linux LVM磁盘管理</h1><h5 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h5><blockquote><p>LVM(Logical Volume Manager,逻辑卷管理器)是一种可用在 Linux 内核的逻辑分卷管理器,可用于管理磁盘驱动器或其他类似的大容量存储设备</p></blockquote><p>在传统 Linux 环境下,磁盘分区是直接与文件目录(filesystem)直接相互挂载的。倘若用户需要对文件目录的容量进行伸缩的话,通常做法有两种:一是新增磁盘分区,二是对原有的磁盘分区进行划分。无论是上述哪一种做法,都会对原有的磁盘分区产生影响,亦或某些文件损坏,亦或磁盘损坏。</p><p>为了更加方便用户对磁盘分区进行操作,LVM 为计算机提供了更高层次的磁盘存储方式。原理如下所示:LVM 将一个或多个磁盘的分区在逻辑上集合,相当于一个整体的、容量大的磁盘,以便用来使用。当磁盘分区空间不足时,可以继续将其他的磁盘的分区加入其中。</p><p><img src="http://on83riher.bkt.clouddn.com/%E7%A3%81%E7%9B%98%E7%AE%A1%E7%90%86-%E6%A6%82%E5%BF%B5.png" alt=""></p><p>与传统的磁盘管理相比,LVM 更富有弹性:</p><ul><li>使用卷组(VG),使众多硬盘空间看起来像一个大硬盘</li><li>使用逻辑卷(LV),可以创建跨越众多硬盘空间的分区</li><li>可以创建小的逻辑卷(LV),在空间不足时再动态调整它的大小</li><li>在调整逻辑卷(LV)大小时可以不用考虑逻辑卷在硬盘上的位置,不用担心没有可用的连续空间</li><li>可以在线(online)对逻辑卷(LV)和卷组(VG)进行创建、删除、调整大小等操作。LVM上的文件系统也需要重新调整大小,某些文件系统也支持这样的在线操作</li><li>无需重新启动服务,就可以将服务中用到的逻辑卷(LV)在线(online)/动态(live)迁移至别的硬盘上</li><li>允许创建快照,可以保存文件系统的备份,同时使服务的下线时间(downtime)降低到最小</li></ul><hr><h5 id="相关于-LVM-的几个重要名词:"><a href="#相关于-LVM-的几个重要名词:" class="headerlink" title="相关于 LVM 的几个重要名词:"></a>相关于 LVM 的几个重要名词:</h5><ul><li><p><strong>Physical Volume,PV, 物理卷</strong></p><p>可以在上面建立卷组的媒介,可以是硬盘分区,也可以是硬盘本身或者回环文件(loopback file)。物理卷包括一个特殊的 header,其余部分被切割为一块块物理区域(physical extends)</p></li><li><p><strong>Volume Group,VG,卷组</strong></p><p>将一组物理卷收集为一个管理单元。卷组可以视为一个由若干个物理卷组合而成的“磁盘”。卷组同时也能够包含若干个逻辑卷(logical volume)</p></li><li><p><strong>Logical Volume,LV,逻辑卷</strong></p><p>一种特殊的虚拟分区,从属于卷组,可以由若干块物理区域构成。</p></li><li><p><strong>Physical Extent,PE,物理区域</strong></p><p>硬盘可供指派给逻辑卷的最小单位(通常为4MB)</p></li></ul><p><img src="http://on83riher.bkt.clouddn.com/%E7%A3%81%E7%9B%98%E7%AE%A1%E7%90%86-%E8%AF%A6%E6%83%85.png" alt=""></p><hr><h5 id="基本操作"><a href="#基本操作" class="headerlink" title="基本操作"></a>基本操作</h5><h4 id="Physical-Volume,物理卷相关操作"><a href="#Physical-Volume,物理卷相关操作" class="headerlink" title="Physical Volume,物理卷相关操作"></a>Physical Volume,物理卷相关操作</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span> 维护命令</span><br><span class="line"><span class="meta">#</span> pvscan # 在系统中的所有磁盘中搜索已存在的物理卷</span><br><span class="line"><span class="meta">#</span> pvdisplay [<物理卷>] # 显示 全部/指定 物理卷的属性信息</span><br><span class="line"><span class="meta">#</span> pvs # pvdisplay 简约版,仅能得到物理卷的概要信息</span><br><span class="line"><span class="meta">#</span> pvchange [-x {y|n}] [-u] # 用于指定物理卷的 PE 是否允许分配或重新生成物理卷的 UUID</span><br><span class="line"><span class="meta">#</span> pvmove <源物理卷> [<目的物理卷>] # 将同一 VG 下的 PV 内容进行迁移,若不指定目的物理卷则由 LVM 决定</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span> 创建与删除命令</span><br><span class="line"><span class="meta">#</span> pvcreate <设备名> # 用于在磁盘或磁盘分区上创建物理卷初始化信息,以便对该物理卷进行操作</span><br><span class="line"><span class="meta">#</span> pvremove <物理卷> [-d][-f][-y] # 删除物理卷</span><br></pre></td></tr></table></figure><h4 id="Volume-Group,卷组相关操作"><a href="#Volume-Group,卷组相关操作" class="headerlink" title="Volume Group,卷组相关操作"></a>Volume Group,卷组相关操作</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span> 维护命令</span><br><span class="line"><span class="meta">#</span> vgscan # 在系统中搜索所有已存在的 vg</span><br><span class="line"><span class="meta">#</span> vgck <卷组> # 用于检查卷组中卷组描述区域信息的一致性</span><br><span class="line"><span class="meta">#</span> vgdisplay [<卷组>] # 显示 全部/指定 卷组的属性信息</span><br><span class="line"><span class="meta">#</span> vgrename <旧卷组名> <新卷组名> # 卷组重命名</span><br><span class="line"><span class="meta">#</span> vgchange [-a {y|n}] [-x {y|n}] # 用于指定卷组是否允许分配或者卷组容量是否可伸缩</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span> 创建与删除命令</span><br><span class="line"><span class="meta">#</span> vgcreate <卷组> # 用于创建 LVM 卷组</span><br><span class="line"><span class="meta">#</span> vgremove <卷组> # 用于删除 LVM 卷组</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span> 扩充与缩小命令</span><br><span class="line"><span class="meta">#</span> vgextend <卷组> <物理卷> # 向卷组中添加物理卷来增加卷组的容量</span><br><span class="line"><span class="meta">#</span> vgreduce <卷组> <物理卷> # 向卷组中删除物理卷来减小卷组的容量</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span> 合并与拆分命令</span><br><span class="line"><span class="meta">#</span> vgmerge <目的卷组> <源卷组> # 将源卷组合并至目的卷组,要求两个卷组的物理区域大小相等且源卷组是非活动的(inactive)</span><br><span class="line"><span class="meta">#</span> vgsplit <源卷组> <目的卷组> <源物理卷> # 将源卷组的源物理卷拆分到目的卷组</span><br><span class="line"><span class="meta">#</span> vgexport <卷组> # 用于输出卷组,将非活动的(inactive)的卷组导出,可用于其他系统中使用</span><br><span class="line"><span class="meta">#</span> vgimport <卷组> <物理卷> # 用于输入卷组</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span> 备份与恢复命令</span><br><span class="line"><span class="meta">#</span> vgcfgbackup <卷组> # 备份卷组的元信息至 /etc/lvml/backup 目录中</span><br><span class="line"><span class="meta">#</span> vgcfgrestore <卷组> # 从备份文件中恢复指定卷组</span><br></pre></td></tr></table></figure><h4 id="Logical-Volume,逻辑卷相关操作"><a href="#Logical-Volume,逻辑卷相关操作" class="headerlink" title="Logical Volume,逻辑卷相关操作"></a>Logical Volume,逻辑卷相关操作</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span> 维护命令</span><br><span class="line"><span class="meta">#</span> lvscan # 在系统中搜索所有已存在的 lv</span><br><span class="line"><span class="meta">#</span> lvdisplay [<逻辑卷>] # 显示 全部/指定 逻辑卷的属性信息</span><br><span class="line"><span class="meta">#</span> lvrename {<卷组> <旧逻辑卷名> <新逻辑卷名> | <旧逻辑卷路径名> <新逻辑卷路径名>}</span><br><span class="line"><span class="meta">#</span> lvchange # 更改逻辑卷的属性</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span> 创建与删除命令</span><br><span class="line"><span class="meta">#</span> lvcreate <逻辑卷> <卷组> # 用于创建卷组中的逻辑卷</span><br><span class="line"><span class="meta">#</span> lvremove <逻辑卷> <卷组> # 用于删除卷组中的逻辑卷</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span> 扩充与缩小命令</span><br><span class="line"><span class="meta">#</span> lvextend -L +<增量> <逻辑卷> # 根据增量对逻辑卷容量进行扩充</span><br><span class="line"><span class="meta">#</span> lvreduce -L -<减量> <逻辑卷> # 根据减量对逻辑卷容量进行缩小</span><br></pre></td></tr></table></figure><hr><hr><p>参考资料:</p><p>[1] <a href="https://jingyan.baidu.com/article/fedf0737772d2835ac897790.html" target="_blank" rel="noopener">https://jingyan.baidu.com/article/fedf0737772d2835ac897790.html</a></p><p>[2] <a href="https://wiki.archlinux.org/index.php/LVM_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)#.E5.88.9B.E5.BB.BA.E7.89.A9.E7.90.86.E5.8D.B7.EF.BC.88PV.EF.BC.89" target="_blank" rel="noopener">https://wiki.archlinux.org/index.php/LVM_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)#.E5.88.9B.E5.BB.BA.E7.89.A9.E7.90.86.E5.8D.B7.EF.BC.88PV.EF.BC.89</a></p><p>[3] <a href="http://www.cnblogs.com/gaojun/archive/2012/08/22/2650229.html" target="_blank" rel="noopener">http://www.cnblogs.com/gaojun/archive/2012/08/22/2650229.html</a></p><p>[4] <a href="http://www.cnblogs.com/xiaoluo501395377/archive/2013/05/22/3093405.html" target="_blank" rel="noopener">http://www.cnblogs.com/xiaoluo501395377/archive/2013/05/22/3093405.html</a></p>]]></content>
<categories>
<category> Linux </category>
</categories>
</entry>
<entry>
<title>未选择的路</title>
<link href="/2017/07/23/%E6%9C%AA%E9%80%89%E6%8B%A9%E7%9A%84%E8%B7%AF/"/>
<url>/2017/07/23/%E6%9C%AA%E9%80%89%E6%8B%A9%E7%9A%84%E8%B7%AF/</url>
<content type="html"><![CDATA[<blockquote><p>—— 罗伯特·弗罗斯特(美)</p></blockquote><p>黄色的林子里有两条路,</p><p>很遗憾我无法同时选择两者</p><p>身在旅途的我久久站立</p><p>对着其中一条极目眺望</p><p>直到它蜿蜒拐进远处的树丛。</p><p>我选择了另外的一条,天经地义,</p><p>也许更为诱人</p><p>因为它充满荆棘,需要开括;</p><p>然而这样的路过</p><p>并未引起太大的改变。</p><p>那天清晨这两条小路一起静卧在</p><p>无人踩过的树叶丛中</p><p>哦,我把另一条路留给了明天!</p><p>明知路连着路,</p><p>我不知是否该回头。</p><p>我将轻轻叹息,叙述这一切</p><p>许多许多年以后:</p><p>树林里有两条路,我——</p><p>选择了行人稀少的那一条</p><p>它改变了我的一生。</p>]]></content>
<categories>
<category> 不正常的日常 </category>
</categories>
</entry>
<entry>
<title>跟着老师喝喝茶聊聊人生</title>
<link href="/2017/05/18/%E8%B7%9F%E7%9D%80%E8%80%81%E5%B8%88%E5%96%9D%E5%96%9D%E8%8C%B6%E8%81%8A%E8%81%8A%E4%BA%BA%E7%94%9F/"/>
<url>/2017/05/18/%E8%B7%9F%E7%9D%80%E8%80%81%E5%B8%88%E5%96%9D%E5%96%9D%E8%8C%B6%E8%81%8A%E8%81%8A%E4%BA%BA%E7%94%9F/</url>
<content type="html"><![CDATA[<p>本来今晚老师说是小组开会的,后来或许是老师刚开完学校那边的会很累,就临时改成了出去喝东西。一路走去甜品店的时候,自己心里还是没底的,因为不知道一会儿聊的话题会是什么,或许严肃,或许轻松,。等到点了饮品,大家都齐齐坐下的时候,才知道今天老师的主题是聊聊现在小组的状态。</p><p>说实话,今天聊了挺开心的,谈到了关于技术的东西,比如着手实验室的项目,也谈到了关于学习的东西,比如准备明年的项目参加竞赛等。</p><p>今晚我问了很多的问题,我就说下今晚我觉得提出的比较有意义的问题,就是自己是否该去参加某些团队去开发一套通用的、开源的项目。跟着老师这边的说法是,其实大部分开源项目都是程序员下班后自己的业余兴趣去完成,但最主要的一点是,是需要一定的、较为深厚的技术基础的。这番话其实我也有所见解,但是听到老一辈的这么说,心里还是有个底,更加加深了自己的一番见解。</p><p>现阶段,就是需要去积累自己技术的广度与深度,主要是深度,其次是广度。在积累深度的同时,可以触类旁通,了解广度的东西。比如,我感兴趣的东西就是后端Web开发的一套,但是我还是得去了解前端的基本开发,基本框架,这样在以后出去工作,或者钻研技术的时候,才能更好地与前端人员进行交流,了解他们的进度,甚至是在一定程度上参与他们的工作。</p><p>在积累自己技术的同时,还得积累自己的实战经历,比如在实验室做项目。实验室这边是与某公司进行合作,进行公司内部的系统的重构。在这个阶段,我能够在浅面上能够了解公司的现有工程结构,以及可以适度掌握如何与公司方进行交涉等能力。</p><p>今晚,我更加印证了自己的想法,<strong>在一定的阶段之前,切记浮躁心态,沉下心来,扎牢基础,打磨自己。</strong></p>]]></content>
<categories>
<category> 不正常的日常 </category>
</categories>
</entry>
<entry>
<title>《大型网站系统与Java中间件开发实践》笔记</title>
<link href="/2017/05/17/%E3%80%8A%E5%A4%A7%E5%9E%8B%E7%BD%91%E7%AB%99%E7%B3%BB%E7%BB%9F%E4%B8%8EJava%E4%B8%AD%E9%97%B4%E4%BB%B6%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%E3%80%8B%E7%AC%94%E8%AE%B0/"/>
<url>/2017/05/17/%E3%80%8A%E5%A4%A7%E5%9E%8B%E7%BD%91%E7%AB%99%E7%B3%BB%E7%BB%9F%E4%B8%8EJava%E4%B8%AD%E9%97%B4%E4%BB%B6%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%E3%80%8B%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<blockquote><p>《大型网站系统与Java中间件开发实践》 - 作者曾宪杰 :本书围绕大型网站和支撑大型网站架构的 Java 中间件的实践展开介绍。从分布式系统的知识切入,让读者对分布式系统有基本的了解;然后介绍大型网站随着数据量、访问量增长而发生的架构变迁;接着讲述构建 Java 中间件的相关知识;之后的几章都是根据笔者的经验来介绍支撑大型网站架构的 Java 中间件系统的设计和实践。希望读者通过本书可以了解大型网站架构变迁过程中的较为通用的问题和解法,并了解构建支撑大型网站的 Java 中间件的实践经验。</p></blockquote><p>这本书通读下来,对于大型网站系统的有了基本的概念和看法。</p><p>第一次使用XMind整理读书笔记,感觉还挺不错的,希望以后坚持使用。</p><p><img src="http://on83riher.bkt.clouddn.com/%E5%A4%A7%E5%9E%8B%E7%BD%91%E7%AB%99%E7%B3%BB%E7%BB%9F%E4%B8%8EJava%E4%B8%AD%E9%97%B4%E4%BB%B6%E5%AE%9E%E8%B7%B5%E7%AC%94%E8%AE%B0.png" alt="img"></p>]]></content>
<categories>
<category> 技术笔记 </category>
</categories>
</entry>
<entry>
<title>Cookie和Session</title>
<link href="/2017/05/14/Cookie%E5%92%8CSession/"/>
<url>/2017/05/14/Cookie%E5%92%8CSession/</url>
<content type="html"><![CDATA[<p>在日常生活中,作为用户的我们经常与互联网中的大大小小的网站进行交互,例如查看新闻、购买商品等等。在浏览商品的时候,我们经常会将喜欢的商品暂时加入购物车,以便后面的时候一起购买。但对于同一个商城的若干次页面请求当中,服务器在后台当中是如何判别该购物车是哪一个用户的呢?</p><p><strong>对于一次网络的请求,一般都是基于HTTP,但是HTTP是一种无状态协议(即不保留管理用户数据)</strong>,所以不能够单纯依靠HTTP协议。为了针对这一现象,运生出了<strong>Cookie(面向客户端)</strong>和<strong>Session(面向服务端)</strong>两种通用方案。</p><h2 id="Cookie"><a href="#Cookie" class="headerlink" title="Cookie"></a>Cookie</h2><blockquote><p>Cookie,中文名称为”小型文本文件“,指某些网站为了辨别用户而存储在用户本地终端上的数据(通常经过加密)。</p></blockquote><hr><h4 id="用途"><a href="#用途" class="headerlink" title="用途"></a>用途</h4><p>在之前的介绍当中,我们谈及到了购物车。当用户选购一件商品的时候,服务器在向用户传递该商品页面的同时,<strong>还附加发送了一段Cookie(Response - Set-Cookie)</strong>。在用户点击加入购物车按钮时,客户端会将该商品的Cookie发送给服务器,这样,服务器就明白该用户的选购了哪些商品。最后,当用户点击查看购物车时,客户端会<strong>发送该用户的Cookie给服务器(Reuqest - Cookie)</strong>,服务器就会回传该用户的购物车记录。</p><p>使用Cookie的另外一个经典场景是当登陆一个网站时,网站往往会请求用户输入用户名和密码,并且用户可以勾选“下次自动登录”。当用户勾选了,那么下次访问同一个网站的时候,用户会自动跳过登录界面,跳转至主页当中。这其中的过程就是,在前一次正常的登陆过程中,服务器回传了包含登录凭据(类似于token)的Cookie,并保存在用户的硬盘空间上,第二次访问时,客户端会自动将该Cookie发送至服务器中,从而无须再次输入凭证,即可成功登陆。</p><hr><h4 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h4><p><img src="http://on83riher.bkt.clouddn.com/1891324-bc6799943027a9a2.png" alt="img"></p><h5 id="常见结构"><a href="#常见结构" class="headerlink" title="常见结构"></a>常见结构</h5><ul><li><p><img src="http://on83riher.bkt.clouddn.com/%E6%8D%95%E8%8E%B7.png" alt="img"></p><p>来源于Amazon</p></li></ul><p>其中Cookie的内部格式常以键值对的形式进行保存,可以是系统规定的,也可以添加自定义的Cookie。以下为常用的Cookie属性。</p><table><thead><tr><th>属性</th><th>说明</th></tr></thead><tbody><tr><td>NAME=VALUE</td><td>赋予 Cookie 的名称和其值(必须项)</td></tr><tr><td>expires=DATE</td><td>Cookie 的有效期(若不明确指定则默认为浏览器关闭前为止)</td></tr><tr><td>path=PATH</td><td>将服务器上的文件目录作为 Cookie 的适用对象(若不指定则默认为文档所在的目录)</td></tr><tr><td>domain=域名</td><td>作为 Cookie 适用对象的域名(若不指定则默认为创建 Cookie 的服务器的域名)</td></tr><tr><td>Secure</td><td>仅在 HTTPS 安全通信时才会发送 Cookie</td></tr><tr><td>HttpOnly</td><td>加以限制,使 Cookie 不能被 Javascript 脚本访问</td></tr></tbody></table><hr><h4 id="缺陷"><a href="#缺陷" class="headerlink" title="缺陷"></a>缺陷</h4><ul><li>Cookie会附加在客户端中的每一个HTTP请求,增加了传输流量。</li><li>Cookie中HTTP中是以明文的方式进行传递(即使进行了值加密也会以加密后的内容传送),倘若保存重要数据的Cookie被窃取(XSS-跨站式脚本),会产生严重的安全性问题。</li><li>Cookie能够发送的数量(最多20个,但通常浏览器支持会大于20个)和大小(最大为4KB)受到限制。</li></ul><hr><h2 id="Session"><a href="#Session" class="headerlink" title="Session"></a>Session</h2><blockquote><p>Session(会话)是一种持久网络协议,在用户(或用户代理)端和服务器端之间的创建关联,从而起到交换数据包的作用机制。</p></blockquote><p>根据上述的介绍,很难将Session与Cookie进行区别。它们两者都用共同的作用——保存用户信息,但是最本质的区别在于Cookie是保留在客户端的,而Session是保留在服务器端。然而Session的实现却需要服务器端和客户端同时实现才能发挥作用。</p><p>以下以Tomcat为容器的Web应用为例。</p><hr><h4 id="客户端的Session"><a href="#客户端的Session" class="headerlink" title="客户端的Session"></a>客户端的Session</h4><p><img src="http://on83riher.bkt.clouddn.com/%E6%8D%95%E8%8E%B72-1.png" alt="img"></p><p>在用户的第一次发起正常的HTTP请求时,服务器端会在请求中(通常在Cookie当中)查看是否已经存在Session标识,若不存在,服务器端将会根据自动生成Session标识(常为SessionID),并在Response中设置Cookie。若存在,服务器将使用请求中的Session标识,来标识该用户。</p><hr><h4 id="服务器端的Session"><a href="#服务器端的Session" class="headerlink" title="服务器端的Session"></a>服务器端的Session</h4><p><img src="http://on83riher.bkt.clouddn.com/%E6%8D%95%E8%8E%B73.png" alt="img"></p><p>在之后的请求当中,客户端都会附加含有SessionID的Cookie到服务器端。服务器端将鉴别该Cookie中的SessionID,赋予该发起请求的用户。</p><hr><h4 id="缺陷-1"><a href="#缺陷-1" class="headerlink" title="缺陷"></a>缺陷</h4><ul><li>生成Session的文件是保留在内存当中。倘若服务器每天接受千万次请求,那么服务器的资源肯定会被消耗殆尽,导致服务器不可用。通常的做法是用Redis保存用户的Session,查询时直接向Redis发出请求即可。</li><li>Session标识通常保存在Cookie当中,若浏览器禁用了Cookie,则导致服务器无法正常识别该用户。另一种可用的方式是URL重写,即直接将SessionID标识写在URL上(以QueryString的形式)</li></ul><p><img src="http://on83riher.bkt.clouddn.com/%E6%8D%95%E8%8E%B74.png" alt="img"></p><hr><hr><h4 id="参考资料:"><a href="#参考资料:" class="headerlink" title="参考资料:"></a>参考资料:</h4><p>[1] <a href="http://www.jianshu.com/p/e143ddf6fc84" target="_blank" rel="noopener">http://www.jianshu.com/p/e143ddf6fc84</a></p><p>[2] <a href="http://www.jianshu.com/p/2b7c10291aad" target="_blank" rel="noopener">http://www.jianshu.com/p/2b7c10291aad</a></p><p>[3] <a href="https://zh.wikipedia.org/wiki/Cookie" target="_blank" rel="noopener">https://zh.wikipedia.org/wiki/Cookie</a></p><p>[4]<a href="https://zh.wikipedia.org/wiki/%E4%BC%9A%E8%AF%9D_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6" target="_blank" rel="noopener">https://zh.wikipedia.org/zh-hans/%E4%BC%9A%E8%AF%9D_</a>)(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)</p>]]></content>
<categories>
<category> 技术笔记 </category>
</categories>
</entry>
<entry>
<title>JSON笔记</title>
<link href="/2017/04/19/JSON%E7%AC%94%E8%AE%B0/"/>
<url>/2017/04/19/JSON%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h4 id="什么是-JSON"><a href="#什么是-JSON" class="headerlink" title="什么是 JSON"></a>什么是 JSON</h4><p>JSON 的全称是 JavaScript Object Notation (JavaScript对象表示法)。它是一种独立于编程语言、轻量级的数据交换格式(类似于XML等)。</p><blockquote><p>JSON 是基于 JavaScript 对象字面量的,是 JavaScript 的一个子集。详细方面可以参考 <a href="http://www.json.org/" target="_blank" rel="noopener">http://www.json.org/</a></p></blockquote><hr><h4 id="为什么使用JSON"><a href="#为什么使用JSON" class="headerlink" title="为什么使用JSON"></a>为什么使用JSON</h4><p>JSON 被认为是 XML 的很好替代者。因为 JSON 的可读性非常好,而且它没有像 XML 那样包含很多冗余的元素标签,这使得应用在使用JSON进行网络传输以及进行解析处理的速度更快,效率更高。</p><hr><h4 id="JSON-语法格式"><a href="#JSON-语法格式" class="headerlink" title="JSON 语法格式"></a>JSON 语法格式</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name": "xiaoming",</span><br><span class="line"> "age": 20,</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>JSON 以<strong>名称 - 值对(又称键 - 值)</strong>的方式存储数据。</li><li>JSON 中使用<strong>冒号(:)分隔名称和值。</strong>名称始终在左侧,值始终在右侧。</li><li><strong>名称必须以双引号(”)括起,</strong>不能够使用单引号,亦或者不用。</li></ul><h5 id=""><a href="#" class="headerlink" title=" "></a> </h5><h5 id="从机器读取-JSON-的角度来解析-JSON-语法结构:"><a href="#从机器读取-JSON-的角度来解析-JSON-语法结构:" class="headerlink" title="从机器读取 JSON 的角度来解析 JSON 语法结构:"></a>从机器读取 JSON 的角度来解析 JSON 语法结构:</h5><ul><li><strong>{ (左花括号)指”开始读取对象“</strong></li><li><strong>} (右花括号)值“结束读取对象”</strong></li><li><strong>[ (左方括号)指“开始读取数组”</strong></li><li><strong>] (右方括号)指”结束读取数组“</strong></li><li><strong>: (冒号)指“在名称 - 值对中分隔名称和值”</strong></li><li><strong>, (逗号)指“分隔对象中名称 - 值”或者“分隔数组中的值”</strong></li></ul><hr><h4 id="JSON-数据类型"><a href="#JSON-数据类型" class="headerlink" title="JSON 数据类型"></a>JSON 数据类型</h4><ul><li><strong>对象数据类型</strong></li></ul><p><strong>JSON 本身就是对象,也就是一个被花括号包裹的名称 - 值对的列表。</strong>在 JSON 内部表示对象的方式,就如同与表示 JSON。下面 JSON 数据中包含一个 person 对象(person对象中还包含一个 hair 对象)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "person": {</span><br><span class="line"> "name": "xiaoming",</span><br><span class="line"> "age": 20,</span><br><span class="line"> "hair": {</span><br><span class="line"> "color": "light blond",</span><br><span class="line"> "length": "short"</span><br><span class="line"> },</span><br><span class="line"> "eyes": "green"</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li><strong>字符串类型</strong></li></ul><p>JSON的值以字符串的形式表示,如同<code>{ "animal": "cat"}</code>中的<code>"cat"</code>。<strong>JSON 中的字符串可以由任何 UniCode 字符构成。字符串的两边必须被双引号包裹。</strong></p><p>在 JSON 的值中,一对双引号代表字符串中起点与终点。如果我们在双引号内部再次使用双引号,JSON编译器就无法识别双引号后面的内容(如下 “Hello 后面的内容),就会出现报错。错误用法如下所示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "description": "Say "Hello" to everyone."</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>一般来说,字符串中如果需要使用特殊符号(例如双引号),则必须使用<strong>转义符号来表示(\)。</strong>正确用法如下所示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "description": "Say \"Hello\" to everyone."</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li><strong>数字类型</strong></li></ul><p>数字是一种常见的用于传递数据的信息片段。<strong>JSON 中的数字可以是整数、浮点数(双精度)、负数或者是指数。</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "widgetInventory": 289,</span><br><span class="line"> "sadSavingAccount": 22.59,</span><br><span class="line"> "seattleLatitude": 47.606209,</span><br><span class="line"> "seattleLongtitude": -122.332071,</span><br><span class="line"> "earthMass": 5.97219e+24</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li><strong>布尔值类型</strong></li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "toastWithBreakfast": false,</span><br><span class="line"> "breadWithLunch": true</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li><strong>null 类型</strong></li></ul><p><strong>null 是一个表示“没有值”的特殊值。</strong>它表示名称对应的值为空。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "shoes": "slippers",</span><br><span class="line"> "watch": null</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li><strong>数组类型</strong></li></ul><p>数组类型表示,可以在形如列表的内部存储若干数组。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "eggCarton": [</span><br><span class="line"> "egg",</span><br><span class="line"> "egg",</span><br><span class="line"> null</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>特别需要注意的是,在 JSON 的数组内部,只存在值,而不存在数据类型等这样的说法。换句话说,<strong>就是在数组内部,可以存储任何合法的 JSON 数据类型(字符串、数字、对象、布尔值等)。</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "eggCarton": [</span><br><span class="line"> "egg",</span><br><span class="line"> 5,</span><br><span class="line"> null</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h4 id="在-Web开发中需要注意的地方"><a href="#在-Web开发中需要注意的地方" class="headerlink" title="在 Web开发中需要注意的地方"></a>在 Web开发中需要注意的地方</h4><ul><li>在 HTTP 请求当中传递 JSON 时,需要设置媒体类型为 <strong>application/json</strong>。</li></ul><hr><hr><p>参考资料:</p><p>[1] <a href="https://book.douban.com/subject/26789960/" target="_blank" rel="noopener">https://book.douban.com/subject/26789960/</a></p>]]></content>
<categories>
<category> 技术笔记 </category>
</categories>
</entry>
<entry>
<title>数据库连接查询</title>
<link href="/2017/04/14/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%9F%A5%E8%AF%A2/"/>
<url>/2017/04/14/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%9F%A5%E8%AF%A2/</url>
<content type="html"><![CDATA[<p>操作数据库当中,常常涉及多表查询。而在进行多表查询操作时,SQL提供了不同的表与表之间的连接方式,来获取用户所需的数据。</p><p>以下我们将主要围绕以下两个表进行数据库的多表查询操作:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">> -- 雇员表</span><br><span class="line">> select * from employee;</span><br><span class="line">+----+------+---------------+</span><br><span class="line">| id | name | department_id |</span><br><span class="line">+----+------+---------------+</span><br><span class="line">| 1 | 张三 | 1 |</span><br><span class="line">| 2 | 李四 | 1 |</span><br><span class="line">| 3 | 王五 | 2 |</span><br><span class="line">| 4 | 赵六 | 2 |</span><br><span class="line">| 5 | 郑七 | 3 |</span><br><span class="line">+----+------+---------------+</span><br><span class="line"></span><br><span class="line">> -- 部门表</span><br><span class="line">> select * from department;</span><br><span class="line">+----+--------+</span><br><span class="line">| id | name |</span><br><span class="line">+----+--------+</span><br><span class="line">| 1 | 技术部 |</span><br><span class="line">| 2 | 技术部 |</span><br><span class="line">| 3 | 工程部 |</span><br><span class="line">+----+--------+</span><br></pre></td></tr></table></figure><hr><ul><li>内连接(INNER JOIN)</li></ul><p>内连接使用比较运算符根据每个表共有的列的值匹配两个表中的行。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">> -- 由于雇员表中郑七没有与部门表中任何一行匹配,即在返回的数据不会显示</span><br><span class="line">> SELECT e.id AS '员工编号', e. NAME AS '员工名称', d. NAME AS '所属部门'</span><br><span class="line">> FROM employee AS e</span><br><span class="line">> INNER JOIN department AS d ON e.department_id = d.id;</span><br><span class="line">+----------+----------+----------+</span><br><span class="line">| 员工编号 | 员工名称 | 所属部门 |</span><br><span class="line">+----------+----------+----------+</span><br><span class="line">| 1 | 张三 | 技术部 |</span><br><span class="line">| 2 | 李四 | 技术部 |</span><br><span class="line">| 3 | 王五 | 市场部 |</span><br><span class="line">| 4 | 赵六 | 市场部 |</span><br><span class="line">+----------+----------+----------+</span><br></pre></td></tr></table></figure><ul><li>左外连接(LEFT JOIN)</li></ul><p>左向外连接的结果集包括LEFT OUTER子句中指定的左表的所有行,而不仅仅是连接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表列均为空值。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">> -- 左外连接会根据左表中的所有内容与右表进行匹配,即使右表不匹配也会显示所有数据</span><br><span class="line">> SELECT e.id AS '员工编号', e. NAME AS '员工名称', d. NAME AS '所属部门'</span><br><span class="line">> FROM employee AS e</span><br><span class="line">> LEFT JOIN department AS d ON e.department_id = d.id;</span><br><span class="line">+----------+----------+----------+</span><br><span class="line">| 员工编号 | 员工名称 | 所属部门 |</span><br><span class="line">+----------+----------+----------+</span><br><span class="line">| 1 | 张三 | 技术部 |</span><br><span class="line">| 2 | 李四 | 技术部 |</span><br><span class="line">| 3 | 王五 | 市场部 |</span><br><span class="line">| 4 | 赵六 | 市场部 |</span><br><span class="line">| 5 | 郑七 | NULL |</span><br><span class="line">+----------+----------+----------+</span><br></pre></td></tr></table></figure><ul><li>右外连接(RIGHT JOIN)</li></ul><p>右向外连接是左向外连接的反向连接。将返回右表的所有行。如果右表的某行在左表中没有匹配行,则将为左表返回空值。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">> -- 与左外连接完全相反</span><br><span class="line">> SELECT e.id AS '员工编号', e. NAME AS '员工名称', d. NAME AS '所属部门'</span><br><span class="line">> FROM employee AS e</span><br><span class="line">> RIGHT JOIN department AS d ON e.department_id = d.id;</span><br><span class="line">+----------+----------+----------+</span><br><span class="line">| 员工编号 | 员工名称 | 所属部门 |</span><br><span class="line">+----------+----------+----------+</span><br><span class="line">| 1 | 张三 | 技术部 |</span><br><span class="line">| 2 | 李四 | 技术部 |</span><br><span class="line">| 3 | 王五 | 市场部 |</span><br><span class="line">| 4 | 赵六 | 市场部 |</span><br><span class="line">| NULL | NULL | 工程部 |</span><br><span class="line">+----------+----------+----------+</span><br></pre></td></tr></table></figure><ul><li>全外连接(FULL JOIN)</li></ul><p>完整外部连接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">> -- MYSQL不支持全外连接,这个例子为手写例子</span><br><span class="line">> SELECT e.id AS '员工编号', e. NAME AS '员工名称', d. NAME AS '所属部门'</span><br><span class="line">> FROM employee AS e</span><br><span class="line">> FULL JOIN department AS d ON e.department_id = d.id;</span><br><span class="line">+----------+----------+----------+</span><br><span class="line">| 员工编号 | 员工名称 | 所属部门 |</span><br><span class="line">+----------+----------+----------+</span><br><span class="line">| 1 | 张三 | 技术部 |</span><br><span class="line">| 2 | 李四 | 技术部 |</span><br><span class="line">| 3 | 王五 | 市场部 |</span><br><span class="line">| 4 | 赵六 | 市场部 |</span><br><span class="line">| 5 | 郑七 | NULL |</span><br><span class="line">| NULL | NULL | 工程部 |</span><br><span class="line">+----------+----------+----------+</span><br></pre></td></tr></table></figure><hr><p>参考资料:</p><p>[1] <a href="http://www.cnblogs.com/devilmsg/archive/2009/03/24/1420543.html" target="_blank" rel="noopener">http://www.cnblogs.com/devilmsg/archive/2009/03/24/1420543.html</a></p><p>[2] <a href="http://www.cnblogs.com/youzhangjin/archive/2009/05/22/1486982.html" target="_blank" rel="noopener">http://www.cnblogs.com/youzhangjin/archive/2009/05/22/1486982.html</a></p>]]></content>
<categories>
<category> MySQL </category>
</categories>
</entry>
<entry>
<title>使用Thymeleaf遇到的问题及解决</title>
<link href="/2017/04/14/%E4%BD%BF%E7%94%A8Thymeleaf%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/"/>
<url>/2017/04/14/%E4%BD%BF%E7%94%A8Thymeleaf%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/</url>
<content type="html"><![CDATA[<ol><li><strong>**使用th:each时,无法作用于循环最开始处获取循环体。</strong></li></ol><p>虽然像大多数的循环中都是无法在循环开始处就获得循环体,但仍有时候,根据某些框架的特性,需要在开始处进行属性设置。</p><p>以下示例中,想要在每次循环中在 tr 中根据 user 中的值 ${user.id} 设置 rel属性。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><!-- 以下方式不会有用,也不会报错 --></span><br><span class="line"><tbody></span><br><span class="line"><tr th:each="user: ${users}" rel="${user.id}" ></span><br><span class="line"><td th:text="${user.id}"></td></span><br><span class="line"><td th:text="${user.username}"></td></span><br><span class="line"><td th:text="${user.password}"></td></span><br><span class="line"></tr></span><br><span class="line"></tbody></span><br></pre></td></tr></table></figure><p> 如果想要必须设置的话,就需要在后期利用JS代码手动实现。</p><hr><ol><li><strong>thymeleaf 提供的自定义属性设置在大多数时候不会有效。</strong></li></ol><p>在官方提供的例子中,用户在使用 thymeleaf 时可以对自定义属性来进行赋值。以下为官方示例:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><!-- </span><br><span class="line">Thymeleaf offers a default attribute processor that allows us to </span><br><span class="line"> set the value of any attribute, even if no specific th:* processor </span><br><span class="line"> has been defined for it at the Standard Dialect.</span><br><span class="line">--></span><br><span class="line"></span><br><span class="line"><!-- So something like: --></span><br><span class="line"><span th:whatever="${user.name}">...</span></span><br><span class="line"></span><br><span class="line"><-- Will result in: --></span><br><span class="line"><span whatever="John Apricot">...</span></span><br></pre></td></tr></table></figure><p>但在实际使用过程中,这个特性不尽人意,从后台获取的值有时无法利用上面的方式作用到页面中。</p><p>有个可取的,稳定的解决方法如下所示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><!-- 利用 th:attr 将需要自定义属性写在里面 --></span><br><span class="line"><span th:attr="whatever=${user.name}, another=${user.age}">...</span></span><br><span class="line"></span><br><span class="line"><!-- 实际效果 --></span><br><span class="line"><span whatever="John Apricot" another="23">...</span></span><br></pre></td></tr></table></figure><hr><ol><li><strong>设置表单中的action属性时,注意花括号的引用</strong></li></ol><p>在设置URL地址时,thymeleaf 推荐使用<code>@</code>符号:<code>@{...}</code>。</p><p>以下示例指明了需要注意的地方:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><!-- Will produce '/gtvg/order/details?orderId=3' --></span><br><span class="line"><a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a></span><br><span class="line"></span><br><span class="line"><!-- Will produce '/gtvg/order/3/details' --></span><br><span class="line"><a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a></span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> 技术笔记 </category>
</categories>
</entry>
<entry>
<title>Linux命令 - netstat</title>
<link href="/2017/04/07/Linux%E5%91%BD%E4%BB%A4-netstat/"/>
<url>/2017/04/07/Linux%E5%91%BD%E4%BB%A4-netstat/</url>
<content type="html"><![CDATA[<h4 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h4><blockquote><p>netstat - 显示各种网络连接的相关信息,如网络连接(network connections)、路由表(routing tables)、接口状态(interface statistics)和多播成员(multicast memberships)。</p></blockquote><hr><h4 id="基本语法"><a href="#基本语法" class="headerlink" title="基本语法"></a>基本语法</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">netstate [-a][-t][-u][-x][-n][-l][-p][-r][-e][-s][-c]</span><br></pre></td></tr></table></figure><h5 id="基本参数解释:"><a href="#基本参数解释:" class="headerlink" title="基本参数解释:"></a>基本参数解释:</h5><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td>-a –all</td><td>显示所有连接和未连接的端口</td></tr><tr><td>-t –tcp</td><td>仅显示TCP套接字连接的端口</td></tr><tr><td>-u –udp</td><td>仅显示UDP套接字连接的端口</td></tr><tr><td>-x -unix</td><td>仅显示UNIX套接字连接的端口</td></tr><tr><td>-n –numeric</td><td>以完整名称的方式(即数字)显示所有端口</td></tr><tr><td>-l –listening</td><td>仅显示所有已连接的端口</td></tr><tr><td>-p –program</td><td>额外显示PID(进程ID)/Program name(进程名称)</td></tr><tr><td>-r –route</td><td>显示路由表(routing table)</td></tr><tr><td>-e –extend</td><td>显示网络连接的额外信息(如User)</td></tr><tr><td>-s –statistics</td><td>显示网络连接的统计信息</td></tr><tr><td>-c –continuous</td><td>持续列出网络状态</td></tr></tbody></table><h4 id="常用基本操作"><a href="#常用基本操作" class="headerlink" title="常用基本操作"></a>常用基本操作</h4><ul><li>显示所有端口</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"># netstat -a</span><br><span class="line">Active Internet connections (servers and established)</span><br><span class="line">Proto Recv-Q Send-Q Local Address Foreign Address State</span><br><span class="line">tcp 0 0 localhost:6379 *:* LISTEN</span><br><span class="line">tcp 0 0 *:sunrpc *:* LISTEN</span><br><span class="line">tcp 0 0 localhost:6379 localhost:36240 ESTABLISHED</span><br><span class="line">tcp 1 0 172.16.8.69:38720 123.58.173.186:http CLOSE_WAIT</span><br><span class="line">tcp 0 0 localhost:36000 localhost:6379 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:36241 localhost:6379 ESTABLISHED</span><br><span class="line">tcp 0 0 *:39395 *:* LISTEN</span><br><span class="line">tcp 0 0 localhost:mxi *:* LISTEN</span><br><span class="line">tcp 0 0 *:8009 *:* LISTEN</span><br><span class="line">udp 0 0 *:sunrpc *:*</span><br><span class="line">udp 0 0 *:ipp *:*</span><br><span class="line">udp 0 0 192.168.122.1:ntp *:*</span><br><span class="line">udp 0 0 192.168.122.1:domain *:*</span><br><span class="line">Active UNIX domain sockets (servers and established)</span><br><span class="line">Proto RefCnt Flags Type State I-Node Path</span><br><span class="line">unix 13 [ ] DGRAM 507392 /dev/log</span><br><span class="line">unix 2 [ ACC ] STREAM LISTENING 507758 /var/run/rpcbind.sock</span><br><span class="line">unix 2 [ ACC ] STREAM LISTENING 15533 /var/run/dbus/system_bus_socket</span><br><span class="line">unix 2 [ ACC ] STREAM LISTENING 18496 @/tmp/gdm-greeter-jHQBuBtc</span><br><span class="line">unix 2 [ ACC ] STREAM LISTENING 22717 @/tmp/dbus-Fzbdj98FAc</span><br><span class="line">...</span><br></pre></td></tr></table></figure><ul><li>仅显示TCP端口(同理于仅显示UDP端口)</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"># netstat -t # 仅显示所有已建立连接的TCP端口</span><br><span class="line">Active Internet connections (w/o servers)</span><br><span class="line">Proto Recv-Q Send-Q Local Address Foreign Address State</span><br><span class="line">tcp 0 0 localhost:35206 localhost:6379 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:35286 localhost:6379 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:36227 localhost:6379 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:6379 localhost:36306 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:6379 localhost:35286 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:6379 localhost:36226 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:6379 localhost:36227 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:6379 localhost:36000 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:35201 localhost:6379 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:6379 localhost:35201 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:6379 localhost:35206 ESTABLISHED</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"># netstat -at # 显示所有TCP端口</span><br><span class="line">Active Internet connections (servers and established)</span><br><span class="line">Proto Recv-Q Send-Q Local Address Foreign Address State</span><br><span class="line">tcp 0 0 localhost:6379 *:* LISTEN</span><br><span class="line">tcp 0 0 *:sunrpc *:* LISTEN</span><br><span class="line">tcp 0 0 localhost:webcache *:* LISTEN</span><br><span class="line">tcp 0 0 *:52912 *:* LISTEN</span><br><span class="line">tcp 0 0 192.168.122.1:domain *:* LISTEN</span><br><span class="line">tcp 0 0 *:ssh *:* LISTEN</span><br><span class="line">tcp 0 0 localhost:ipp *:* LISTEN</span><br><span class="line">tcp 0 0 localhost:smtp *:* LISTEN</span><br><span class="line">tcp 0 0 *:rxapi *:* LISTEN</span><br><span class="line">tcp 0 0 localhost:35206 localhost:6379 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:35286 localhost:6379 ESTABLISHED</span><br></pre></td></tr></table></figure><ul><li>显示所有处于监听状态(LISTEN)的端口</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"># netstat -l</span><br><span class="line">Active Internet connections (only servers)</span><br><span class="line">Proto Recv-Q Send-Q Local Address Foreign Address State</span><br><span class="line">tcp 0 0 localhost:6379 *:* LISTEN</span><br><span class="line">tcp 0 0 localhost:smtp *:* LISTEN</span><br><span class="line">tcp 0 0 *:39395 *:* LISTEN</span><br><span class="line">tcp 0 0 localhost:mxi *:* LISTEN</span><br><span class="line">tcp 0 0 *:8009 *:* LISTEN</span><br><span class="line">udp 0 0 *:sunrpc *:*</span><br><span class="line">udp 0 0 *:ipp *:*</span><br><span class="line">udp 0 0 192.168.122.1:ntp *:*</span><br><span class="line">udp 0 0 172.16.8.69:ntp *:*</span><br><span class="line">Active UNIX domain sockets (only servers)</span><br><span class="line">Proto RefCnt Flags Type State I-Node Path</span><br><span class="line">unix 2 [ ACC ] STREAM LISTENING 507758 /var/run/rpcbind.sock</span><br><span class="line">unix 2 [ ACC ] STREAM LISTENING 15533 /var/run/dbus/system_bus_socket</span><br><span class="line">unix 2 [ ACC ] STREAM LISTENING 18496 @/tmp/gdm-greeter-jHQBuBtc</span><br><span class="line">...</span><br></pre></td></tr></table></figure><ul><li>显示所有端口的统计信息</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"># netstat -s</span><br><span class="line">Ip:</span><br><span class="line"> 6626695 total packets received</span><br><span class="line"> 6625 with invalid addresses</span><br><span class="line"> 0 forwarded</span><br><span class="line"> 0 incoming packets discarded</span><br><span class="line"> 5055725 incoming packets delivered</span><br><span class="line"> 4377026 requests sent out</span><br><span class="line"> 96 dropped because of missing route</span><br><span class="line">...</span><br><span class="line">Tcp:</span><br><span class="line"> 3196 active connections openings</span><br><span class="line"> 1666 passive connection openings</span><br><span class="line"> 382 failed connection attempts</span><br><span class="line"> 39 connection resets received</span><br><span class="line"> 22 connections established</span><br><span class="line"> 5043080 segments received</span><br><span class="line"> 4352432 segments send out</span><br><span class="line"> 6833 segments retransmited</span><br><span class="line"> 73 bad segments received.</span><br><span class="line"> 443 resets sent</span><br><span class="line">Udp:</span><br><span class="line"> 10653 packets received</span><br><span class="line"> 80 packets to unknown port received.</span><br><span class="line"> 0 packet receive errors</span><br><span class="line"> 14349 packets sent</span><br><span class="line">...</span><br></pre></td></tr></table></figure><ul><li>利用数字取代别名(主机、用户名),并加速输出所有网络连接信息</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"># netstat -n</span><br><span class="line">Active Internet connections (w/o servers)</span><br><span class="line">Proto Recv-Q Send-Q Local Address Foreign Address State</span><br><span class="line">tcp 0 0 127.0.0.1:35206 127.0.0.1:6379 ESTABLISHED</span><br><span class="line">tcp 0 0 127.0.0.1:35286 127.0.0.1:6379 ESTABLISHED</span><br><span class="line">tcp 0 0 127.0.0.1:36227 127.0.0.1:6379 ESTABLISHED</span><br><span class="line">tcp 0 0 127.0.0.1:6379 127.0.0.1:36306 ESTABLISHED</span><br><span class="line">tcp 0 0 127.0.0.1:6379 127.0.0.1:35286 ESTABLISHED</span><br><span class="line">tcp 0 0 127.0.0.1:6379 127.0.0.1:36226 ESTABLISHED</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"># netstat --numeric-ports # 仅替换端口别名</span><br><span class="line"># netstat --numeric-hosts # 仅替换主机别名</span><br><span class="line"># netstat --numeric-users # 仅替换用户别名</span><br></pre></td></tr></table></figure><ul><li>持续显示网络连接状况(以秒为单位,默认为1秒)</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># netstat -c 60 # 每隔60秒运行一次</span><br><span class="line">Active Internet connections (w/o servers)</span><br><span class="line">Proto Recv-Q Send-Q Local Address Foreign Address State</span><br><span class="line">tcp 0 0 localhost:35206 localhost:6379 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:35286 localhost:6379 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:36227 localhost:6379 ESTABLISHED</span><br><span class="line">tcp 0 0 localhost:6379 localhost:36306 ESTABLISHED</span><br><span class="line">...</span><br></pre></td></tr></table></figure><ul><li>显示核心路由信息</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># netstat -r</span><br><span class="line">Kernel IP routing table</span><br><span class="line">Destination Gateway Genmask Flags MSS Window irtt Iface</span><br><span class="line">192.168.0.1 * 255.255.255.0 U 0 0 0 eth0</span><br><span class="line">192.168.122.0 * 255.255.255.0 U 0 0 0 virbr0</span><br><span class="line">default 192.168.0.1 0.0.0.0 UG 0 0 0 eth0</span><br></pre></td></tr></table></figure><hr><h4 id="与其他命令搭配使用"><a href="#与其他命令搭配使用" class="headerlink" title="与其他命令搭配使用"></a>与其他命令搭配使用</h4><ul><li>显示程序所占用的端口</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># netstat -ap | grep ssh</span><br><span class="line">tcp 0 0 *:ssh *:* LISTEN 18116/sshd</span><br><span class="line">tcp 0 0 173.15.1.50:ssh 113.56.213.179:newheights ESTABLISHED 6111/sshd</span><br><span class="line">tcp 0 0 *:ssh *:* LISTEN 18116/sshd</span><br><span class="line">...</span><br></pre></td></tr></table></figure><hr><hr><p>参考资料:</p><p>[1] <a href="http://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316661.html" target="_blank" rel="noopener">http://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316661.html</a></p>]]></content>
<categories>
<category> 技术笔记 </category>
</categories>
</entry>
<entry>
<title>死锁的概念</title>
<link href="/2017/04/06/%E6%AD%BB%E9%94%81%E7%9A%84%E6%A6%82%E5%BF%B5/"/>
<url>/2017/04/06/%E6%AD%BB%E9%94%81%E7%9A%84%E6%A6%82%E5%BF%B5/</url>
<content type="html"><![CDATA[<h4 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h4><blockquote><p>由 Dijkstra 提出的哲学家进餐问题(The Dinning Philosophers Problem)是典型的进程同步问题。该问题描述的是有五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,在饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子的时候才能够进餐。进餐完毕后,放下筷子继续思考。</p></blockquote><p>在上述问题中,假设五个哲学家同时进入饥饿的状态,会同时拿起身边左边最靠近自己的筷子,但当有一个哲学家想要拿起右边最靠近自己的筷子时,由于该筷子已被拿起,并且获取该筷子的哲学家不放下,而造成无限地等待,这种特殊现象就被称之为死锁。</p><hr><h4 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h4><p>每个进程所等待的事件是该组中其他进程释放所占有的资源,由于所有这些进程处于阻塞态,都无法正常运行,因此它们谁也不能释放资源,致使没有任何一个进程可被唤醒。这样这组进程只能无限期地等待下去。由此可以给死锁做出如下的定义:</p><p><strong>如果一组进程中的每一个进程都在等待仅由该组进程中的其他进程才能引发的时间,那么该组进程是死锁(Deadlock)。</strong></p><hr><h4 id="产生死锁的必要条件"><a href="#产生死锁的必要条件" class="headerlink" title="产生死锁的必要条件"></a>产生死锁的必要条件</h4><p>虽然进程在运行过程中可能会发生死锁,但产生进程死锁是必须具备一定条件的。综上所述,<strong>产生死锁必须同时具备下面四个必要条件</strong>,只要其中任意一个条件不成立,死锁就不会发生。</p><ol><li><strong>互斥条件。</strong>进程对所分配到的资源进行排他性使用,即在一段时间内,某资源只能被一个进程占用。如果此时还有其它进程请求该资源,则请求进程只能等待,直至占有该资源的进程使用完后,进行释放。</li><li><strong>请求和保持条件。</strong>进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。</li><li><strong>不可抢占条件。</strong>进程已获得的资源在未使用完之前不能被抢占,只能在进程使用完时自己释放。</li><li><strong>循环等待条件。</strong>在发生死锁时,必然存在一个进程-资源的循环链,即进程集合 {A, B, C, D, ..} 中 A 正在等待一个 B 占用的资源,B 正在等待 C 占用的资源,… ,Z 正在等待已被 A 占用的资源。</li></ol><hr><h4 id="处理死锁的方法"><a href="#处理死锁的方法" class="headerlink" title="处理死锁的方法"></a>处理死锁的方法</h4><p>目前处理死锁的方法可归纳总结为四种:</p><ol><li><strong>预防死锁。</strong>这是一个较简答和直观的事先预防方法。该方法是通过设置某些限制条件,去破坏产生死锁四个条件的一个或几个来预防死锁。预防死锁是一种较易实现的方法,已被广泛使用。</li><li><strong>避免死锁。</strong>同样是属于事先预防策略,但它并不是事先采取各种限制措施,去破坏产生死锁的四个必要条件,而是在资源的动态分配中,用某些方法防止系统进入不安全状态,从而避免发生死锁。</li><li><strong>监测死锁。</strong>这种方法无须事先采取任何限制性措施,而是允许进程在运行过程中发生死锁。但该方法可以通过检测机制及时地检测出死锁的发生,根据检测结果,采取某些适当的措施。</li><li><strong>解除死锁。</strong>当检测到系统中已发生死锁时,就采取相应措施,将进程从死锁状态中释放。常用的方法是撤销一些进程,强制回收它们的资源,将它们分配给已处于阻塞状态的进程,使其能够继续运行。</li></ol><p><strong>上述的四种方法,从(1)到(4)对死锁的防范程度逐渐减弱,但相对应的是对资源利用率的提高,以及进程因资源因素而阻塞的频率下降(即并发程度提高)。</strong></p>]]></content>
<categories>
<category> 技术笔记 </category>
</categories>
</entry>
<entry>
<title>设计模式笔记 - 中介者模式(Mediator Pattern)</title>
<link href="/2017/03/29/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0-%E4%B8%AD%E4%BB%8B%E8%80%85%E6%A8%A1%E5%BC%8F%EF%BC%88Mediator-Pattern%EF%BC%89/"/>
<url>/2017/03/29/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0-%E4%B8%AD%E4%BB%8B%E8%80%85%E6%A8%A1%E5%BC%8F%EF%BC%88Mediator-Pattern%EF%BC%89/</url>
<content type="html"><![CDATA[<h4 id="模式定义"><a href="#模式定义" class="headerlink" title="模式定义"></a>模式定义</h4><blockquote><p>中介者模式(Mediator Pattern)用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使耦合松散,而且可以独立地改变它们之间的交互。</p><p>中介者模式又称为调停者模式,它是一种对象行为型模式。</p></blockquote><hr><h4 id="模式结构"><a href="#模式结构" class="headerlink" title="模式结构"></a>模式结构</h4><p>中介者模式包含如下角色:</p><ul><li>Mediator - 抽象中介者</li><li>ConcreteMediator - 具体中介者</li><li>Colleague - 抽象同事类</li><li>ConcreteColleague - 具体同事类</li></ul><p><img src="http://on83riher.bkt.clouddn.com/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%20-%20%E4%B8%AD%E4%BB%8B%E8%80%85%E6%A8%A1%E5%BC%8F.JPG" alt="img"></p><hr><h4 id="模式实例"><a href="#模式实例" class="headerlink" title="模式实例"></a>模式实例</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line"> * 本例为建立一个虚拟聊天室。允许会员通过该聊天室进行信息交流,普通会员(Common Member)可以给其他会员发</span><br><span class="line"> * 送文本信息,钻石会员(Diamond Member)除了可以发送文本消息外,还可以发送图片。</span><br><span class="line"> */</span><br><span class="line">// Mediator - 抽象中介者</span><br><span class="line">public abstract class AbstractChatroom {</span><br><span class="line"> private List<Member> members;</span><br><span class="line"> </span><br><span class="line"> public AbstractChatroom() {</span><br><span class="line"> this.members = new ArrayList<Member>();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void register(Member member) {</span><br><span class="line"> this.members.add(member);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public List<Member> getMembers() {</span><br><span class="line"> return this.members;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public abstract void sendText(String text);</span><br><span class="line"> public abstract void sendImage(Photo photo);</span><br><span class="line">} </span><br><span class="line"></span><br><span class="line">// ConcreteMediator - 具体中介者</span><br><span class="line">public class ChatGroup extends AbstractChatroom {</span><br><span class="line"> public void register(Member member) {</span><br><span class="line"> super.register(member);</span><br><span class="line"> member.setChatroom(this);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void sendText(String text) {</span><br><span class="line"> List<Member> members = getMembers();</span><br><span class="line"> </span><br><span class="line"> for (Member member: members) {</span><br><span class="line"> member.receiveText(text);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void sendImage(Photo photo) {</span><br><span class="line"> List<Member> members = getMembers();</span><br><span class="line"> </span><br><span class="line"> for (Member member: members) {</span><br><span class="line"> member.receiveImage(photo);</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">// Colleague - 抽象同事类</span><br><span class="line">public abstract class Member {</span><br><span class="line"> private String name;</span><br><span class="line"> private AbstractChatroom chatroom;</span><br><span class="line"> </span><br><span class="line"> public Member(String name) {</span><br><span class="line"> this.name = name;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void setChatroom(AbstractChatroom chatroom) {</span><br><span class="line"> this.chatroom = chatroom;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public AbstractChatroom getChatroom() {</span><br><span class="line"> return this.chatroom;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void receiveImage(Photo photo) {</span><br><span class="line"> System.out.println(name + " receive photo: " + photo.getName());</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void receiveText(String text) {</span><br><span class="line"> System.out.println(name + " receive text: " + text);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void sendImage(Photo photo) {</span><br><span class="line"> throw new IllegalStateException("you don't have an access");</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void sendText(String text) {</span><br><span class="line"> this.chatroom.sendText(text);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// ConcreteColleague - 具体同事类</span><br><span class="line">// 普通会员</span><br><span class="line">public class CommonMember extends Member {</span><br><span class="line"> public CommonMember(String name) {</span><br><span class="line"> super(name);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void sendText(String text) {</span><br><span class="line"> getChatroom().sendText(text);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 钻石会员</span><br><span class="line">public class DiamondMember extends Member {</span><br><span class="line"> public DiamondMember(String name) {</span><br><span class="line"> super(name);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void sendText(String text) {</span><br><span class="line"> getChatroom().sendText(text);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void sendImage(Photo photo) {</span><br><span class="line"> getChatroom().sendImage(photo);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 测试类</span><br><span class="line">public class Text {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> AbstractChatroom chatroom = new ChatGroup();</span><br><span class="line"> Member common = new CommonMember("Peter");</span><br><span class="line"> Member diamond = new DiamondMember("Mary");</span><br><span class="line"> </span><br><span class="line"> chatroom.register(common);</span><br><span class="line"> chatroom.register(diamond);</span><br><span class="line"> </span><br><span class="line"> common.sendText("Hello");</span><br><span class="line"> diamond.sendImage(new Photo("Cool"));</span><br><span class="line"> </span><br><span class="line"> System.out.println("END");</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h4 id="模式分析"><a href="#模式分析" class="headerlink" title="模式分析"></a>模式分析</h4><ul><li>中介者模式可以使对象之间的关系数量急剧减少。</li><li>通过引入中介者对象,可以将系统的网状结构变成以中介者为中心的星型结构,中介者承担了中转作用和协调作用。中介者是中介者模式的核心,它对整个系统进行控制和协调,简化了对象之间的交互,还可以对对象间的交互进行进一步的控制。</li></ul><p><strong>中介者模式的优点:</strong></p><ul><li>简化对象之间的引用关系,降低对象间的耦合。</li><li>集中式管理,以中介者为中心,协调各对象之间的关系。</li></ul><p><strong>中介者模式的缺点:</strong></p><ul><li>在具体中介者类中包含了同事之间的交互细节,增加了具体中介者的复杂度,如果系统中具有太多的同时,会使得系统变得难以维护。</li></ul><hr><h4 id="适用环境"><a href="#适用环境" class="headerlink" title="适用环境"></a>适用环境</h4><p>在以下情况下可以使用中介者模式:</p><ul><li>系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解。</li><li>一个对象由于引用了其他很多对象并且直接和这些对象通信,即耦合程度大,导致难以复用该对象。</li></ul><hr><hr><p>参考资料:</p><p>[1] <a href="https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/mediator.html" target="_blank" rel="noopener">https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/mediator.html</a></p>]]></content>
<categories>
<category> 设计模式 </category>
</categories>
</entry>
<entry>
<title>设计模式笔记 - 建造者模式(Builder Pattern)</title>
<link href="/2017/03/29/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0-%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F%EF%BC%88Builder-Pattern%EF%BC%89/"/>
<url>/2017/03/29/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0-%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F%EF%BC%88Builder-Pattern%EF%BC%89/</url>
<content type="html"><![CDATA[<h4 id="模式定义"><a href="#模式定义" class="headerlink" title="模式定义"></a>模式定义</h4><blockquote><p>建造者模式(Builder Pattern)将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户无需知道内部的具体构建细节。</p><p>建造者模式属于对象创建型模式。</p></blockquote><hr><h4 id="模式结构"><a href="#模式结构" class="headerlink" title="模式结构"></a>模式结构</h4><p>建造者模式包含如下角色:</p><ul><li>Builder - 抽象建造者 - 定义了产品的创建方法和返回方法</li><li>ConcreteBuilder - 具体建造者</li><li>Director - 指挥者 - 隔离了客户与生产过程,并负责控制产品的生成过程</li><li>Product - 产品角色</li></ul><p><img src="http://on83riher.bkt.clouddn.com/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%20-%20%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F.JPG" alt="img"></p><hr><h4 id="模式实例"><a href="#模式实例" class="headerlink" title="模式实例"></a>模式实例</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line"> * 本例为McDonald's中超值套餐系列构造。虽然不同的超值套餐都自己不同的特点,但是总体的构造成分都是一致的,主食(汉堡)以及饮料(可乐)等。</span><br><span class="line"> */</span><br><span class="line">// Product - 产品角色</span><br><span class="line">public class ValueMeal {</span><br><span class="line"> private Meal meal;</span><br><span class="line"> private Drink drink;</span><br><span class="line"> </span><br><span class="line"> public void setMeal(Meal meal) {</span><br><span class="line"> this.meal = meal;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void setDrink(Drink drink) {</span><br><span class="line"> this.drink = drink;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public Meal getMeal(Meal meal) {</span><br><span class="line"> return this.meal;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public Drink getDrink(Drink drink) {</span><br><span class="line"> return this.drink;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// Builder - 抽象建造者</span><br><span class="line">public interface class valueMealBuilder {</span><br><span class="line"> public void buildMeal(Meal meal);</span><br><span class="line"> public void buildDrink(Drink drink);</span><br><span class="line"> public ValueMeal getValueMeal(); </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// ConcreteBuilder - 具体建造者</span><br><span class="line">// 麦辣鸡腿堡</span><br><span class="line">public class McCrispyPackage implements valueMealBuilder {</span><br><span class="line"> private ValueMeal valueMeal = new ValueMeal();</span><br><span class="line"> </span><br><span class="line"> public void buildMeal() {</span><br><span class="line"> valueMeal.setMeal(new McCrispyChickenBurger());</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void buildDrink() {</span><br><span class="line"> valueMeal.setDrink(new CocaCola());</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public ValueMeal getValueMeal() {</span><br><span class="line"> return this.valueMeal;</span><br><span class="line"> } </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// ConcreteBuilder - 具体建造者</span><br><span class="line">// 巨无霸</span><br><span class="line">public class BigMacPackage implements valueMealBuilder {</span><br><span class="line"> private ValueMeal valueMeal = new ValueMeal();</span><br><span class="line"> </span><br><span class="line"> public void buildMeal() {</span><br><span class="line"> valueMeal.setMeal(new BigMac());</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void buildDrink() {</span><br><span class="line"> valueMeal.setDrink(new CocaCola());</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public ValueMeal getValueMeal() {</span><br><span class="line"> return this.valueMeal;</span><br><span class="line"> } </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// Direct - 指挥者</span><br><span class="line">public class McDonaldWaiter {</span><br><span class="line"> private ValueMealBuilder valueMealBuilder;</span><br><span class="line"> </span><br><span class="line"> public McDonaldWaiter(ValueMealBuilder valueMealBuilder) {</span><br><span class="line"> this.valueMealBuilder = valueMealBuilder;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void construct() {</span><br><span class="line"> valueMealBuilder.buildMeal();</span><br><span class="line"> valueMealBuilder.buildDrink();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public ValueMeal getValueMeal() {</span><br><span class="line"> valueMealBuilder.getValueMeal();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> public void setValueMealBuilder(ValueMealBuilder valueMealBuilder) {</span><br><span class="line"> this.valueMealBuilder = valueMealBuilder;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 测试类</span><br><span class="line">public class Test {</span><br><span class="line"> public static void main(String[] args) {</span><br><span class="line"> ValueMealBuilder mcCrispyPackage = new McCrispyPackage();</span><br><span class="line"> McDonaldWaiter waiter = new McDonaldWaiter(mcCrispyPackage);</span><br><span class="line"> </span><br><span class="line"> waiter.construct();</span><br><span class="line"> ValueMeal mcCrispy = waiter.getValueMeal();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h4 id="模式分析"><a href="#模式分析" class="headerlink" title="模式分析"></a>模式分析</h4><ul><li>建造者模式的重心在于分离构建算法和具体的构造实现,从而使得构建算法可以重用,具体的构造实现可以方便地扩展和切换,从而可以灵活地组合来构造出不同的产品对象。</li><li>增加新的具体建造者无需修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”。</li><li>建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性较大,则不适合使用建造者模式,因此其适用范围受到一定的限制。</li></ul><p><strong>建造者模式的优点:</strong></p><ul><li>在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。</li><li>每一个具体建造者都相对独立,而与其他的具体建造者无关,因此很方便地替换不同的具体建造者或增加新的具体建造者。</li><li>可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法当中,使得构建者可以更加专注于逻辑处理部分。</li></ul><p><strong>建造者模式的缺点:</strong></p><ul><li>如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,增加系统复杂度,难以维护。</li></ul><hr><h4 id="模式应用"><a href="#模式应用" class="headerlink" title="模式应用"></a>模式应用</h4><ul><li>在游戏软件设计中,不同的地图包括天空、地面和背景等组成部分,不同的人物也包括身体,服装和装备等组成部分,可以利用建造者模式对其设计,通过不同的具体建造者创建不同类型的地图或人物。</li></ul><hr><h4 id="与抽象工厂模式的对比:"><a href="#与抽象工厂模式的对比:" class="headerlink" title="与抽象工厂模式的对比:"></a>与抽象工厂模式的对比:</h4><ul><li>与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,可以类比于<strong>汽车组装工厂</strong>。而抽象工厂模式返回一系列的相关产品,这些产品位于不同的产品等级结构,构成了一个产品族,可以类比于<strong>汽车配件生产工厂</strong>。</li><li>在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用具体建造者的相关方法,而是通过指挥者类来指导如何生成对象,它<strong>侧重于如何一步步构建一个复杂对象,返回一个完整的对象。</strong></li></ul><hr><hr><p>参考资料:</p><p>[1] <a href="https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/builder.html" target="_blank" rel="noopener">https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/builder.html</a></p><p>[2] <a href="http://www.cnblogs.com/java-my-life/archive/2012/04/07/2433939.html" target="_blank" rel="noopener">http://www.cnblogs.com/java-my-life/archive/2012/04/07/2433939.html</a></p>]]></content>
<categories>
<category> 设计模式 </category>
</categories>
</entry>
<entry>
<title>设计模式笔记 - 状态模式(State Pattern)</title>
<link href="/2017/03/28/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0-%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F%EF%BC%88State-Pattern%EF%BC%89/"/>
<url>/2017/03/28/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0-%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F%EF%BC%88State-Pattern%EF%BC%89/</url>
<content type="html"><![CDATA[<h1 id="设计模式笔记-状态模式(State-Pattern)"><a href="#设计模式笔记-状态模式(State-Pattern)" class="headerlink" title="设计模式笔记 - 状态模式(State Pattern)"></a>设计模式笔记 - 状态模式(State Pattern)</h1><h4 id="模式定义"><a href="#模式定义" class="headerlink" title="模式定义"></a>模式定义</h4><blockquote><p>状态模式(State Pattern)允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。状态模式是一种对象行为型模式。</p></blockquote><hr><h4 id="模式结构"><a href="#模式结构" class="headerlink" title="模式结构"></a>模式结构</h4><p>状态模式包含如下角色:</p><ul><li>Context - 环境类</li><li>State - 抽象状态类</li><li>ConcreteState - 具体状态类</li></ul><p><img src="http://on83riher.bkt.clouddn.com/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%20-%20%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F.JPG" alt=""></p><hr><h4 id="模式实例"><a href="#模式实例" class="headerlink" title="模式实例"></a>模式实例</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><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="comment"> */</span></span><br><span class="line"><span class="comment">// Context - 环境类</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GamePlayer</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> GamePlayerState gamePlayerState;</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">GamePlayer</span><span class="params">(GamePlayerState gamePlayerState)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.gamePlayerState = gamePlayerState;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">attack</span><span class="params">()</span> </span>{</span><br><span class="line"> gamePlayerState.attack();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">reborn</span><span class="params">()</span> </span>{</span><br><span class="line"> gamePlayerState.reborn();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">buy</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"buying sth."</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">changeState</span><span class="params">(GamePlayerState gamePlayerState)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.gamePlayerState = gamePlayerState;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// State - 抽象状态类</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">GamePlayerState</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">attack</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">reborn</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// ConcreteState - 具体抽象类</span></span><br><span class="line"><span class="comment">// 活着</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Alive</span> <span class="keyword">extends</span> <span class="title">GamePlayerState</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">attack</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"Attacking!"</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">reborn</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"You are alive!"</span>);</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">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Death</span> <span class="keyword">extends</span> <span class="title">GamePlayerState</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">attack</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"You are dead!"</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">reborn</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"Alive for attack!"</span>);</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">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</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"> GamePlayerState alive = <span class="keyword">new</span> Alive();</span><br><span class="line"> GamePlayerState death = <span class="keyword">new</span> Death();</span><br><span class="line"> </span><br><span class="line"> GamePlayer gamePlayer = <span class="keyword">new</span> GamePlayer(alive);</span><br><span class="line"> gamePlayer.attack();</span><br><span class="line"> gamePlayer.changeState(death);</span><br><span class="line"> gamePlayer.attack();</span><br><span class="line"> gamePlayer.reborn();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h4 id="模式分析"><a href="#模式分析" class="headerlink" title="模式分析"></a>模式分析</h4><ul><li>状态模式描述了对象状态的变化,以及对象如何在每一种状态下表现出不同的行为。</li><li>状态模式的关键是导入了一个抽象类来专门表示对象的状态,这个类我们叫做抽象状态类,而对象的每一种具体状态类都继承了该类,并在不同的具体状态类中实现了不同状态的行为,包括各种状态之间的转换。</li></ul><p>在状态模式结构中需要理解环境类与抽象状态类的作用:</p><ul><li>环境类 - 实际上就是拥有状态的对象,环境类有时候可以充当状态管理器(State Manager)的角色,可以在环境类中对状态进行切换操作。</li><li>抽象状态类 - 可以是抽象类,也可以是接口,不同状态类就是继承这个父类的不同子类,状态类的产生是由于环境类存在多个状态,同时还满足两个条件:一是这些状态经常需要切换,二是在不同状态下对象的行为不同。因此可以将不同对象下的行为单独提取出来封装在具体的状态类中,使得环境类对象在其内部状态改变时可以改变它的行为,让对象看起来似乎修改了它的类,而实际上是由于切换到不同的具体状态类实现的。</li></ul><p><strong>状态模式的优点:</strong></p><ul><li>封装了转换规则。</li><li>将所有与某个状态有关的行为放到同一个类中,并且方便地添加新的状态,只需改变对象状态,即可改变对象的行为。</li><li>允许状态转换逻辑与状态对象合为一体,舍弃巨大的逻辑条件块(if..else..)</li><li>可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。</li></ul><p><strong>状态模式的缺点:</strong></p><ul><li>状态模式的使用必然会增加系统类和对象的个数。</li><li>状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。</li><li>状态模式对“开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。</li></ul><hr><h4 id="模式应用"><a href="#模式应用" class="headerlink" title="模式应用"></a>模式应用</h4><ul><li>状态模式在工作流或游戏等类型的软件中得以广泛使用,甚至可以用于这些系统的核心功能设计,如在政府OA办公系统中,一个批文的状态有多种:尚未办理,正在办理,正在批示,正在审核,已经完成等各种状态,而且批文状态不同时对批文的操作也有所差异。使用状态模式可以描述工作流对象(如批文)的状态转换以及不同状态下它所具有的行为。</li></ul><hr><h4 id="模式扩展"><a href="#模式扩展" class="headerlink" title="模式扩展"></a>模式扩展</h4><p>共享状态</p><ul><li>在有些情况下多个环境对象需要共享同一个状态,如果希望在系统中实现多个环境对象实例共享一个或多个状态对象,那么需要将这些状态对象定义为环境的静态成员对象。</li></ul><hr><hr><p>参考资料:</p><p>[1] <a href="https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/state.html" target="_blank" rel="noopener">https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/state.html</a></p>]]></content>
<categories>
<category> 设计模式 </category>
</categories>
</entry>
<entry>
<title>SQL - GROUP BY 和 HAVING</title>
<link href="/2017/03/27/SQL-GROUP-BY-%E5%92%8C-HAVING/"/>
<url>/2017/03/27/SQL-GROUP-BY-%E5%92%8C-HAVING/</url>
<content type="html"><![CDATA[<h2 id="GROUP-BY"><a href="#GROUP-BY" class="headerlink" title="GROUP BY"></a>GROUP BY</h2><h4 id="GROUP-BY-简介"><a href="#GROUP-BY-简介" class="headerlink" title="GROUP BY 简介"></a>GROUP BY 简介</h4><p>GROUP BY 语句根据字面上的意思为“根据(by)一定的规则进行分组(group)”。它的主要作用是通过一定的规则将一个数据集划分成若干个小的区域,然后针对若干个小的区域进行数据处理。其常常结合聚合函数(COUNT/SUM)等一起使用。</p><hr><h4 id="GROUP-BY-语法"><a href="#GROUP-BY-语法" class="headerlink" title="GROUP BY 语法"></a>GROUP BY 语法</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">SELECT column_name, aggregate_function(column_name)</span><br><span class="line">FROM table_name</span><br><span class="line">WHERE column_name operator value</span><br><span class="line">GROUP BY column_name</span><br></pre></td></tr></table></figure><hr><h4 id="GROUP-BY-使用"><a href="#GROUP-BY-使用" class="headerlink" title="GROUP BY 使用"></a>GROUP BY 使用</h4><ol><li><strong>GROUP BY [EXPERSSIONS]:</strong></li></ol><p>数据集根据表达式中的若干字段将一个数据集划分成不同的分组。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">-- 以下例子为,统计某人在全国范围中购买的地产总数</span><br><span class="line"></span><br><span class="line">SELECT owner.Name, COUNT(*) AS '购买总数' FROM registration </span><br><span class="line">LEFT JOIN owner ON owner.PersonID = registration.PersonID</span><br><span class="line">GROUP BY registration.PersonID</span><br><span class="line"></span><br><span class="line">+------+----------+</span><br><span class="line">| Name | 购买总数 |</span><br><span class="line">+------+----------+</span><br><span class="line">| 小明 | 2 |</span><br><span class="line">| 小红 | 1 |</span><br><span class="line">| 小刚 | 2 |</span><br><span class="line">+------+----------+</span><br></pre></td></tr></table></figure><p>以上可以SQL语句可以解释为“按照房产登记表(regisration)中的身份证(PersonID)将数据集进行分组,然后按照分组来分别统计出各自的记录数量”。</p><ol><li><strong>GROUP BY [EXPERSSIONS] WITH ROLLUP</strong></li></ol><p>指定在结果集内不仅包含由 GROUP BY 提供的正常行,还包含汇总行。按层次结构顺序,从组内的最低级别到最高级别汇总组。组的层次结构取决于指定分组列时所使用的顺序。更改分组列的顺序会影响在结果集内生成的行数。”按层结结构顺序“这段话是指按照 GROUP BY 语句中字段的顺序进行排序,比如 GROUG BY column1,column2, column3,那么这个分组的级别从高到低的顺序是 column1 > column2 > column3。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">-- 以下例子为,统计全国各类型房产的销售面积情况</span><br><span class="line"></span><br><span class="line">select ESTATE.EstateCity, ESTATE.EstateType, SUM(ESTATE.PropertyArea) AS '销售面积' FROM ESTATE </span><br><span class="line">LEFT JOIN REGISTRATION ON REGISTRATION.EstateID=ESTATE.EstateID </span><br><span class="line">GROUP BY ESTATE.EstateCity, ESTATE.EstateType WITH ROLLUP</span><br><span class="line"></span><br><span class="line">+------------+------------+----------+</span><br><span class="line">| EstateCity | EstateType | 销售面积 |</span><br><span class="line">+------------+------------+----------+</span><br><span class="line">| 北京市 | 住宅 | 71.00 |</span><br><span class="line">| 北京市 | NULL | 71.00 |</span><br><span class="line">| 天津市 | 住宅 | 50.00 |</span><br><span class="line">| 天津市 | NULL | 50.00 |</span><br><span class="line">| 惠州市 | 别墅 | 170.00 |</span><br><span class="line">| 惠州市 | NULL | 170.00 |</span><br><span class="line">| 成都市 | 住宅 | 95.00 |</span><br><span class="line">| 成都市 | 商铺 | 500.00 |</span><br><span class="line">| 成都市 | NULL | 595.00 |</span><br><span class="line">| NULL | NULL | 886.00 |</span><br><span class="line">+------------+------------+----------+</span><br></pre></td></tr></table></figure><p>查询结果的第一句、第二句为WITH ROLLUP分析的第一组,可以解释为在北京市中,所有类型的房产总销售面积的总结。而查询结果中的最后一句解释为在全国中,所有类型的房产总销售面积的总结。</p><hr><h4 id="GROUP-BY-语句容易出现的错误"><a href="#GROUP-BY-语句容易出现的错误" class="headerlink" title="GROUP BY 语句容易出现的错误"></a>GROUP BY 语句容易出现的错误</h4><p>通过以下语句利用 GROUP BY 进行查询的时候,会出现错误。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">-- 利用城市进行分类返回查询到的所有数据</span><br><span class="line"></span><br><span class="line">select ESTATE.EstateCity, *, SUM(ESTATE.PropertyArea) AS '销售面积' FROM ESTATE </span><br><span class="line">LEFT JOIN REGISTRATION ON REGISTRATION.EstateID=ESTATE.EstateID </span><br><span class="line">GROUP BY ESTATE.EstateCity</span><br></pre></td></tr></table></figure><p>这是利用 GROUP BY 进行单表查询,抑或者多表查询时需要注意的点。在需要查询的字段中,即在返回集字段中,这些字段要么就要包含在 GROUP BY 后面,作为分组的依据。要么就要被包含在聚合函数(COUNT/SUM等)中。</p><blockquote><p>不同平台出现的错误不同,如SQL SERVER返回的错误为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">> 消息 8120,级别 16,状态 1,第 1 行</span><br><span class="line">> 选择列表中的列 'ESTATE.EstateID' 无效,因为该列没有包含在聚合函数或 GROUP BY 子句中。</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><blockquote><p>在MySQL中,返回的错误为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">> [Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '*, SUM(ESTATE.PropertyArea) AS '销售面积' FROM ESTATE </span><br><span class="line">> LEFT JOIN REGISTRAT' at line 1</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><hr><hr><h2 id="HAVING"><a href="#HAVING" class="headerlink" title="HAVING"></a>HAVING</h2><h4 id="HAVING-简介"><a href="#HAVING-简介" class="headerlink" title="HAVING 简介"></a>HAVING 简介</h4><p>Having 和 GROUP BY 设置条件的方式与 WHERE 和 SELECT 的交互方式类似。WHERE 搜索条件在进行分组操作之前应用。而 HAVING 搜索条件在进行分组操作之后应用。 HAVING 语法与 WHERE 语法类似,但 HAVING 中可以包含聚合函数并可以引用选择列表中显示的任一项。</p><hr><h4 id="HAVING-语法"><a href="#HAVING-语法" class="headerlink" title="HAVING 语法"></a>HAVING 语法</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">SELECT column_name, aggregate_function(column_name)</span><br><span class="line">FROM table_name</span><br><span class="line">WHERE column_name operator value</span><br><span class="line">GROUP BY column_name</span><br><span class="line">HAVING aggregate_function(column_name) operator value</span><br></pre></td></tr></table></figure><hr><h4 id="HAVING-使用"><a href="#HAVING-使用" class="headerlink" title="HAVING 使用"></a>HAVING 使用</h4><p>HAVING 语句一般与GROUP BY搭配使用,没什么特别用法。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">-- 以下例子为,统计各地城市中各类住宅的总销售面积小于250平方米</span><br><span class="line"></span><br><span class="line">select ESTATE.EstateCity, ESTATE.EstateType, SUM(ESTATE.PropertyArea) AS '销售面积' FROM ESTATE </span><br><span class="line">LEFT JOIN REGISTRATION ON REGISTRATION.EstateID=ESTATE.EstateID </span><br><span class="line">GROUP BY ESTATE.EstateCity, ESTATE.EstateType</span><br><span class="line">Having SUM(ESTATE.PropertyArea) < 250</span><br><span class="line"></span><br><span class="line">+------------+------------+----------+</span><br><span class="line">| EstateCity | EstateType | 销售面积 |</span><br><span class="line">+------------+------------+----------+</span><br><span class="line">| 北京市 | 住宅 | 71.00 |</span><br><span class="line">| 天津市 | 住宅 | 50.00 |</span><br><span class="line">| 惠州市 | 别墅 | 170.00 |</span><br><span class="line">| 成都市 | 住宅 | 95.00 |</span><br><span class="line">+------------+------------+----------+</span><br></pre></td></tr></table></figure><hr><hr><p>参考资料:</p><p>[1] <a href="http://www.cnblogs.com/glaivelee/archive/2010/11/19/1881381.html" target="_blank" rel="noopener">http://www.cnblogs.com/glaivelee/archive/2010/11/19/1881381.html</a></p><p>[2]<a href="http://blog.csdn.net/qq_26562641/article/details/53301063" target="_blank" rel="noopener">http://blog.csdn.net/qq_26562641/article/details/53301063</a></p>]]></content>
<categories>
<category> MySQL </category>
</categories>
</entry>
<entry>
<title>正向代理与反向代理</title>
<link href="/2017/03/27/%E6%AD%A3%E5%90%91%E4%BB%A3%E7%90%86%E4%B8%8E%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/"/>
<url>/2017/03/27/%E6%AD%A3%E5%90%91%E4%BB%A3%E7%90%86%E4%B8%8E%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/</url>