-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
692 lines (678 loc) · 56.5 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Android点击事件处理]]></title>
<url>http://yoursite.com/2016/05/13/%E7%82%B9%E5%87%BB%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86/</url>
<content type="html"><![CDATA[<p>该文章是我在极客学院Wiki学习时整理所得.<br><a id="more"></a></p>
<h3 id="安卓系统点击事件处理"><a href="#安卓系统点击事件处理" class="headerlink" title="安卓系统点击事件处理"></a>安卓系统点击事件处理</h3><ul>
<li>用户的点击事件均被包装为MotionEvent</li>
<li>MotionEvent描述了用户的行为<ul>
<li>ACTION_DOWN</li>
<li>ACTION_UP</li>
<li>ACTION_MOVE</li>
<li>ACTION_POINTER_DOWN</li>
<li>ACTION_POINTER_UP</li>
<li>ACTION_CANCEL</li>
<li>使用MotionEventCompat.getActionMasker(ev)获取MotionEvent对应的action</li>
</ul>
</li>
<li>MotionEvent还包括以下信息<ul>
<li>点击的位置(x,y坐标)</li>
<li>触点的数量(手指)</li>
<li>事件发生的时间戳</li>
</ul>
</li>
<li>任何一个手势,都是以ACTION_DOWN起始,ACTION_UP结束</li>
<li>事件从Activity的dispatchTouchEvent()函数开始,沿着View层次树依次向下传递<ul>
<li>父元素把事件dispatch到子元素</li>
<li>事件能在任意阶段被intercept</li>
</ul>
</li>
<li>事件会沿着View的层次树依次向下传递,然后有反向向上传递,知道被”消费”<ul>
<li>View如果对手势感兴趣,就必须消费掉ACTION_DOWN的事件</li>
<li>出于性能的考虑,同一手势的后续事件将不会按照完整的路径进行传递,而是直接传递到消费了ACTION_DOWN事件的View</li>
<li>如果所有的View(ViewGroup)都没有消费掉事件,那它将传递到Activity的onTouchEvent()函数中,并结束传递过程,即如果没有被消费,也不会在继续传递了</li>
</ul>
</li>
<li>可选的OnTouchListener能在任一View(ViewGroup)上intercept事件,事件被intercept之后,后面的调用将被传入ACTION_CANCEL?啥意思??</li>
<li>Activity.dispatchTouchEvent()<ul>
<li>总是首先被调用</li>
<li>Sends event to root view attached to Window</li>
<li>如果所有的View(ViewGroup)都没有消费该事件,那么Activity.onTouchEvent()将被调用,而且这个函数是最后一个被调用的函数</li>
</ul>
</li>
<li>ViewGroup.dispatchTouchEvent()<ul>
<li>首先调用onInterceptTouchEvent()函数,判断是否需要拦截<ul>
<li>检查是否应该替代自View的处理</li>
<li>Passes ACTION_CANCEL to active child</li>
<li>如果要消费掉同一手势的所有后续事件,需要返回true</li>
</ul>
</li>
<li>对所有的孩子,以添加顺序的逆序进行遍历<ul>
<li>如果点击在孩子的边界内,则调用child.dispatchTouchEvent()</li>
<li>如果没有被当前的孩子消费,则传递到下一个孩子</li>
</ul>
</li>
<li>如果所有的孩子都未消费该事件,则传递给listener,OnTouchListener.onTouch()</li>
<li>如果没有listener,或者listener也未消费,则自己处理,调用ViewGroup.onTouchEvent()</li>
<li>Intercepted events jump over child step</li>
</ul>
</li>
<li>View.dispatchTouchEvent()<ul>
<li>如果被设置了OnTouchListener,那么将先把事件发送到listener,调用View.OnTouchListener.onTouch()</li>
<li>如果listener没有消费事件,将调用View.onTouchEvent(),即自己处理点击事件</li>
</ul>
</li>
<li>小结<ul>
<li>手势以ACTION_DOWN起始,以ACTION_UP结束</li>
<li>ACTION_DOWN,在每一层View上都会调用dispatchTouchEvent(),该View会判断是否对接下来的手势感兴趣,后续的点击事件将直接传递到感兴趣的View</li>
<li>ViewGroup可以intercept一个手势,因为onInterceptTouchEvent()是在dispatchTouchEvent()函数中最先被调用的,如果onInterceptTouchEvent()返回true,它的孩子将不会收到该手势的后续事件</li>
</ul>
</li>
</ul>
<h3 id="自定义点击事件处理"><a href="#自定义点击事件处理" class="headerlink" title="自定义点击事件处理"></a>自定义点击事件处理</h3><ul>
<li>途径<ul>
<li>(View/ViewGroup子类,Target)重载onTouchEvent()函数</li>
<li>为Target设置OnTouchListener</li>
</ul>
</li>
<li>消费事件(onTouchEvent())<ul>
<li>ACTION_DOWN:如果对手势感兴趣,那么ACTION_DOWN的event就要返回true,即便对于ACTION_DOWN不感兴趣</li>
<li>后续的事件,同样返回true,结束事件的处理流程(不会再传递给其他view或者parent view)</li>
</ul>
</li>
<li>ViewConfiguration的一些有用方法:<ul>
<li>getScaledTouchSlop():判断一个移动距离是否为drag</li>
<li>getScaledMinimumFlingVelocity():判断一个拖拽速度是否为fling</li>
<li>getLongPressTimeOut():判断一个touch时间段是否为long press</li>
</ul>
</li>
<li>传递点击事件:调用target的dispatchTouchEvent(),不要直接调用target的onTouchEvent()</li>
<li>ViewGroup拦截点击事件<ul>
<li>重载onInterceptTouchEvent()</li>
<li>如果对当前的手势感兴趣,onInterceptTouchEvent()返回true,之后的点击事件将不再经过onInterceptTouchEvent()函数</li>
<li>其他的target(之前消费事件的View/ViewGroup)将收到ACTION_CANCEL</li>
</ul>
</li>
<li>一些建议/警告<ul>
<li>尽量调用super的对应方法,父类中已经做了很多基础工作了</li>
<li>ACTION_MOVE的处理中,检查移动距离是否超过slop(getScaledTouchSlop())</li>
<li>处理ACTION_CANCEL事件,父View可能会拦截事件,ACTION_CANCEL后需要重置状态,且之后该手势将不会再收到任何事件</li>
<li>intercept之后,该手势之后的所有事件都将被拦截,所以不要轻易拦截</li>
</ul>
</li>
<li>多触点事件响应<ul>
<li>MotionEvent.getPointerCount():获取当前屏幕上的触点数量</li>
<li>ACTION_POINTER_DOWN,ACTION_POINTER_UP用来响应次触点的事件,MotionEvent.getActionMasked(),MotionEvent.getActionIndex()</li>
<li>MotionEvent的有些方法会有两个版本,带index参数的,用于获取第index个触点的数据;不带参数的,获取主触点(第一个触点)的数据</li>
</ul>
</li>
<li>批量处理<ul>
<li>出于效率的考虑,ACTION_MOVE可以被打包到一个MotionEvent进行处理</li>
<li>最近一次(本次)事件的信息,通过标准的方法获取:getX(),getY(),getEventTime()</li>
<li>本次和最早一次ACTION_MOVE的信息,通过相应historical的方法获取<ul>
<li>getHistorySize()获取打包的数量</li>
<li>getHistorical*(pos)获取第一个触点的第pos个历史事件的信息</li>
<li>getHistorical*(index,pos)获取第index个触点的第pos个历史事件的信息</li>
</ul>
</li>
<li>Can reconstruct all events as they occurred in time for maximum precision</li>
</ul>
</li>
<li>System Touch Handlers<ul>
<li>不要首先就考虑使用自定义的事件处理方式</li>
<li>OnClickListener</li>
<li>OnLongClickListener</li>
<li>OnTouchListener<ul>
<li>监听每一个MotionEvent,而不需要编写子类</li>
<li>可以在Listener中消费事件</li>
<li>view的onTouchEvent处理中,优先调用的是listener的处理函数</li>
</ul>
</li>
<li>OnScrollListener / View.onScrollChanged()</li>
<li>GestureDetector<ul>
<li>onDown(),onSingleTapUP(),onDoubleTap()</li>
<li>onLongPress()</li>
<li>onScroll()(缓慢滚动)</li>
<li>onFLing()(快速滚动后释放手机)</li>
</ul>
</li>
<li>ScaleGestureDetector<ul>
<li>onScaleBegin(),onScale(),OnScaleEnd()</li>
</ul>
</li>
<li>一个拓展的gesture detector库:android-gesture-detectors</li>
<li>通过OnTouchListener或者onTouchEvent()进行处理</li>
<li>缺点<ul>
<li>Consume UP events and exposes no interface for CANCEL events</li>
<li>May require added touch handling if these cases need special handling (e.g. reset a View’s appearance)</li>
</ul>
</li>
</ul>
</li>
<li>Touch Delegate<ul>
<li>Specialized object to assist in forwarding touches from a parent view to its child</li>
<li>Allows for the touch area of a specific view to be different than its actual bounds</li>
<li>Called in onTouchEvent() of attached View(Events have to make it that far without being consumed by a child or listener)</li>
<li>TouchDelegate is designed to be set on the PARENT and passed the CHILD view that touches should be forwarded to</li>
</ul>
</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ViewGroup parent;</span><br><span class="line">View child;</span><br><span class="line">Rect touchArea;</span><br><span class="line">parent.setTouchDelegate(new TouchDelegate(touchArea, child));</span><br></pre></td></tr></table></figure>
<ul>
<li><p>ViewDragHelper<br>快速处理view拖拽的辅助类.</p>
<ul>
<li>创建ViewDragHelper:</li>
</ul>
<p><code>mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());</code></p>
<ul>
<li>把ViewGroup的点击事件传递给ViewDragHelper:</li>
</ul>
</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">public boolean onInterceptTouchEvent(MotionEvent event) {</span><br><span class="line"> if (mDragHelper.shouldInterceptTouchEvent(event)) {</span><br><span class="line"> return true;</span><br><span class="line"> }</span><br><span class="line"> return super.onInterceptTouchEvent(event);</span><br><span class="line">}</span><br><span class="line">@Override</span><br><span class="line">public boolean onTouchEvent(MotionEvent event) {</span><br><span class="line"> mDragHelper.processTouchEvent(event);</span><br><span class="line"> return true;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>ViewDragHelper.Callback的实现类DragHelperCallback中,重载感兴趣的函数,实现自己的逻辑</li>
<li>有一个用于边缘拖拽结束activity的库,边缘拖拽使用的就是ViewDragHelper:Slidr</li>
</ul>
]]></content>
</entry>
<entry>
<title><![CDATA[评论功能数据库设计和开发]]></title>
<url>http://yoursite.com/2016/05/12/%E8%AF%84%E8%AE%BA%E5%8A%9F%E8%83%BD%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AE%BE%E8%AE%A1/</url>
<content type="html"><![CDATA[<h3 id="评论功能数据库设计和开发"><a href="#评论功能数据库设计和开发" class="headerlink" title="评论功能数据库设计和开发"></a>评论功能数据库设计和开发</h3><p>本文是作者在评论功能设计方面在数据库上的一些总结。<br><a id="more"></a></p>
<h4 id="需求分析"><a href="#需求分析" class="headerlink" title="需求分析"></a>需求分析</h4><ol>
<li><p>一问一答:网易新闻(app)</p>
<p><img src="http://img.kuqin.com/upimg/allimg/160113/1J641G95-0.jpg?imageView/2/w/480/h/640|watermark/2/text/aHR0cDovL21pY2hhZWwtai54eXo=" alt="一问一答"></p>
</li>
<li><p>评论为主:新浪新闻(分为评论和回复)</p>
<p><img src="http://img.kuqin.com/upimg/allimg/160113/1J6415144-1.jpg?imageView/2/w/500/h/800|watermark/2/text/aHR0cDovL21pY2hhZWwtai54eXo=" alt="一问一答"></p>
</li>
<li><p>同级显示</p>
<p><img src="http://img.kuqin.com/upimg/allimg/160113/1J6413964-2.jpg?imageView/2/w/400/h/600|watermark/2/text/aHR0cDovL21pY2hhZWwtai54eXo=" alt="一问一答"></p>
</li>
</ol>
<h4 id="数据库设计"><a href="#数据库设计" class="headerlink" title="数据库设计"></a>数据库设计</h4><p>由于我一直使用<strong> mysql</strong>,我就以<strong> mysql</strong>为例谈一下针对上面三种场景的设计</p>
<ul>
<li>一问一答:</li>
</ul>
<p>这种场景下一般评论数量较少,评论不为活跃,可以不区分评论和回复,而同意看成评论.区别在于有的评论是直接评论<strong> 主题</strong>(每个评论都挂在某个主题下,如文章帖子等),而有些评论是<strong> @</strong> 其他用户的,为了能cover这两种场景,使用一张表就可以达到效果,评论表如下设计:</p>
<table>
<thead>
<tr>
<th>表字段</th>
<th style="text-align:center">字段说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td style="text-align:center">主键</td>
</tr>
<tr>
<td>topic_id</td>
<td style="text-align:center">主题ID</td>
</tr>
<tr>
<td>topic_type</td>
<td style="text-align:center">主题type</td>
</tr>
<tr>
<td>content</td>
<td style="text-align:center">评论内容</td>
</tr>
<tr>
<td>from_uid</td>
<td style="text-align:center">评论用户id</td>
</tr>
<tr>
<td>to_uid</td>
<td style="text-align:center">评论目标用户id</td>
</tr>
</tbody>
</table>
<p>为了能复用评论模块,我们引入了一个topic_type字段来却分主题的类别。from_uid表示评论人的id,通过该id我们可以检索到评论人的相关信息。to_uid是评论目标人的id,如果没有目标人,则该字段为空。</p>
<p>出于性能的考虑,往往我们会冗余评论人的相关信息到评论表中,比如评论人的nick、头像等,目标用户也是如此。这样一来我们就只用查询单表就可以达到显示的效果。</p>
<p>有时,目标用户有多个,那么可以将to_uid字段修改为to_uids,保存时用分隔符来分隔用户id,而目标用户的信息再去查询缓存或者数据库。也可以简单的将多个目标用户的信息一起存成json格式,可以应付简单的展现需求。</p>
<ul>
<li>评论为主:</li>
</ul>
<p>在以评论为主的树形显示的情况下,数据库的设计十分灵活,可以使用单表,添加一个parent_id字段来指向父评论.如果数据库本身支持嵌套查询,那么还是比较方便的,SqlServer、Oracle都支持,但是mysql不支持,那么只能通过存储过程来实现。在互联网应用中,能不使用触发器存储过程的话,尽量不要去使用,因为其对性能有影响。</p>
<p>我们还可以将评论拆分为评论表和回复表,评论挂在各种主题下面,而回复都挂在评论下面。</p>
<p>评论表的设计如下:</p>
<table>
<thead>
<tr>
<th>表字段</th>
<th style="text-align:center">字段说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td style="text-align:center">主键</td>
</tr>
<tr>
<td>topic_id</td>
<td style="text-align:center">主题ID</td>
</tr>
<tr>
<td>topic_type</td>
<td style="text-align:center">主题type</td>
</tr>
<tr>
<td>content</td>
<td style="text-align:center">评论内容</td>
</tr>
<tr>
<td>from_uid</td>
<td style="text-align:center">评论用户id</td>
</tr>
</tbody>
</table>
<p>回复表的设计如下:</p>
<table>
<thead>
<tr>
<th>表字段</th>
<th style="text-align:center">字段说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td style="text-align:center">主键</td>
</tr>
<tr>
<td>comment_id</td>
<td style="text-align:center">评论ID</td>
</tr>
<tr>
<td>reply_id</td>
<td style="text-align:center">回复目标id</td>
</tr>
<tr>
<td>reply_type</td>
<td style="text-align:center">回复类型</td>
</tr>
<tr>
<td>content</td>
<td style="text-align:center">回复内容</td>
</tr>
<tr>
<td>from_uid</td>
<td style="text-align:center">回复用户id</td>
</tr>
<tr>
<td>to_uid</td>
<td style="text-align:center">目标用户id</td>
</tr>
</tbody>
</table>
<p>由于我们拆分了评论和回复,那么评论表就不再需要目标用户字段了,因为评论均是用户对主题的评论,评论表的设计更加简洁了。</p>
<p>回复表我添加了一个comment_id字段来表示该回复挂在的根评论id,这样设计也是出于性能方面的考虑,我们可以直接通过评论id一次性的捞出该评论下的所有回复,然后通过程序来编排回复的显示结构。通过适当的冗余来提高性能也是常用的优化手段之一。这里给出一段我通过评论id来查找并组织所有回复的代码:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">public List<ReplyDTO> getReplyListByRid(long rid){</span><br><span class="line"> List<ReplyDO> replyDOList = replyDAO.queryReplyByCid(rid);</span><br><span class="line"> if(replyDOList == null || replyDOList.size() == 0){</span><br><span class="line"> return new ArrayList<>();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> List<ReplyDTO> ReplyDTOList = new ArrayList<>();</span><br><span class="line"> List<ReplyDTO> parentList = new ArrayList<>();</span><br><span class="line"> for(ReplyDO replyDO:replyDOList){</span><br><span class="line"> ReplyDTO replyDTO = convertReplyToDTO(replyDO);</span><br><span class="line"> if(replyDTO.getReplyType == ReplyType.COMMENT){</span><br><span class="line"> replyDTOList.add(replyDTO);</span><br><span class="line"> parentList.add(replyDTO);</span><br><span class="line"> }else{</span><br><span class="line"> boolean foundParent = false;</span><br><span class="line"> if(replyDTOList.size()>0){</span><br><span class="line"> for(ReplyDTO parent:parentList){</span><br><span class="line"> if(parent.getId().equals(replyDTO.getReplyId())){</span><br><span class="line"> if(parent.getNext() == null){</span><br><span class="line"> parent.setNext(new ArrayList<ReplyDTO>());</span><br><span class="line"> }</span><br><span class="line"> parent.getNext().add(replyDTO);</span><br><span class="line"> parentList.add(replyDTO);</span><br><span class="line"> foundParent = true;</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> if(!foundParent){</span><br><span class="line"> throw new RuntimeException("sort reply error,should not go here");</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return replyDTOList;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>reply_type表示回复的类型,因为回复可以是针对评论的回复(comment),也可以是针对回复的回复(reply),通过这个字段来区分两种情景.</p>
<p>reply_id是表示回复目标的id,如果reply_type是comment的话,那么reply_id=comment_id,如果reply_type是reply的话,这表示这条回复的父回复.</p>
<p>在数据结构的设计上,我在replyDTO中设计了一个List<replydto> next属性,这样在形成了一个树形的结构,类似如下结构.<br> <img src="http://img.kuqin.com/upimg/allimg/160113/1J64121D-3.jpg?imageView/2/w/300/h/600|watermark/2/text/aHR0cDovL21pY2hhZWwtai54eXo=" alt="树形结构"></replydto></p>
<p>客户端可以直接根据该结构来进行树形结构的显示.</p>
<ul>
<li>同级显示:</li>
</ul>
<p>要达到网易新闻中评论的效果我还没有特别好的建议.这种场景中评论和回复是同级显示的,回复不在显示结构上不用挂在一个评论下面.双表的设计在这里就不太合适了,因为涉及到评论和回复的混排,使用双表则会导致查询的逻辑过于复杂.所以建议还是采用单表的实际,不区分评论和回复会简化应用层的逻辑.我们同意都看成评论,而有些评论是可以引用其他评论的.本人推荐采用闭包表的设计,例如:</p>
<pre><code>comment表设计:
</code></pre><table>
<thead>
<tr>
<th>表字段</th>
<th style="text-align:center">字段说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td style="text-align:center">主键</td>
</tr>
<tr>
<td>topic_id</td>
<td style="text-align:center">主题ID</td>
</tr>
<tr>
<td>topic_type</td>
<td style="text-align:center">主题type</td>
</tr>
<tr>
<td>content</td>
<td style="text-align:center">评论内容</td>
</tr>
<tr>
<td>from_uid</td>
<td style="text-align:center">评论用户id</td>
</tr>
</tbody>
</table>
<pre><code>parent_children表:
</code></pre><table>
<thead>
<tr>
<th>表字段</th>
<th style="text-align:center">字段说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td style="text-align:center">主键</td>
</tr>
<tr>
<td>parent_id</td>
<td style="text-align:center">父ID</td>
</tr>
<tr>
<td>child_id</td>
<td style="text-align:center">子id</td>
</tr>
</tbody>
</table>
<p>comment表保存所有评论内容,而parent_children表则记录评论表中各个评论的父子关系.</p>
<p>查询时往往会按照时间排序,我们可以直接按照id或者创建事件降序排列查询comment表即可.如果用户想查询一条评论的完整引用,则可以通过parent_children来找打对应的路径.向上查找到评论只需要可执行:</p>
<p><code>select parent_id from parent_children where child_id=${id} and parent_id != ${id}</code></p>
<p>向下查找所有的子孙评论可执行:</p>
<p><code>select child_id from parent_children where parent_id = ${id} and parent_id != ${id}</code></p>
<p>闭包表在查询时非常方便,但是插入的性能稍差,因为除了插入评论表以外,还需要把该条评论所有的父子关系插入到父子关系表中.插入性能会随着评论层级的加深而线性下降.</p>
<h4 id="海量数据优化"><a href="#海量数据优化" class="headerlink" title="海量数据优化"></a>海量数据优化</h4><p>如果你的系统每天都会有成千上万条评论,那么单表的设计肯定是不行,优化的方式也有很多.</p>
<ul>
<li>分库分表. 分库分表是最为常用也最有效的优化方式,建议按照主题来分库分表.这样同一个主题下面的评论就会落到同一张表里,避免了跨表查询.</li>
<li>适当的数据冗余. 如果你需要显示评论人的相关信息,那么在插入评论时就把这些信息写入评论表中,避免多次查询.实际上,如果是记录数据,都可以冗余对应的数据信息,因为它们的数据的实时行和一致性要求并不高,用户不会因为评论中的头像没更新而撕了你,哈哈.</li>
<li>附加幂等数据只允许单项操作.如果pd要求你能给评论点赞,那么你可以告诉它只能点赞,不能取消.因为从幂等性的要求来说,每个赞都是一条记录.评论的赞数如果都从点赞表中统计得出,那么性能开销会十分巨大,而且点赞如此轻量级的一个操作一定会加剧点赞表的竞争操作.所以建议直接在评论表中添加一个like_count的计数器,该字段只增不减.</li>
<li>热门评论加缓存.类似于网易新闻的热门评论,读取频度非常高,可以专门开接口给客户端,同时该接口做缓存.</li>
</ul>
<p>####参考文献:</p>
<p><a href="http://blog.csdn.net/tiantiandjava/article/details/45390859" target="_blank" rel="external">逻辑数据库设计-单纯的树(递归关系数据)</a></p>
<p><a href="http://qinxuye.me/article/storing-hierachical-data-in-database/" target="_blank" rel="external">在数据库中存储层级结构</a></p>
<p><a href="http://stackoverflow.com/questions/4048151/what-are-the-options-for-storing-hierarchical-data-in-a-relational-database" target="_blank" rel="external">What are the Options for Storing Hierarchical Data in a Relational Database</a> </p>
]]></content>
</entry>
<entry>
<title><![CDATA[android studio快捷键]]></title>
<url>http://yoursite.com/2016/05/10/AS%E5%BF%AB%E6%8D%B7%E9%94%AE/</url>
<content type="html"><![CDATA[<h3 id="AS快捷键"><a href="#AS快捷键" class="headerlink" title="AS快捷键"></a>AS快捷键</h3><ol>
<li>ctrl+D 复制粘贴</li>
<li>ctrl+space 提示</li>
<li>ctrl+shift+enter 自动匹配相对应的语法结构</li>
<li>ctrl+F 搜索</li>
<li>ctrl+句点 选中第一个item</li>
</ol>
]]></content>
</entry>
<entry>
<title><![CDATA[Markdown语法]]></title>
<url>http://yoursite.com/2016/05/10/MD%E8%AF%AD%E6%B3%95/</url>
<content type="html"><![CDATA[<h3 id="MarkDown语法"><a href="#MarkDown语法" class="headerlink" title="MarkDown语法"></a>MarkDown语法</h3><ul>
<li><p>引用:</p>
<p> 只需要在文本钱加入>这种尖括号(大于号)即可</p>
<blockquote>
<p>例如这样</p>
</blockquote>
<p> 要注意符号和文本间的空格.</p>
</li>
<li><p>图片与链接:</p>
<p> 插入链接与插入图片的语法很像,区别在一个!号</p>
<p> 图片为: <code>![](){ImgCap}{/ImgCap}</code></p>
<p> 链接为: <code>[]()</code></p>
<p> 插入图片的地址需要图床.</p>
</li>
<li><p>粗体与斜体:</p>
<p> markdown的粗体和斜体也非常简单,用两个<em>包含一段文本就是粗体的语法,用一个</em>包含一段文本就是斜体的语法.</p>
<p> 例如:<strong>这里是粗体</strong> <em>这里是斜体</em></p>
</li>
<li><p>代码框:</p>
<p> 如果你是个程序猿,需要在文章里优雅的引用代码框那也很简单,只需要用两个`把中间代码包裹起来就好了.</p>
<p> 代码块的话要用三个`包裹.</p>
</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">//helloworld.java </span><br><span class="line">public static void main(arg[]){</span><br><span class="line"> system.out.println("hello world!");</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li><p>表格:</p>
<p> 表格是我觉得markdown比较麻烦的地方,例子如下:</p>
</li>
</ul>
<pre><code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">| Tables | Are | Cool |</span><br><span class="line">| ------------- |:-------------:| -----:|</span><br><span class="line">| col 3 is | right-aligned | $1600 |</span><br><span class="line">| col 2 is | centered | $12 |</span><br><span class="line">| zebra stripes | are neat | $1 |</span><br></pre></td></tr></table></figure>
这种语法生成的表格如下:
| Tables | Are | Cool |
| ------------- |:-------------:| -----:|
| col 3 is | right-aligned | $1600 |
| col 2 is | centered | $12 |
| zebra stripes | are neat | $1 |
</code></pre>]]></content>
</entry>
<entry>
<title><![CDATA[MVP架构]]></title>
<url>http://yoursite.com/2016/05/10/MVP%E6%A1%86%E6%9E%B6/</url>
<content type="html"><![CDATA[<h4 id="什么是MVP?"><a href="#什么是MVP?" class="headerlink" title="什么是MVP?"></a>什么是MVP?</h4><p>MVP代表Model,view和Presenter。</p>
<blockquote>
<p>view层负责处理用户事件和视图部分的展示。在android中,他可能是Activity或者Fragment类。<br>Model层负责访问数据。数据可以是远端的Server API,本地数据库或者SharedPreference等。<br>Presenter层是连接(或适配)view和model的桥梁。<br><a id="more"></a><br>下图是基于MVP架构的模式之一。view是UI线程。Presenter是view与model之间的适配器。UseCase或者Domain在Model层中,负责从实体获取或载入数据。依赖规则如下:</p>
</blockquote>
<p><img src="http://static.codeceo.com/images/2016/01/c81e728d9d4c2f636f067f89cc14862c1.jpg" alt="The Dependency Injection"></p>
<p>关键是,高层接口不知道底层接口的细节,或者更准确地说,高层接口不能,不应该,并且必须不了解底层接口的细节,是(面向)抽象的,并且是细节隐藏的。</p>
<p><img src="http://static.codeceo.com/images/2016/01/c81e728d9d4c2f636f067f89cc14862c2.jpg" alt="The higher interfaces do not know about the details of the lower ones"></p>
<h4 id="依赖规则?"><a href="#依赖规则?" class="headerlink" title="依赖规则?"></a>依赖规则?</h4><p>Uncle Bob 的“the Clean Architecture”描述了依赖的规则是什么。</p>
<blockquote>
<p>同心圆将软件划分为不同的区域,一般的,随着层级的深入,软件的等级也就越高。外圆是实现机制,内圆是核心策略。</p>
</blockquote>
<p>这是上面文章的摘要:</p>
<ul>
<li>Entities:<ul>
<li>可以是一个持有方法函数的对象</li>
<li>可以是一组数据结构或方法函数</li>
<li>它并不重要,能在项目中被不同应用程序使用即可</li>
</ul>
</li>
<li>Use Cases:<ul>
<li>包含特定于应用程序的业务规则</li>
<li>精心编排流入Entity或从Entity流出的数据</li>
<li>指挥Entity直接使用项目范围内的业务规则,从而实现Use Case的目标</li>
</ul>
</li>
<li>Presenter,Controllers:<ul>
<li>将Use Case和Entity中的数据转换成格式最方便的数据</li>
<li>外部系统,如数据库或网页能够方便的使用这些数据</li>
<li>完全包含GUI的MVC架构</li>
</ul>
</li>
<li>External Interfaces,UI,DB<ul>
<li>所有的细节所在</li>
<li>如数据库细节,Web框架细节,等等</li>
</ul>
</li>
</ul>
<h4 id="MVC,MVP还是MVVM?"><a href="#MVC,MVP还是MVVM?" class="headerlink" title="MVC,MVP还是MVVM?"></a>MVC,MVP还是MVVM?</h4><p>那么哪一个才是最好的呢?哪一个比其他的更优秀呢?我能只选一个吗?</p>
<p>答案是,NO。</p>
<p>这些模式的动机都是一样的。那就是如何避免复杂混乱的代码,让执行单元测试变得更容易,创造高质量应用程序。就这样。</p>
<p>当然,远不止这三种架构模式。而且任何一种模式都不可能是银弹,他们只是架构模式之一,不是解决问题的唯一途径。这些只是方法、手段而不是目的、目标。</p>
<h4 id="利与弊"><a href="#利与弊" class="headerlink" title="利与弊"></a>利与弊</h4><p>OK,让我们回到MVP架构上。刚刚 我们了解了什么是MVP,讨论了MVP以及其他热门架构,并且介绍了MVC,MVP和MVVM三者间的不同。这是关于MVP架构利与弊的总结:</p>
<ul>
<li>利:<ul>
<li>可测试(TDD)</li>
<li>可维护(代码复用)</li>
<li>容易Reviewe</li>
<li>信息隐蔽</li>
</ul>
</li>
<li>弊:<ul>
<li>冗余的,尤其是小型App开发</li>
<li>(有可能)额外的学习曲线</li>
<li>开始编写代码之前需要时间成本(但是我敢打赌,设计架构是所有项目开发所必需的)</li>
</ul>
</li>
</ul>
<h4 id="show-me-the-code!!!"><a href="#show-me-the-code!!!" class="headerlink" title="show me the code!!!"></a>show me the code!!!</h4><p>这里仅展示了MVP模式的一小段结构。如果你想了解更多项目或生动的代码示例,请参考文章末尾的“链接和资源”。那里有非常丰富和设计巧妙的示例,基本都托管在Github上,以便你能clone,在设备上运行,并了解工作原理。</p>
<p>首先,为每一个View定义接口。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> *Interface classes for the Top view</span><br><span class="line"> */</span><br><span class="line">public Interface TopView {</span><br><span class="line"> /**</span><br><span class="line"> *Initialize the view.</span><br><span class="line"> *</span><br><span class="line"> *e.g. the facade-pattern mothod for handing all ActionBar settings</span><br><span class="line"> */</span><br><span class="line"> void initViews();</span><br><span class="line"> /**</span><br><span class="line"> * Open {@link DatePickerDialog}</span><br><span class="line"> */</span><br><span class="line"> void openDatePickerDialog();</span><br><span class="line"> /**</span><br><span class="line"> * Start ListActivity</span><br><span class="line"> */</span><br><span class="line"> void startListActivity();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>让我们重写TopView类,重点如下:</p>
<ul>
<li>TopActivty只是负责处理事件监听或者展示每个视图组件</li>
<li>所有的业务逻辑必须委托给Presenter类</li>
<li>在MVP中,View和Presenter是一一对应的(在MVVM中是一对多的)</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">public class TopActivty extends Activity implements TopView{</span><br><span class="line"> // here we use ButterKnife to inject views</span><br><span class="line"> /**</span><br><span class="line"> * Calendar Title</span><br><span class="line"> */</span><br><span class="line"> @Bind(R.id.calendar_title)</span><br><span class="line"> TextView mCalendarTitle;</span><br><span class="line"></span><br><span class="line"> private TopPresenter mTopPresenter;</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> protected void onCreate(Bundle savedIntanceState){</span><br><span class="line"> super.onCreate(savedIntanceState);</span><br><span class="line"> setContentView(R.layout.activity_top);</span><br><span class="line"> ButterKnife.bind(this);</span><br><span class="line"></span><br><span class="line"> // Save TopPresenter instance in a member variable field</span><br><span class="line"> mTopPresenter = new TopPresenter();</span><br><span class="line"> mTopPresenter.onCreate(this);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> /*</span><br><span class="line"> * Overrides method from the {@link TopView} interfaces</span><br><span class="line"> */</span><br><span class="line"> @Override</span><br><span class="line"> public void initViews(){</span><br><span class="line"> //ActionBar settings</span><br><span class="line"></span><br><span class="line"> //set event listeners</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void openDatePickerDialog(){</span><br><span class="line"> DatePickerFragment.newInstace().show(getSupportFragmentManager(),DatePickerFragment.TAG);</span><br><span class="line"></span><br><span class="line"> //do not write logic here... all logic must be passed to the Presenter</span><br><span class="line"> mTopPresenter.updateCalendarDate();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public void startListActivity(){</span><br><span class="line"> startActivity(new Intent(this,ListActivity.class));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这是Presenter类,最重要的一点是Presenter仅仅是连接View与Model的适配桥梁。比如,<strong> TopUseCase#saveCalendarDate()</strong> 是对TopPresenter细节隐藏的,同样对TopView也是如此。你不需要关心数据结构,也不需要关心业务逻辑是如何工作。因此你可以对TopUseCase执行单元测试,因为业务逻辑与视图层是分离的。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">public class TopPresenter{</span><br><span class="line"></span><br><span class="line"> @Nullable</span><br><span class="line"> private TopView mView;</span><br><span class="line"></span><br><span class="line"> private TopUseCase mUseCase;</span><br><span class="line"></span><br><span class="line"> public TopPresenter(){</span><br><span class="line"> mUseCase = new TopUseCase();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void onCreate(@NonNull TopView TopView){</span><br><span class="line"> mView = TopView;</span><br><span class="line"></span><br><span class="line"> //here you call View's implemented methods</span><br><span class="line"> mView.initViews();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> public void updateCalendarDate(){</span><br><span class="line"> //do not forget to return if view instances is null</span><br><span class="line"> if(mView == null){</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //here logic comes</span><br><span class="line"> String dateToDisplay = mUseCase.getDateToDisplay(mContex.getResources());</span><br><span class="line"> mView.updateCalendarDate(dateToDisplay);</span><br><span class="line"></span><br><span class="line"> //here you save date, and this logic is hiden in UseCase class</span><br><span class="line"> mUseCase.saveCalendarDate();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>当然,尽管业务逻辑被实现在Activity类中,你依然可以执行单元测试,只不过这会耗费很多时间,而且有些复杂。可能需要更多的时间来运行App,相反,你本应该充分利用测试类库的性能,如Robolectric。</p>
<p>#### 总结</p>
<p>这里没有万能药,而且MVP也仅仅是解决方案之一,他可以与其他方法协同使用,同样,也可以有选择的用于不同项目。</p>
]]></content>
</entry>
<entry>
<title><![CDATA[Hello Hexo‘s World!]]></title>
<url>http://yoursite.com/2016/05/10/hello-world/</url>
<content type="html"><![CDATA[<p>Welcome to <a href="https://hexo.io/" target="_blank" rel="external">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/" target="_blank" rel="external">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html" target="_blank" rel="external">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues" target="_blank" rel="external">GitHub</a>.<br><a id="more"></a></p>
<h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/writing.html" target="_blank" rel="external">Writing</a></p>
<h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/server.html" target="_blank" rel="external">Server</a></p>
<h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/generating.html" target="_blank" rel="external">Generating</a></p>
<h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/deployment.html" target="_blank" rel="external">Deployment</a></p>
]]></content>
</entry>
<entry>
<title><![CDATA[正则表达式]]></title>
<url>http://yoursite.com/2016/05/10/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/</url>
<content type="html"><![CDATA[<h4 id="正则表达式-语法"><a href="#正则表达式-语法" class="headerlink" title="正则表达式-语法"></a>正则表达式-语法</h4><ul>
<li><p>限定符</p>
<p>限定符用来指定正则表达式中的一个给定组件必须要出现多少次才能满足匹配。有*或+或?或{n}或{n,}或{n,m}共6种。</p>
<ul>
<li><ul>
<li>:匹配前面的子表达式零次或多次。等价于{0,}。</li>
</ul>
</li>
<li>+:匹配前面的子表达式一次或多次。等价于{1,}。</li>
<li>?:匹配前面的子表达式零次或一次。等价于{0,1}。</li>
<li>{n}:n是一个非负整数。匹配确定的n次。</li>
<li>{n,}:n是一个非负整数。至少匹配n次。</li>
<li>{n,m}:m和n均为非负整数,其中n<=m。最好匹配n次且最多匹配m次。请注意在逗号和两个数之间不能有空格。<a id="more"></a>
限定符出现在范围表达式之后。因此,它应用于整个范围表达式。* 、+和?限定符都是贪婪的,因为他们毁尽可能多的匹配文字,只有在他们的后面加上一个?就可以实现非贪婪或最小匹配。</li>
</ul>
</li>
<li><p>定位符</p>
<p>定位符使您能够将正则表达式固定到行首或行尾。他们还使您能够创建这样的正则表达式,这些正则表达式出现在一个单词内、在一个单词的开头或者一个单词的结尾。</p>
<p>定位符用来描述字符串或单词的边界,^和$分别指字符串的开始与结束,\b描述单词的前或后边界,\B表示非单词边界。</p>
<p>正则表达式的限定符有:</p>
<ul>
<li>^:匹配<strong>输入字符串开始</strong>的位置。如果设置了RegExp对象的Multiline属性,^还会与\n或\r之后的位置匹配。</li>
<li>$:匹配<strong>输入字符串结尾</strong>的位置。如果设置了RegExp对象的Multiline属性,$还会与\n或\r之后的位置匹配。</li>
<li>\b:匹配一个字边界,即字与空格件的位置。</li>
<li>\B:非字边界匹配。</li>
</ul>
<p><strong>注意</strong>:不能将限定符与定点符一起使用。由于在紧靠换行或者字边界的前面或后面不能有一个以上位置,因此不允许诸如^* 之类的表达式。</p>
</li>
<li><p>选择</p>
<p>用圆括号将所有选择项括起来,相邻的选择项之间用|分隔。但是圆括号会有一个副作用,是相关的匹配会被缓存,此时用?:放在第一个选项前来消除这种副作用。</p>
<p>其中?:是非捕获元之一,还有两个非捕获元是?=和?!,这两个还有更多的含义,前者为正向预查,在任何开始匹配圆括号内的正则表达式模式的位置来匹配搜索字符串,后者为负向预查,在任何开始不匹配该正则表达式模式的位置来匹配搜索字符串。</p>
</li>
<li><p>反向引用</p>
<p>对于一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个自匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从1开始,最多可存储99个捕获的子表达式。每个缓冲区都可以使用‘\n’访问,其中n为一个标识特定缓冲区的一位或两位十进制数。</p>
<p>可以使用非捕获元字符 ‘?:’、‘?=’或‘?!’来重写捕获,忽略对相关匹配的保存。</p>
<p>反向引用的最简单的、最有用的应用之一,是提供查找文本中两个相同的相邻单词的匹配项的能力。</p>
</li>
</ul>
<h4 id="正则表达式-元字符"><a href="#正则表达式-元字符" class="headerlink" title="正则表达式-元字符"></a>正则表达式-元字符</h4><ul>
<li><p>下表包含了元字符的完整列表以及他们在正则表达式上下文中的行为:</p>
<ul>
<li>\:将下一个字符标记为一个特殊字符、或将一个原义字符、或一个后向引用、或一个八进制转义符。例如,‘n’匹配字符“n”。‘\n’匹配一个换行符。序列‘\’匹配“\”而“(”则匹配“(”。</li>
<li>.:匹配除“\n”之外的任何单个字符。要匹配包括‘\n’在内的任何字符,请使用像‘[.\n]’的模式。</li>
<li>(pattern):匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用‘(’或‘)’。</li>
<li>(?:pattern):匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用”或”字符(|)来组合一个模式的各个部分是很有用。例如,‘industr(?:y|ies)’就是一个比‘industry|industries’更简略的表达式。</li>
<li>(?=pattern):正向预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,’Windows (?=95|98|NT|2000)’ 能匹配 “Windows 2000” 中的 “Windows” ,但不能匹配 “Windows 3.1” 中的 “Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。</li>
<li>(?!pattern):负向预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如’Windows (?!95|98|NT|2000)’ 能匹配 “Windows 3.1” 中的 “Windows”,但不能匹配 “Windows 2000” 中的 “Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。</li>
<li>x|y:匹配x或y。例如,‘z|food’能匹配“z”或“food”。‘(z|f)ood’则匹配“zood”或“food”。</li>
<li>[xyz]:字符集合。匹配所包含的任意一个字符。例如,’[abc]’可以匹配“plain”中的’a’。</li>
<li>[^xyz]:负值字符集合。匹配未包含的任意字符。例如,’[^abc]’可以匹配”plain”中的’p’、’l’、’i’、’n’。</li>
<li>[a-z]:字符范围。匹配制定范围内的任意字符。</li>
<li>[^a-z]:负值字符集合。匹配任何不在指定范围内的任意字符。</li>
<li>\b:匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配”never” 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。</li>
<li>\B:匹配非单词边界。’er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。</li>
<li>\cx:匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的‘c’字符。</li>
<li>\d:匹配一个数字字符。等价于[0-9]。</li>
<li>\D:匹配一个非数字字符。等价于[^0-9]。</li>
<li>\f:匹配一个换行符。等价于\xOc和\cL。</li>
<li>\n 匹配一个换行符。等价于 \x0a 和 \cJ。</li>
<li>\r 匹配一个回车符。等价于 \x0d 和 \cM。</li>
<li>\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。</li>
<li>\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。</li>
<li>\t 匹配一个制表符。等价于 \x09 和 \cI。</li>
<li>\v 匹配一个垂直制表符。等价于 \x0b 和 \cK。</li>
<li>\w 匹配包括下划线的任何单词字符。等价于’[A-Za-z0-9_]’。</li>
<li>\W 匹配任何非单词字符。等价于 ‘[^A-Za-z0-9_]’。</li>
<li>\xn 匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,’\x41’ 匹配 “A”。’\x041’ 则等价于 ‘\x04’ & “1”。正则表达式中可以使用 ASCII 编码。</li>
<li>\num 匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,’(.)\1’ 匹配两个连续的相同字符。</li>
<li>\n 标识一个八进制转义值或一个向后引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。</li>
<li>\nm 标识一个八进制转义值或一个向后引用。如果 \nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。</li>
<li>\nml 如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。</li>
<li>\un 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (?)。</li>
</ul>
</li>
</ul>
<h4 id="正则表达式-运算符优先级"><a href="#正则表达式-运算符优先级" class="headerlink" title="正则表达式-运算符优先级"></a>正则表达式-运算符优先级</h4><ul>
<li><p>正则表达式从左到右进行计算,并遵循优先级顺序,这与算术表达式非常类似。</p>
<p>相同优先级的从左到右进行运算,不同优先级的运算先高后低。下面从最高到最低说明了各种正则表达式运算符的优先级顺序:</p>
<ul>
<li>\: 转义符</li>
<li>(), (?:), (?=), []:圆括号和方括号</li>
<li><ul>
<li>, +, ?, {n}, {n,}, {n,m}:限定符<br>^, $, \任何元字符、任何字符 定位点和序列(即:位置和顺序)</li>
</ul>
</li>
<li>|:替换,”或”操作<br>字符具有高于替换运算符的优先级,使得”m|food”匹配”m”或”food”。若要匹配”mood”或”food”,请使用括号创建子表达式,从而产生”(m|f)ood”。</li>
</ul>
</li>
</ul>
<h4 id="正则表达式-匹配规则"><a href="#正则表达式-匹配规则" class="headerlink" title="正则表达式-匹配规则"></a>正则表达式-匹配规则</h4><ul>
<li><p>基本模式匹配</p>
<p>一切从最基本的开始。模式,是正则表达式最基本的元素,他们是一组描述字符串特征的字符。模式可以很简单,由普通的字符串组成,也可以非常复杂,往往用特殊的字符表示一个范围内的字符、重复出现,或表示上下文。</p>
</li>
<li><p>字符簇</p>
<p>在Internet的程序中,正则表达式通常用来验证用户的输入。当用户提交了一个Form以后,要判断输入的电话号码、地址、Email地址、信用卡号码等是否有效,用普通的基于字面的字符是不够的。所以要用一个中更自由的描述我们要的模式的方法,他就是字符簇。</p>
<blockquote>
<p>匹配所有的数字,句号和减号</p>
</blockquote>
<p><code>[0-9\.\-]</code> </p>
<blockquote>
<p>匹配所有的白字符</p>
</blockquote>
<p><code>[ \f\r\t\n]</code></p>
<p>PHP的正则表达式有一些内置的通用字符簇:</p>
<ul>
<li>[[:alpha:]] 任何字母</li>
<li>[[:digit:]] 任何数字</li>
<li>[[:alnum:]] 任何字母和数字</li>
<li>[[:space:]] 任何空白字符</li>
<li>[[:upper:]] 任何大写字母</li>
<li>[[:lower:]] 任何小写字母</li>
<li>[[:punct:]] 任何标点符号</li>
<li>[[:xdigit:]] 任何16进制的数字,相当于[0-9a-fA-F]</li>
</ul>
</li>
</ul>
<ul>
<li><p>确定重复出现</p>
<p>到现在为止,你已经知道如何去匹配一个字母或数字,但更多的情况下,可能要匹配一个单词或一组数字。一个单词有若干个字母组成,一组数字有若干个单数组成。跟在字符或字符簇后面的花括号({})用来确定前面的内容的重复出现的次数。</p>
<ul>
<li>^[a-zA-Z_]$ ——– 所有的字母和下划线</li>
<li>^[[:alpha:]]{3}$ ——–所有的3个字母的单词</li>
<li>^a$ ——– 字母a</li>
<li>^a{4}$ ——–aaaa</li>
<li>^a{2,4}$ ——– aa,aaa或aaaa</li>
<li>^a{1,3}$ ——–a,aa或aaa</li>
<li>^a{2,}$ ——– 包含多于两个a的字符串</li>
<li>^a{2,} ——– 如:aardvark和aaab,但apple不行</li>
<li>a{2,} ——– 如:baad和aaa,但Nantucket不行</li>
<li>\t{2} ——– 两个制表符</li>
<li>.{2} ——– 所有的两个字符</li>
</ul>
</li>
</ul>
]]></content>
</entry>
</search>