-
Notifications
You must be signed in to change notification settings - Fork 1
/
atom.xml
550 lines (312 loc) · 203 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>vzard's blog</title>
<subtitle>just do it</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://vzardlloo.github.io/"/>
<updated>2020-09-26T07:32:17.434Z</updated>
<id>http://vzardlloo.github.io/</id>
<author>
<name>vzardlloo</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>二叉搜索树的一些性质</title>
<link href="http://vzardlloo.github.io/2020/09/22/%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%A7%E8%B4%A8/"/>
<id>http://vzardlloo.github.io/2020/09/22/%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%A7%E8%B4%A8/</id>
<published>2020-09-21T17:38:06.000Z</published>
<updated>2020-09-26T07:32:17.434Z</updated>
<content type="html"><![CDATA[<h4><span id="二叉搜索树的中序遍历的结果序列是一个递增排序的序列">二叉搜索树的中序遍历的结果序列是一个递增排序的序列。</span></h4><blockquote><p>中序遍历的顺序是:左节点 - 根节点 - 右节点</p></blockquote><p>示例代码:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">result := <span class="built_in">make</span>([]*Node,<span class="number">0</span>)</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">inorder</span><span class="params">(root *Node)</span></span> {</span><br><span class="line"> <span class="keyword">if</span> root == <span class="literal">nil</span> {</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> inorder(root.Left)</span><br><span class="line"> result = <span class="built_in">append</span>(result,root)</span><br><span class="line"> inorder(root.Right)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4><span id="二叉搜索树的后继节点">二叉搜索树的后继节点</span></h4><blockquote><p>即比当前节点大的最小节点,主要思路:取改节点的右节点,然后一直取左节点直到左节点为空,最后指向的就是该节点的后继节点</p></blockquote><p>示例代码:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Successor</span><span class="params">(node *Node)</span> *<span class="title">Node</span></span> {</span><br><span class="line"> succ := node.Right</span><br><span class="line"> <span class="keyword">for</span> succ.Left != <span class="literal">nil</span> {</span><br><span class="line"> succ = node.Left</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> succ</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4><span id="二叉搜索树的前驱节点">二叉搜索树的前驱节点</span></h4><blockquote><p>即比当前节点小的最大节点,主要思路:取改节点的左节点,然后一直取右节点直到右节点为空,最后指向的就是该节点的前驱节点</p></blockquote><p>示例代码:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Predecessor</span><span class="params">(node *Node)</span> *<span class="title">Node</span></span> {</span><br><span class="line"> pre := node.Left</span><br><span class="line"> <span class="keyword">for</span> pre.Right != <span class="literal">nil</span> {</span><br><span class="line"> pre = pre.Right</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> pre</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h4><span id="二叉搜索树的中序遍历的结果序列是一个递增排序的序列">二叉搜索树的中序遍历的结果序列是一个递增排序的序列。</span></h4><blockquote>
<p>中序遍历的顺序是:左节点 - 根节点 - 右节点</p>
</blockquote>
<p
</summary>
<category term="数据结构" scheme="http://vzardlloo.github.io/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
<category term="数据结构" scheme="http://vzardlloo.github.io/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
</entry>
<entry>
<title>跳表</title>
<link href="http://vzardlloo.github.io/2020/09/14/%E8%B7%B3%E8%A1%A8/"/>
<id>http://vzardlloo.github.io/2020/09/14/%E8%B7%B3%E8%A1%A8/</id>
<published>2020-09-13T18:24:58.000Z</published>
<updated>2020-09-20T11:47:27.824Z</updated>
<content type="html"><![CDATA[<h2><span id="跳表为解决什么问题而诞生">跳表为解决什么问题而诞生</span></h2><p> 跳表本质上是一个查找结构,为了解决算法里面的查找问题而诞生。就是根据给定的key快速找到它所在的位置或者value。跳表基于一个有序链表,有序链表的查询时间复杂度是<code>O(n)</code>,跳表可以将其优化到<code>O(log n)</code>。跳表经常和平衡树放在一起比较。</p><h2><span id="跳表的实现">跳表的实现</span></h2><h4><span id="数据结构">数据结构</span></h4><p>跳表是基于有序列表实现的,先看一下有序列表的结构:</p><p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gix83b8adzj318g056jrt.jpg" alt="有序链表"></p><p>在这样一个有序链表中,如果我们要查找某个数据,那么需要从头开始逐个遍历进行比较,直到找到包含数据的那个节点。为了减少遍历次数我们可以给有序链表加一层索引:</p><p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gix87fjvkdj319606qwf5.jpg" alt="跳表1"></p><p>增加的一层索引实际上也是一个有序链表,查找数据的时候可以先沿着这个索引进行查找,遇到比待查数据大的节点时在“跳”到原始的节点进行查找。 比如打算查找23,查找的路径就是是沿着下图中标红的指针所指向的方向进行。</p><p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gix8eegc1ij31aa07wmy6.jpg" alt="跳表2"></p><p>在这个查找过程中,由于新增加的索引,我们不再需要与链表中每个节点逐个进行比较了。需要比较的节点数大概只有原来的一半。</p><p>利用同样的方式,我们还可以在上层新产生的链表上,继续为每相邻的两个节点增加一个指针,从而产生第三层链表。如下图:</p><p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gix8pzdabfj30y206c3yx.jpg" alt="跳表3"></p><p>在这个新的三层链表结构上,如果我们还是查找23,那么沿着最上层链表首先要比较的是19,发现23比19大,接下来我们就知道只需要到19的后面去继续查找,从而一下子跳过了19前面的所有节点。可以想象,当链表足够长的时候,这种多层链表的查找方式能让我们跳过很多下层节点,大大加快查找的速度。</p><h4><span id="层的随机生成">层的随机生成</span></h4><p> 上面讨论的都是一个静态的结构,但是实际中数据不会是静态的,一定是会伴随着增、删、改等操作的。那么一个问题就来了,当新增一个节点的时候这个节点应该出现在第几层索引上,换句话就是如何给他分配层数。跳表采用的是一种随机的方式生产层数,具体如下:</p><ul><li><p>每个节点都肯定在第一层链表里面,即每个节点的层数>= 1。</p></li><li><p>如果一个节点有第 <code>i</code> 层指针(即节点在第1~<code>i</code> 层),那么这个节点有第<code>i+1</code> 层指针的概率为p。</p></li><li><p>节点的最大层数不允许超过一个最大值,记为<code>MaxLevel</code>。</p></li></ul><p>这个计算随机层数的伪码如下所示:<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">randomLevel()</span><br><span class="line"> level := <span class="number">1</span></span><br><span class="line"> <span class="comment">// random()返回一个[0...1)的随机数</span></span><br><span class="line"> <span class="keyword">while</span> <span class="built_in">random</span>() < p <span class="keyword">and</span> level < MaxLevel <span class="keyword">do</span></span><br><span class="line"> level := level + <span class="number">1</span></span><br><span class="line"> <span class="keyword">return</span> level</span><br></pre></td></tr></table></figure></p><h2><span id="复杂度分析">复杂度分析</span></h2><h4><span id="空间复杂度">空间复杂度</span></h4><p>我们先来计算一下每个节点所包含的平均指针数目(概率期望)。节点包含的指针数目,相当于这个算法在空间上的额外开销(overhead),可以用来度量空间复杂度。<br>根据前面randomLevel()的伪码,我们很容易看出,产生越高的节点层数,概率越低。定量的分析如下:</p><ul><li><p>节点层数至少为1。而大于1的节点层数,满足一个概率分布。</p></li><li><p>节点层数恰好等于1的概率为1-p。</p></li><li><p>节点层数大于等于2的概率为p,而节点层数恰好等于2的概率为p(1-p)。</p></li><li><p>节点层数大于等于3的概率为p2,而节点层数恰好等于3的概率为p^2*(1-p)。</p></li><li><p>节点层数大于等于4的概率为p3,而节点层数恰好等于4的概率为p^3*(1-p)。</p></li><li><p>……</p></li></ul><p>因此,一个节点的平均层数(也即包含的平均指针数目),计算如下:</p><script type="math/tex; mode=display">1*(1-p)+ 2*p(1-p)+ 3*p^2(1-p) = (1-p)\sum\limits_{k=1}^{\infty}kp^{k-1}=(1-p)*1/(1-p)^2 = 1/(1-p)</script><p>现在很容易计算出:</p><ul><li>当p=1/2时,每个节点所包含的平均指针数目为2;</li><li>当p=1/4时,每个节点所包含的平均指针数目为1.33。这也是Redis里的skiplist实现在空间上的开销。</li></ul><p>相比有序链表的额外开销是 <code>p/(1-p)</code>,复杂度为<code>O(n)</code></p><h4><span id="时间复杂度">时间复杂度</span></h4><p> 为了计算查找长度,我们可以将查找过程倒过来看,从右下方第1层上最后到达的那个节点开始,沿着查找路径向左向上回溯,类似于爬楼梯的过程。</p><p>现在假设我们从一个层数为i的节点x出发,需要向左向上攀爬k层。这时我们有两种可能:</p><ul><li><p>如果节点x有第(i+1)层指针,那么我们需要向上走。这种情况概率为p。</p></li><li><p>如果节点x没有第(i+1)层指针,那么我们需要向左走。这种情况概率为(1-p)。</p></li></ul><p>用C(k)表示向上攀爬k个层级所需要走过的平均查找路径长度(概率期望),那么:</p><script type="math/tex; mode=display">\left\{\begin{aligned}C(0)= 0\\C(k) = (1-p)*(C(k)+1) + p*(C(k-1)+1) \\\end{aligned}\right.\Longrightarrow C(k) = C(k-1)/p\Longrightarrow C(k) = k/p (k为总层数-1)</script><p>下面需要估算k的值,即分析一下当跳表中有n个节点的时候,它的总层数的概率均值是多少。这个问题直观上比较好理解。根据节点的层数随机算法,容易得出:</p><ul><li><p>第1层链表固定有n个节点,k = 0;</p></li><li><p>第2层链表平均有n*p个节点,k = 1;</p></li><li><p>第3层链表平均有n*p^2个节点,k = 2;</p></li></ul><script type="math/tex; mode=display">k = \log_p (\frac{1}{n})</script><p> 那么: </p><script type="math/tex; mode=display">C(k) = \frac{k}{p} =\frac{\log_p (\frac{1}{n})}{p}</script><p>所以时间平均复杂度为<code>O(log n)</code></p><h4><span id="小结">小结</span></h4><ul><li>各种搜索结构提高效率的方式都是通过空间换时间得到的。</li><li>跳表最终形成的结构和搜索树很相似。</li><li>跳表通过随机的方式来决定新插入节点来决定索引的层数。</li><li>跳表搜索的时间复杂度是 O(logn),插入/删除也是,空间复杂度为<code>O(n)</code></li></ul><h2><span id="实际应用">实际应用</span></h2><ul><li>redis 的有序集合</li><li>leveldb</li></ul>]]></content>
<summary type="html">
<h2><span id="跳表为解决什么问题而诞生">跳表为解决什么问题而诞生</span></h2><p> 跳表本质上是一个查找结构,为了解决算法里面的查找问题而诞生。就是根据给定的key快速找到它所在的位置或者value。跳表基于一个有序链表,有序链表的查询时
</summary>
<category term="数据结构" scheme="http://vzardlloo.github.io/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
<category term="数据结构" scheme="http://vzardlloo.github.io/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
</entry>
<entry>
<title>redis的事务</title>
<link href="http://vzardlloo.github.io/2020/09/11/redis%E7%9A%84%E4%BA%8B%E5%8A%A1/"/>
<id>http://vzardlloo.github.io/2020/09/11/redis%E7%9A%84%E4%BA%8B%E5%8A%A1/</id>
<published>2020-09-10T17:07:55.000Z</published>
<updated>2020-09-13T18:23:24.657Z</updated>
<content type="html"><![CDATA[<h2><span id="什么是事务">什么是事务</span></h2><p>事务是<a href="https://zh.wikipedia.org/wiki/数据库管理系统" target="_blank" rel="noopener">数据库管理系统</a>执行过程中的一个逻辑单位,由一个有限的<a href="https://zh.wikipedia.org/wiki/数据库" target="_blank" rel="noopener">数据库</a>操作序列构成。它有4个特性:</p><ul><li><strong>原子性(Atomicity)</strong>:事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行<a href="https://zh.wikipedia.org/wiki/数据库事务#cite_note-acid-3" target="_blank" rel="noopener">[3]</a>。</li><li><strong>一致性(Consistency)</strong>:事务应确保数据库的状态从一个一致状态转变为另一个一致状态。<em>一致状态</em>的含义是数据库中的数据应满足完整性约束<a href="https://zh.wikipedia.org/wiki/数据库事务#cite_note-acid-3" target="_blank" rel="noopener">[3]</a></li><li><strong>隔离性(Isolation)</strong>:多个事务并发执行时,一个事务的执行不应影响其他事务的执行<a href="https://zh.wikipedia.org/wiki/数据库事务#cite_note-acid-3" target="_blank" rel="noopener">[3]</a>。</li><li><strong>持久性(Durability)</strong>:已被提交的事务对数据库的修改应该永久保存在数据库中<a href="https://zh.wikipedia.org/wiki/数据库事务#cite_note-acid-3" target="_blank" rel="noopener">[3]</a>。</li></ul><h2><span id="redis的事务">Redis的事务</span></h2><h4><span id="redis如何开启事务">Redis如何开启事务</span></h4><p>redis开启事务主要有如下4个命令:<code>MULTI</code>、<code>EXEC</code>、<code>DISCARD</code>、 <code>WATCH</code>。</p><table><thead><tr><th align="center">命令</th><th align="center">作用</th></tr></thead><tbody><tr><td align="center"><code>MULTI</code></td><td align="center">显式的开启事务,之后的命令将被缓存在命令队列里面,入队的时候会对命令进行检查,命令错误就入队失败,直到使用<code>EXEC</code>命令才一起执行,</td></tr><tr><td align="center"><code>EXEC</code></td><td align="center">执行事务中的<code>commands</code>队列,恢复连接状态。如果在<code>watch</code>之前被调用,只有监测中的<code>Keys</code>没有被修改,命令才会被执行,否则停止执行</td></tr><tr><td align="center"><code>DISCARD</code></td><td align="center">清空事务中的<code>commands</code>队列,恢复连接状态,如果有使用<code>watch</code>命令监控<code>key</code>的话,监控状态将被取消</td></tr><tr><td align="center"><code>WATCH</code></td><td align="center">将给出的<code>Keys</code>标记为<code>监测态</code>,作为事务执行的条件,一旦一个被监控的键被改了,事务将执行失败</td></tr></tbody></table><h4><span id="redis事务是真正的事务么满足acid么">Redis事务是真正的事务么?满足ACID么?</span></h4><ul><li><p>原子性:redis通过<code>commands</code>队列的实现,要么队列里面的全部执行要么全部不执行,这个满足原子性。值得注意的是,redis在执行队列里面的命令在执行的时候有某一条发生错误的时候,redis并不会进行回滚(rollback),官方解释是:</p><ul><li>Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。</li><li>因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。</li></ul><p>鉴于没有任何机制能避免程序员自己造成的错误, 并且这类错误通常不会在生产环境中出现, 所以 Redis 选择了更简单、更快速的无回滚方式来处理事务。</p></li><li><p>一致性:redis有三个地方有可能执行事务出错,redis都进行了妥善的处理保证其一致性。</p><ul><li><p>入队错误:在命令进入<code>commands</code>队列的时候,会检查命令的合法性,发现错误,不允许入队列,命令不会执行,所以不会导致一致性问题。</p></li><li><p>执行错误:命令执行期间报错的时候,错误的命令不会对数据库进行任何改动,所以不会导致一致性问题。</p></li><li><p>服务器挂了:没有开启持久化,重启后是个空的数据库,可以达到一个新的一致的状态;开了RDB或者AOF持久化,可以将数据库恢复到一个一致性的状态。</p></li></ul></li><li><p>隔离性:redis服务器总是单线程执行事务并且不会被中断,即事务总是串行执行的,A事务执行完才会执行B事务,所以没有隔离性的问题。</p></li><li><p>持久性:redis的事务只是简单的将一堆redis命令打包到一个队列里面,没有任何额外的持久化保证,所以redis事务的持久化要依赖redis使用的持久化机制。</p><ul><li><p>未开启持久化:redis没法进行持久化,不能保证持久性</p></li><li><p>RDB模式: RDB模式要在某种触发条件下才会进行持久化操作,所以没法保证持久化。</p></li><li><p>AOF模式:需要看<code>appendfsync</code>参数具体的值,如果是alway 则每次执行命令都会进行持久化操作,那么可以保证持久化,在<code>everysec</code>和<code>no</code>的情况下没法保证持久化。</p></li></ul></li></ul><p> 黑魔法: 事务里面最后一句加个<code>save</code>命令可以保证持久性,但是效率比较低下</p><h2><span id="总结">总结</span></h2><p>redis支持事务,在一定的配置下满足事务的ACID四个特性,但是又和传统关系性数据库的事务有所区别。</p><p></p>]]></content>
<summary type="html">
<h2><span id="什么是事务">什么是事务</span></h2><p>事务是<a href="https://zh.wikipedia.org/wiki/数据库管理系统" target="_blank" rel="noopener">数据库管理系统</a>执行过程中的
</summary>
<category term="redis" scheme="http://vzardlloo.github.io/categories/redis/"/>
<category term="redis" scheme="http://vzardlloo.github.io/tags/redis/"/>
</entry>
<entry>
<title>redis数据类型及使用场景</title>
<link href="http://vzardlloo.github.io/2020/09/08/redis%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%8F%8A%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF/"/>
<id>http://vzardlloo.github.io/2020/09/08/redis%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%8F%8A%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF/</id>
<published>2020-09-07T16:46:44.000Z</published>
<updated>2020-09-09T16:33:11.050Z</updated>
<content type="html"><![CDATA[<h2><span id="redis的数据类型">Redis的数据类型</span></h2><h4><span id="数据类型对应编码方式">数据类型对应编码方式</span></h4><table><thead><tr><th align="center">数据类型</th><th align="center">编码方式</th></tr></thead><tbody><tr><td align="center">String</td><td align="center"><code>raw</code>、<code>embstr</code>、<code>int</code></td></tr><tr><td align="center">Hash</td><td align="center"><code>hashtable</code>、<code>ziplist</code></td></tr><tr><td align="center">List</td><td align="center"><code>linkedlist</code>、<code>ziplist</code>、<code>quicklist</code></td></tr><tr><td align="center">Set</td><td align="center"><code>hashtable</code>、<code>intset</code></td></tr><tr><td align="center">Zset</td><td align="center"><code>skiplist</code>、<code>ziplist</code></td></tr></tbody></table><h4><span id="编码类型对应数据结构">编码类型对应数据结构</span></h4><table><thead><tr><th align="center">编码方式</th><th align="center">数据结构</th></tr></thead><tbody><tr><td align="center"><code>raw</code></td><td align="center">动态字符串编码</td></tr><tr><td align="center"><code>embstr</code></td><td align="center">优化内存分配饿字符串分配</td></tr><tr><td align="center"><code>int</code></td><td align="center">整数编码</td></tr><tr><td align="center"><code>hashtable</code></td><td align="center">散列表编码</td></tr><tr><td align="center"><code>ziplist</code></td><td align="center">压缩列表编码</td></tr><tr><td align="center"><code>linkedlist</code></td><td align="center">双向链表编码</td></tr><tr><td align="center"><code>quicklist</code></td><td align="center">快速列表编码</td></tr><tr><td align="center"><code>intset</code></td><td align="center">整数集合编码</td></tr><tr><td align="center"><code>skiplist</code></td><td align="center">跳跃表编码</td></tr></tbody></table><h2><span id="使用场景">使用场景</span></h2><table><thead><tr><th align="center">数据类型</th><th align="center">使用场景</th></tr></thead><tbody><tr><td align="center">String</td><td align="center">普通的k-v缓存,分布式锁</td></tr><tr><td align="center">Hash</td><td align="center">字典结构,比如缓存一个创意的消耗信息,创意id对应曝光数、点击数等多个subkey</td></tr><tr><td align="center">List</td><td align="center">关注列表,简单的队列</td></tr><tr><td align="center">Set</td><td align="center">去重器,集合运算</td></tr><tr><td align="center">Zset</td><td align="center">排行榜,优先级队列</td></tr></tbody></table>]]></content>
<summary type="html">
<h2><span id="redis的数据类型">Redis的数据类型</span></h2><h4><span id="数据类型对应编码方式">数据类型对应编码方式</span></h4><table>
<thead>
<tr>
<th align="center">数据类型
</summary>
<category term="redis" scheme="http://vzardlloo.github.io/categories/redis/"/>
<category term="redis" scheme="http://vzardlloo.github.io/tags/redis/"/>
</entry>
<entry>
<title>二叉树非递归遍历统一写法</title>
<link href="http://vzardlloo.github.io/2020/09/06/%E4%BA%8C%E5%8F%89%E6%A0%91%E9%9D%9E%E9%80%92%E5%BD%92%E9%81%8D%E5%8E%86%E7%BB%9F%E4%B8%80%E5%86%99%E6%B3%95/"/>
<id>http://vzardlloo.github.io/2020/09/06/%E4%BA%8C%E5%8F%89%E6%A0%91%E9%9D%9E%E9%80%92%E5%BD%92%E9%81%8D%E5%8E%86%E7%BB%9F%E4%B8%80%E5%86%99%E6%B3%95/</id>
<published>2020-09-06T14:37:47.000Z</published>
<updated>2020-09-06T15:13:55.640Z</updated>
<content type="html"><![CDATA[<h2><span id="前言">前言</span></h2><p> 二叉树的遍历,从编程方式上来说主要有两种写法,一种是递归的写法,一种是非递归的写法,其中递归的写法很容易,在<code>leetcode</code>上都是属于<code>easy</code>级别的题目,风格也非常一致,学会了前序遍历,调整一下代码顺序,几秒之内中序、后序遍历就都可以写出来了。但是非递归的写法则不然,市面上包括很多教科书上二叉树前、中、后序的非递归遍历的写法都不一样,在<code>leetcode</code>上非递归的写法也基本都是属于<code>medium</code>级别的,其中后序遍历属于<code>hard</code>级别的,本文主要是介绍一种二叉树非递归遍历的统一写法,可以帮助你像写递归遍历那样,只要调整一下代码顺序就可以很快写好前、中、后序非递归遍历。</p><h2><span id="二叉树非递归遍历的一种统一写法">二叉树非递归遍历的一种统一写法</span></h2><p>首先定义二叉树结构</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> TreeNode <span class="keyword">struct</span> {</span><br><span class="line"> Val <span class="keyword">int</span></span><br><span class="line"> Left *TreeNode</span><br><span class="line"> Right *TreeNode</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后对二叉树节点进行一次Wrapper, 增加一个字段标示这个节点的访问状态,这个方法我也叫它为节点标记法(雾</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> TreeNodeWrapper <span class="keyword">struct</span> {</span><br><span class="line">Node *TreeNode</span><br><span class="line">Visited <span class="keyword">bool</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后先写最难的后序遍历</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">func</span> <span class="title">postorderTraversal</span><span class="params">(root *TreeNode)</span> []<span class="title">int</span></span> {</span><br><span class="line">res := <span class="built_in">make</span>([]<span class="keyword">int</span>,<span class="number">0</span>)</span><br><span class="line"><span class="keyword">if</span> root == <span class="literal">nil</span> {</span><br><span class="line"><span class="keyword">return</span> res</span><br><span class="line">}</span><br><span class="line">stack := <span class="built_in">make</span>([]*TreeNodeWrapper,<span class="number">0</span>)</span><br><span class="line">stack = <span class="built_in">append</span>(stack,&TreeNodeWrapper{root,<span class="literal">false</span>})</span><br><span class="line"><span class="keyword">for</span> <span class="built_in">len</span>(stack) > <span class="number">0</span> {</span><br><span class="line">node := stack[<span class="built_in">len</span>(stack)<span class="number">-1</span>]</span><br><span class="line">stack = stack[:<span class="built_in">len</span>(stack)<span class="number">-1</span>]</span><br><span class="line"><span class="keyword">if</span> node != <span class="literal">nil</span> {</span><br><span class="line"><span class="keyword">if</span> !node.Visited {</span><br><span class="line">node.Visited = <span class="literal">true</span></span><br><span class="line">stack = <span class="built_in">append</span>(stack, node) <span class="comment">// 1</span></span><br><span class="line"><span class="keyword">if</span> node.Node.Right != <span class="literal">nil</span> {</span><br><span class="line">stack = <span class="built_in">append</span>(stack, &TreeNodeWrapper{node.Node.Right, <span class="literal">false</span>}) <span class="comment">// 2</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span> node.Node.Left != <span class="literal">nil</span> {</span><br><span class="line">stack = <span class="built_in">append</span>(stack, &TreeNodeWrapper{node.Node.Left, <span class="literal">false</span>}) <span class="comment">// 3</span></span><br><span class="line">}</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line">res = <span class="built_in">append</span>(res,node.Node.Val)</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> res</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>主要需要关注的就是注释1、2、3的地方,由于后序遍历的顺序是左-右-根,所以入栈的顺序就是根-右-左。以此类推,前序遍历的非递归写法调整对应代码的顺序就是2-3-1,中序则是2-1-3,完整代码如下:</p><ul><li><p>前序遍历</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">func</span> <span class="title">preorderTraversal</span><span class="params">(root *TreeNode)</span> []<span class="title">int</span></span> {</span><br><span class="line">res := <span class="built_in">make</span>([]<span class="keyword">int</span>,<span class="number">0</span>)</span><br><span class="line"><span class="keyword">if</span> root == <span class="literal">nil</span> {</span><br><span class="line"><span class="keyword">return</span> res</span><br><span class="line">}</span><br><span class="line">stack := <span class="built_in">make</span>([]*TreeNodeWrapper,<span class="number">0</span>)</span><br><span class="line">stack = <span class="built_in">append</span>(stack,&TreeNodeWrapper{root,<span class="literal">false</span>})</span><br><span class="line"><span class="keyword">for</span> <span class="built_in">len</span>(stack) > <span class="number">0</span> {</span><br><span class="line">node := stack[<span class="built_in">len</span>(stack)<span class="number">-1</span>]</span><br><span class="line">stack = stack[:<span class="built_in">len</span>(stack)<span class="number">-1</span>]</span><br><span class="line"><span class="keyword">if</span> node != <span class="literal">nil</span> {</span><br><span class="line"><span class="keyword">if</span> !node.Visited {</span><br><span class="line"><span class="keyword">if</span> node.Node.Right != <span class="literal">nil</span> {</span><br><span class="line">stack = <span class="built_in">append</span>(stack, &TreeNodeWrapper{node.Node.Right, <span class="literal">false</span>})</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span> node.Node.Left != <span class="literal">nil</span> {</span><br><span class="line">stack = <span class="built_in">append</span>(stack, &TreeNodeWrapper{node.Node.Left, <span class="literal">false</span>})</span><br><span class="line">}</span><br><span class="line"> node.Visited = <span class="literal">true</span></span><br><span class="line">stack = <span class="built_in">append</span>(stack, node)</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line">res = <span class="built_in">append</span>(res,node.Node.Val)</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> res</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><ul><li><p>中序遍历</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">inorderTraversal</span><span class="params">(root *TreeNode)</span> []<span class="title">int</span></span> {</span><br><span class="line">res := <span class="built_in">make</span>([]<span class="keyword">int</span>,<span class="number">0</span>)</span><br><span class="line"><span class="keyword">if</span> root == <span class="literal">nil</span> {</span><br><span class="line"><span class="keyword">return</span> res</span><br><span class="line">}</span><br><span class="line">stack := <span class="built_in">make</span>([]*TreeNodeWrapper,<span class="number">0</span>)</span><br><span class="line">stack = <span class="built_in">append</span>(stack,&TreeNodeWrapper{root,<span class="literal">false</span>})</span><br><span class="line"><span class="keyword">for</span> <span class="built_in">len</span>(stack) > <span class="number">0</span> {</span><br><span class="line">node := stack[<span class="built_in">len</span>(stack)<span class="number">-1</span>]</span><br><span class="line">stack = stack[:<span class="built_in">len</span>(stack)<span class="number">-1</span>]</span><br><span class="line"><span class="keyword">if</span> node != <span class="literal">nil</span> {</span><br><span class="line"><span class="keyword">if</span> !node.Visited {</span><br><span class="line"><span class="keyword">if</span> node.Node.Right != <span class="literal">nil</span> {</span><br><span class="line">stack = <span class="built_in">append</span>(stack, &TreeNodeWrapper{node.Node.Right, <span class="literal">false</span>})</span><br><span class="line">}</span><br><span class="line"> node.Visited = <span class="literal">true</span></span><br><span class="line">stack = <span class="built_in">append</span>(stack, node)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> node.Node.Left != <span class="literal">nil</span> {</span><br><span class="line">stack = <span class="built_in">append</span>(stack, &TreeNodeWrapper{node.Node.Left, <span class="literal">false</span>})</span><br><span class="line">}</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line">res = <span class="built_in">append</span>(res,node.Node.Val)</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> res</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<summary type="html">
<h2><span id="前言">前言</span></h2><p> 二叉树的遍历,从编程方式上来说主要有两种写法,一种是递归的写法,一种是非递归的写法,其中递归的写法很容易,在<code>leetcode</code>上都是属于<code>easy</code>
</summary>
<category term="算法" scheme="http://vzardlloo.github.io/tags/%E7%AE%97%E6%B3%95/"/>
</entry>
<entry>
<title>灵魂七问</title>
<link href="http://vzardlloo.github.io/2020/08/22/%E7%81%B5%E9%AD%82%E4%B8%83%E9%97%AE/"/>
<id>http://vzardlloo.github.io/2020/08/22/%E7%81%B5%E9%AD%82%E4%B8%83%E9%97%AE/</id>
<published>2020-08-21T17:14:12.000Z</published>
<updated>2020-08-23T13:14:20.890Z</updated>
<content type="html"><![CDATA[<ul><li><p>你为什么做这个东西?</p></li><li><p>它和其他东西有什么不同?</p></li><li><p>你可以做些什么,比起其他的呢?</p></li><li><p>你是怎么实现的?</p></li><li><p>实现过程中的核心问题是如何解决的,解决方案是一种么?为什么你这么选择?</p></li><li><p>你对你做的这个东西还有什么规划?</p></li><li><p>你如何看待你做的东西所处领域的发展?</p></li></ul>]]></content>
<summary type="html">
<ul>
<li><p>你为什么做这个东西?</p>
</li>
<li><p>它和其他东西有什么不同?</p>
</li>
<li><p>你可以做些什么,比起其他的呢?</p>
</li>
<li><p>你是怎么实现的?</p>
</li>
<li><p>实现过程中的核心问题是
</summary>
<category term="随笔" scheme="http://vzardlloo.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
</entry>
<entry>
<title>XPath线索追踪技术</title>
<link href="http://vzardlloo.github.io/2020/03/05/XPath%E7%BA%BF%E7%B4%A2%E8%BF%BD%E8%B8%AA%E6%8A%80%E6%9C%AF/"/>
<id>http://vzardlloo.github.io/2020/03/05/XPath%E7%BA%BF%E7%B4%A2%E8%BF%BD%E8%B8%AA%E6%8A%80%E6%9C%AF/</id>
<published>2020-03-05T14:33:23.000Z</published>
<updated>2020-03-07T14:54:48.855Z</updated>
<content type="html"><![CDATA[<h3><span id="什么是xpath">什么是XPath</span></h3><p> XPath全称XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的计算机语言。XPath基于XML的树状结构,提供在数据结构树中找寻节点的能力。举例来说:<br> 我们有网页A,他的页面代码为:<br><img src="https://tva1.sinaimg.cn/large/00831rSTly1gclqy3wbaej30wc0lsabl.jpg" alt="code.jpg"></p><p>它对应的domtree为<br><img src="https://tva1.sinaimg.cn/large/00831rSTly1gclqxj0iixj30t60gi0tf.jpg" alt="domtree.jpg"><br> 每个网页的页面结构都可以解析为一颗domtree,每个页面元素都是这棵树的一个节点,那如何唯一的表示每一个节点呢?很自然的由于每个节点到根节点的路径都是不同的,所以可以用节点到根路径的路径来表示,比如meta元素可以用/html/head/meta来表示,这样的话有个问题,比如最右和皮皮搞笑这个两个元素都可以表示成/html/head/h1,这样岂不是乱了套,所以对于这样的情况可以用/html/head/h1[0],/html/head/h1[1]来表示,至于谁是h1[0]谁是h1[1]取决于具体实现。这样用路径表达式唯一标识一个页面元素的方法就叫做XPath。<br> <strong>总结:XPath是我们用来唯一标识页面元素的一种方法和工具。</strong></p><h3><span id="为什么要做xpath线索追踪">为什么要做XPath线索追踪</span></h3><p> 为什么要做XPath线索追踪?这其实可以分解为两个问题:1、为什么要做线索追踪 2、为什么要选择XPath做线索追踪。<br> 首先是问题一,为什么要做线索追踪?这个答案很简单,因为我们要做oCPX,需要根据转化情况(像提交的表单、咨询工具等),系统自动优化转化效果。它的第一步就是转化数据收集,目前我们只对于App下载类有转化数据的收集,而对于网页线索类特别是对第三方网页的线索收集是一片空白,如果无法收集到这些信息,那么对于这些类型广告的oCPX将无从做起,所以我们有必要对于这些类型的广告做线索追踪。这是做XPath线索追踪的必要性。<br> 其次是问题二,为什么选择选择XPath做线索追踪。对于线索追踪目前主要有:js布码、api回传、XPath,这三种做法。其中js布码和api回传都需要平台方面和广告主侧投入较大的技术支持,而XPath只需要平台侧提供对应功能,投放时广告主可以自主选择线索绑定,不需要投入技术支持,较为方便和易于推广。另外因为业内有已经有应用的比较好的先例,可以省去一些调研选型的时间,还有完整的产品形态可以参考,对于最终可以达到的效果也有个预期。这是做XPath线索追踪的充分性。<br> <strong>总结:通过问题一,二的讨论,我们知道我们有充分且必要的理由做XPath线索追踪这件事。</strong></p><h3><span id="xpath线索追踪技术实现">XPath线索追踪技术实现</span></h3><ol><li><p><strong>服务端domtree和截图技术选型</strong><br>通过调研可供我们选择的有三种方案:1、基于PhantomJS的方案,2、基于ChromeDP的方案,3、基于Puppeteer的方案。<br>经过尝试调研后总结他们的优缺点对比如下:</p><table><thead><tr><th align="center"></th><th align="center">优点</th><th align="center">缺点</th></tr></thead><tbody><tr><td align="center">phantomjs</td><td align="center">有过使用经验,学习成本低,二进制文件部署简单</td><td align="center">已经停止维护,web引擎实现不一定和现在主流一致</td></tr><tr><td align="center">chromedp</td><td align="center">go语言实现,底层使用Chrome(我们服务端主要语言是go)</td><td align="center">性能不稳定,社区不活跃,资料不多</td></tr><tr><td align="center">puppeteer</td><td align="center">Chrome官方推荐,社区活跃</td><td align="center">需要在服务器搭建对应环境,同时有一些学习成本</td></tr></tbody></table><p><strong>经过实际的尝试我们最终选择使用Puppeter作为服务端domtree解析和服务端截图的工具。</strong></p></li><li><p><strong>页面domtree json和页面截图匹配</strong><br>domtree json是一串描述元素位置信息和其XPath表达式的json,它通过服务器模拟访问页面,然后遍历页面的domtree生成,例如: </p><figure class="highlight javascript"><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"> <span class="string">"height"</span>: <span class="number">200</span>,</span><br><span class="line"> <span class="string">"left"</span>: <span class="number">400</span>,</span><br><span class="line"> <span class="string">"top"</span>: <span class="number">500</span>,</span><br><span class="line"> <span class="string">"width"</span>: <span class="number">300</span>,</span><br><span class="line"> <span class="string">"xpath"</span>: <span class="string">"/html/body/div[1]"</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure></li></ol><p>的含义是: xpath表达式为/html/body/div[1]的元素,它的左上角距离视窗左上角右偏400像素, 下偏500像素,同时该元素高200像素,宽300像素。根据这些信息我们可以在截图的对应区域进行框选,如果在截图上框选到的这块区域刚好是路径表达式/html/body/div[1]表示的区域,那么这个元素即匹配成功。</p><p><img src="https://tva1.sinaimg.cn/large/00831rSTly1gclqum5elgj30dw0dwt94.jpg" alt="rect.png"></p><p>下面主要介绍两种较难匹配的元素的解决: <strong>一、固定浮动元素 二、重叠元素</strong> </p><ol><li><p><strong>固定浮动元素的匹配</strong><br>固定浮动元素指的是网页上一些不随着页面滑动而移动的元素,它们与页面其他元素的相对位置是不固定的,比如一些悬浮球,底部栏之类的元素,这类元素的位置完全取决于你的设备型号,一个悬浮球在小屏幕设备上可能位于屏幕中部,换成大屏幕后它可能就位于屏幕靠下的位置了,而且在服务端截图的时候,对于一些需要滑动的页面截得是展开的长图。所以此时如果模拟移动设备去访问页面解析domtree的话,浮动元素的domtree json必然与截图无法匹配。对于这个问题摸索出的解决方案为: 放弃chrome模拟移动设备的方法,手动设置ua,然后截图之后,在解析domtree之前,获取页面高度,然后设置模拟访问的页面视图打开高度为页面高度,对应不同动态的设置视图高度,由于这样的视图高度和页面高度一致,所以在这样的视图下打开的页面不存在滑动的情况,所以完美解决浮动元素的问题。</p></li><li><p><strong>重叠元素的匹配</strong><br>domtree json只表示了元素之间二维的位置关系,但是对于三维上的重叠等情况没有描述,所以当一个位置出现两个元素,到底应该框选谁变成了问题。这个问题解决最容易想到的就是给domtree json加上三维信息的描述,并且元素确实有z-index这个属性表示层级关系,但是实际中发现z-index属性有许多的坑,不是很靠谱,比如父标签 position属性为relative,标签无position属性,标签含有浮动(float)属性等都会导致z-index失效(根本原因是css属性间作用不正交),同时发现巨量引擎也并没有使用z-index属性来进行匹配。后来我们观察了一些页面,发现其实大部分页面都存在着小元素在在大元素上层的规律,于是我们决定按照元素面积来做层级的判断,即面积小的元素默认在面积大的上层,这样还意外的解决了一些有蒙层的弹窗元素导致整个页面被蒙层遮盖无法选择的问题,并且在测试一些页面后在使用上已经达到类似工具的同等的水平。同时我们好奇是否同行也是使用同样的手法来解决这个问题,于是我们制作了一个特殊的页面,一个600<em>400的元素在上层,两个600</em>200的元素在下层,上下层边缘轮廓完全重合,这样按照面积小的元素默认在上的实现方式会导致实际在上层的大元素无法选中,我们拿着这个页面去类型工具尝试,发现果然上层的大元素无法选中,这说明了大家用的手法都是一致的!<br>至此,我们的XPath工具已经达到业内类似工具的使用水平,然后在此基础上对一些业内工具支持不太好的地方进行了优化,主要是:</p><ol><li><p>对重定向页面的支持,技术选项使用puppeteer自动解决</p></li><li><p>对于懒加载页面的优化,截图解析之前服务器模拟对页面进行滑动,使得页面完全加载 </p><p><em>最终元素选择效果:</em><br><img src="https://tva1.sinaimg.cn/large/00831rSTly1gclqwsm2lgj30la0v2k3t.jpg" alt="effect.png"></p></li></ol><p><strong>END: 更完整详细技术及产品实现细节在:<a href="https://download.csdn.net/download/qq_33446401/12233649" target="_blank" rel="noopener">内部分享PPT</a></strong></p></li></ol>]]></content>
<summary type="html">
<h3><span id="什么是xpath">什么是XPath</span></h3><p> XPath全称XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的计算机语言。XPath基于XML的树状结构,提供在数据结构树中
</summary>
<category term="计算广告" scheme="http://vzardlloo.github.io/categories/%E8%AE%A1%E7%AE%97%E5%B9%BF%E5%91%8A/"/>
<category term="线索追踪" scheme="http://vzardlloo.github.io/tags/%E7%BA%BF%E7%B4%A2%E8%BF%BD%E8%B8%AA/"/>
</entry>
<entry>
<title>cron表达式小记</title>
<link href="http://vzardlloo.github.io/2019/04/08/cron%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%B0%8F%E8%AE%B0/"/>
<id>http://vzardlloo.github.io/2019/04/08/cron%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%B0%8F%E8%AE%B0/</id>
<published>2019-04-08T06:58:43.000Z</published>
<updated>2019-04-09T16:33:16.946Z</updated>
<content type="html"><![CDATA[<h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p> Cron表达式的作用是用来配置定时任务,它是一个字符串,由七个元素或者说叫域组成的,元素之间用空格来分割。</p><p> 组成Cron表达式的七个元素及其可能出现的值分别是:</p><table><thead><tr><th>元素</th><th>Seconds</th><th>Minutes</th><th>Hours</th><th>Day-of-Month</th><th>Month</th><th>Day-of-Week</th><th>Year(可选)</th></tr></thead><tbody><tr><td>值</td><td>[, - * /],[0-59]</td><td>[, - * /],[0-59]</td><td>[, - * /],[0-23]</td><td>[, - * / ? L W C],[1-31]</td><td>[, - * /],[1-12],[JAN-DEC]</td><td>[, - * / ? L C #],[1-7],[SUN-SAT]</td><td>[, - * /],[1970-2099]</td></tr></tbody></table><p>[^注]: Day-of-week: 1表示星期日,以此类推</p><p>特殊符号意义:</p><ol><li><p>* :匹配该域的任意值,假如在Minutes域使用 *,即表示每分钟都会触发事件。</p></li><li><p>?:只可以用于Day-of-Month和Day-of-Week两个域,它实际上就是为了解决星期和天之间的冲突而产生的,因为”星期”并不在” 年月日”这种时间记法体系里面,如果设置成每个月一号同时设置每个星期一执行任务的话,任务便会不知道该哪天执行。具体用法是当Day-of-Month和Day-of-Week中其中有一个有值的话另一个就应该设置成”?”。</p></li><li><p>-: 表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次 。</p></li><li><p>/: 表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次。</p></li><li><p>,: 表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。</p></li><li><p>L: 表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。</p></li><li><p>W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份</p></li><li><p>LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。</p></li><li><p># :用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。</p></li></ol><h3 id="举例"><a href="#举例" class="headerlink" title="举例"></a>举例</h3> <figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">*/5 * * * * ? 每隔5秒执行一次:</div><div class="line">0 */1 * * * ? 每隔1分钟执行一次:</div><div class="line">0 15 10 * * ? *每天10点15分触发</div><div class="line">0 15 10 * * ? 20172017年每天10点15分触发</div><div class="line">0 * 14 * * ?每天下午的 2点到2点59分每分触发</div><div class="line">0 0/5 14 * * ?每天下午的 2点到2点59分(整点开始,每隔5分触发)</div><div class="line">0 0/5 14,18 * * ?每天下午的 2点到2点59分、18点到18点59分(整点开始,每隔5分触发)</div><div class="line">0 0-5 14 * * ?每天下午的 2点到2点05分每分触发</div><div class="line">0 15 10 ? * 6L每月最后一周的星期五的10点15分触发</div><div class="line">0 15 10 ? * 6#3每月的第三周的星期五开始触发</div></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p> Cron表达式的作用是用来配置定时任务,它是一个字符串,由七个元素或者说叫域组成的,元素之间用空格来分割。</p>
<p>
</summary>
<category term="杂记" scheme="http://vzardlloo.github.io/tags/%E6%9D%82%E8%AE%B0/"/>
</entry>
<entry>
<title>计算广告核心问题总结</title>
<link href="http://vzardlloo.github.io/2019/03/28/%E8%AE%A1%E7%AE%97%E5%B9%BF%E5%91%8A%E6%A0%B8%E5%BF%83%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/"/>
<id>http://vzardlloo.github.io/2019/03/28/%E8%AE%A1%E7%AE%97%E5%B9%BF%E5%91%8A%E6%A0%B8%E5%BF%83%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/</id>
<published>2019-03-28T09:56:58.000Z</published>
<updated>2019-04-14T14:44:21.283Z</updated>
<content type="html"><![CDATA[<p>计算广告的核心问题(目标):<br>$$<br>max \sum^T_{i=1}(r_i-q_i)<br>$$<br> 就是这个最优化问题。上面的<code>i</code>代表从第<code>1</code>次到第<code>T</code>次之间的广告展示,<code>r</code>代表收入,<code>q</code>代表成本,<code>(r-q)</code> 代表这次广告活动的利润,最终目标是广告活动的总利润最大化。</p><p>同时由上面的可以导出投入产出比公式:<br>$$<br>ROI=max({\sum_ir_i}/{\sum_iq_i})<br>$$<br>点击率:<br>$$<br>CTR=广告点击数/广告展现数<br>$$<br>到达率:<br>$$<br>到达率=落地页成功打开次数/点击次数<br>$$<br>转化率:<br>$$<br>CVR=转化次数/到达次数<br>$$<br>描述网站盈利能力的参数:<br>$$<br>eCPM=r(a,u,c)=\mu(a,u,c)*v(a,u,c)<br>$$<br>$\mu$表示点击率, $v$代表点击价值,前者是在媒体上发生的行为,后者是在广告主网站上发生的行为。</p><p>$eCPM$一般指的是估计的千次展示收益。</p><p>$CPM$结算:按照千次展示结算。— 大多数互联网品牌广告特别是视频广告使用</p><p>$CPC$结算:按点击结算。— 在效果类广告市场中广泛使用</p><p>$$CSP/CPA/ROI$$结算:按照销售订单数,转化行为数或者投入产出比结算 — 用的不太多</p><p>$$CPT$$结算:按时间段结算。 — 针对大品牌广告主的广告活动</p>]]></content>
<summary type="html">
<p>计算广告的核心问题(目标):<br>$$<br>max \sum^T_{i=1}(r_i-q_i)<br>$$<br> 就是这个最优化问题。上面的<code>i</code>代表从第<code>1</code>次到第<code>T</code>次之间的广告展示,<code>
</summary>
<category term="计算广告" scheme="http://vzardlloo.github.io/tags/%E8%AE%A1%E7%AE%97%E5%B9%BF%E5%91%8A/"/>
</entry>
<entry>
<title>实现一个简单的http服务器</title>
<link href="http://vzardlloo.github.io/2018/02/10/%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84http%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
<id>http://vzardlloo.github.io/2018/02/10/%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84http%E6%9C%8D%E5%8A%A1%E5%99%A8/</id>
<published>2018-02-10T12:38:49.000Z</published>
<updated>2019-03-29T03:15:48.480Z</updated>
<content type="html"><![CDATA[<p><img src="https://raw.githubusercontent.com/vzardlloo/imageHub/master/avatar.jpg" alt=""><br>这篇文章主要写一下如何实现一个简单的http服务器,并且借由这个问题讨论一下别的相关问题。首先实现一个http服务器是一件很难的事吗?这个问题不好回答,如果是一个demo级的http服务器,答案是:很简单,可以说是JAVA刚入门水平就可以做到的。如果是一个可供实际生产的、性能优良,架构优雅的服务器,答案是:很不容易,对大部分的人来说可能很难。不过不管是demo还是成熟的生产用的http服务器,他们核心的部分应该都是相似的,下面我们来分析一下服务器的工作过程:</p><p>首先何谓“服务”?服务就是按照客户的要求来完成客户的需求。服务器更具体的表现形式是按照客户的的请求来执行某种操作,返回指定数据,这里的数据和请求更具体的表现形式通常是<em>字符串</em>。也就是说<em>客户端发给服务器一个包含客户端需求的字符串,服务端通过解析客户端发来的字符串,获取客户端的需求,然后完成需求</em>。那么问题来了,服务端如何解析客户端发来的字符串呢?他们之间可是素不相识的,每个客户端按何种组织方式来发送自己的需求对服务器来说完全是不可预期的。于是这就需要一个统一的规范来统一客户端的请求和服务端的返回,以及一些别的行为规范,于是http协议合情合理的出现了。现在事情就变成<em>客户端发给服务器一个满足http协议的、包含客户端需求的字符串,服务端根据http协议来解析客户端发来的字符串获取客户端的需求,然后完成需求,返回符合http协议的字符串给客户端</em>。再然后这里的“发送”,“返回”又具体是什么呢?它们就是客户端和服务器之间通过socket连接执行的一些IO操作。这样分析之后我们大概知道整个的工作流程:<em>客户端建立一个连接到服务端,执行IO操作,把满足http协议的字符串发送给服务器,服务器根据http协议来解析客户端发来的字符串的含义,然后执行对应操作,最后把作为返回结果的、满足http协议的字符串执行IO操作发送给客户端。</em> </p><p>通过上面简单的分析我们大概知道http服务器具体干了啥(实际比较复杂)以及我们需要哪些基础前置知识:</p><ul><li>http协议(编程语言无关,核心):参见<a href="https://www.rfc-editor.org/rfc/rfc2616.txt" target="_blank" rel="external">RFC2616</a></li><li>IO操作(编程语言有关):各大编程语言的IO模块</li><li>字符串操作:这就是各大公司考算法题的原因吧(望天==)</li></ul><p>后续当然还有:</p><ul><li>多线程并发优化处理</li><li>设计模式</li><li>……</li></ul><p>下面是我用JAVA实现一个HTTP服务器,我这个服务器不是简单的小demo(demo只需两个JAVA 类即可搞定==),当然也不是可以生产用的(你可以把它写成可以生成用的),先导知识为Java NIO编程和Java 多线程编程,代码太长不贴,放在github。<em>代码地址:<a href="https://github.com/vzardlloo/crab" target="_blank" rel="external">Crab</a></em><br>项目截图:<br><img src="https://camo.githubusercontent.com/b5a6153196f06168813f24483056cbc054eb2944/687474703a2f2f6f6f336171336163382e626b742e636c6f7564646e2e636f6d2f637261622e706e67" alt=""></p><p>项目的目的是什么呢?最重要的目的是希望可以写出规范化的、工程化的代码以及项目,特别是与入门者来说常常只写一些玩具式的demo,或者盲目的去看一下大型项目的源码,我觉得这样对自己水平的提升帮助有限,事实上我认为对一个人提升最大的是从最开始就接触某个项目或领域,从它萌芽的时候开始参与,一步步由项目和问题驱动着来学习,在遇到问题的时候可以想办法来解决它,想不出来可以去别的项目看看别人是如何解决的,这样既提高了自己的水平,也对别的项目有了更深的理解,知道别人为何这样做,而不是去记忆别人是怎么做的。这里引出的一个问题是:当我们在阅读源码时我认为至少要带着这样几个问题:1.这个东西是干什么的,解决了什么问题。2。这里作者这里为什么要这样做,他的目的是什么,他想干什么。3.我现在想要做的东西需要它吗?需要到什么程度(会用?会魔改?)?以项目驱动学习,把从书上,别人源码里的技巧立马用到自己的项目里,才可以让知识真的化为己用。</p><h5 id="题外话当我们学习时我们通常实际上在学习什么"><a href="#题外话:当我们学习时我们通常实际上在学习什么?" class="headerlink" title="题外话:当我们学习时我们通常实际上在学习什么?"></a>题外话:当我们学习时我们通常实际上在学习什么?</h5><p>我的观点是我们在学习时通常实际上更多的在学习各种规范,比如JavaWeb其实通常就是Java语言规范+各种框架的使用规范+网络协议规范,因为计算机科学以及很多工程科学实际是一个人造的体系,支撑这个体系的就是各种规范,修补和发展这个体系也是依赖于各种规范,由于规范可以人造,因此规范可以说是学不完的,因为会不停的有新的规范出来,所以说学无止境。说起学无止境好像每门科学都是学无止境的,我认为科学大致可以分成两种,一直是研究的是基于一种人造的体系,一直是基于自然的体系(当然人造的最后也是基于自然的)。前者的学无止境是由于人类创造的无止境,不断地会有新的问题和规范被创造出来,后者的学无止境更多的是由于大自然的无止境,大到宇宙星辰,小到原子夸克,浩瀚无边地大自然的规范就在那里等待人们去发现。</p>]]></content>
<summary type="html">
<p><img src="https://raw.githubusercontent.com/vzardlloo/imageHub/master/avatar.jpg" alt=""><br>这篇文章主要写一下如何实现一个简单的http服务器,并且借由这个问题讨论一下别的相关问题
</summary>
<category term="网络" scheme="http://vzardlloo.github.io/categories/%E7%BD%91%E7%BB%9C/"/>
<category term="http java" scheme="http://vzardlloo.github.io/tags/http-java/"/>
</entry>
<entry>
<title>由三次握手想到的...</title>
<link href="http://vzardlloo.github.io/2017/12/01/%E7%94%B1%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B%E6%83%B3%E5%88%B0%E7%9A%84/"/>
<id>http://vzardlloo.github.io/2017/12/01/%E7%94%B1%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B%E6%83%B3%E5%88%B0%E7%9A%84/</id>
<published>2017-12-01T14:44:31.000Z</published>
<updated>2017-12-01T16:56:55.816Z</updated>
<content type="html"><![CDATA[<p><img src="http://oo3aq3ac8.bkt.clouddn.com/tcp.png" alt="tcp.png"><br>上面是一个tcp链接的过程,也就是俗称的三次握手,图上把每次握手做了什么也表示的比较清楚,不过这种表述的方式比较僵硬,没有自然而然的说明三次握手中“三次”的充分性和必要性。在建立可靠通信的准备过程中,客户端和服务端都要确认这样四件事:1、自己拥有发送信息的能力,2、自己拥有接受信息的能力,3、对方拥有发送信息的能力,4、对方拥有接受信息的能力。</p><ul><li>在第一次握手时(也就是客户端发送信息给服务端,服务端接受到信息那一刻),这个时候服务端明确了这样两件事,即对方(客户端)具有发送信息的能力,自己具有接受信息的能力。</li><li>在第二次握手的时候(也就是服务端返回信息给客户端,客户端接受到信息那一刻),这个时候客户端可以明确这样四件事:由于自己之前发出的信息有了反馈,可以推断出自己之前的信息发送成功,即自己拥有发送信息的能力,对方接受到信息并给出反馈,说明对方具有接受信息和发送信息的能力,自己接受到反馈说明自己拥有接受信息的能力。</li><li>在第三次握手的时候(服务端发送确认信息给服务端,服务端接受到信息那一刻),服务端可以明确这样两件事:由于自己发送的信息有了反馈可以推断出自己拥有发送信息的能力,对方具有接受信息的能力。</li></ul><p>这样通过三次握手双方都恰好完成了自己在建立可靠通信需要明确的四件事,所以可以进行可靠通信。那么可以做一下推广,三点,四点,乃至N点建立可靠通信需要握几次手呢?先看一下三点,三点就是在两点的基础上再加一点,那么对于第三点来说,对方(点一,点二)的接受和发送信息的能力均已经得到了验证,所以他只需验证自己的接受和发送信息的能力即可,那么验证自己的发送信息和接受信息的能力需要握几次手呢?答案是三次,因为接受信息的能力需要一次握手来验证,发送信息的能力需要两次握手来验证(先发送,等待反馈,接受到反馈说明具有接受发送信息的能力)。所以对于四点也是这样,只要通过三次握手确认自己的发送信息和接受信息的能力,就可以加入已有的三点可靠通信网络,推广一下就是N点可靠通信网络的建立需要[3*(n-1)]次握手,当N=2时,就是3次握手。</p>]]></content>
<summary type="html">
<p><img src="http://oo3aq3ac8.bkt.clouddn.com/tcp.png" alt="tcp.png"><br>上面是一个tcp链接的过程,也就是俗称的三次握手,图上把每次握手做了什么也表示的比较清楚,不过这种表述的方式比较僵硬,没有自然而然的说
</summary>
<category term="网络" scheme="http://vzardlloo.github.io/categories/%E7%BD%91%E7%BB%9C/"/>
<category term="网络" scheme="http://vzardlloo.github.io/tags/%E7%BD%91%E7%BB%9C/"/>
</entry>
<entry>
<title>Js原型链解读</title>
<link href="http://vzardlloo.github.io/2017/11/06/Js%E5%8E%9F%E5%9E%8B%E9%93%BE%E8%A7%A3%E8%AF%BB/"/>
<id>http://vzardlloo.github.io/2017/11/06/Js%E5%8E%9F%E5%9E%8B%E9%93%BE%E8%A7%A3%E8%AF%BB/</id>
<published>2017-11-06T13:41:43.000Z</published>
<updated>2017-11-07T11:01:09.692Z</updated>
<content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>在JavaScript中没”子类”和“父类”的概念,进一步地也没有“类”和“实例”的的区分。它靠一种看上去十分古怪的”原型链“(prototype chain)模式来实现继承。学过JAVA等编程语言的人可能会认为这是对Java等语言的继承实现方式的一种拙劣并且失败的模仿,然而事实并非如此,原型链模式和我们常见的Class模式分别是两种编程范式prototype_base和class_base的实现,前者在动态语言中似乎十分常见,而后者主要在静态语言领域流行。下面是维基百科关于prototype_base模式的介绍:</p><blockquote><p>Prototype-based programming is a style of object-oriented programming in which behaviour reuse (known as inheritance) is performed via a process of reusing existing objects via delegation that serve as prototypes. This model can also be known as prototypal, prototype-oriented, classless, or instance-based programming. Delegation is the language feature that supports prototype-based programming.<br>Prototype object oriented programming uses generalized objects, which can then be cloned and extended. Using fruit as an example, a “fruit” object would represent the properties and functionality of fruit in general. A “banana” object would be cloned from the “fruit” object, and would also be extended to include general properties specific to bananas. Each individual “banana” object would be cloned from the generic “banana” object. Compare to the class-based paradigm, where a “fruit” class (not object) would be extended by a “banana” class</p></blockquote><p><a href="https://en.wikipedia.org/wiki/Prototype-based_programming" target="_blank" rel="external">维基原文</a><br><a id="more"></a></p><h3 id="如何理解原型链"><a href="#如何理解原型链" class="headerlink" title="如何理解原型链"></a>如何理解原型链</h3><p>我们以一个名字叫<code>Foo()</code>的函数为例。我们定义:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">function Foo(){</div><div class="line"></div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure></p><p>然后再<code>var f1 = new Foo()</code>,<code>var f2 = new Foo()</code>,这期间都有什么事情发生呢?我们通过一张图来看一下:<br><img src="http://oo3aq3ac8.bkt.clouddn.com/proto.jpg" alt="prototype.jpg"></p><p>先介绍两个概念:<code>_proto_</code>和<code>prototype</code>:</p><ul><li><p><code>_proto_</code>:引用《JavaScript权威指南》中的说明:</p><blockquote><p>Every JavaScript object has a second JavaScript object (or null ,<br>but this is rare) associated with it. This second object is known as a prototype, and the first<br>object inherits properties from the prototype.<br>就是说就是每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法。既然有这么一个原型对象,那么对象怎么和它对应的?如何描述这种对应关系?答案就是通过<code>_proto_</code>,对象<code>__proto__</code>属性的值就是它所对应的原型对象。</p></blockquote></li><li><p><code>prototype</code>: 与<code>_proto_</code>不同,<code>prototype</code>是函数才有的属性。当你创建函数时,JS会为这个函数自动添加<code>prototype</code>属性,值是空对象。而一旦你把这个函数当作构造函数(<code>constructor</code>)调用(即通过<code>new</code>关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数<code>prototype</code>的所有属性和方法(实例通过设置自己的<code>__proto__</code>指向承构造函数的<code>prototype</code>来实现这种继承)。它的存在主要是为了存放共享的数据和实现继承。</p></li></ul><p>  下面结合上面的图示来分析,我们可以看到<code>function Foo()</code>对应一个<code>Foo.prototype</code>的原型,那么<code>function Foo</code>和<code>Foo.prototype</code>之间的关系是什么尼?<br>  图里其实已经展示得很清楚了,<code>functon Foo()</code>是<code>Foo.prototype</code>的构造函数,<code>Foo.prototype</code>是<code>function Foo()</code>的原型实例。当我们使用<code>new</code>关键字创建<code>var f1 = new Foo()</code>,<code>var f2 = new Foo()</code>后,<code>f1、f2</code>中会有一个<code>_proto_</code>字段指向<code>Foo.prototype</code>,这种<code>xxx._proto_._proto....</code>的指向就代表了原型链的结构(应该是个森林)。同时每个函数<code>function xxx()</code>其实都是通过<code>function Function()</code>来创造的,所以<code>function Foo()</code>的<code>_proto_</code>应该指向<code>Function.prototype</code>,并且<code>function Function()</code>自身的<code>_proto_</code>也指向<code>Function.prototype</code>。<br>  事实上,所有<code>function xxx()</code>的<code>_proto_</code>最终都会指向<code>Function.prototype</code>,而所有的<code>xxx.prototype</code>最后都会指向<code>Object.prototype</code>,最终指向<code>null</code>。关于<code>function Object()</code>这个函数其实有点像Java中的Object对象,所有原型都会继承自它的原型。这里有个有意思的问题,<code>function Function()</code>也是个函数,所以<code>function Function()</code>的<code>_proto_</code>属性的值为<code>Function.prototype</code>,这也就意味着它自己创造了自己。这样的结果就是<code>function Object()._proto_ = Function.prototype</code>、而<code>function Function()._proto._proto_ = Object.prototype</code>,即<code>Object instanceof Function == true</code>、<code>Function instanceof Object == true</code>翻译过来就是<code>Object</code>是<code>Function</code>的实例,<code>Function</code>是<code>Object</code>的实例,这是一种类似先有鸡还是先有蛋的蜜汁尴尬局面。</p><p>总结:</p><ul><li>所有对象的<code>_proto_</code>字段都指向创建它的构造函数的原型, 最终都指向<code>Object.prototype</code>,类似<code>xxx.prototype._proto_._proto_..._proto_ = Object.prototype = null</code>就是原型链。</li><li>所有函数都由<code>function Function()</code>创建,所以所有函数的(包括它本身)<code>_proto_</code>字段都会指向<code>Function.prototype</code>,最后才指向<code>Object.prototype</code>。</li></ul><h3 id="使用原型链实现继承"><a href="#使用原型链实现继承" class="headerlink" title="使用原型链实现继承"></a>使用原型链实现继承</h3><p>定义父函数:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">Father</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">this</span>.age = <span class="string">"56"</span>; }</div><div class="line"></div><div class="line">Father.prototype.say = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</div><div class="line"> alert(<span class="string">"my age is "</span>+<span class="keyword">this</span>.age); </div><div class="line">}</div></pre></td></tr></table></figure></p><p>定义子函数:<br><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">Son</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">this</span>.age = <span class="string">'26'</span>;</div><div class="line"> <span class="keyword">this</span>.play = <span class="string">"football"</span>; }</div><div class="line"></div><div class="line">Son.prototype.play = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</div><div class="line"> alert(<span class="string">"I like play "</span>+<span class="keyword">this</span>.play);</div><div class="line"> }</div></pre></td></tr></table></figure></p><p>实现继承后的原型链应该是:<code>Son.prototype._proto_ = Father.prototype</code><br>实现方式:借用第三个函数过渡<br><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">extends</span>(<span class="params">Child,Father</span>)</span>{</div><div class="line"><span class="keyword">var</span> F = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{};</div><div class="line">F.prototype = Father.prototype;</div><div class="line"><span class="comment">//Child.prototype._proto_ = F.prototype = Father.prototype</span></div><div class="line">Child.prototype = <span class="keyword">new</span> F();</div><div class="line"><span class="comment">//原本Child.prototype.constructor = F,修改为Child</span></div><div class="line">Child.prototype.constructor = Child;</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure></p><p>测试验证:<code>Son</code>的实例可以调用<code>say()</code>则说明继承成功。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">Father</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">this</span>.age = <span class="string">"56"</span>; }</div><div class="line"></div><div class="line">Father.prototype.say = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</div><div class="line"> alert(<span class="string">"my age is "</span>+<span class="keyword">this</span>.age); }</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">Son</span>(<span class="params"></span>) </span>{</div><div class="line"> <span class="keyword">this</span>.age = <span class="string">'26'</span>;</div><div class="line"> <span class="keyword">this</span>.play = <span class="string">"football"</span>; }</div><div class="line"></div><div class="line">Son.prototype.play = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</div><div class="line"> alert(<span class="string">"I like play "</span>+<span class="keyword">this</span>.play); }</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">excents</span>(<span class="params">Child,Father</span>) </span>{</div><div class="line"></div><div class="line"> <span class="keyword">var</span> F = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{}</div><div class="line"> F.prototype = Father.prototype;</div><div class="line"> Child.prototype = <span class="keyword">new</span> F();</div><div class="line"> Child.prototype.constructor = Child; }</div><div class="line"></div><div class="line">excents(Son,Father); </div><div class="line"></div><div class="line"><span class="keyword">var</span> son = <span class="keyword">new</span> Son(); </div><div class="line">son.say();</div></pre></td></tr></table></figure></p><p>运行结果:<br><img src="http://oo3aq3ac8.bkt.clouddn.com/%E5%BC%B9%E7%AA%97" alt=""></p><p>继承成功!</p>]]></content>
<summary type="html">
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>在JavaScript中没”子类”和“父类”的概念,进一步地也没有“类”和“实例”的的区分。它靠一种看上去十分古怪的”原型链“(prototype chain)模式来实现继承。学过JAVA等编程语言的人可能会认为这是对Java等语言的继承实现方式的一种拙劣并且失败的模仿,然而事实并非如此,原型链模式和我们常见的Class模式分别是两种编程范式prototype_base和class_base的实现,前者在动态语言中似乎十分常见,而后者主要在静态语言领域流行。下面是维基百科关于prototype_base模式的介绍:</p>
<blockquote>
<p>Prototype-based programming is a style of object-oriented programming in which behaviour reuse (known as inheritance) is performed via a process of reusing existing objects via delegation that serve as prototypes. This model can also be known as prototypal, prototype-oriented, classless, or instance-based programming. Delegation is the language feature that supports prototype-based programming.<br>Prototype object oriented programming uses generalized objects, which can then be cloned and extended. Using fruit as an example, a “fruit” object would represent the properties and functionality of fruit in general. A “banana” object would be cloned from the “fruit” object, and would also be extended to include general properties specific to bananas. Each individual “banana” object would be cloned from the generic “banana” object. Compare to the class-based paradigm, where a “fruit” class (not object) would be extended by a “banana” class</p>
</blockquote>
<p><a href="https://en.wikipedia.org/wiki/Prototype-based_programming">维基原文</a><br>
</summary>
<category term="前端" scheme="http://vzardlloo.github.io/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="前端" scheme="http://vzardlloo.github.io/tags/%E5%89%8D%E7%AB%AF/"/>
</entry>
<entry>
<title>统计知识(1)</title>
<link href="http://vzardlloo.github.io/2017/10/16/%E7%BB%9F%E8%AE%A1%E7%9F%A5%E8%AF%86%EF%BC%881%EF%BC%89/"/>
<id>http://vzardlloo.github.io/2017/10/16/%E7%BB%9F%E8%AE%A1%E7%9F%A5%E8%AF%86%EF%BC%881%EF%BC%89/</id>
<published>2017-10-16T08:44:17.000Z</published>
<updated>2017-10-19T03:55:44.230Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近为了参加一个数据分析比赛,在复习统计这块的知识。首先什么是统计呢?我采用北京大学出版社出版的《应用经济统计学》中的定义:统计学是一门对群体现象数量特征进行计量描述和分析推论的科学。从定义可以看出统计学主要干两件事一个是对群体现象特征进行计量描述,第二个是对群体数量进行分析得出推论。那我们一件一件事来,先来讲一下统计学干的第一件事:对群体现象特征进行计量描述。本文主要理一下脉络,对于细节不过多纠结(主要是Markdown打公式太麻烦……)<br><a id="more"></a></p><h2 id="描述集中趋势的计量"><a href="#描述集中趋势的计量" class="headerlink" title="描述集中趋势的计量"></a>描述集中趋势的计量</h2><h3 id="算数平均数"><a href="#算数平均数" class="headerlink" title="算数平均数"></a>算数平均数</h3><ul><li>简单算数平均数<br>简单算数平均数就是一组数据N个数值的和除以N。</li><li>加权算数平均数<br>每个数值的权重乘以数值的和除以数据个数。</li></ul><p>缺点:容易受极端值影响</p><h3 id="中位数"><a href="#中位数" class="headerlink" title="中位数"></a>中位数</h3><p>将一组数据排序,处在数据中点位置的数值就是中位数。(位置平均数)</p><p>优点:稳健,不收极端值影响<br>缺点:缺乏敏感性,不适合代数运算</p><h3 id="众数"><a href="#众数" class="headerlink" title="众数"></a>众数</h3><p>一组数据中出现次数最多的数值。</p><p>优缺点:和中位数一样</p><h3 id="三者的关系"><a href="#三者的关系" class="headerlink" title="三者的关系"></a>三者的关系</h3><p>我们知道如果一直增加观察项数,同时又缩小组距,那么分布的直方图就接近一条光滑的曲线。按这条曲线来解释的话,均值是数据分布的平衡点或者说是中心,中位数把这个分布划分为两半,众数<br>正好是分布顶端的数值。并且在对称分布中三个测度重合,斜分布中三个测度分离。</p><h3 id="其他测度"><a href="#其他测度" class="headerlink" title="其他测度"></a>其他测度</h3><p>1.分位数<br>中位数的推广,中位数可以看做二分位数,同样也有四分位数,十分位数,N分位数…<br>2.几何平均数<br>变量X的n项观察值x_1,x_2,x_3…x_n的乘积的n次根。<br>3.调和平均数<br>一组观测值的倒数的算数平均数的倒数。</p><h2 id="描述离中趋势的计量"><a href="#描述离中趋势的计量" class="headerlink" title="描述离中趋势的计量"></a>描述离中趋势的计量</h2><h3 id="极差"><a href="#极差" class="headerlink" title="极差"></a>极差</h3><p>一组观测数据中最大值与最小值的差。表现数据的变动范围。</p><h3 id="平均差"><a href="#平均差" class="headerlink" title="平均差"></a>平均差</h3><p>一组数据值与其均值之差的绝对值的和的平均值。</p><h3 id="方差"><a href="#方差" class="headerlink" title="方差"></a>方差</h3><p>一组资料中各数值与其算数平均数离差平方的平均数。</p><h3 id="标准差"><a href="#标准差" class="headerlink" title="标准差"></a>标准差</h3><p>标准差的平方就是方差</p><h3 id="chebishev定理"><a href="#Chebishev定理" class="headerlink" title="Chebishev定理"></a>Chebishev定理</h3><p>对于任何一组资料,观测值落于均值左右k个标准差的区间内的比例至少为(1-1/k^2).</p><h3 id="四分位差"><a href="#四分位差" class="headerlink" title="四分位差"></a>四分位差</h3><p>四分位差是第三个四分位值于第一个四分位值之差的二分之一。实际就是一组资料中间一半观测值的极差。</p><h3 id="异众比率"><a href="#异众比率" class="headerlink" title="异众比率"></a>异众比率</h3><p>非众数值的次数之和占总次数之比重。</p><h3 id="平均差系数"><a href="#平均差系数" class="headerlink" title="平均差系数"></a>平均差系数</h3><p>平均差于均值之比。</p>]]></content>
<summary type="html">
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近为了参加一个数据分析比赛,在复习统计这块的知识。首先什么是统计呢?我采用北京大学出版社出版的《应用经济统计学》中的定义:统计学是一门对群体现象数量特征进行计量描述和分析推论的科学。从定义可以看出统计学主要干两件事一个是对群体现象特征进行计量描述,第二个是对群体数量进行分析得出推论。那我们一件一件事来,先来讲一下统计学干的第一件事:对群体现象特征进行计量描述。本文主要理一下脉络,对于细节不过多纠结(主要是Markdown打公式太麻烦……)<br>
</summary>
<category term="统计" scheme="http://vzardlloo.github.io/categories/%E7%BB%9F%E8%AE%A1/"/>
<category term="统计" scheme="http://vzardlloo.github.io/tags/%E7%BB%9F%E8%AE%A1/"/>
</entry>
<entry>
<title>同步Github上的fork</title>
<link href="http://vzardlloo.github.io/2017/10/13/%E5%90%8C%E6%AD%A5Github%E4%B8%8A%E7%9A%84fork/"/>
<id>http://vzardlloo.github.io/2017/10/13/%E5%90%8C%E6%AD%A5Github%E4%B8%8A%E7%9A%84fork/</id>
<published>2017-10-13T07:08:51.000Z</published>
<updated>2017-10-13T07:55:08.966Z</updated>
<content type="html"><![CDATA[<p>我们有时候会在Github上fork一个我们感兴趣的项目到自己的仓库,等一段时间过后这个项目已经更新了,但是自己仓库里还停留在刚fork时的状态,那么我们如何把自己fork到仓库的项目同步到作者的进度呢?这篇博客主要就是讲一下这个问题。<br>这个问题我有两种解决的办法:<br>一种是在Github上操作,把自己仓库的项目设成<code>base</code>,把作者的的项目设成<code>head</code>,然后给自己提pr,最后在pull下来就行了。这种方法的缺点就是要开网页点来点去,如果你正在沉迷在命令行里无法自拔可能不太喜欢这种方法。<br><a id="more"></a><br>第二种方法是在把作者的项目添加为自己的远程分支,然后需要同步的时候<code>fetch</code>一下这个远程分支,然后切换的本地分支(如果不在的话),然后把远程仓库合并到本地分支。具体方法如下:</p><ul><li><p>先查看一下自己的远程分支:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">git remote -v</div><div class="line"># origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch)</div><div class="line"># origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push)</div></pre></td></tr></table></figure></li><li><p>如果作者的仓库不在远程分支中,则把他添加到远程分支:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git</div></pre></td></tr></table></figure></li><li><p>再次查看远程分支情况,确保已经加入:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">git remote -v</div><div class="line"># origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch)</div><div class="line"># origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push)</div><div class="line"># upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (fetch)</div><div class="line"># upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (push)</div></pre></td></tr></table></figure></li><li><p>从上游仓库 fetch 分支和提交点,传送到本地,并会被存储在一个本地分支 upstream/master</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">git fetch upstream</div><div class="line"># remote: Counting objects: 75, done.</div><div class="line"># remote: Compressing objects: 100% (53/53), done.</div><div class="line"># remote: Total 62 (delta 27), reused 44 (delta 9)</div><div class="line"># Unpacking objects: 100% (62/62), done.</div><div class="line"># From https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY</div><div class="line"># * [new branch] master -> upstream/master</div></pre></td></tr></table></figure></li><li><p>切换到本地主分支(如果不在的话)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">git checkout master</div><div class="line"># Switched to branch 'master'</div></pre></td></tr></table></figure></li><li><p>把 upstream/master 分支合并到本地 master 上,这样就完成了同步,并且不会丢掉本地修改的内容。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line">git merge upstream/master</div><div class="line"># Updating a422352..5fdff0f</div><div class="line"># Fast-forward</div><div class="line"># README | 9 -------</div><div class="line"># README.md | 7 ++++++</div><div class="line"># 2 files changed, 7 insertions(+), 9 deletions(-)</div><div class="line"># delete mode 100644 README</div><div class="line"># create mode 100644 README.md</div></pre></td></tr></table></figure></li><li><p>同步到Github上的自己的项目</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">git push origin master</div></pre></td></tr></table></figure></li></ul><p>OK,这个小问题都到此结束!</p>]]></content>
<summary type="html">
<p>我们有时候会在Github上fork一个我们感兴趣的项目到自己的仓库,等一段时间过后这个项目已经更新了,但是自己仓库里还停留在刚fork时的状态,那么我们如何把自己fork到仓库的项目同步到作者的进度呢?这篇博客主要就是讲一下这个问题。<br>这个问题我有两种解决的办法:<br>一种是在Github上操作,把自己仓库的项目设成<code>base</code>,把作者的的项目设成<code>head</code>,然后给自己提pr,最后在pull下来就行了。这种方法的缺点就是要开网页点来点去,如果你正在沉迷在命令行里无法自拔可能不太喜欢这种方法。<br>
</summary>
<category term="Github" scheme="http://vzardlloo.github.io/categories/Github/"/>
<category term="Github" scheme="http://vzardlloo.github.io/tags/Github/"/>
</entry>
<entry>
<title>Http协议杂谈</title>
<link href="http://vzardlloo.github.io/2017/09/19/Http%E5%8D%8F%E8%AE%AE%E6%9D%82%E8%B0%88/"/>
<id>http://vzardlloo.github.io/2017/09/19/Http%E5%8D%8F%E8%AE%AE%E6%9D%82%E8%B0%88/</id>
<published>2017-09-19T08:42:49.000Z</published>
<updated>2017-09-25T08:52:44.846Z</updated>
<content type="html"><![CDATA[<h4 id="http的特性"><a href="#HTTP的特性" class="headerlink" title="HTTP的特性"></a>HTTP的特性</h4><ul><li>HTTP构建于TCP/IP协议之上,默认端口号是80</li><li>HTTP是无连接无状态的</li></ul><h4 id="http报文"><a href="#HTTP报文" class="headerlink" title="HTTP报文"></a>HTTP报文</h4><p>Http协议是以ASCII码传输,建立在TCP/IP协议之上的应用层规范。规范把HTTP请求分为三个部分:状态行、请求头、消息主题。类似于下面这样:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">GET /en-US/docs/Web/HTTP/Headers/Content-Disposition HTTP/1.1</div><div class="line">Host: developer.mozilla.org</div><div class="line">Connection: keep-alive</div><div class="line">Cache-Control: max-age=0</div><div class="line">Upgrade-Insecure-Requests: 1</div><div class="line">User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Mobile Safari/537.36</div><div class="line">Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8</div><div class="line">Referer: https://hit-alibaba.github.io/interview/basic/network/HTTP.html</div><div class="line">Accept-Encoding: gzip, deflate, br</div><div class="line">Accept-Language: zh-CN,zh;q=0.8</div><div class="line">Cookie: csrftoken=0QVKqjybZR4MatNi0vCP03XIDrEWODO9; dwf_section_edit=True; dwf_sg_task_completion=False; _ga=GA1.2.817713072.1498834189; _gid=GA1.2.1652928103.1505806097</div><div class="line">If-None-Match: "76da2643e0174c564b0a898793e76edda496498f"</div></pre></td></tr></table></figure></p><a id="more"></a><p>HTTP定义了于服务器交互的不同方法,最基本的方法有4中,分别是<code>GET</code>,<code>POST</code>,<code>PUT</code>,<code>DELETE</code>。<code>URL</code>全称是资源描述符,我们可以这样认为:一个URL地址,它用于<br>描述一个网络上的资源,而HTTP中的<code>GET</code>,<code>POST</code>,<code>PUT</code>,<code>DELETE</code>就对应着对这个资源的查,增,改,删4个操作。</p><ol><li>GET用于信息获取,而且应该是安全的 和 幂等的。<br>所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,GET 请求一般不应产生副作用。就是说,它仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会影响资源的状态。<br>幂等的意味着对同一URL的多个请求应该返回同样的结果。</li></ol><p>GET请求报文示例:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">GET /books/?sex=man&name=Professional HTTP/1.1</div><div class="line"> Host: www.example.com</div><div class="line"> User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)</div><div class="line"> Gecko/20050225 Firefox/1.0.1</div><div class="line"> Connection: Keep-Alive</div></pre></td></tr></table></figure></p><ol><li>POST表示可能修改变服务器上的资源的请求。</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">POST / HTTP/1.1</div><div class="line">Host: www.example.com</div><div class="line">User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)</div><div class="line">Gecko/20050225 Firefox/1.0.1</div><div class="line">Content-Type: application/x-www-form-urlencoded</div><div class="line">Content-Length: 40</div><div class="line">Connection: Keep-Alive</div><div class="line"></div><div class="line">sex=man&name=Professional</div></pre></td></tr></table></figure><ol><li>注意:<ul><li>GET 可提交的数据量受到URL长度的限制,HTTP 协议规范没有对 URL 长度进行限制。这个限制是特定的浏览器及服务器对它的限制</li><li>理论上讲,POST 是没有大小限制的,HTTP 协议规范也没有进行大小限制,出于安全考虑,服务器软件在实现时会做一定限制</li><li>参考上面的报文示例,可以发现 GET 和 POST 数据内容是一模一样的,只是位置不同,一个在URL里,一个在 HTTP 包的包体里</li></ul></li></ol><h4 id="post提交数据的方式"><a href="#POST提交数据的方式" class="headerlink" title="POST提交数据的方式"></a>POST提交数据的方式</h4><p>HTTP 协议中规定 POST 提交的数据必须在 body 部分中,但是协议中没有规定数据使用哪种编码方式或者数据格式。实际上,开发者完全可以自己决定消息主体的格式,只要最后发送的 HTTP 请求满足上面的格式就可以。</p><p>但是,数据发送出去,还要服务端解析成功才有意义。一般服务端语言如 php、python 等,以及它们的 framework,都内置了自动解析常见数据格式的功能。服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。所以说到 POST 提交数据方案,包含了 Content-Type 和消息主体编码方式两部分。下面就正式开始介绍它们:</p><blockquote><p>application/x-www-form-urlencoded</p></blockquote><p>这是最常见的POST数据提交方式。浏览器的原生<form>表单,如果不设置entype属性,那么最终就会以<code>application/x-www-form-urlencoded</code>方式提交数据。上个小节当中的例子便是使用了这种提交方式。可以看到 body 当中的内容和 GET 请求是完全相同的。</form></p><h4 id="响应报文"><a href="#响应报文" class="headerlink" title="响应报文"></a>响应报文</h4><p>HTTP 响应与 HTTP 请求相似,HTTP响应也由3个部分构成,分别是:</p><ul><li>状态行</li><li>响应头</li><li>响应正文</li></ul><p>状态行由协议版本、数字形式的状态代码、及相应的状态描述,各元素之间以空格分隔。</p><p>常见的状态码有如下几种:</p><ul><li><code>200 OK</code> 客户端请求成功</li><li><code>301 Moved permanently</code> 请求永久重定向</li><li><code>302 Moved Temporarily</code> 请求临时重定向</li><li><code>304 Not Modified</code> 文件未修改,可以直接使用缓存的文件</li><li><code>400 Bad Request</code> 由于客户端请求有语法错误,不能被服务器所理解</li><li><code>401 Unauthorized</code>请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用</li><li><code>403 Forbidden</code> 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因</li><li><code>404 Not Found</code> 请求的资源不存在,例如,输入了错误的URL</li><li><code>500 Internal Server Error</code> 服务器发生不可预期的错误</li><li><code>503 Server Unavailable</code> 服务器当前不能处理客户端的请求,一段时间后可能恢复正常</li></ul><p>下面是一个HTTP响应的例子:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">HTTP/1.1 200 OK</div><div class="line"></div><div class="line">Server:Apache Tomcat/5.0.12</div><div class="line">Date:Mon,6Oct2003 13:23:42 GMT</div><div class="line">Content-Length:112</div><div class="line"></div><div class="line"><html>...</div></pre></td></tr></table></figure></p>]]></content>
<summary type="html">
<h4 id="HTTP的特性"><a href="#HTTP的特性" class="headerlink" title="HTTP的特性"></a>HTTP的特性</h4><ul>
<li>HTTP构建于TCP/IP协议之上,默认端口号是80</li>
<li>HTTP是无连接无状态的</li>
</ul>
<h4 id="HTTP报文"><a href="#HTTP报文" class="headerlink" title="HTTP报文"></a>HTTP报文</h4><p>Http协议是以ASCII码传输,建立在TCP/IP协议之上的应用层规范。规范把HTTP请求分为三个部分:状态行、请求头、消息主题。类似于下面这样:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">GET /en-US/docs/Web/HTTP/Headers/Content-Disposition HTTP/1.1</div><div class="line">Host: developer.mozilla.org</div><div class="line">Connection: keep-alive</div><div class="line">Cache-Control: max-age=0</div><div class="line">Upgrade-Insecure-Requests: 1</div><div class="line">User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Mobile Safari/537.36</div><div class="line">Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8</div><div class="line">Referer: https://hit-alibaba.github.io/interview/basic/network/HTTP.html</div><div class="line">Accept-Encoding: gzip, deflate, br</div><div class="line">Accept-Language: zh-CN,zh;q=0.8</div><div class="line">Cookie: csrftoken=0QVKqjybZR4MatNi0vCP03XIDrEWODO9; dwf_section_edit=True; dwf_sg_task_completion=False; _ga=GA1.2.817713072.1498834189; _gid=GA1.2.1652928103.1505806097</div><div class="line">If-None-Match: &quot;76da2643e0174c564b0a898793e76edda496498f&quot;</div></pre></td></tr></table></figure></p>
</summary>
<category term="JAVA" scheme="http://vzardlloo.github.io/categories/JAVA/"/>
<category term="JAVA" scheme="http://vzardlloo.github.io/tags/JAVA/"/>
</entry>
<entry>
<title>JAVA虚拟机的类加载机制</title>
<link href="http://vzardlloo.github.io/2017/09/19/JAVA%E8%99%9A%E6%8B%9F%E6%9C%BA%E7%9A%84%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6/"/>
<id>http://vzardlloo.github.io/2017/09/19/JAVA%E8%99%9A%E6%8B%9F%E6%9C%BA%E7%9A%84%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6/</id>
<published>2017-09-19T02:27:43.000Z</published>
<updated>2017-09-19T04:27:47.731Z</updated>
<content type="html"><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>这篇博客讲一下JAVA虚拟机的类加载机制,一个类从被加载到虚拟机内存开始,到卸载处内存为止,它的整个生命周期包括:</p><ol><li>加载</li><li>验证</li><li>准备</li><li>解析</li><li>初始化</li><li>使用</li><li>卸载</li></ol><p>其中1,2,3,5,7这五个步骤的顺序是固定的,而解析却不一定,它可以在初始化之后再开始,这是为了支持JAVA语言的运行时绑定。下面开始逐一介绍每个步骤。<br><a id="more"></a></p><h4 id="加载"><a href="#加载" class="headerlink" title="加载"></a>加载</h4><h5 id="加载的时机"><a href="#加载的时机" class="headerlink" title="加载的时机"></a>加载的时机</h5><ul><li>遇到<code>new</code>,<code>getstatic</code>,<code>putstatic</code>,<code>invokestatic</code>这四条字节码指令时,如果类没有经过初始化则进行初始化。</li><li>使用`java.lang.reflect包的方法对类进行反射调用的时候,如果类没有经过初始化则进行初始化。</li><li>当初始化该类的子类的时候若该类没有经过初始化则进行初始化。</li><li>当虚拟机启动的时候,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会初始化这个主类。</li><li>使用JDK 1.7后的动态语言支持时,如果一个<code>java.lang.invoke.MethodHandle</code>实例最后的解析结果<code>REF_getStatic</code>,<code>REF_putStatic</code>,<code>REF_invokeStatic</code>的方法句柄,并且如果该句柄对应的类没有经过初始化,则需要先触发其初始化。</li></ul><h5 id="加载过程"><a href="#加载过程" class="headerlink" title="加载过程"></a>加载过程</h5><ol><li>通过一个类的全限定名来获取定义此类的二进制字节流。</li><li>将这个字节流所代表的静态储存结构转化为方法区的运行时数据结构。</li><li>在内存中生成一个代表这个类的<code>java.lang.Class</code>对象,作为方法区这个类的各种数据的访问接口。</li></ol><h4 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h4><h5 id="文件格式验证"><a href="#文件格式验证" class="headerlink" title="文件格式验证"></a>文件格式验证</h5><p>验证字节流是否符合Class文件格式的规范,并且可以被当前版本的虚拟机处理。比如:</p><ul><li>验证是否以魔数0xCAFFBABE开头。</li><li>验证主次版本号是否在当前虚拟机处理范围之内。<br>……</li></ul><h5 id="元数据验证"><a href="#元数据验证" class="headerlink" title="元数据验证"></a>元数据验证</h5><p>对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求,比如:</p><ul><li>这个类是否有父类(除了<code>java.lang.Object</code>之外,所有的类都应该有父类)。</li><li>这个类的父类是否继承了不允许被继承的类(被final修饰的类)。<br>……</li></ul><h5 id="字节码验证"><a href="#字节码验证" class="headerlink" title="字节码验证"></a>字节码验证</h5><p>这是整个验证过程中最为复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。比如:</p><ul><li>保证任意时刻操作数栈的数据类型与指令代码序列都可以配合工作,例如这样的情况:在操作数栈放置了一个<code>int</code>类型的数据,使用时却按照<code>long</code>类型来加载本地变量表中。</li><li>保证跳转指令不会跳转到方法体以外的字节码指令上。<br>……</li></ul><h5 id="符号引用验证"><a href="#符号引用验证" class="headerlink" title="符号引用验证"></a>符号引用验证</h5><p>最后一个阶段的验证发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在解析阶段发生。符号引用验证可以看做是对类自身以外(常量池中的各种符号引用连接)的信息进行匹配性效验,比如:</p><ul><li>符号引用中通过字符串描述的全限定名是否能找到对应的类。</li><li>在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。<br>……</li></ul><h4 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h4><p>准备阶段是正式为类变量分配内存并且设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。</p><h4 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h4><p>解析过程是将常量池中的符号引用替换为直接引用的过程。<br>主要有:</p><ul><li>类/接口的解析</li><li>字段解析</li><li>类方法解析</li><li>接口方法解析</li></ul><h4 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h4><p>初始化是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制,到了初始化阶段才开始执行类中定义的Java川程序代码。<br>初始化顺序:</p><ol><li>父类静态代码块 ( java虚拟机加载类时,就会执行该块代码,故只执行一次)</li><li>子类静态代码块 ( java虚拟机加载类时,就会执行该块代码,故只执行一次)</li><li>父类属性对象初始化</li><li>父类普通代码块(每次new,每次执行 )</li><li>父类构造函数(每次new,每次执行)</li><li>子类属性对象初始化</li><li>子类普通代码块(每次new,每次执行 )</li><li>子类构造函数(每次new,每次执行)</li></ol><p>初始化完成之后就是对类的使用,最后直到卸载。</p>]]></content>
<summary type="html">
<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>这篇博客讲一下JAVA虚拟机的类加载机制,一个类从被加载到虚拟机内存开始,到卸载处内存为止,它的整个生命周期包括:</p>
<ol>
<li>加载</li>
<li>验证</li>
<li>准备</li>
<li>解析</li>
<li>初始化</li>
<li>使用</li>
<li>卸载</li>
</ol>
<p>其中1,2,3,5,7这五个步骤的顺序是固定的,而解析却不一定,它可以在初始化之后再开始,这是为了支持JAVA语言的运行时绑定。下面开始逐一介绍每个步骤。<br>
</summary>
<category term="JAVA" scheme="http://vzardlloo.github.io/categories/JAVA/"/>
<category term="JAVA" scheme="http://vzardlloo.github.io/tags/JAVA/"/>
</entry>
<entry>
<title>HashMap源码剖析</title>
<link href="http://vzardlloo.github.io/2017/09/08/HashMap%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
<id>http://vzardlloo.github.io/2017/09/08/HashMap%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/</id>
<published>2017-09-08T12:17:06.000Z</published>
<updated>2017-09-09T14:05:25.736Z</updated>
<content type="html"><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>这篇博客讲一下JAVA集合类中的HashMap。HashMap底层是通过维护一个数组来保存元素。当创建HashMap实例的时候,会通过指定的数组大小以及负载因子等参数创建一个空的数组,当在容器中添加元素的时候,首先会通过hash算法求得key的hash值,再根据hash值确定元素在数组中对应的位置,最后将元素放入数组中的对应位置。这里我们需要考虑的一个问题是hash冲突问题,即两个元素key的hash值一样,这就要求我们一方面hash算法要足够”发散”来避免这种情况,另一方面我们也要采取措施来解决这种冲突,在HashMap中采取的方法是链地址法,即当两个元素的key的hash值是一样的,首先判断key值是否是一样的,如果是一样的则替换value值。如果key的值不一样,则把后面添加的元素链接到之前添加的值的后面,形成一个链表。所以HashMap的数据结构实际是:hash表+单向链表。通过链表的形式把所有冲突元素放到了数组同一个位置,但是又引出另一个问题就是当链表过长时会影响HashMap的存取效率,因为在数组长度固定的情况下,存储的数据越多,hash冲突是不可避免的,那么控制hash冲突可以通过扩容来解决,在HashMap中有个负载因子(loadFactor)的概念。HashMap允许实际存储的元素的个数size = loadFactor*数组长度,一旦容器元素超出了这个size,HashMap就会自动扩容,并对所有元素重新执行hash操作,重新调整位置。最后说明本文基于Oracle JDK 1.8。<br><a id="more"></a></p><h4 id="node结构介绍"><a href="#Node结构介绍" class="headerlink" title="Node结构介绍"></a>Node结构介绍</h4><p>Node类是HashMap中的一个静态内部类。它实现了Map.Entry接口,是用于存放数据的实体,前面说的数组也指Node数组。Node的数据结构是一个单向链表,下面是它的源码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Node</span><<span class="title">K</span>,<span class="title">V</span>> <span class="keyword">implements</span> <span class="title">Map</span>.<span class="title">Entry</span><<span class="title">K</span>,<span class="title">V</span>> </span>{</div><div class="line"> <span class="keyword">final</span> <span class="keyword">int</span> hash;</div><div class="line"> <span class="keyword">final</span> K key;</div><div class="line"> V value;</div><div class="line"> Node<K,V> next;</div><div class="line"></div><div class="line"> Node(<span class="keyword">int</span> hash, K key, V value, Node<K,V> next) {</div><div class="line"> <span class="keyword">this</span>.hash = hash;</div><div class="line"> <span class="keyword">this</span>.key = key;</div><div class="line"> <span class="keyword">this</span>.value = value;</div><div class="line"> <span class="keyword">this</span>.next = next;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> K <span class="title">getKey</span><span class="params">()</span> </span>{ <span class="keyword">return</span> key; }</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> V <span class="title">getValue</span><span class="params">()</span> </span>{ <span class="keyword">return</span> value; }</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> String <span class="title">toString</span><span class="params">()</span> </span>{ <span class="keyword">return</span> key + <span class="string">"="</span> + value; }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> Objects.hashCode(key) ^ Objects.hashCode(value);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> V <span class="title">setValue</span><span class="params">(V newValue)</span> </span>{</div><div class="line"> V oldValue = value;</div><div class="line"> value = newValue;</div><div class="line"> <span class="keyword">return</span> oldValue;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object o)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (o == <span class="keyword">this</span>)</div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line"> <span class="keyword">if</span> (o <span class="keyword">instanceof</span> Map.Entry) {</div><div class="line"> Map.Entry<?,?> e = (Map.Entry<?,?>)o;</div><div class="line"> <span class="keyword">if</span> (Objects.equals(key, e.getKey()) &&</div><div class="line"> Objects.equals(value, e.getValue()))</div><div class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure></p><p>这个类结构很简单,定义了hash,key,value,next四个属性,hash值是对key进行hash操作以后得到的,hash方法源码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div 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>{</div><div class="line"> <span class="keyword">int</span> h;</div><div 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>);</div><div class="line"> }</div></pre></td></tr></table></figure></p><p>key不可重复,value保存实际存储的对象,next指向下一个节点。当hash冲突后会将冲突的元素放入这个单向链表中。</p><h4 id="创建hashmap"><a href="#创建HashMap" class="headerlink" title="创建HashMap"></a>创建HashMap</h4><p>创建HashMap实例有四个构造方法,这里只介绍一个,源码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div></pre></td><td class="code"><pre><div class="line"></div><div 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>{</div><div class="line"> <span class="keyword">if</span> (initialCapacity < <span class="number">0</span>)</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"Illegal initial capacity: "</span> +</div><div class="line"> initialCapacity);</div><div class="line"> <span class="keyword">if</span> (initialCapacity > MAXIMUM_CAPACITY)</div><div class="line"> initialCapacity = MAXIMUM_CAPACITY;</div><div class="line"> <span class="keyword">if</span> (loadFactor <= <span class="number">0</span> || Float.isNaN(loadFactor))</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"Illegal load factor: "</span> +</div><div class="line"> loadFactor);</div><div class="line"> <span class="keyword">this</span>.loadFactor = loadFactor;</div><div class="line"> <span class="comment">//数组大小</span></div><div class="line"> <span class="keyword">this</span>.threshold = tableSizeFor(initialCapacity);</div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"> <span class="comment">//调整数组大小的方法,数组大小很有讲究,它必须是2的幂,这里JDK的工程师通过一个厉害的算法实现了找到大于或等于initialCapacity的最小幂。</span></div><div 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>{</div><div class="line"> <span class="keyword">int</span> n = cap - <span class="number">1</span>;</div><div class="line"> n |= n >>> <span class="number">1</span>;</div><div class="line"> n |= n >>> <span class="number">2</span>;</div><div class="line"> n |= n >>> <span class="number">4</span>;</div><div class="line"> n |= n >>> <span class="number">8</span>;</div><div class="line"> n |= n >>> <span class="number">16</span>;</div><div 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>;</div><div class="line"> }</div></pre></td></tr></table></figure></p><p>构造方法中有两个参数,构造方法中有两个参数,第一个initialCapacity定义map的数组大小,第二个loadFactor意为负载因子,它的作用就是当容器中存储的数据达到loadFactor限度以后,就开始扩容。如果不设定这样参数的话,loadFactor就等于默认值0.75。但是细心的你会发现,容器创建以后,并没有创建数组,原来table是在第一次被使用的时候才创建的,而这个时候threshold = initialCapacity * loadFactor。 这才是这个容器的真正的负载能力。<br>tableSizeFor这个方法的目的是找到大于或等于initialCapacity的最小的2的幂,这个算法写的非常妙,值得我们去分析一下:<br>假设cap = 7<br>第一步n = cap-1 = 6 = 00000110<br>第三步n|=n>>>2 => 00000111|0000000</p><p>第二步n|=n>>>1 => 00000110|00000011 = 000001111 = 00000111<br>第四步n|=n>>>4 => 00000111|00000000 = 00000111<br>第五步n|=n>>>8 => 00000111|00000000 = 00000111<br>第六步n|=n>>>16 => 00000111|00000000 = 00000111<br>最后 n+1 = 00001000 = 8<br>经检验8确实是大于等于7的最小的2的幂。<br>这个算法看上去很newbility,但是呢仔细看一下原理还是比较简单的,首先减一是因为如果cap本来就是2的幂的话,如果不减一会导致经过后面的操作后这个值变成原来的两倍,但是事实上这个cap本身就是2的幂。后面的几步位运算操作的功能是通过不断的高位补给低位,最后的值必定是00..0001111..111这个形式,最后加一后变成00..001000..00(2的幂次),这就是通过cap找到2的幂的方法,可以说是非常巧妙了。</p><h4 id="put添加元素"><a href="#put添加元素" class="headerlink" title="put添加元素"></a>put添加元素</h4><p>添加一个元素是所有容器中的标配功能,但是至于添加方式那就各有千秋,Map添加元素的方式是通过put,向容器中存入一个Key-Value对。下面将详细介绍put的实现过程,这个方法非常重要,吃透了这个方法的实现原理,基本也就能搞懂HashMap的基本原理了。关于put的源码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div></pre></td><td class="code"><pre><div class="line"> </div><div class="line"> <span class="comment">//获取key的hash值,这里将hash值得高16位右移和低16位做异或操作,目的是为了减少hash冲突,使hash值能均匀分布 </span></div><div 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>{</div><div class="line"> <span class="keyword">int</span> h;</div><div 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>);</div><div class="line"> }</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>{</div><div class="line"> <span class="keyword">return</span> putVal(hash(key), key, value, <span class="keyword">false</span>, <span class="keyword">true</span>);</div><div class="line"> }</div><div class="line"></div><div 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></div><div class="line"> <span class="keyword">boolean</span> evict) {</div><div class="line"> <span class="comment">//tab是存放数据的数组</span></div><div class="line"> Node<K,V>[] tab; Node<K,V> p; <span class="keyword">int</span> n, i;</div><div class="line"> <span class="comment">//如果是第一次添加元素,那么table是空的,首先创建一个指定大小的table.</span></div><div class="line"> <span class="keyword">if</span> ((tab = table) == <span class="keyword">null</span> || (n = tab.length) == <span class="number">0</span>)</div><div class="line"> n = (tab = resize()).length;</div><div class="line"> <span class="comment">//通过对hash于数组长度的操作,确定keyd对应的数组位置,如果该位置为空则创建一个一个节点</span></div><div class="line"> <span class="keyword">if</span> ((p = tab[i = (n - <span class="number">1</span>) & hash]) == <span class="keyword">null</span>)</div><div class="line"> tab[i] = newNode(hash, key, value, <span class="keyword">null</span>);</div><div class="line"> <span class="keyword">else</span> {</div><div class="line"> <span class="comment">//如果当前位置已经存在元素,那么就要逐个读取这条链表的元素</span></div><div class="line"> Node<K,V> e; K k;</div><div class="line"> <span class="comment">//如果key和hash值都等于当前头元素,那么这两个元素是相同的</span></div><div class="line"> <span class="keyword">if</span> (p.hash == hash &&</div><div class="line"> ((k = p.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</div><div class="line"> e = p;</div><div class="line"> <span class="comment">//如果当前位置的链表类型是treeNode,那么就将当前元素以红黑树的形式存放。</span></div><div class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (p <span class="keyword">instanceof</span> TreeNode)</div><div class="line"> e = ((TreeNode<K,V>)p).putTreeVal(<span class="keyword">this</span>, tab, hash, key, value);</div><div class="line"> <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> binCount = <span class="number">0</span>; ; ++binCount) {</div><div class="line"> <span class="comment">//遍历链表的所有元素,如果都未找到相同key的元素,那么说明这个元素并不在容器中存在,因此将他添加到链表尾部,并结束遍历</span></div><div class="line"> <span class="keyword">if</span> ((e = p.next) == <span class="keyword">null</span>) {</div><div class="line"> p.next = newNode(hash, key, value, <span class="keyword">null</span>);</div><div class="line"> <span class="keyword">if</span> (binCount >= TREEIFY_THRESHOLD - <span class="number">1</span>) <span class="comment">// -1 for 1st</span></div><div class="line"> treeifyBin(tab, hash);</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">//如果在遍历过程中,发现了相同的key值,结束遍历</span></div><div class="line"> <span class="keyword">if</span> (e.hash == hash &&</div><div class="line"> ((k = e.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> p = e;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="comment">// 如果e != null 说明在当前容器中,存在一个相同的key值,那么就要替换key所对应的value</span></div><div class="line"> <span class="keyword">if</span> (e != <span class="keyword">null</span>) { <span class="comment">// existing mapping for key</span></div><div class="line"> V oldValue = e.value;</div><div class="line"> <span class="keyword">if</span> (!onlyIfAbsent || oldValue == <span class="keyword">null</span>)</div><div class="line"> e.value = value;</div><div class="line"> <span class="comment">// 这是专门留给LinkedHashMap调用的回调函数,LinkedHashMap会实现这个方法。从这里可以看出,HashMap充分的考虑了他的扩展性。</span></div><div class="line"> afterNodeAccess(e);</div><div class="line"> <span class="keyword">return</span> oldValue;</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> ++modCount;</div><div class="line"> <span class="comment">// 这里判断当前元素的数量是否超过了容量的上限,如果超过了,就要重新进行扩容,并对当前元素重新hash,所以再次扩容以后的元素位置都是会改变的。</span></div><div class="line"> <span class="keyword">if</span> (++size > threshold)</div><div class="line"> resize();</div><div class="line"> <span class="comment">// 此方法也是HashMap留给LinkedHashMap实现的回调方法。透露一下,因为LinkedHashMap在插入元素以后,都会维护他的一个双向链表</span></div><div class="line"> afterNodeInsertion(evict);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> }</div></pre></td></tr></table></figure></p><h4 id="get获取元素"><a href="#get获取元素" class="headerlink" title="get获取元素"></a>get获取元素</h4><p>使用HashMap有一个明显的优点,就是他的存取时间开销基本维持在O(1),除非在数据量大了以后hash冲突的元素多了以后,对其性能有一定的影响。那么现在介绍的get方法很好的体现了这个优势。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>{</div><div class="line"> Node<K,V> e;</div><div class="line"> <span class="keyword">return</span> (e = getNode(hash(key), key)) == <span class="keyword">null</span> ? <span class="keyword">null</span> : e.value;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 同一个key的hash值是相同的,通过hash就可以求出数组的下标,便可以在O(1)的时间内获取元素。</span></div><div 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>{</div><div class="line"> Node<K,V>[] tab; Node<K,V> first, e; <span class="keyword">int</span> n; K k;</div><div class="line"> <span class="comment">// 在容器不为空,并且对应位置也存在元素的情况下,那么就要遍历链表,找到相同key值的元素。</span></div><div class="line"> <span class="keyword">if</span> ((tab = table) != <span class="keyword">null</span> && (n = tab.length) > <span class="number">0</span> &&</div><div class="line"> (first = tab[(n - <span class="number">1</span>) & hash]) != <span class="keyword">null</span>) {</div><div class="line"> <span class="comment">// 如果第一个元素的key值相同,那么这个元素就是我们要找的。</span></div><div class="line"> <span class="keyword">if</span> (first.hash == hash &&</div><div class="line"> ((k = first.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</div><div class="line"> <span class="keyword">return</span> first;</div><div class="line"> <span class="comment">// 如果第一个元素不是我们要找的,接下来就遍历链表元素,如果遍历完了以后都没找到,说明不存在这个key值</span></div><div class="line"> <span class="keyword">if</span> ((e = first.next) != <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">if</span> (first <span class="keyword">instanceof</span> TreeNode)</div><div class="line"> <span class="keyword">return</span> ((TreeNode<K,V>)first).getTreeNode(hash, key);</div><div class="line"> <span class="keyword">do</span> {</div><div class="line"> <span class="keyword">if</span> (e.hash == hash &&</div><div class="line"> ((k = e.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</div><div class="line"> <span class="keyword">return</span> e;</div><div class="line"> } <span class="keyword">while</span> ((e = e.next) != <span class="keyword">null</span>);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line">}</div></pre></td></tr></table></figure></p><h4 id="remove删除元素"><a href="#remove删除元素" class="headerlink" title="remove删除元素"></a>remove删除元素</h4><p>删除元素的实现原理和put,get都类似。remove通过给定的key值,找到在hash表中对应的位置,然后找出相同key值的元素,对其删除。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> V <span class="title">remove</span><span class="params">(Object key)</span> </span>{</div><div class="line"> Node<K,V> e;</div><div class="line"> <span class="keyword">return</span> (e = removeNode(hash(key), key, <span class="keyword">null</span>, <span class="keyword">false</span>, <span class="keyword">true</span>)) == <span class="keyword">null</span> ?</div><div class="line"> <span class="keyword">null</span> : e.value;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 通过key的hash值定位元素位置,并对其删除。这里的实现和put基本相同,我只在不同的地方做一下解释。</span></div><div class="line"><span class="function"><span class="keyword">final</span> Node<K,V> <span class="title">removeNode</span><span class="params">(<span class="keyword">int</span> hash, Object key, Object value,</span></span></div><div class="line"> <span class="keyword">boolean</span> matchValue, <span class="keyword">boolean</span> movable) {</div><div class="line"> Node<K,V>[] tab; Node<K,V> p; <span class="keyword">int</span> n, index;</div><div class="line"> <span class="keyword">if</span> ((tab = table) != <span class="keyword">null</span> && (n = tab.length) > <span class="number">0</span> &&</div><div class="line"> (p = tab[index = (n - <span class="number">1</span>) & hash]) != <span class="keyword">null</span>) {</div><div class="line"> Node<K,V> node = <span class="keyword">null</span>, e; K k; V v;</div><div class="line"> <span class="keyword">if</span> (p.hash == hash &&</div><div class="line"> ((k = p.key) == key || (key != <span class="keyword">null</span> && key.equals(k))))</div><div class="line"> node = p;</div><div class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((e = p.next) != <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">if</span> (p <span class="keyword">instanceof</span> TreeNode)</div><div class="line"> node = ((TreeNode<K,V>)p).getTreeNode(hash, key);</div><div class="line"> <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">do</span> {</div><div class="line"> <span class="keyword">if</span> (e.hash == hash &&</div><div class="line"> ((k = e.key) == key ||</div><div class="line"> (key != <span class="keyword">null</span> && key.equals(k)))) {</div><div class="line"> node = e;</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> }</div><div class="line"> p = e;</div><div class="line"> } <span class="keyword">while</span> ((e = e.next) != <span class="keyword">null</span>);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="comment">// 如果找到了相同的key,接下来就要判断matchValue参数,matchValue如果是true的话,就说明</span></div><div class="line"> <span class="comment">// 需要检查被删除的value是否相同,只有相同的情况下才能删除元素。如果matchValue是false的话</span></div><div class="line"> <span class="comment">// 就不需要判断value是否相同。</span></div><div class="line"> <span class="keyword">if</span> (node != <span class="keyword">null</span> && (!matchValue || (v = node.value) == value ||</div><div class="line"> (value != <span class="keyword">null</span> && value.equals(v)))) {</div><div class="line"> <span class="keyword">if</span> (node <span class="keyword">instanceof</span> TreeNode)</div><div class="line"> ((TreeNode<K,V>)node).removeTreeNode(<span class="keyword">this</span>, tab, movable);</div><div class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (node == p)</div><div class="line"> tab[index] = node.next;</div><div class="line"> <span class="keyword">else</span></div><div class="line"> p.next = node.next;</div><div class="line"> ++modCount;</div><div class="line"> --size;</div><div class="line"> afterNodeRemoval(node);</div><div class="line"> <span class="keyword">return</span> node;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line">}</div></pre></td></tr></table></figure></p><h4 id="resize动态扩容"><a href="#resize动态扩容" class="headerlink" title="resize动态扩容"></a>resize动态扩容</h4><p>resize这个方法非常重要,它在添加元素的时候就会被调用到。resize的目的是在容器的容量达到上限的时候,对其扩容,使得元素可以继续被添加进来。这里需要关注两个参数threshold和loadFactor,threshold表示容量的上限,当容器中元素数量大于threshold的时候,就要扩容,并且每次扩容都是原来的两倍。loadFactor表示hash表的数组大小。这两个参数的配合使用可以有效的控制hash冲突数量。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div><div class="line">84</div><div class="line">85</div><div class="line">86</div><div class="line">87</div><div class="line">88</div><div class="line">89</div><div class="line">90</div><div class="line">91</div><div class="line">92</div><div class="line">93</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">final</span> Node<K,V>[] resize() {</div><div class="line"> Node<K,V>[] oldTab = table;</div><div class="line"> <span class="keyword">int</span> oldCap = (oldTab == <span class="keyword">null</span>) ? <span class="number">0</span> : oldTab.length;</div><div class="line"> <span class="keyword">int</span> oldThr = threshold;</div><div class="line"> <span class="keyword">int</span> newCap, newThr = <span class="number">0</span>;</div><div class="line"> <span class="comment">// 如果容器并不是第一次扩容的话,那么oldCap必定会大于0</span></div><div class="line"> <span class="keyword">if</span> (oldCap > <span class="number">0</span>) {</div><div class="line"> <span class="keyword">if</span> (oldCap >= MAXIMUM_CAPACITY) {</div><div class="line"> threshold = Integer.MAX_VALUE;</div><div class="line"> <span class="keyword">return</span> oldTab;</div><div class="line"> }</div><div class="line"> <span class="comment">// threshold和数组大小cap共同扩大为原来的两倍</span></div><div class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ((newCap = oldCap << <span class="number">1</span>) < MAXIMUM_CAPACITY &&</div><div class="line"> oldCap >= DEFAULT_INITIAL_CAPACITY)</div><div class="line"> newThr = oldThr << <span class="number">1</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">// 第一次扩容,并且设定了threshold值。</span></div><div class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (oldThr > <span class="number">0</span>)</div><div class="line"> newCap = oldThr;</div><div class="line"> <span class="keyword">else</span> {</div><div class="line"> <span class="comment">// 如果在创建的时候并没有设置threshold值,那就用默认值</span></div><div class="line"> newCap = DEFAULT_INITIAL_CAPACITY;</div><div class="line"> newThr = (<span class="keyword">int</span>)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (newThr == <span class="number">0</span>) {</div><div class="line"> <span class="comment">// 第一次扩容的时候threshold = cap * loadFactor</span></div><div class="line"> <span class="keyword">float</span> ft = (<span class="keyword">float</span>)newCap * loadFactor;</div><div class="line"> newThr = (newCap < MAXIMUM_CAPACITY && ft < (<span class="keyword">float</span>)MAXIMUM_CAPACITY ?</div><div class="line"> (<span class="keyword">int</span>)ft : Integer.MAX_VALUE);</div><div class="line"> }</div><div class="line"> threshold = newThr;</div><div class="line"> <span class="comment">// 创建数组</span></div><div class="line"> <span class="meta">@SuppressWarnings</span>({<span class="string">"rawtypes"</span>,<span class="string">"unchecked"</span>})</div><div class="line"> Node<K,V>[] newTab = (Node<K,V>[])<span class="keyword">new</span> Node[newCap];</div><div class="line"> table = newTab;</div><div class="line"> <span class="comment">// 如果不是第一次扩容,那么hash表中必然存在数据,需要将这些数据重新hash</span></div><div class="line"> <span class="keyword">if</span> (oldTab != <span class="keyword">null</span>) {</div><div class="line"> <span class="comment">// 遍历所有元素</span></div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < oldCap; ++j) {</div><div class="line"> Node<K,V> e;</div><div class="line"> <span class="keyword">if</span> ((e = oldTab[j]) != <span class="keyword">null</span>) {</div><div class="line"> oldTab[j] = <span class="keyword">null</span>;</div><div class="line"> <span class="keyword">if</span> (e.next == <span class="keyword">null</span>)</div><div class="line"> <span class="comment">// 重新计算在数组中的位置。</span></div><div class="line"> newTab[e.hash & (newCap - <span class="number">1</span>)] = e;</div><div class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> TreeNode)</div><div class="line"> ((TreeNode<K,V>)e).split(<span class="keyword">this</span>, newTab, j, oldCap);</div><div class="line"> <span class="keyword">else</span> { <span class="comment">// preserve order</span></div><div class="line"> <span class="comment">// 这里分两串,lo表示原先位置的所有,hi表示新的索引</span></div><div class="line"> Node<K,V> loHead = <span class="keyword">null</span>, loTail = <span class="keyword">null</span>;</div><div class="line"> Node<K,V> hiHead = <span class="keyword">null</span>, hiTail = <span class="keyword">null</span>;</div><div class="line"> Node<K,V> next;</div><div class="line"> <span class="keyword">do</span> {</div><div class="line"> next = e.next;</div><div class="line"> <span class="comment">// 因为cap都是2的幂次,假设oldCap == 10000,</span></div><div class="line"> <span class="comment">// 假设e.hash= 01010 那么 e.hash & oldCap == 0。</span></div><div class="line"> <span class="comment">// 老位置= e.hash & oldCap-1 = 01010 & 01111 = 01010</span></div><div class="line"> <span class="comment">// newCap此时为100000,newCap-1=011111。</span></div><div class="line"> <span class="comment">// 此时e.hash & newCap 任然等于01010,位置不变。</span></div><div class="line"> <span class="comment">// 如果e.hash 假设为11010,那么 e.hash & oldCap != 0</span></div><div class="line"> <span class="comment">// 原来的位置为 e.hash & oldCap-1 = 01010</span></div><div class="line"> <span class="comment">// 新位置 e.hash & newCap-1 = 11010 & 011111 = 11010</span></div><div class="line"> <span class="comment">// 此时 新位置 != 老位置 新位置=老位置+oldCap</span></div><div class="line"> <span class="comment">// 因此这里分类两个索引的链表</span></div><div class="line"> <span class="keyword">if</span> ((e.hash & oldCap) == <span class="number">0</span>) {</div><div class="line"> <span class="keyword">if</span> (loTail == <span class="keyword">null</span>)</div><div class="line"> loHead = e;</div><div class="line"> <span class="keyword">else</span></div><div class="line"> loTail.next = e;</div><div class="line"> loTail = e;</div><div class="line"> }</div><div class="line"> <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">if</span> (hiTail == <span class="keyword">null</span>)</div><div class="line"> hiHead = e;</div><div class="line"> <span class="keyword">else</span></div><div class="line"> hiTail.next = e;</div><div class="line"> hiTail = e;</div><div class="line"> }</div><div class="line"> } <span class="keyword">while</span> ((e = next) != <span class="keyword">null</span>);</div><div class="line"> <span class="keyword">if</span> (loTail != <span class="keyword">null</span>) {</div><div class="line"> loTail.next = <span class="keyword">null</span>;</div><div class="line"> newTab[j] = loHead;</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (hiTail != <span class="keyword">null</span>) {</div><div class="line"> hiTail.next = <span class="keyword">null</span>;</div><div class="line"> newTab[j + oldCap] = hiHead;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> newTab;</div><div class="line">}</div></pre></td></tr></table></figure></p><h4 id="遍历"><a href="#遍历" class="headerlink" title="遍历"></a>遍历</h4><p>HashMap遍历有三种方式,一种是对key遍历,还有一种是对entry遍历和对value遍历。这三种遍历方式都是基于对HashIterator的封装,三种实现方式大同小异,因此我着重介绍EntryIterator的实现。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div><div class="line">84</div><div class="line">85</div><div class="line">86</div><div class="line">87</div><div class="line">88</div><div class="line">89</div><div class="line">90</div><div class="line">91</div><div class="line">92</div><div class="line">93</div><div class="line">94</div><div class="line">95</div><div class="line">96</div><div class="line">97</div><div class="line">98</div><div class="line">99</div><div class="line">100</div><div class="line">101</div><div class="line">102</div><div class="line">103</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 对HashMap元素进行遍历。</span></div><div class="line"><span class="keyword">public</span> Set<Map.Entry<K,V>> entrySet() {</div><div class="line"> Set<Map.Entry<K,V>> es;</div><div class="line"> <span class="comment">// 第一次遍历的时候,实例化entrySet。</span></div><div class="line"> <span class="keyword">return</span> (es = entrySet) == <span class="keyword">null</span> ? (entrySet = <span class="keyword">new</span> EntrySet()) : es;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">EntrySet</span> <span class="keyword">extends</span> <span class="title">AbstractSet</span><<span class="title">Map</span>.<span class="title">Entry</span><<span class="title">K</span>,<span class="title">V</span>>> </span>{</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">size</span><span class="params">()</span> </span>{ <span class="keyword">return</span> size; }</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">clear</span><span class="params">()</span> </span>{ HashMap.<span class="keyword">this</span>.clear(); }</div><div class="line"> <span class="keyword">public</span> <span class="keyword">final</span> Iterator<Map.Entry<K,V>> iterator() {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> EntryIterator();</div><div class="line"> }</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">contains</span><span class="params">(Object o)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> Map.Entry))</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> Map.Entry<?,?> e = (Map.Entry<?,?>) o;</div><div class="line"> Object key = e.getKey();</div><div class="line"> Node<K,V> candidate = getNode(hash(key), key);</div><div class="line"> <span class="keyword">return</span> candidate != <span class="keyword">null</span> && candidate.equals(e);</div><div class="line"> }</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">remove</span><span class="params">(Object o)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (o <span class="keyword">instanceof</span> Map.Entry) {</div><div class="line"> Map.Entry<?,?> e = (Map.Entry<?,?>) o;</div><div class="line"> Object key = e.getKey();</div><div class="line"> Object value = e.getValue();</div><div class="line"> <span class="keyword">return</span> removeNode(hash(key), key, value, <span class="keyword">true</span>, <span class="keyword">true</span>) != <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">public</span> <span class="keyword">final</span> Spliterator<Map.Entry<K,V>> spliterator() {</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> EntrySpliterator<>(HashMap.<span class="keyword">this</span>, <span class="number">0</span>, -<span class="number">1</span>, <span class="number">0</span>, <span class="number">0</span>);</div><div class="line"> }</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">forEach</span><span class="params">(Consumer<? <span class="keyword">super</span> Map.Entry<K,V>> action)</span> </span>{</div><div class="line"> Node<K,V>[] tab;</div><div class="line"> <span class="keyword">if</span> (action == <span class="keyword">null</span>)</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</div><div class="line"> <span class="keyword">if</span> (size > <span class="number">0</span> && (tab = table) != <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">int</span> mc = modCount;</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < tab.length; ++i) {</div><div class="line"> <span class="keyword">for</span> (Node<K,V> e = tab[i]; e != <span class="keyword">null</span>; e = e.next)</div><div class="line"> action.accept(e);</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (modCount != mc)</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ConcurrentModificationException();</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">EntryIterator</span> <span class="keyword">extends</span> <span class="title">HashIterator</span></span></div><div class="line"> <span class="keyword">implements</span> <span class="title">Iterator</span><<span class="title">Map</span>.<span class="title">Entry</span><<span class="title">K</span>,<span class="title">V</span>>> {</div><div class="line"> <span class="keyword">public</span> <span class="keyword">final</span> Map.<span class="function">Entry<K,V> <span class="title">next</span><span class="params">()</span> </span>{ <span class="keyword">return</span> nextNode(); }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// HashMap自己实现的遍历方法。上面的所有方法都是围绕这个类展开的。下面具体讲解这个类的实现原理。</span></div><div class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">HashIterator</span> </span>{</div><div class="line"> Node<K,V> next; <span class="comment">// 指向下一个元素</span></div><div class="line"> Node<K,V> current; <span class="comment">// 指向当前元素</span></div><div class="line"> <span class="keyword">int</span> expectedModCount;</div><div class="line"> <span class="keyword">int</span> index; <span class="comment">// 当前元素位置</span></div><div class="line"></div><div class="line"> HashIterator() {</div><div class="line"> expectedModCount = modCount;</div><div class="line"> Node<K,V>[] t = table;</div><div class="line"> current = next = <span class="keyword">null</span>;</div><div class="line"> index = <span class="number">0</span>;</div><div class="line"> <span class="keyword">if</span> (t != <span class="keyword">null</span> && size > <span class="number">0</span>) { <span class="comment">// 找到table中的第一个元素</span></div><div class="line"> <span class="keyword">do</span> {} <span class="keyword">while</span> (index < t.length && (next = t[index++]) == <span class="keyword">null</span>);</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">hasNext</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> next != <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">final</span> Node<K,V> <span class="title">nextNode</span><span class="params">()</span> </span>{</div><div class="line"> Node<K,V>[] t;</div><div class="line"> Node<K,V> e = next;</div><div class="line"> <span class="keyword">if</span> (modCount != expectedModCount)</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ConcurrentModificationException();</div><div class="line"> <span class="keyword">if</span> (e == <span class="keyword">null</span>)</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NoSuchElementException();</div><div class="line"> <span class="comment">// 判断当前元素是否为链表中的最后一个元素,如果在链表尾部,那么就需要重新遍历table,</span></div><div class="line"> <span class="comment">// 顺序找到下元素的位置。</span></div><div class="line"> <span class="keyword">if</span> ((next = (current = e).next) == <span class="keyword">null</span> && (t = table) != <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">do</span> {} <span class="keyword">while</span> (index < t.length && (next = t[index++]) == <span class="keyword">null</span>);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> e;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">// 删除当前遍历的元素。</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">remove</span><span class="params">()</span> </span>{</div><div class="line"> Node<K,V> p = current;</div><div class="line"> <span class="keyword">if</span> (p == <span class="keyword">null</span>)</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException();</div><div class="line"> <span class="keyword">if</span> (modCount != expectedModCount)</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ConcurrentModificationException();</div><div class="line"> current = <span class="keyword">null</span>;</div><div class="line"> K key = p.key;</div><div class="line"> removeNode(hash(key), key, <span class="keyword">null</span>, <span class="keyword">false</span>, <span class="keyword">false</span>);</div><div class="line"> expectedModCount = modCount;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p><p>总结一下这个遍历的过程是 EntrySet -> EntryIterator -> HashIterator。同理对key的遍历过程就是 KeySet -> KeyIterator -> HashIterator。可以看出来不管是哪种遍历,最终都是调用了HashIterator。</p><p><a href="https://github.com/vzardlloo/jdk_source_learning" target="_blank" rel="external">欢迎参与Java源码分析的开源项目</a></p>]]></content>
<summary type="html">
<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>这篇博客讲一下JAVA集合类中的HashMap。HashMap底层是通过维护一个数组来保存元素。当创建HashMap实例的时候,会通过指定的数组大小以及负载因子等参数创建一个空的数组,当在容器中添加元素的时候,首先会通过hash算法求得key的hash值,再根据hash值确定元素在数组中对应的位置,最后将元素放入数组中的对应位置。这里我们需要考虑的一个问题是hash冲突问题,即两个元素key的hash值一样,这就要求我们一方面hash算法要足够”发散”来避免这种情况,另一方面我们也要采取措施来解决这种冲突,在HashMap中采取的方法是链地址法,即当两个元素的key的hash值是一样的,首先判断key值是否是一样的,如果是一样的则替换value值。如果key的值不一样,则把后面添加的元素链接到之前添加的值的后面,形成一个链表。所以HashMap的数据结构实际是:hash表+单向链表。通过链表的形式把所有冲突元素放到了数组同一个位置,但是又引出另一个问题就是当链表过长时会影响HashMap的存取效率,因为在数组长度固定的情况下,存储的数据越多,hash冲突是不可避免的,那么控制hash冲突可以通过扩容来解决,在HashMap中有个负载因子(loadFactor)的概念。HashMap允许实际存储的元素的个数size = loadFactor*数组长度,一旦容器元素超出了这个size,HashMap就会自动扩容,并对所有元素重新执行hash操作,重新调整位置。最后说明本文基于Oracle JDK 1.8。<br>
</summary>
<category term="JAVA" scheme="http://vzardlloo.github.io/categories/JAVA/"/>
<category term="JAVA" scheme="http://vzardlloo.github.io/tags/JAVA/"/>
</entry>
<entry>
<title>Java中的锁</title>
<link href="http://vzardlloo.github.io/2017/09/07/Java%E4%B8%AD%E7%9A%84%E9%94%81/"/>
<id>http://vzardlloo.github.io/2017/09/07/Java%E4%B8%AD%E7%9A%84%E9%94%81/</id>
<published>2017-09-07T12:04:42.000Z</published>
<updated>2017-09-08T03:39:46.029Z</updated>
<content type="html"><![CDATA[<p>这篇博客主要讲一下Java中的各种各样五花八门的锁。这些五花八门的锁肯定不是工程师没事做闲着无聊倒腾出来的,他们都是为了在某些环境下的性能而对传统的锁做了些改造。</p><h4 id="公平锁和非公平锁"><a href="#公平锁和非公平锁" class="headerlink" title="公平锁和非公平锁"></a>公平锁和非公平锁</h4><p>公平锁是指当多个线程在等待同一锁时,必须按照申请锁的先后顺序排成一个等待队列依次来一次获得锁。<br>非公平锁是指加锁时不用考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待,是抢占式的。<br>对比:非公平锁的性能比公平锁搞5~10倍,因为公平锁需要在多核的情况下维护一个队列。但是非公平锁有可能会导致饥饿问题。</p><h4 id="自旋锁"><a href="#自旋锁" class="headerlink" title="自旋锁"></a>自旋锁</h4><p>由于Java的线程是映射到操作系统的原生线程上的,如果要阻塞一个线程或者唤醒一个线程,都需要操作系统来帮忙完成,需要操作系统从用户态转换到核心态。因此状态转换需要耗费很多处理器时间。同时虚拟机的开发团队注意到许多应用上共享数据的锁定状态只会持续很短的一段时间,如果一个线程只是很短暂的占用一个共享资源,当在它短暂占用这段资源的时候如果有别的线程试图占用这个资源的时候,后来的线程会因为该资源被占用而陷入阻塞状态,丢失对时间片的占用,这会导致资源的大量浪费。所以针对这种情况开发团队设计出了自旋锁,即如果物理机器有一个以上的处理器,可以让两个或两个以上的线程同时并行执行,我们就可以让后面的线程“稍等一下”,但是不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),等待锁被释放。<br>但是自旋也不可以替代锁,自旋虽然避免了线程切换的开销,但是它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果会非常好。如果锁被占用的时间很长。那么自旋只会白白占用处理器的资源。因此自旋等待时间必须要有一定限制,如果自旋超过一定次数(默认是10次,使用-XX:PreBlockSpin来更改)没有获得锁,就应该使用传统的方式去挂起线程了。需要注意的是自旋是在轻量级锁中使用的,在重量级锁中,线程不能用自旋锁。</p><a id="more"></a><h4 id="偏向锁"><a href="#偏向锁" class="headerlink" title="偏向锁"></a>偏向锁</h4><p>偏向锁是JDK6中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。</p><p>偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。</p><h4 id="悲观锁和乐观锁"><a href="#悲观锁和乐观锁" class="headerlink" title="悲观锁和乐观锁"></a>悲观锁和乐观锁</h4><p>悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。<br>乐观锁:假定不会发生并发冲突,只在提交操作时检测是否违反数据完整性。(使用版本号或者时间戳来配合实现)</p><h4 id="共享锁和排它锁"><a href="#共享锁和排它锁" class="headerlink" title="共享锁和排它锁"></a>共享锁和排它锁</h4><p>共享锁:如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁。获准共享锁的事务只能读数据,不能修改数据。<br>排它锁:如果事务T对数据A加上排它锁后,则其他事务不能再对A加任何类型的锁。获得排它锁的事务即能读数据又能修改数据</p><h4 id="读写锁"><a href="#读写锁" class="headerlink" title="读写锁"></a>读写锁</h4><p>读写锁是一个资源能够被多个读线程访问,或者被一个写线程访问但不能同时存在读线程。Java当中的读写锁通过ReentrantReadWriteLock实现。</p><p>还有其他的一些锁这里不一一列举。</p>]]></content>
<summary type="html">
<p>这篇博客主要讲一下Java中的各种各样五花八门的锁。这些五花八门的锁肯定不是工程师没事做闲着无聊倒腾出来的,他们都是为了在某些环境下的性能而对传统的锁做了些改造。</p>
<h4 id="公平锁和非公平锁"><a href="#公平锁和非公平锁" class="headerlink" title="公平锁和非公平锁"></a>公平锁和非公平锁</h4><p>公平锁是指当多个线程在等待同一锁时,必须按照申请锁的先后顺序排成一个等待队列依次来一次获得锁。<br>非公平锁是指加锁时不用考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待,是抢占式的。<br>对比:非公平锁的性能比公平锁搞5~10倍,因为公平锁需要在多核的情况下维护一个队列。但是非公平锁有可能会导致饥饿问题。</p>
<h4 id="自旋锁"><a href="#自旋锁" class="headerlink" title="自旋锁"></a>自旋锁</h4><p>由于Java的线程是映射到操作系统的原生线程上的,如果要阻塞一个线程或者唤醒一个线程,都需要操作系统来帮忙完成,需要操作系统从用户态转换到核心态。因此状态转换需要耗费很多处理器时间。同时虚拟机的开发团队注意到许多应用上共享数据的锁定状态只会持续很短的一段时间,如果一个线程只是很短暂的占用一个共享资源,当在它短暂占用这段资源的时候如果有别的线程试图占用这个资源的时候,后来的线程会因为该资源被占用而陷入阻塞状态,丢失对时间片的占用,这会导致资源的大量浪费。所以针对这种情况开发团队设计出了自旋锁,即如果物理机器有一个以上的处理器,可以让两个或两个以上的线程同时并行执行,我们就可以让后面的线程“稍等一下”,但是不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),等待锁被释放。<br>但是自旋也不可以替代锁,自旋虽然避免了线程切换的开销,但是它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果会非常好。如果锁被占用的时间很长。那么自旋只会白白占用处理器的资源。因此自旋等待时间必须要有一定限制,如果自旋超过一定次数(默认是10次,使用-XX:PreBlockSpin来更改)没有获得锁,就应该使用传统的方式去挂起线程了。需要注意的是自旋是在轻量级锁中使用的,在重量级锁中,线程不能用自旋锁。</p>
</summary>
<category term="JAVA" scheme="http://vzardlloo.github.io/categories/JAVA/"/>
<category term="Lock" scheme="http://vzardlloo.github.io/tags/Lock/"/>
</entry>
<entry>
<title>关于volatile</title>
<link href="http://vzardlloo.github.io/2017/08/29/%E5%85%B3%E4%BA%8Evolatile/"/>
<id>http://vzardlloo.github.io/2017/08/29/%E5%85%B3%E4%BA%8Evolatile/</id>
<published>2017-08-29T14:21:44.000Z</published>
<updated>2017-08-31T09:37:05.672Z</updated>
<content type="html"><![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>volatile关键字可以说是JAVA比较难理解的一个关键字了,很多书感觉讲的都不太清楚。这篇博客主要梳理一下它的含义,是对自己学习的一个总结,参考了不少资料和博客,希望可以到帮助别人。本文的主要讲一下下面几件事:</p><ul><li>JAVA内存模型简介</li><li>volatile的语义:<code>可见性</code>、<code>禁止重排序</code>。</li><li>为什么volatile不能保证一致性。</li><li>volatile的应用场景举例。</li></ul><h4 id="java内存模型简介"><a href="#JAVA内存模型简介" class="headerlink" title="JAVA内存模型简介"></a>JAVA内存模型简介</h4><p>这块知识必须要对JAVA内存模型有个基本的认识,所以先简单讲一下JAVA内存模型。<br><img src="http://oo3aq3ac8.bkt.clouddn.com/jmm.png" alt=""><br><a id="more"></a><br>Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,线程的工作内存中保存了被该线程所使用到的变量(从主内存中拷贝而来)。线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。由于这种内存模型结构,在多线程情况下会产生很多问题。比如执行如下代码:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">i = 8</div></pre></td></tr></table></figure></p><p>程序必须先在自己的工作内存中把i进行赋值,然后再将i的值写入到主存中。在这个过程中如果有两个线程A,B均对i进行自增操作,期望得到的值是10,在没有同步机制的情况下可能会有如下的情形发生:A和B同时从主存中读取i,然后分别在自己的工作内存中进行自增操作,然后先后写回主存,则此时主存中的值为9,与我们预期不符。</p><h4 id="volatile的语义"><a href="#volatile的语义" class="headerlink" title="volatile的语义"></a>volatile的语义</h4><h5 id="可见性"><a href="#可见性" class="headerlink" title="可见性"></a>可见性</h5><p>可见性的含义是指:一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。还是之前那个例子:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">i = 8</div></pre></td></tr></table></figure></p><p>如果i用volatile修饰的话,当有一个线程在主存中读取i,并在自己的工作内存中进行修改的时候,修改后的值会立即强制同步到主存中,并且其他线程中这个值的缓存也都无效。相比之下普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。<br>原理:如果对声明了volatile变量进行写操作时,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写会到系统内存。 这一步确保了如果有其他线程对声明了volatile变量进行修改,则立即更新主内存中数据。<br>但这时候其他处理器的缓存还是旧的,所以在多处理器环境下,为了保证各个处理器缓存一致,每个处理会通过嗅探在总线上传播的数据来检查 自己的缓存是否过期,当处理器发现自己缓存行对应的内存地址被修改了,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作时,会强制重新从系统内存把数据读到处理器缓存里。 这一步确保了其他线程获得的声明了volatile变量都是从主内存中获取最新的。</p><h5 id="禁止重排序"><a href="#禁止重排序" class="headerlink" title="禁止重排序"></a>禁止重排序</h5><p>重排序的含义是:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。这样听着比较拗口,举个例子吧:比如我们要计算<code>23+36+17+44</code>的值,灵活一点的人肯定会这么算<code>(23+17)+(36+44)-->40+80 = 120</code>,而不会按照顺序来算,应为这样进行重排序之后更加便于人脑计算,并且变换顺序之后最终的结果和顺序计算的结果是一致的。总之,重排序就是在保证最后结果一样的情况下,为了处理器的运行效率而对代码执行顺序进行优化的一种操作。<br>volatile禁止指令重排序的含义:</p><ul><li>当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行</li><li><p>在进行指令优化时,不能将在对volatile变量的读操作或者写操作的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。<br>举例说明:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//x,y为普通变量</span></div><div class="line"><span class="comment">//volflag被volatile修饰</span></div><div class="line"></div><div class="line"></div><div class="line">x = <span class="number">10</span>; <span class="comment">//语句1</span></div><div class="line">y = <span class="number">3</span>; <span class="comment">//语句2</span></div><div class="line">volflag = <span class="keyword">true</span>; <span class="comment">//语句3</span></div><div class="line">x= <span class="number">5</span>; <span class="comment">//语句4</span></div><div class="line">y = <span class="number">9</span>; <span class="comment">//语句5</span></div></pre></td></tr></table></figure></li></ul><p>这个例子中,由于volflag被volatile修饰,所以语句3不会被重排到语句1、语句2前面,也不会被重排到语句4、语句5的后面,但语句1、2和语句4、5的顺序是不能保证的。<br>另外volatile可以保证在执行到语句3的时候语句1、2是执行完毕的,语句4、5是没有执行的,并且语句1、2的执行结果是对语句4、5是可见的。<br>原理:Lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。</p><h4 id="为什么volatile不能保证一致性"><a href="#为什么volatile不能保证一致性?" class="headerlink" title="为什么volatile不能保证一致性?"></a>为什么volatile不能保证一致性?</h4><p>首先,我们要知道保证一致性要满足三个条件:原子性,有序性,可见性。</p><ul><li>原子性:即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。</li><li>可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。</li><li>有序性:即程序执行的顺序按照代码的先后顺序执行。<br>前面已经讲了volatile的可见性和有序性,但它不能保证原子性。下面设想一个情景:<br>假如某个时刻变量i的值为10</li></ul><p>线程1对变量进行自增操作,线程1先读取了变量i的原始值,然后线程1被阻塞了;<br>然后线程2对变量进行自增操作,线程2也去读取变量i的原始值,由于线程1只是对变量i进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量i的缓存行无效,也不会导致主存中的值刷新,所以线程2会直接去主存读取inc的值,发现i的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。<br>然后线程1接着进行加1操作,由于已经读取了i的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后i的值为11,然后将11写入工作内存,最后写入主存。<br>那么两个线程分别进行了一次自增操作后,i只增加了1。</p><p>可以看出,volatile变量是比锁弱一级的同步机制。当一个线程获取锁之后,别的线程就不能对其进行读取,修改等任何操作,但是获取一个volatile变量之后,只会让该线程对改变量的任何修改对其他线程都可见,但无法阻止其他线程对该变量的执行读取、修改等操作。锁和volatile的对比就好比中国和联合国,有人想干涉中国内政,中国可以发表声明并且同时使用武装力量强制抵挡入侵,但如果是联合国遇到干涉内政的问题就只能发表发表声明了。</p><h4 id="volatile的应用场景举例"><a href="#volatile的应用场景举例" class="headerlink" title="volatile的应用场景举例"></a>volatile的应用场景举例</h4><p>单例模式中的double check:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">Singleton</span></span>{</div><div class="line"> <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">static</span> Singleton instance = <span class="keyword">null</span>;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">private</span> <span class="title">Singleton</span><span class="params">()</span> </span>{</div><div class="line"> </div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title">getInstance</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">if</span>(instance==<span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">synchronized</span> (Singleton.class) {</div><div class="line"> <span class="keyword">if</span>(instance==<span class="keyword">null</span>)</div><div class="line"> instance = <span class="keyword">new</span> Singleton();</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> instance;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p><p>为什么要使用volatile 修饰instance?<br>主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情:</p><p>1.给 instance 分配内存</p><p>2.调用 Singleton 的构造函数来初始化成员变量</p><p>3.将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)。</p><p>但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回instance,然后使用,然后理所当然地报错。</p><p><a href="https://vcfvct.wordpress.com/2017/04/10/java-volatile-%E5%8F%AF%E8%A7%81%E8%A1%8Cvisibility%EF%BC%8C%E5%8E%9F%E5%AD%90%E6%80%A7atomicity%EF%BC%8C%E6%9C%89%E5%BA%8F%E6%80%A7ordering/#comment-1824" target="_blank" rel="external">参考资料</a></p>]]></content>
<summary type="html">
<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>volatile关键字可以说是JAVA比较难理解的一个关键字了,很多书感觉讲的都不太清楚。这篇博客主要梳理一下它的含义,是对自己学习的一个总结,参考了不少资料和博客,希望可以到帮助别人。本文的主要讲一下下面几件事:</p>
<ul>
<li>JAVA内存模型简介</li>
<li>volatile的语义:<code>可见性</code>、<code>禁止重排序</code>。</li>
<li>为什么volatile不能保证一致性。</li>
<li>volatile的应用场景举例。</li>
</ul>
<h4 id="JAVA内存模型简介"><a href="#JAVA内存模型简介" class="headerlink" title="JAVA内存模型简介"></a>JAVA内存模型简介</h4><p>这块知识必须要对JAVA内存模型有个基本的认识,所以先简单讲一下JAVA内存模型。<br><img src="http://oo3aq3ac8.bkt.clouddn.com/jmm.png" alt=""><br>
</summary>
<category term="JAVA" scheme="http://vzardlloo.github.io/categories/JAVA/"/>
<category term="JAVA" scheme="http://vzardlloo.github.io/tags/JAVA/"/>
</entry>
<entry>
<title>修复hexo博客的一个bug</title>
<link href="http://vzardlloo.github.io/2017/08/25/%E4%BF%AE%E5%A4%8Dhexo%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%B8%AAbug/"/>
<id>http://vzardlloo.github.io/2017/08/25/%E4%BF%AE%E5%A4%8Dhexo%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%B8%AAbug/</id>
<published>2017-08-25T10:18:37.000Z</published>
<updated>2017-08-26T00:28:34.644Z</updated>
<content type="html"><![CDATA[<p>之前不小心把JAVA分类写成了java发布了,然后又改了回来,并且手动地在博客<code>public/categories/</code>和<code>.deploy_git/categories/</code>下的java文件夹改成JAVA重新发布,结果在点击JAVA分类时会报404。经过一番研究我发现虽然我本地都将java改成了JAVA,但是GitHub仓库里的文件夹名称还是java。对比“java”和“JAVA”我猜测可能是git大小写不敏感所以JAVA并没有覆盖掉java,谷歌一下果然如此。由于我把java改成了JAVA所以在网页点击”分类”时访问的是:<code>https://xxxxxxxx/JAVA/</code>,而GitHub仓库里依然是java,只能访问<code>https://xxxxxxxxxx/java/</code>,由于http协议对于URL是大小写敏感的,所以访问<code>https://xxxxxxxx/JAVA/</code>必然会报404。找到问题所在下面开始修复bug:</p><ol><li>进入.deploy_git文件夹,输入<code>git config core.ignorecase false</code>把忽略大小写<br>关闭。(必须要进入.deploy_git执行命令,因为只有这个文件夹是git仓库,这里的文件才会推送到GitHub仓库)</li><li>回到博客根目录,执行<code>hexo d</code>重新发布博客</li></ol><p>执行完毕,bug修复!</p>]]></content>
<summary type="html">
<p>之前不小心把JAVA分类写成了java发布了,然后又改了回来,并且手动地在博客<code>public/categories/</code>和<code>.deploy_git/categories/</code>下的java文件夹改成JAVA重新发布,结果在点击JAVA分
</summary>
<category term="hexo" scheme="http://vzardlloo.github.io/categories/hexo/"/>
<category term="hexo" scheme="http://vzardlloo.github.io/tags/hexo/"/>
</entry>
</feed>