-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
2292 lines (2190 loc) · 290 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>C语言文件操作相关</title>
<url>/2020/08/14/C%E8%AF%AD%E8%A8%80%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C%E7%9B%B8%E5%85%B3/</url>
<content><![CDATA[<p>做课设时遇到的各种问题及其解决</p>
<a id="more"></a>
<h1 id="1-修改文件内容"><a href="#1-修改文件内容" class="headerlink" title="1.修改文件内容"></a>1.修改文件内容</h1><p>做课设的时候,需要用到 C 的文件操作,本意是想实现文件的<strong>覆盖写入</strong>(即修改文件内容但不改变文件大小),如下:</p>
<blockquote>
<p>原本文件内容为 abcdefg,现在第3位后写入123,写入后预期文件内容为abc123g</p>
</blockquote>
<p>但实际做出来的效果是<em>每次写完后文件大小都会改变</em>,恰巧我写的是二进制文件而不是文本文件(而且大小有10M),不好直接打开文件查看问题</p>
<h2 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h2><p>调试后发现文件大小改变是因为最后一次写操作之后的内容都会丢失,此时考虑到是写操作的方式有问题,而写操作的方式是由函数<code>fopen</code>决定的</p>
<p>先来看看<code>fopen</code>的函数原型</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="function">FILE *<span class="title">fopen</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *filename, <span class="keyword">const</span> <span class="keyword">char</span> *mode)</span></span>;</span><br></pre></td></tr></table></figure>
<p>参数如下:</p>
<ul>
<li><em>filename</em>:字符串,包含了要打开的文件名称;</li>
<li><em>mode</em>:字符串,包含了文件访问模式;</li>
</ul>
<p>返回值为文件指针,在不同模式下的初始值不同</p>
<h2 id="问题的关键"><a href="#问题的关键" class="headerlink" title="问题的关键"></a>问题的关键</h2><p>显然,需要在意的是参数<strong>mode</strong>,其可选项如下:</p>
<table>
<thead>
<tr>
<th><strong>字符串</strong></th>
<th align="left"><strong>说明</strong></th>
</tr>
</thead>
<tbody><tr>
<td>r</td>
<td align="left">以只读方式打开文件,该文件必须存在。</td>
</tr>
<tr>
<td>r+</td>
<td align="left">以读/写方式打开文件,该文件必须存在。</td>
</tr>
<tr>
<td>rb+</td>
<td align="left">以读/写方式打开一个二进制文件,只允许读/写数据。</td>
</tr>
<tr>
<td>rt+</td>
<td align="left">以读/写方式打开一个文本文件,允许读和写。</td>
</tr>
<tr>
<td>w</td>
<td align="left">打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。</td>
</tr>
<tr>
<td>w+</td>
<td align="left">打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。</td>
</tr>
<tr>
<td>a</td>
<td align="left">以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF 符保留)。</td>
</tr>
<tr>
<td>a+</td>
<td align="left">以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF符不保留)。</td>
</tr>
<tr>
<td>wb</td>
<td align="left">以只写方式打开或新建一个二进制文件,只允许写数据。</td>
</tr>
<tr>
<td>wb+</td>
<td align="left">以读/写方式打开或新建一个二进制文件,允许读和写。</td>
</tr>
<tr>
<td>wt+</td>
<td align="left">以读/写方式打开或新建一个文本文件,允许读和写。</td>
</tr>
<tr>
<td>at+</td>
<td align="left">以读/写方式打开一个文本文件,允许读或在文本末追加数据。</td>
</tr>
<tr>
<td>ab+</td>
<td align="left">以读/写方式打开一个二进制文件,允许读或在文件末追加数据。</td>
</tr>
</tbody></table>
<blockquote>
<p>From百度百科</p>
</blockquote>
<p>我最初选择的参数是<code>wb+</code>,会出现上一节所提到的文件大小改变的问题,而后来选择<code>rb+</code>则不会;</p>
<p>从表格上看,这二者的功能描述并无太大区别,其实表格内容省略了一些说明,因为<code>wb+</code>本身应当是<code>w+</code>的<em>二进制文件版</em>,所以其完整说明应该参照<code>w+</code>来补全,得到如下区别:</p>
<ul>
<li><code>wb+</code>无论文件是否存在都可以打开成功,<code>rb+</code>必须要文件存在时才能打开成功;</li>
<li>若文件存在,<code>wb+</code><strong>将文件长度清为零,重新写入数据</strong>,<code>rb+</code><strong>覆盖写入文件</strong>;</li>
</ul>
<p>现在可以知道,文件大小改变是因为每次打开文件后,原文件内容就被清除了,只剩下最近一次写入操作的内容;</p>
<h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><p>关于<em>修改指定位置的文件内容</em>,需要搭配函数<code>fseek</code>一起使用,该函数的作用是调整文件指针的位置,函数原型如下:</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fseek</span><span class="params">(FILE *stream, <span class="keyword">long</span> offset, <span class="keyword">int</span> fromwhere)</span></span>;</span><br></pre></td></tr></table></figure>
<p>参数:</p>
<ul>
<li>*<em>stream</em>:文件指针</li>
<li><em>offset</em>:相对于基址的偏移量,基址由后一个参数决定</li>
<li><em>fromwhere</em>:基址,有三种基础选项:<code>SEEK_SET</code>——文件开头,<code>SEEK_CUR</code>——文件指针当前指向位置,<code>CUR_END</code>——文件末尾</li>
</ul>
<p>写入操作应当使用<code>fwrite</code>而不是<code>fputs</code>/<code>fputc</code>,因为<code>fwrite</code>是<strong>以二进制格式向文件写入数据块</strong>,而<code>fputs</code>/<code>fputc</code>是将<strong>字符串/字符</strong>输入到文件,在对二进制文件进行操作时不应当使用;</p>
<h1 id="2-fscanf读取多行数据"><a href="#2-fscanf读取多行数据" class="headerlink" title="2.fscanf读取多行数据"></a>2.fscanf读取多行数据</h1><p>现有文本文件内容如下:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">a 1 4 50</span><br><span class="line">b 2 3 100</span><br><span class="line">c 4 5 40</span><br></pre></td></tr></table></figure>
<p>每一行均为 “字符串 整型 整型 整型” 的格式,欲使用函数<code>fscanf</code>读取多行数据</p>
<h2 id="解决-1"><a href="#解决-1" class="headerlink" title="解决"></a>解决</h2><p>先了解<code>fscanf</code>的函数原型:</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fscanf</span><span class="params">(FILE * stream, <span class="keyword">const</span> <span class="keyword">char</span> * format, [argument...])</span></span>;</span><br></pre></td></tr></table></figure>
<p>参数:</p>
<ul>
<li><em>stream</em>: 指向 FILE 对象的指针,该 FILE 对象标识了流;</li>
<li><em>format</em>: 字符串,包含了以下各项中的一个或多个:<em>空格字符、非空格字符</em> 和 <em>format 说明符</em>;</li>
</ul>
<p>使用方法与<code>scanf</code>几乎一致,只不过<code>fscanf</code>是用于读取<strong>文件</strong>内容的;</p>
<p>实现代码如下:</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">readMultLines</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">char</span> str[<span class="number">20</span>];</span><br><span class="line"> <span class="keyword">int</span> int1;</span><br><span class="line"> <span class="keyword">int</span> int2;</span><br><span class="line"> <span class="keyword">int</span> int3;</span><br><span class="line"> FILE *fp = fopen(<span class="string">"filename.txt"</span>, <span class="string">"r"</span>);</span><br><span class="line"> <span class="keyword">if</span>(fp == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span>(!feof(file)) {</span><br><span class="line"> <span class="built_in">fscanf</span>(file, <span class="string">"%s %d %d %d\n"</span>, str, &int1, &int2, &int3);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
]]></content>
<tags>
<tag>C</tag>
</tags>
</entry>
<entry>
<title>SpringBoot访问上传资源的问题</title>
<url>/2022/02/19/SpringBoot%E8%AE%BF%E9%97%AE%E4%B8%8A%E4%BC%A0%E8%B5%84%E6%BA%90%E7%9A%84%E9%97%AE%E9%A2%98/</url>
<content><![CDATA[<p>本篇分析SpringBoot项目访问上传资源需要重新启动项目的原因以及解决方法</p>
<a id="more"></a>
<h1 id="SpringBoot访问上传资源的问题"><a href="#SpringBoot访问上传资源的问题" class="headerlink" title="SpringBoot访问上传资源的问题"></a>SpringBoot访问上传资源的问题</h1><h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>项目需要上传图片到后端,因为项目不大就没做图片服务器,直接将上传的图片保存到项目目录下。这里我将图片文件保存在了存放静态资源的目录中,即<code>\src\main\resources\static\</code>。在访问上传资源的时候发现并不能访问到上传的资源,报错404,而查看目录发现图片文件又确实保存在里面了。</p>
<h2 id="问题原因"><a href="#问题原因" class="headerlink" title="问题原因"></a>问题原因</h2><p>服务器对资源文件实现了保护机制,本地服务器不能对外暴露真实的资源路径。<br>所以前端请求资源的时候并不是在<code>\src\main\resources\static\</code>目录下寻找对应文件,而是在项目编译后生成的target目录下寻找资源,一般静态资源在<code>\target\classes\static</code>中;<br>所以在文件上传完成后,如果没有重启项目把上传的资源编译到 target 文件夹里(这是对外暴露的),便无法访问</p>
<h2 id="解决办法"><a href="#解决办法" class="headerlink" title="解决办法"></a>解决办法</h2><p>配置资源访问路径映射,新建 ResourceConfig.java :</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Configuration;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.servlet.config.annotation.WebMvcConfigurer;</span><br><span class="line"> </span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ResourceConfig</span> <span class="keyword">implements</span> <span class="title">WebMvcConfigurer</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addResourceHandlers</span><span class="params">(ResourceHandlerRegistry registry)</span> </span>{</span><br><span class="line"> </span><br><span class="line"> String path = <span class="keyword">new</span> FileSystemResource(<span class="string">""</span>).getFile().getAbsolutePath() + <span class="string">"\\webip\\src\\main\\resources\\static\\img\\"</span>;<span class="comment">// 这里的path是你保存资源的绝对路径</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">//配置静态资源访问路径</span></span><br><span class="line"> registry.addResourceHandler(<span class="string">"/upload/**"</span>).addResourceLocations(<span class="string">"file:"</span>+path);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>基本思路是<strong>实现 WebMvcConfigurer</strong>,重写 <code>addResourceHandlers(ResourceHandlerRegistry registry)</code>方法,<code>addResourceHandler()</code>添加的是访问路径,即前端发送请求时的url,<code>addResourceLocations()</code>添加的是映射后的真实路径。实际上就是配置一个拦截器, 如果访问路径是<code>addResourceHandler()</code>的参数中的路径,那么就映射到访问本地的<code>addResourceLocations()</code> 的参数的这个路径上,这样就可以让前端访问服务器的本地文件了。</p>
]]></content>
<tags>
<tag>后端</tag>
</tags>
</entry>
<entry>
<title>Skewed-associative_Caches</title>
<url>/2020/07/01/Skewed-associative-Caches/</url>
<content><![CDATA[<p>参考几篇论文后总结得出的关于偏斜相联缓存的一些基础知识</p>
<a id="more"></a>
<h2 id="产生原因"><a href="#产生原因" class="headerlink" title="产生原因"></a>产生原因</h2><p>CPU 速度与主存速度的剪刀差导致主存成为性能瓶颈,为了缓和这一速度差而提出了缓存技术,而缓存技术的应用则带来了诸如缓存命中率、失效开销、硬件复杂度、替换策略等一系列问题;<br>偏斜相联 Cache 具有更高的的命中率和更低的硬件复杂度,如:二体偏斜相联 Cache (<em>two-banks skewed-associative cache</em>) 拥有相当于<strong>四路组相联 Cache 的命中率</strong>,而只拥有接近<strong>二路组相联 Cache 的硬件复杂度</strong></p>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>偏斜相联 Cache 的硬件组织方式与组相联 Cache 十分类似</p>
<h3 id="组相联-Cache-的硬件组织"><a href="#组相联-Cache-的硬件组织" class="headerlink" title="组相联 Cache 的硬件组织"></a>组相联 Cache 的硬件组织</h3><p>将 Cache 分为多个存储体(<em>Bank</em>),内存行地址的基址 D 会被映射成 Cache 存储体的物理行地址 f(D),此处 <strong>Cache 中所有存储体的行地址都相同</strong><br>即:访存地址 D 经过散列函数映射到<strong>不同存储体的同一行</strong><br>如下图:<br><img src="/2020/07/01/Skewed-associative-Caches/2way_set-asso.jpg" alt="2way"></p>
<h3 id="一点微小的改动"><a href="#一点微小的改动" class="headerlink" title="一点微小的改动"></a>一点微小的改动</h3><p>在上面组相联 Cache 的基础上,做一点微小的工作,就能得到*偏斜相联 Cache *:</p>
<ul>
<li>每个存储体使用<strong>不同的</strong>散列函数</li>
<li>使得访存地址 D 映射到<strong>不同存储体的不同行</strong><br>如下图:<br><img src="/2020/07/01/Skewed-associative-Caches/skewed-asso.jpg" alt="skewed-asso"><blockquote>
<p>可以看到,对于左边的存储体使用了函数 f<sub>1</sub> , 而对于右边的存储体则使用了另一个函数 f<sub>2</sub></p>
</blockquote>
</li>
</ul>
<p>由于散列函数由硬件直接实现,所以使用不同的散列函数必然会导致<strong>硬件复杂度的增加</strong>,但是这种复杂度的增加是十分微小的(在实现时,会选择用尽量少的门来实现散列函数),同时它又会带来相当可观的<strong>命中率的提升</strong>,进而带来处理器整体性能的提升</p>
<h3 id="偏斜函数的选择"><a href="#偏斜函数的选择" class="headerlink" title="偏斜函数的选择"></a>偏斜函数的选择</h3><p>需要考虑如下因素</p>
<h4 id="均匀度"><a href="#均匀度" class="headerlink" title="均匀度"></a>均匀度</h4><p>对于缓存中的的每行,应当有相同数量的主存行映射到该行</p>
<h4 id="存储体间的分散"><a href="#存储体间的分散" class="headerlink" title="存储体间的分散"></a>存储体间的分散</h4><p>这是偏斜相联 Cache 提高性能的关键原理之一<br>普通组相联 Cache 当两行数据被映射到 Cache 的同一行时,这些数据在所有存储体(所有组)中都是冲突的<br>为了避免这种情况,偏斜相联 Cache 的映射函数应当通过打散这些数据来避免这种情况的发生,即映射函数应该满足如下要求:当几行数据被映射到 Cache 的<em>存储体 i</em> 中的同一行时,它们有<strong>极低的概率</strong>被映射到<em>存储体 j</em> 的同一行而产生冲突,如下图<br><img src="/2020/07/01/Skewed-associative-Caches/inter-bank_dispersion.jpg" alt="inter-bank"></p>
<h4 id="存储体内的分散"><a href="#存储体内的分散" class="headerlink" title="存储体内的分散"></a>存储体内的分散</h4><p>这是考虑到程序的<strong>空间局部性</strong>而提出的要求<br>映射函数应该满足如下要求:避免将两个在主存中距离很近的行映射到 Cache 中的同一行</p>
<h4 id="简单的硬件实现"><a href="#简单的硬件实现" class="headerlink" title="简单的硬件实现"></a>简单的硬件实现</h4><p>为了尽量减小实现多个映射函数对整体性能的影响,应当选择硬件实现简单(门延迟尽可能少)的映射函数</p>
<h3 id="替换策略的选择"><a href="#替换策略的选择" class="headerlink" title="替换策略的选择"></a>替换策略的选择</h3><h4 id="LRU-Least-Recently-Used"><a href="#LRU-Least-Recently-Used" class="headerlink" title="LRU(Least Recently Used)"></a>LRU(Least Recently Used)</h4><p>虽然 LRU 算法通常被认为是最有效,同时也是被广泛使用的策略,但是对于偏斜相联 Cache 来说,<strong>难以找到在偏斜相联 Cache 中对 LRU 的简单硬件实现</strong>,故偏斜相联 Cache 一般<strong>不采用</strong> LRU 策略</p>
<h4 id="NRUNRW-Not-Recently-Used-Not-Recently-Written"><a href="#NRUNRW-Not-Recently-Used-Not-Recently-Written" class="headerlink" title="NRUNRW(Not Recently Used Not Recently Written)"></a>NRUNRW(Not Recently Used Not Recently Written)</h4><p>实质上是一种“伪 LRU 算法”<br>工作原理:</p>
<ul>
<li>Cache 中每行附加 1 bit 的 RU(Recently Used) 位,<strong>当该行被访问时,RU 置1</strong></li>
<li>周期性清零 RU 位</li>
</ul>
<p>当需要进行替换时,按下列顺序进行规则选择:</p>
<ol>
<li>随机选择 RU 位为零的行</li>
<li>随机选择 RU 位为 1 ,但装入 Cache 后<strong>未被修改</strong>的行</li>
<li>随机选择 RU 位为 1 ,且被修改过的行<blockquote>
<ul>
<li>所谓“按顺序”,即当按照规则 1 无法找出符合条件的行时,则寻找符合规则 2 的行,以此类推</li>
<li>对于指令缓存(instruction cache)来说,不存在规则 3</li>
</ul>
</blockquote>
</li>
</ol>
]]></content>
<tags>
<tag>Computer Architecture</tag>
</tags>
</entry>
<entry>
<title>node.js学习_2</title>
<url>/2020/02/26/node-js%E5%AD%A6%E4%B9%A0-2/</url>
<content><![CDATA[<p>Node.js 学习之二:代码的组织和部署<br>本文参考:<a href="http://nqdeng.github.io/7-days-nodejs" target="_blank" rel="noopener">http://nqdeng.github.io/7-days-nodejs</a></p>
<a id="more"></a>
<h1 id="代码的组织和部署"><a href="#代码的组织和部署" class="headerlink" title="代码的组织和部署"></a>代码的组织和部署</h1><h2 id="模块路径解析规则"><a href="#模块路径解析规则" class="headerlink" title="模块路径解析规则"></a>模块路径解析规则</h2><h3 id="绝对路径和相对路径的问题"><a href="#绝对路径和相对路径的问题" class="headerlink" title="绝对路径和相对路径的问题"></a>绝对路径和相对路径的问题</h3><p>在之前的学习中,我们已知<code>require</code>函数同时支持<em>绝对路径</em> 和<em>相对路径</em> (见<a href="https://table-the-cat.github.io/2020/02/24/node-js%E5%AD%A6%E4%B9%A0-1/"> node.js 学习_1 </a>),但这种引用方式存在一定的弊端:当某个模块A的文件的存放位置发生改变时,所有使用了模块A的模块代码都需要修改</p>
<h3 id="第三种形式"><a href="#第三种形式" class="headerlink" title="第三种形式"></a>第三种形式</h3><p>为了改善上述问题,<code>require</code>函数支持了另一种形式的路径,写法类似于<code>foo/bar</code>,并按照以下规则解析路径,直到找到模块位置</p>
<ol>
<li><p>内置模块<br> 若传递给<code>require</code>的是 Node.js 内置模块的名称,则不做路径解析,直接返回内部模块的导出对象,如<code>require('fs')</code></p>
<blockquote>
<p>关于内置模块,可以查阅<a href="https://nodejs.org/zh-cn/docs/" target="_blank" rel="noopener">官方文档</a><br>比如上文提到的 fs 模块,其官方文档<a href="https://nodejs.org/dist/latest-v11.x/docs/api/fs.html" target="_blank" rel="noopener">在这里</a></p>
</blockquote>
</li>
<li><p>node_modules 目录<br> <code>node_modules</code> 目录专门用于存放模块<br> 若某个模块A的绝对路径是<code>/home/user/hello.js</code>,则在模块A中使用<code>require('foo/bar')</code>方式加载模块时,Node.js 依次在以下路径中寻找目标文件</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">/home/user/node_modules/foo/bar</span><br><span class="line">/home/node_modules/foo/bar</span><br><span class="line">/node_modules/foo/bar</span><br></pre></td></tr></table></figure>
</li>
<li><p>NODE_PATH 环境变量<br> Node.js 允许通过<code>NODE_PATH</code>环境变量来指定额外的模块搜索路径</p>
<blockquote>
<p>NODE_PATH环境变量可以包含多个路径,路径之间在Linux下使用<code>:</code>分隔,在Windows下使用<code>;</code>分隔</p>
</blockquote>
<p> 如,若定义<code>NODE_PATH</code>环境变量如下:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">/home/user/lib:/home/lib</span><br></pre></td></tr></table></figure>
</li>
</ol>
<p> 则当用<code>require('foo/bar')</code>方式加载模块时,Node.js 依次在以下路径中寻找目标文件</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">/home/user/lib/foo/bar</span><br><span class="line">/home/lib/foo/bar</span><br></pre></td></tr></table></figure>
<p><span style="color:red">注意!</span>只有在 Node.js <strong>没有</strong>在第二步的路径中(即node_modules的那三个路径)找到目标模块时,才会在 <code>NODE_PATH</code>保存的路径中寻找</p>
<h2 id="包-package"><a href="#包-package" class="headerlink" title="包(package)"></a>包(package)</h2><p>为了便于管理和使用,我们将由多个<em>子模块</em> 组成的模块称为<strong>包</strong>,并将所有子模块保存在同一个目录</p>
<h3 id="入口模块"><a href="#入口模块" class="headerlink" title="入口模块"></a>入口模块</h3><p>在一个包中,需要指定其中的一个子模块为<strong>入口模块</strong>,该入口模块的导出对象作为整个包的导出对象</p>
<p>例如有以下目录结构:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">- /home/user/lib/</span><br><span class="line"> - cat/</span><br><span class="line"> head.js</span><br><span class="line"> body.js</span><br><span class="line"> main.js</span><br></pre></td></tr></table></figure>
<p>其中<code>cat</code>目录定义了一个包,其中包含了3个子模块。<code>main.js</code>作为入口模块,其内容如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> head = <span class="built_in">require</span>(<span class="string">'./head'</span>);</span><br><span class="line"><span class="keyword">var</span> body = <span class="built_in">require</span>(<span class="string">'./body'</span>);</span><br><span class="line"></span><br><span class="line">exports.create = <span class="function"><span class="keyword">function</span> (<span class="params">name</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> name: name,</span><br><span class="line"> head: head.create(),</span><br><span class="line"> body: body.create()</span><br><span class="line"> };</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<h3 id="包的使用"><a href="#包的使用" class="headerlink" title="包的使用"></a>包的使用</h3><p>在其它模块里使用包的时候,需要加载包的<em>入口模块</em>。接上例,使用<code>require('/home/user/lib/cat/main')</code>能达到目的,但是入口模块名称出现在路径里看上去不是个好主意。因此我们需要做点额外的工作,让包使用起来更像是单个模块</p>
<h3 id="index-js"><a href="#index-js" class="headerlink" title="index.js"></a>index.js</h3><p>当我们将文件命名为<code>index.js</code>时,加载模块时可以使用<em>模块所在目录的路径</em> 代替<em>模块文件路径</em> 。所以,我们可以将包的入口模块命名为<code>index.js</code>,在上例中即为将<code>main.js</code>改为<code>index.js</code>,重命名后,以下两条语句等价</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> cat = <span class="built_in">require</span>(<span class="string">'/home/user/lib/cat'</span>);</span><br><span class="line"><span class="keyword">var</span> cat = <span class="built_in">require</span>(<span class="string">'/home/user/lib/cat/index'</span>);</span><br></pre></td></tr></table></figure>
<p>这样,引用包时可以只将包的目录路径传给require函数,使用起来更像是单个模块</p>
<h3 id="package-json"><a href="#package-json" class="headerlink" title="package.json"></a>package.json</h3><h4 id="package-json-是什么"><a href="#package-json-是什么" class="headerlink" title="package.json 是什么"></a>package.json 是什么</h4><p>package.json 是一个项目或模块(包也可以看作是一个模块)的<em>描述文件</em>,该文件包含诸如项目名称、版本、依赖等</p>
<h4 id="文件创建"><a href="#文件创建" class="headerlink" title="文件创建"></a>文件创建</h4><ul>
<li><p>手动创建<br> 在项目根目录新建一个 package.json 文件,输入相关内容</p>
</li>
<li><p>自动创建<br> 在项目根目录下输入命令行指令 <code>npm init</code> ,交互式地生成一份最简单的 package.json 文件</p>
</li>
</ul>
<h4 id="文件概览"><a href="#文件概览" class="headerlink" title="文件概览"></a>文件概览</h4><figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"exchange"</span>,</span><br><span class="line"> <span class="attr">"version"</span>: <span class="string">"0.1.0"</span>,</span><br><span class="line"> <span class="attr">"author"</span>: <span class="string">"zhangsan <[email protected]>"</span>,</span><br><span class="line"> <span class="attr">"description"</span>: <span class="string">"第一个node.js程序"</span>,</span><br><span class="line"> <span class="attr">"keywords"</span>:[<span class="string">"node.js"</span>,<span class="string">"javascript"</span>],</span><br><span class="line"> <span class="attr">"private"</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">"bugs"</span>:{<span class="attr">"url"</span>:<span class="string">"http://path/to/bug"</span>,<span class="attr">"email"</span>:<span class="string">"[email protected]"</span>},</span><br><span class="line"> <span class="attr">"contributors"</span>:[{<span class="attr">"name"</span>:<span class="string">"李四"</span>,<span class="attr">"email"</span>:<span class="string">"[email protected]"</span>}],</span><br><span class="line"> <span class="attr">"repository"</span>: {</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"git"</span>,</span><br><span class="line"> <span class="attr">"url"</span>: <span class="string">"https://path/to/url"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"homepage"</span>: <span class="string">"http://necolas.github.io/normalize.css"</span>,</span><br><span class="line"> <span class="attr">"license"</span>:<span class="string">"MIT"</span>,</span><br><span class="line"> <span class="attr">"dependencies"</span>: {</span><br><span class="line"> <span class="attr">"react"</span>: <span class="string">"^16.8.6"</span>,</span><br><span class="line"> <span class="attr">"react-dom"</span>: <span class="string">"^16.8.6"</span>,</span><br><span class="line"> <span class="attr">"react-router-dom"</span>: <span class="string">"^5.0.1"</span>,</span><br><span class="line"> <span class="attr">"react-scripts"</span>: <span class="string">"3.0.1"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"devDependencies"</span>: {</span><br><span class="line"> <span class="attr">"browserify"</span>: <span class="string">"~13.0.0"</span>,</span><br><span class="line"> <span class="attr">"karma-browserify"</span>: <span class="string">"~5.0.1"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"scripts"</span>: {</span><br><span class="line"> <span class="attr">"start"</span>: <span class="string">"react-scripts start"</span>,</span><br><span class="line"> <span class="attr">"build"</span>: <span class="string">"react-scripts build"</span>,</span><br><span class="line"> <span class="attr">"test"</span>: <span class="string">"react-scripts test"</span>,</span><br><span class="line"> <span class="attr">"eject"</span>: <span class="string">"react-scripts eject"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"bin"</span>: {</span><br><span class="line"> <span class="attr">"webpack"</span>: <span class="string">"./bin/webpack.js"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"main"</span>: <span class="string">"lib/webpack.js"</span>,</span><br><span class="line"> <span class="attr">"module"</span>: <span class="string">"es/index.js"</span>,</span><br><span class="line"> <span class="attr">"eslintConfig"</span>: {</span><br><span class="line"> <span class="attr">"extends"</span>: <span class="string">"react-app"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"engines"</span> : { </span><br><span class="line"> <span class="attr">"node"</span> : <span class="string">">=0.10.3 <0.12"</span> </span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"browserslist"</span>: {</span><br><span class="line"> <span class="attr">"production"</span>: [</span><br><span class="line"> <span class="string">">0.2%"</span>,</span><br><span class="line"> <span class="string">"not dead"</span>,</span><br><span class="line"> <span class="string">"not op_mini all"</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="attr">"development"</span>: [</span><br><span class="line"> <span class="string">"last 1 chrome version"</span>,</span><br><span class="line"> <span class="string">"last 1 firefox version"</span>,</span><br><span class="line"> <span class="string">"last 1 safari version"</span></span><br><span class="line"> ]</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"style"</span>: [</span><br><span class="line"> <span class="string">"./node_modules/tipso/src/tipso.css"</span></span><br><span class="line">],</span><br><span class="line"> <span class="attr">"files"</span>: [</span><br><span class="line"> <span class="string">"lib/"</span>,</span><br><span class="line"> <span class="string">"bin/"</span>,</span><br><span class="line"> <span class="string">"buildin/"</span>,</span><br><span class="line"> <span class="string">"declarations/"</span>,</span><br><span class="line"> <span class="string">"hot/"</span>,</span><br><span class="line"> <span class="string">"web_modules/"</span>,</span><br><span class="line"> <span class="string">"schemas/"</span>,</span><br><span class="line"> <span class="string">"SECURITY.md"</span></span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="参数说明"><a href="#参数说明" class="headerlink" title="参数说明"></a>参数说明</h4><p><strong>name</strong>:项目/模块名称,长度必须小于等于214个字符,不能以”.”(点)或者”_”(下划线)开头,不能包含大写字母;<br><strong>version</strong>:项目版本;<br><strong>author</strong>:项目开发者,它的值是你在 <a href="https://npmjs.org" target="_blank" rel="noopener">https://npmjs.org</a> 网站的有效账户名;<br><strong>description</strong>:项目描述,是一个字符串。它可以帮助人们在使用<code>npm search</code>时找到这个包;<br><strong>keywords</strong>:项目关键字,是一个字符串数组。它可以帮助人们在使用<code>npm search</code>时找到这个包;<br><strong>private</strong>:是否私有,设置为 <code>true</code>时,npm 拒绝发布;<br><strong>license</strong>:软件授权条款,让用户知道他们的使用权利和限制;<br><strong>bugs</strong>:bug 提交地址;<br><strong>contributors</strong>:项目贡献者 ;<br><strong>repository</strong>:项目仓库地址;<br><strong>homepage</strong>:项目包的官网 URL;<br><strong>dependencies</strong>:生产环境下,项目运行所需依赖;<br><strong>devDependencies</strong>:开发环境下,项目所需依赖;<br><strong>scripts</strong>:执行 npm 脚本命令简写,比如 <code>“start”: “react-scripts start”</code>, 执行 <code>npm start</code> 就是运行 “react-scripts start”;<br><strong>bin</strong>:内部命令对应的可执行文件的路径;<br><strong>main</strong>:项目默认执行文件,比如 <code>require(‘webpack’)</code>;就会默认加载 lib 目录下的 <code>webpack.js</code> 文件,如果没有设置,则默认加载项目根目录下的<code>index.js</code>文件;<br><strong>module</strong>:是以 ES Module(也就是 ES6)模块化方式进行加载,因为早期没有 ES6 模块化方案时,都是遵循 CommonJS 规范,而 CommonJS 规范的包是以 main 的方式表示入口文件的,为了区分就新增了 module 方式,但是 ES6 模块化方案效率更高,所以会优先查看是否有 module 字段,没有才使用 main 字段;<br><strong>eslintConfig</strong>:EsLint 检查文件配置,自动读取验证;<br><strong>engines</strong>:项目运行的平台;<br><strong>browserslist</strong>:供浏览器使用的版本列表;<br><strong>style</strong>:供浏览器使用时,样式文件所在的位置;样式文件打包工具parcelify,通过它知道样式文件的打包位置;<br><strong>files</strong>:被项目包含的文件名数组;</p>
<h4 id="回到开始的问题"><a href="#回到开始的问题" class="headerlink" title="回到开始的问题"></a>回到开始的问题</h4><p>绕了一圈之后,我们对 package.json 已经有了一定了解,那么回到开始的问题:如何使用 package.json 实现使用<code>require('/home/user/lib/cat')</code>的方式加载模块?<br>方法很简单,设置参数<code>main</code>的值即可;<br>回到上面<code>cat</code>包的例子,我们将模块重构如下:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">- /home/user/lib/</span><br><span class="line"> - cat/</span><br><span class="line"> + doc/</span><br><span class="line"> - lib/</span><br><span class="line"> head.js</span><br><span class="line"> body.js</span><br><span class="line"> main.js</span><br><span class="line"> + tests/</span><br><span class="line"> package.json</span><br></pre></td></tr></table></figure>
<p>其中<code>package.json</code>中参数<code>main</code>赋值如下:</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"main"</span>: <span class="string">"./lib/main.js"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如此一来,就同样可以使用<code>require('/home/user/lib/cat')</code>的方式加载模块。NodeJS会根据包目录下的<code>package.json</code>找到入口模块所在位置</p>
<h2 id="命令行程序"><a href="#命令行程序" class="headerlink" title="命令行程序"></a>命令行程序</h2><blockquote>
<p>使用NodeJS编写的东西,要么是一个包,要么是一个命令行程序,而<strong>前者最终也会用于开发后者</strong>。因此我们在部署代码时需要一些技巧,让用户觉得自己是在使用一个命令行程序。</p>
</blockquote>
<p>例如我们有一个程序在<code>/home/user/bin/node-echo.js</code>,现在我们要运行它,则应当输入:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">node /home/user/bin/node-echo.js Hello World</span><br><span class="line">//Hello World 为输入的参数</span><br></pre></td></tr></table></figure>
<p>但是我们期望像使用命令行程序一样使用它,即我们期望下面的输入方式:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">node-echo Hello World</span><br></pre></td></tr></table></figure>
<h3 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h3><p>在Linux系统下,我们可以把JS文件当作<em>shell脚本</em> 来运行,从而达到上述目的:</p>
<ol>
<li><p>在shell脚本中,可以通过<code>#!</code>注释来指定当前脚本使用的解析器。所以我们首先在<code>node-echo.js</code>文件顶部增加以下一行注释,表明当前脚本使用NodeJS解析。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">#! /usr/bin/env node</span><br></pre></td></tr></table></figure>
<blockquote>
<p>NodeJS会忽略掉位于JS模块首行的<code>#!</code>注释,不必担心这行注释是非法语句。</p>
</blockquote>
</li>
<li><p>然后,我们使用以下命令赋予<code>node-echo.js</code>文件执行权限。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ chmod +x /home/user/bin/node-echo.js</span><br></pre></td></tr></table></figure>
</li>
<li><p>最后,我们在PATH环境变量中指定的某个目录下,例如在<code>/usr/local/bin</code>下边创建一个软链文件,文件名与我们希望使用的终端命令同名,命令如下:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ sudo ln -s /home/user/bin/node-echo.js /usr/local/bin/node-echo</span><br></pre></td></tr></table></figure>
</li>
</ol>
<p>这样处理后,我们就可以在任何目录下使用<code>node-echo</code>命令了</p>
<h3 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h3><p>在Windows系统下,需要用<code>.cmd</code>文件实现<br>假设<code>node-echo.js</code>存放在<code>C:\Users\user\bin</code>目录,并且该目录已经添加到PATH环境变量中。然后在该目录下新建一个文件,命名为<code>node-echo.cmd</code>,内容如下:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">@node "C:\Users\user\bin\node-echo.js" %*</span><br></pre></td></tr></table></figure>
<p>这样,便可以直接使用<code>node-echo</code>命令了</p>
<h2 id="工程目录"><a href="#工程目录" class="headerlink" title="工程目录"></a>工程目录</h2><p>现在,我们可以尝试完整地规划一个工程目录。<br>以编写一个<em>命令行程序</em> 为例,标准的工程目录大致如下:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">- /home/user/workspace/node-echo/ # 工程目录</span><br><span class="line"> - bin/ # 存放命令行相关代码</span><br><span class="line"> node-echo</span><br><span class="line"> + doc/ # 存放文档</span><br><span class="line"> - lib/ # 存放API相关代码</span><br><span class="line"> echo.js</span><br><span class="line"> - node_modules/ # 存放三方包</span><br><span class="line"> + argv/</span><br><span class="line"> + tests/ # 存放测试用例</span><br><span class="line"> package.json # 元数据文件</span><br><span class="line"> README.md # 说明文件</span><br></pre></td></tr></table></figure>
<p>其中部分文件内容如下:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">/* bin/node-echo */</span><br><span class="line">var argv = require('argv'),</span><br><span class="line"> echo = require('../lib/echo');</span><br><span class="line">console.log(echo(argv.join(' ')));</span><br><span class="line"></span><br><span class="line">/* lib/echo.js */</span><br><span class="line">module.exports = function (message) {</span><br><span class="line"> return message;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">/* package.json */</span><br><span class="line">{</span><br><span class="line"> "name": "node-echo",</span><br><span class="line"> "main": "./lib/echo.js"</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>以上例子中分类存放了不同类型的文件,并通过<code>node_moudles</code>目录直接使用三方包名加载模块。此外,定义了<code>package.json</code>之后,<code>node-echo</code>目录也可被当作一个包来使用</p>
<h2 id="NPM"><a href="#NPM" class="headerlink" title="NPM"></a>NPM</h2><h3 id="npm是什么"><a href="#npm是什么" class="headerlink" title="npm是什么"></a>npm是什么</h3><blockquote>
<p>npm 为你和你的团队打开了连接整个 JavaScript 天才世界的一扇大门。它是世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 600000 个 包(package) (即,代码模块)。来自各大洲的开源软件开发者使用 npm 互相分享和借鉴。包的结构使您能够轻松跟踪依赖项和版本。<br>——From <a href="https://www.npmjs.cn/getting-started/what-is-npm/" target="_blank" rel="noopener">https://www.npmjs.cn/getting-started/what-is-npm/</a></p>
</blockquote>
<p>npm即<em>node package manager</em>,是随同Node.js一起安装的包管理工具,常见使用场景如下:</p>
<ul>
<li>允许用户从npm服务器下载<em>三方包</em>到本地使用</li>
<li>允许用户从npm服务器下载安装他人编写的<em>命令行程序</em>到本地使用</li>
<li>允许用户将自己编写的包或命令行程序上传至npm服务器</li>
</ul>
<h3 id="下载三方包"><a href="#下载三方包" class="headerlink" title="下载三方包"></a>下载三方包</h3><p>通过如下命令来下载三方包:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">npm install packageName</span><br></pre></td></tr></table></figure>
<blockquote>
<p>在 <a href="https://www.npmjs.com/" target="_blank" rel="noopener">npmjs.com</a> 中提供了搜索框,用于<strong>根据包名</strong>搜索有哪些包是可用的</p>
</blockquote>
<p>例如我们可用<code>npm install argv</code>来下载<code>argv</code>包,下载完成后该包便被放在工程目录下的<code>node_modules</code>目录中,因此可用直接通过包名使用</p>
<h4 id="指定版本"><a href="#指定版本" class="headerlink" title="指定版本"></a>指定版本</h4><p>上述指令默认下载三方包的最新版本,当我们需要指定下载版本时,可以使用<code>@<version></code>来指定版本,如下:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">npm install [email protected]</span><br></pre></td></tr></table></figure>
<h4 id="配合package-json"><a href="#配合package-json" class="headerlink" title="配合package.json"></a>配合package.json</h4><p>若一个工程需要较多的三方包,一条一条地输入指令下载显然是低效且无意义的,我们可以在<code>package.json</code>中的<code>dependencies</code>字段上声明所需的三方包依赖,然后直接使用<code>npm install</code>指令实现<strong>批量下载</strong></p>
<p>这样做还有一个好处:当你将自己的包发布到npm服务器上供他人使用时,别人下载这个包的时候,npm会根据包中声明的三方包依赖自动下载进一步依赖的三方包。如此用户在使用包时,可以<strong>只关心自己直接使用的三方包,不需要自己去解决所有包的依赖关系</strong></p>
<h3 id="安装命令行程序"><a href="#安装命令行程序" class="headerlink" title="安装命令行程序"></a>安装命令行程序</h3><p>与三方包的下载方式类似。例如上例中的<code>node-echo</code>提供了命令行使用方式,只要<code>node-echo</code>自己配置好了相关的<code>package.json</code>字段,对于用户而言,只需要使用以下命令安装程序。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ npm install node-echo -g</span><br></pre></td></tr></table></figure>
<p>参数<code>-g</code>表示全局安装,因此<code>node-echo</code>会默认安装到以下位置,并且NPM会自动创建好Linux系统下需要的软链文件或Windows系统下需要的<code>.cmd</code>文件。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">- /usr/local/ # Linux系统下</span><br><span class="line"> - lib/node_modules/</span><br><span class="line"> + node-echo/</span><br><span class="line"> ...</span><br><span class="line"> - bin/</span><br><span class="line"> node-echo</span><br><span class="line"> ...</span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line">- %APPDATA%\npm\ # Windows系统下</span><br><span class="line"> - node_modules\</span><br><span class="line"> + node-echo\</span><br><span class="line"> ...</span><br><span class="line"> node-echo.cmd</span><br><span class="line"> ...</span><br></pre></td></tr></table></figure>
<h3 id="发布代码"><a href="#发布代码" class="headerlink" title="发布代码"></a>发布代码</h3><p>发布代码指令如下:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">npm publish</span><br></pre></td></tr></table></figure>
<p>第一次使用npm发布代码时需要<strong>注册账号</strong>,在终端下输入指令<code>npm adduser</code>,随后按照提示进行注册即可。账号注册成功后,编辑<code>package.json</code>的内容,填充必要字段,必要字段如下:</p>
<ul>
<li>name:包名,在npm服务器上要<strong>保持唯一</strong></li>
<li>version:当前版本号</li>
<li>dependencies:三方包依赖,需要指定<strong>包名</strong>和<strong>版本号</strong></li>
<li>main:入口模块位置</li>
<li>bin:命令行程序名和主模块位置</li>
</ul>
<p>之后,需要在<code>package.json</code><strong>所在目录下</strong>执行<code>npm publish</code>指令发布代码</p>
<h3 id="版本号"><a href="#版本号" class="headerlink" title="版本号"></a>版本号</h3><p>npm使用<em>语义版本号</em> 管理代码</p>
<p>语义版本号结构为<code>X.Y.Z</code>,分为三位,分别代表<strong>主版本号</strong>、<strong>次版本号</strong>和<strong>补丁号</strong>,在代码变更时,应当按照以下原则更新版本:</p>
<ul>
<li>若只是<strong>修复bug</strong>,则更新Z位</li>
<li>若<strong>增加了新功能</strong>,但是<strong>向下兼容</strong>,则更新Y位</li>
<li>若有大变动且<strong>向下不兼容</strong>,则更新X位</li>
</ul>
<p>版本号有了这个保证后,在申明三方包依赖时,除了可依赖于一个固定版本号外,还可依赖于某个范围的版本号。例如<code>"argv": "0.0.x"</code>表示依赖于<code>0.0.x</code>系列的最新版<code>argv</code>。NPM支持的所有版本号范围指定方式可以查看<a href="https://npmjs.org/doc/files/package.json.html#dependencies" target="_blank" rel="noopener">官方文档</a>。</p>
<h3 id="npm常用操作"><a href="#npm常用操作" class="headerlink" title="npm常用操作"></a>npm常用操作</h3><ul>
<li>使用<code>npm help</code>查看所有命令。</li>
<li>使用<code>npm help <command></code>可查看某条命令的详细帮助,例如<code>npm help install</code>。</li>
<li>在<code>package.json</code>所在目录下使用<code>npm install . -g</code>可先在本地安装当前命令行程序,可用于发布前的本地测试。</li>
<li>使用<code>npm update</code>可以把当前目录下<code>node_modules</code>子目录里边的对应模块更新至最新版本。</li>
<li>使用<code>npm update -g</code>可以把全局安装的对应命令行程序更新至最新版。</li>
<li>使用<code>npm cache clear</code>可以清空NPM本地缓存,用于对付使用相同版本号发布新版本代码的人。</li>
<li>使用<code>npm unpublish <package>@<version></code>可以撤销发布自己发布过的某个版本代码。</li>
</ul>
<p>关于npm的更多功能,可以参考<a href="https://docs.npmjs.com/" target="_blank" rel="noopener">官方文档</a></p>
]]></content>
<tags>
<tag>node.js</tag>
</tags>
</entry>
<entry>
<title>node.js学习_1</title>
<url>/2020/02/24/node-js%E5%AD%A6%E4%B9%A0-1/</url>
<content><![CDATA[<p>node.js 学习的第一篇,主要介绍node.js的一些基本概念</p>
<a id="more"></a>
<h1 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h1><h2 id="Node-js-是什么"><a href="#Node-js-是什么" class="headerlink" title="Node.js 是什么"></a>Node.js 是什么</h2><blockquote>
<p>Node.js 是一个基于 Chrome V8 引擎的 Javascript 运行环境</p>
</blockquote>
<h2 id="Node-js-能做什么"><a href="#Node-js-能做什么" class="headerlink" title="Node.js 能做什么"></a>Node.js 能做什么</h2><p>简单来说,Node.js 能让 Javascript 代码在服务端运行</p>
<p>Javascript 是<strong>脚本语言</strong>,脚本语言的运行需要<strong>解析器</strong>,对于在浏览器端运行的 JS ,浏览器充当了解析器,而如果需要独立运行 JS ,则需要 Node.js 作为解析器。</p>
<h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><ul>
<li>单线程</li>
<li>异步 I/O</li>
<li>事件驱动<blockquote>
<p>以上特性会在后续文章中详细介绍</p>
</blockquote>
</li>
</ul>
<h1 id="模块"><a href="#模块" class="headerlink" title="模块"></a>模块</h1><h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>在Node环境中,一个.js文件久称为一个模块<em>(module)</em>,文件路径就是模块名</p>
<h2 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h2><ul>
<li>提高代码的可维护性</li>
<li>提高开发效率</li>
<li>避免函数名和变量名冲突<blockquote>
<p>相同名字的函数和变量完全可以分别存在不同的模块中</p>
</blockquote>
</li>
</ul>
<hr>
<p>在编写每个模块时,都有<code>require</code>, <code>exports</code>, <code>module</code>三个预先定义好的变量可供使用</p>
<h2 id="require"><a href="#require" class="headerlink" title="require"></a>require</h2><p><code>require</code>函数用于在当前模块中<strong>加载</strong>和<strong>使用</strong>别的模块,传入一个模块名,返回一个模块导出对象。</p>
<blockquote>
<p>模块名可以使用<em>相对路径<em>,也可以使用</em>绝对路径</em><br>模块名中的 <code>.js</code>扩展名可以省略</p>
</blockquote>
<p>如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> foo1 = <span class="built_in">require</span>(<span class="string">'./foo'</span>);</span><br><span class="line"><span class="keyword">var</span> foo2 = <span class="built_in">require</span>(<span class="string">'./foo.js'</span>);</span><br><span class="line"><span class="keyword">var</span> foo3 = <span class="built_in">require</span>(<span class="string">'/home/user/foo'</span>);</span><br><span class="line"><span class="keyword">var</span> foo4 = <span class="built_in">require</span>(<span class="string">'/home/user/foo.js'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// foo1至foo4中保存的是同一个模块的导出对象</span></span><br></pre></td></tr></table></figure>
<p><strong>另</strong>,<code>require</code>还可以用于加载和使用<em>JSON文件</em></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> data = <span class="built_in">require</span>(<span class="string">'./data.json'</span>);</span><br></pre></td></tr></table></figure>
<h2 id="exports"><a href="#exports" class="headerlink" title="exports"></a>exports</h2><p><code>exports</code>对象是当前模块的<strong>导出对象</strong>,用于导出模块的公有方法和属性。<br>别的模块通过<code>require</code>函数引用当前模块时得到的就是<strong>当前模块的<code>export</code>对象</strong><br>如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">exports.hello = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'hello world!'</span>);</span><br><span class="line">};</span><br><span class="line"><span class="comment">//以上代码导出了一个公有方法,即在别的模块调用require函数使用该模块时,可以使用hello方法</span></span><br></pre></td></tr></table></figure>
<h2 id="module"><a href="#module" class="headerlink" title="module"></a>module</h2><p>通过<code>module</code>对象可以访问当前模块的一些相关信息,但最多的用途是<strong>替换当前模块的导出对象</strong>。<br>例如模块导出对象默认是一个普通对象,如果想改成一个函数的话,可以使用以下方式:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'Hello World!'</span>);</span><br><span class="line">};</span><br><span class="line"><span class="comment">//模块默认导出对象被替换成一个函数</span></span><br></pre></td></tr></table></figure>
<h2 id="模块初始化"><a href="#模块初始化" class="headerlink" title="模块初始化"></a>模块初始化</h2><p>一个模块中的 JS 代码仅在模块第一次被使用时执行一次,并在执行过程中初始化模块的导出对象;导出对象被<strong>缓存</strong>后可以重复利用</p>
<p><strong>所有模块在执行过程中只初始化一次</strong></p>
<h2 id="主模块"><a href="#主模块" class="headerlink" title="主模块"></a>主模块</h2><p>通过命令行参数传递给 Node.js 以启动程序的模块被称为主模块,主模块负责<strong>调度组成整个程序的其它模块完成工作</strong></p>
<p><strong>主模块是程序的入口</strong></p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ node main.js</span><br></pre></td></tr></table></figure>
<blockquote>
<p>通过上述命令启动时,<code>main.js</code>就是主模块</p>
</blockquote>
<h2 id="完整示例"><a href="#完整示例" class="headerlink" title="完整示例"></a>完整示例</h2><blockquote>
<p>该示例引用自 <a href="http://nqdeng.github.io/7-days-nodejs/#1.5.6" target="_blank" rel="noopener"> 7-days-nodejs </a></p>
</blockquote>
<p>例如有以下目录。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">- /home/user/hello/</span><br><span class="line"> - util/</span><br><span class="line"> counter.js</span><br><span class="line"> main.js</span><br></pre></td></tr></table></figure>
<p>其中<code>counter.js</code>内容如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> i = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">count</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> ++i;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">exports.count = count;</span><br></pre></td></tr></table></figure>
<p>该模块内部定义了一个私有变量<code>i</code>,并在<code>exports</code>对象导出了一个公有方法<code>count</code>。</p>
<p>主模块<code>main.js</code>内容如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> counter1 = <span class="built_in">require</span>(<span class="string">'./util/counter'</span>);</span><br><span class="line"><span class="keyword">var</span> counter2 = <span class="built_in">require</span>(<span class="string">'./util/counter'</span>);<span class="comment">//若此处被重新初始化,则counter2.count()应该返回1</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(counter1.count());</span><br><span class="line"><span class="built_in">console</span>.log(counter2.count());</span><br><span class="line"><span class="built_in">console</span>.log(counter2.count());</span><br></pre></td></tr></table></figure>
<p>运行该程序的结果如下:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ node main.js</span><br><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td></tr></table></figure>
<p>可以看到,<code>counter.js</code>并没有因为被require了两次而初始化两次。</p>
<h2 id="二进制模块"><a href="#二进制模块" class="headerlink" title="二进制模块"></a>二进制模块</h2><p>Node.js 也支持使用 C/C++ 编写二进制模块<br>编译好的二进制模块的扩展名是<code>.node</code>,使用方式与 JS 模块使用方式相同。</p>
<blockquote>
<p>二进制模块难以跨平台,一般情况下不建议使用</p>
</blockquote>
]]></content>
<tags>
<tag>node.js</tag>
</tags>
</entry>
<entry>
<title>getImageData方法获取不正确的问题</title>
<url>/2022/04/02/getImageData-%E8%8E%B7%E5%8F%96%E4%B8%8D%E6%AD%A3%E7%A1%AE%E7%9A%84%E9%97%AE%E9%A2%98/</url>
<content><![CDATA[<p>本篇分析getImageData()方法无法在画布旋转后正确获取指定区域的ImageData的原因以及解决方法</p>
<a id="more"></a>
<h1 id="getImageData-无法正确获取ImageData的问题"><a href="#getImageData-无法正确获取ImageData的问题" class="headerlink" title="getImageData()无法正确获取ImageData的问题"></a>getImageData()无法正确获取ImageData的问题</h1><h2 id="getImageData"><a href="#getImageData" class="headerlink" title="getImageData()"></a>getImageData()</h2><h3 id="接口简介"><a href="#接口简介" class="headerlink" title="接口简介"></a>接口简介</h3><ul>
<li><strong><code>CanvasRenderingContext2D.getImageData()</code></strong> 返回一个<code>ImageData</code>对象,用来描述canvas区域隐含的像素数据,这个区域通过矩形表示,起始点为(sx, sy)、宽为sw、高为sh。</li>
</ul>
<h3 id="语法"><a href="#语法" class="headerlink" title="语法"></a>语法</h3><blockquote>
<p>ImageData ctx.getImageData(sx, sy, sw, sh);</p>
</blockquote>
<h3 id="参数"><a href="#参数" class="headerlink" title="参数"></a>参数</h3><p><code>sx</code><br>将要被提取的图像数据矩形区域的左上角 x 坐标。</p>
<p><code>sy</code><br>将要被提取的图像数据矩形区域的左上角 y 坐标。</p>
<p><code>sw</code><br>将要被提取的图像数据矩形区域的宽度。</p>
<p><code>sh</code><br>将要被提取的图像数据矩形区域的高度。</p>
<h2 id="错误原因"><a href="#错误原因" class="headerlink" title="错误原因"></a>错误原因</h2><h3 id="translate-amp-rotate"><a href="#translate-amp-rotate" class="headerlink" title="translate() & rotate()"></a>translate() & rotate()</h3><p>translate()和rotate()方法本质上是修改当前变换矩阵,以达到平移/旋转的效果:</p>
<blockquote>
<p>The <code>rotate(angle)</code> method, when invoked, must run these steps:</p>
<ol>
<li>If angle is infinite or NaN, then return. </li>
<li>Add the rotation transformation described by the argument to the <a href="https://html.spec.whatwg.org/multipage/canvas.html#current-transformation-matrix" target="_blank" rel="noopener">current transformation matrix</a>. The angle argument represents a clockwise rotation angle expressed in radians.</li>
</ol>
</blockquote>
<blockquote>
<p>The <code>translate(x, y)</code> method, when invoked, must run these steps:</p>
<ol>
<li>If either of the arguments are infinite or NaN, then return. </li>
<li>Add the translation transformation described by the arguments to the <a href="https://html.spec.whatwg.org/multipage/canvas.html#current-transformation-matrix" target="_blank" rel="noopener">current transformation matrix</a>. The x argument represents the translation distance in the horizontal direction and the y argument represents the translation distance in the vertical direction. The arguments are in coordinate space units.</li>
</ol>
</blockquote>
<blockquote>
<p>摘自<a href="https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-rotate" target="_blank" rel="noopener">https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-rotate</a></p>
</blockquote>
<h3 id="getImageData-的特性"><a href="#getImageData-的特性" class="headerlink" title="getImageData()的特性"></a>getImageData()的特性</h3><p><code>createImageData()</code>、<code>getImageData()</code>、<code>putImageData()</code>等方法具有不受当前路径、变换矩阵、阴影属性、全局 alpha、裁剪区域和全局合成算子影响的特性:</p>
<blockquote>
<p>The current path, <a href="https://html.spec.whatwg.org/multipage/canvas.html#transformations" target="_blank" rel="noopener">transformation matrix</a>, <a href="https://html.spec.whatwg.org/multipage/canvas.html#shadows" target="_blank" rel="noopener">shadow attributes</a>, <a href="https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-globalalpha" target="_blank" rel="noopener">global alpha</a>, the <a href="https://html.spec.whatwg.org/multipage/canvas.html#clipping-region" target="_blank" rel="noopener">clipping region</a>, and <a href="https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-globalcompositeoperation" target="_blank" rel="noopener">global composition operator</a> must not affect the methods described in this section.</p>
</blockquote>
<blockquote>
<p>摘自 <a href="https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation" target="_blank" rel="noopener">https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation</a></p>
</blockquote>
<h3 id="图示"><a href="#图示" class="headerlink" title="图示"></a>图示</h3><p>假设使用translate()和rotate()之后,绘制<em>宽为20,高为10</em>的矩形如下<br><img src="/2022/04/02/getImageData-%E8%8E%B7%E5%8F%96%E4%B8%8D%E6%AD%A3%E7%A1%AE%E7%9A%84%E9%97%AE%E9%A2%98/image_20220123160743.png" alt><br>蓝色矩形是canvas的边界,黑色矩形是绘制的矩形,当前context的x、y坐标轴如图所示</p>
<p>若此时想要获取绘制的矩形部分的ImageData,很自然的会想到使用<code>getImageData(0, 0, 20, 10)</code>获取ImageData,而由于<strong>getImageData()方法不受变换矩阵影响</strong>,此时调用getImageData方法取到的ImageData实际上是下图黄色区域部分的ImageData<br><img src="/2022/04/02/getImageData-%E8%8E%B7%E5%8F%96%E4%B8%8D%E6%AD%A3%E7%A1%AE%E7%9A%84%E9%97%AE%E9%A2%98/image_20220123161550.png" alt></p>
<h2 id="解决办法"><a href="#解决办法" class="headerlink" title="解决办法"></a>解决办法</h2><ul>
<li>如果只进行了平移操作,则在进行操作的时候手动添加x轴和y轴方向的位移,例如:</li>
</ul>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">ctx.translate(<span class="number">10</span>, <span class="number">10</span>);</span><br><span class="line">ctx.fillRect(<span class="number">0</span>,<span class="number">0</span>,<span class="number">100</span>,<span class="number">100</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> imageData = ctx.getImagedata(<span class="number">10</span>, <span class="number">10</span>, <span class="number">100</span>, <span class="number">100</span>);</span><br></pre></td></tr></table></figure>
<ul>
<li>如果进行了旋转操作,且旋转角度不是90°的整数倍,则没有很好的办法能准确取到指定矩形的ImageData,此时可以考虑新建另一个canvas元素,在新的canvas上绘制一个相同的图像,再调用<code>getImageData()</code>方法在这个新的canvas中获取ImageData。</li>
</ul>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">ctx.rotate(<span class="number">59</span> * <span class="built_in">Math</span>.PI / <span class="number">180</span>);</span><br><span class="line">ctx.fillRect(<span class="number">0</span>,<span class="number">0</span>,<span class="number">100</span>,<span class="number">100</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> <span class="keyword">let</span> canvas = <span class="built_in">document</span>.createElement(<span class="string">'canvas'</span>);</span><br><span class="line"><span class="keyword">var</span> context = canvas.getContext(<span class="string">'2d'</span>);</span><br><span class="line">canvas.width = <span class="number">100</span>;</span><br><span class="line">canvas.height = <span class="number">100</span>;</span><br><span class="line">context.fillRect(<span class="number">0</span>,<span class="number">0</span>,<span class="number">100</span>,<span class="number">100</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> imageData = context.getImagedata(<span class="number">10</span>, <span class="number">10</span>, <span class="number">100</span>, <span class="number">100</span>);</span><br></pre></td></tr></table></figure>
<p>将从新canvas中取到的ImageData进行处理后使用<code>putImageData()</code>方法放入原canvas,也可以得到相同的效果</p>
]]></content>
<tags>
<tag>前端</tag>
<tag>canvas</tag>
<tag>Javascript</tag>
</tags>
</entry>
<entry>
<title>node.js学习_3</title>
<url>/2020/03/03/node-js%E5%AD%A6%E4%B9%A0-3/</url>
<content><![CDATA[<p>Node.js 学习之三:文件操作<br>摸鱼摸得太久了<br>本文参考:<a href="http://nqdeng.github.io/7-days-nodejs" target="_blank" rel="noopener">http://nqdeng.github.io/7-days-nodejs</a></p>
<a id="more"></a>
<h1 id="文件操作"><a href="#文件操作" class="headerlink" title="文件操作"></a>文件操作</h1><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p>Node.js提供了基本的文件操作API,但是像文件拷贝这种高级功能就没有提供,因此我们先拿文件拷贝程序练手。与<code>copy</code>命令类似,程序需要能接受<em>源文件路径</em> 与<em>目标文件路径</em> 两个参数。</p>
<h3 id="小文件拷贝"><a href="#小文件拷贝" class="headerlink" title="小文件拷贝"></a>小文件拷贝</h3><p>使用Node.js内置的<code>fs</code>模块实现:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fileCopy</span>(<span class="params">src, dst</span>)</span>{</span><br><span class="line"> fs.writeFileSync(dst, fs.readFileSync(src));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">main</span>(<span class="params">argv</span>)</span>{</span><br><span class="line"> fileCopy(argv[<span class="number">0</span>], argv[<span class="number">1</span>]);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">main(process.argv.slice(<span class="number">2</span>));</span><br></pre></td></tr></table></figure>
<p>以上程序使用<code>fs.readFileSync</code>从源路径读取文件内容,然后将内容通过<code>fs.writeFileSync</code>写入目标路径</p>
<blockquote>
<p><code>process</code>是一个全局变量,可以通过<code>process.argv</code>获得命令行参数。<br><code>argv[0]</code>固定等于Node.js执行程序的绝对路径,<code>argv[1]</code>固定等于主模块的绝对路径,因此第一个命令行参数从<code>argv[2]</code>开始</p>
</blockquote>
<h3 id="大文件拷贝"><a href="#大文件拷贝" class="headerlink" title="大文件拷贝"></a>大文件拷贝</h3><p>上述程序存在一个问题:它将文件内容<strong>一次性读入内存</strong>,然后一次性写入目标地址,对于大文件来说,可能造成内存溢出。所以在拷贝大文件时,需要进行如下改进:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fileCopy</span>(<span class="params">src, dst</span>)</span>{</span><br><span class="line"> fs.createReadStream(src).pipe(fs.createWriteStream(dst));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">main</span>(<span class="params">argv</span>)</span>{</span><br><span class="line"> fileCopy(argv[<span class="number">0</span>], argv[<span class="number">1</span>]);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">mian(process.argv.slice(<span class="number">2</span>));</span><br></pre></td></tr></table></figure>
<p>上述程序使用<code>createReadStream</code>创建了源文件的<strong>只读数据流</strong>,然后通过<code>createWriteStream</code>船舰了目标文件的<strong>只写数据流</strong>。并且<strong>用<code>pipe</code>方法将两个数据流连接起来</strong></p>
<h2 id="API概览"><a href="#API概览" class="headerlink" title="API概览"></a>API概览</h2><p>本节只对部分与文件操作相关的API做一个大概的介绍,详情可以查阅官方文档</p>
<h3 id="Buffer(数据块)"><a href="#Buffer(数据块)" class="headerlink" title="Buffer(数据块)"></a>Buffer(数据块)</h3><p>JS 语言自身只有<strong>字符串</strong>数据类型,没有<strong>二进制</strong>数据类型,因此Node.js提供了一个与<code>String</code>对等的<em>全局构造函数</em><code>Buffer</code>来提供对二进制数据的操作<br><strong>Buffer 将 JS 的数据处理能力从字符串扩展到了任意二进制数据</strong></p>
<p>除了可以读取文件得到Buffer的实例外,还能够直接构造,例如:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> bin = <span class="keyword">new</span> Buffer([<span class="number">0x68</span>, <span class="number">0x65</span>, <span class="number">0x6c</span>, <span class="number">0x6c</span>, <span class="number">0x6f</span>]);</span><br></pre></td></tr></table></figure>
<p>与字符串类似,Buffer除了可以用<code>.length</code>属性得到字节长度外,还可以用<code>[index]</code>方式读取指定位置的字节;<br>Buffer 与字符串能够相互转换,如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = bin.toString(<span class="string">'utf-8'</span>); <span class="comment">// => "hello"</span></span><br><span class="line"><span class="comment">//指定编码将二进制数据转化为字符串</span></span><br></pre></td></tr></table></figure>
<p>Buffer 与字符串有一个重要区别:字符串是<strong>只读</strong>的,且对字符串的任何修改得到的都是一个<strong>新字符串</strong>,原字符串保持不变;<br>而 Buffer 类似于数组,可以利用 <code>[index]</code> 方式直接修改某个位置的值:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">bin[<span class="number">0</span>] = <span class="number">0x48</span>;</span><br></pre></td></tr></table></figure>
<p>且<code>.slice</code>方法也不返回一个新的 Buffer,而是返回了指向原 Buffer 中指定位置的<strong>指针</strong>(尽管JS中没有指针,但并不妨碍它使用指针的概念)</p>
<p style="color:red">因此,对`.slice`方法返回的Buffer的修改会作用于原Buffer</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> bin = <span class="keyword">new</span> Buffer([ <span class="number">0x68</span>, <span class="number">0x65</span>, <span class="number">0x6c</span>, <span class="number">0x6c</span>, <span class="number">0x6f</span> ]);</span><br><span class="line"><span class="keyword">var</span> sub = bin.slice(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">sub[<span class="number">0</span>] = <span class="number">0x65</span>;</span><br><span class="line"><span class="built_in">console</span>.log(bin); <span class="comment">// => <Buffer 68 65 65 6c 6f></span></span><br></pre></td></tr></table></figure>
<p>也因此,如果想要拷贝一份 Buffer,得首先创建一个新的 Buffer,并通过<code>.copy</code>方法把原 Buffer 中的数据复制过去,类似于申请一块新内存再复制数据。</p>
<blockquote>
<p>更多内容可参照官方文档: <a href="http://nodejs.org/api/buffer.html" target="_blank" rel="noopener">http://nodejs.org/api/buffer.html</a></p>
</blockquote>
<h3 id="Stream(数据流)"><a href="#Stream(数据流)" class="headerlink" title="Stream(数据流)"></a>Stream(数据流)</h3><p>当内存中无法一次装下需要处理的数据时,或者一边读取一边处理更加高效时,需要用到数据流。<br><strong>Node.js中通过各种Stream来提供对数据流的操作</strong></p>
<p>以上文提到的<em>大文件拷贝</em> 为例,为数据创建一个只读数据流:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> rs = fs.creatReadStream(src)</span><br><span class="line"></span><br><span class="line">rs.on(<span class="string">'data'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">chunk</span>)</span>{</span><br><span class="line"> doSomething(chunk);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">rs.on(<span class="string">'end'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> cleanUp();</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<blockquote>
<p>Stream基于事件机制工作,所有Stream的实例都继承于Node.js提供的<a href="https://nodejs.org/api/events.html" target="_blank" rel="noopener"> EventEmitter</a></p>
</blockquote>
<p>上述代码中<code>data</code>事件会被持续触发,不管函数<code>doSomething</code>是否能够处理。故代码可以做如下改造:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> rs = fs.creatReadStream(src)</span><br><span class="line"></span><br><span class="line">rs.on(<span class="string">'data'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">chunk</span>)</span>{</span><br><span class="line"> rs.pause();<span class="comment">//pause方法用于使流动模式的流停止触发 'data' 事件,并切换出流动模式。 任何可用的数据都会保留在内部缓存中。</span></span><br><span class="line"> doSomething(chunk, <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> rs.resume();<span class="comment">//resume方法将被暂停的可读流恢复触发 'data' 事件,并将流切换到流动模式</span></span><br><span class="line"> });</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">rs.on(<span class="string">'end'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> cleanUp();</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<blockquote>
<p>以上代码为<code>doSomething</code>函数增加了回调,因此可以在处理数据前暂停数据读取,并在处理数据后继续读取数据</p>
</blockquote>
<p>此外,可以为数据目标创建一个只写数据流 :</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> rs = fs.createReadStream(src);</span><br><span class="line"><span class="keyword">var</span> ws = fs.createWriteStream(dst);</span><br><span class="line"></span><br><span class="line">rs.on(<span class="string">'data'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">chunk</span>) </span>{</span><br><span class="line"> ws.write(chunk);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">rs.on(<span class="string">'end'</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> ws.end();</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>我们把<code>doSomething</code>换成了往只写数据流里写入数据后,以上代码看起来就像是一个文件拷贝程序了。但是以上代码存在上边提到的问题,如果写入速度跟不上读取速度的话,只写数据流内部的缓存会爆仓。对此,Node.js 提供了<code>.pipe</code>方法来实现防爆仓控制</p>
<blockquote>
<p>官方文档:<a href="http://nodejs.org/api/stream.html" target="_blank" rel="noopener">http://nodejs.org/api/stream.html</a></p>
</blockquote>
<h3 id="File-System(文件系统)"><a href="#File-System(文件系统)" class="headerlink" title="File System(文件系统)"></a>File System(文件系统)</h3><p><strong>Node.js 通过内置模块<code>fs</code>实现对文件的操作</strong><br>fs提供的API基本课分为以下三类:</p>
<ul>
<li><p>文件属性读写</p>
<blockquote>
<p>常用的有<code>fs.stat</code>、<code>fs.chmod</code>、<code>fs.chown</code>等</p>
</blockquote>
</li>
<li><p>文件内容读写</p>
<blockquote>
<p>常用的有<code>fs.readFile</code>、<code>fs.readdir</code>、<code>fs.writeFile</code>、<code>fs.mkdir</code>等</p>
</blockquote>
</li>
<li><p>底层文件操作</p>
<blockquote>
<p>常用的有<code>fs.open</code>、<code>fs.read</code>、<code>fs.write</code>、<code>fs.close</code>等</p>
</blockquote>
</li>
</ul>
<p><code>fs</code>充分体现了Node.js <span style="color:red"><em>异步I/O</em></span> 的特性,如上文提到的API都通过<em>回调函数</em> 传递结果,以<code>fs.readFile</code>为例:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">fs.readFile(pathname, <span class="function"><span class="keyword">function</span> (<span class="params">err, data</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="comment">// Deal with error.</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// Deal with data.</span></span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>如上,基本上所有<code>fs</code>模块API的回调参数都有两个。第一个参数在有错误发生时等于异常对象,第二个参数始终用于返回API方法执行结果<br>此外,<code>fs</code>模块的所有异步API都有对应的同步版本,用于无法使用异步操作时,或者同步操作更方便时的情况;同步API除了方法名的末尾多了一个<strong><em>Sync</em></strong> 之外,异常对象与执行结果的传递方式也有相应变化。同样以<code>fs.readFileSync</code>为例:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">var</span> data = fs.readFileSync(pathname);</span><br><span class="line"> <span class="comment">// Deal with data.</span></span><br><span class="line">} <span class="keyword">catch</span> (err) {</span><br><span class="line"> <span class="comment">// Deal with error.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>官方文档:<a href="http://nodejs.org/api/fs.html" target="_blank" rel="noopener">http://nodejs.org/api/fs.html</a></p>
</blockquote>
<h3 id="Path(路径)"><a href="#Path(路径)" class="headerlink" title="Path(路径)"></a>Path(路径)</h3><p><strong>内置模块<code>path</code>用于简化路径相关操作,并提升相关代码可读性</strong></p>
<p>常用API:</p>
<ul>
<li>path.normalize<br>将传入的路径转换为<em>标准路径</em> ,如下:<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> cache = {};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">store</span>(<span class="params">key, value</span>) </span>{</span><br><span class="line"> cache[path.normalize(key)] = value;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">store(<span class="string">'foo/bar'</span>, <span class="number">1</span>);</span><br><span class="line">store(<span class="string">'foo//baz//../bar'</span>, <span class="number">2</span>);</span><br><span class="line"><span class="built_in">console</span>.log(cache); <span class="comment">// => { "foo/bar": 2 }</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>除了解析路径中的.与..外,还能去掉多余的斜杠。如果有程序需要使用路径作为某些数据的索引,但又允许用户随意输入路径时,就需要使用该方法保证路径的唯一性</p>
</blockquote>
</li>
</ul>
<p><span style="color:red"> 注意:</span>标准化之后的路径里的斜杠在Windows系统下是<code>\</code>,而在Linux系统下是<code>/</code>。如果想保证任何系统下都使用<code>/</code>作为路径分隔符的话,需要用<code>.replace(/\\/g, '/')</code>再替换一下标准路径</p>
<ul>
<li><p>path.join<br>将传入的多个路径拼接为标准路径,如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">path.join(<span class="string">'foo/'</span>, <span class="string">'baz/'</span>, <span class="string">'../bar'</span>); <span class="comment">// => "foo/bar"</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>该方法可避免手工拼接路径字符串的繁琐,并且能在不同系统下正确使用相应的路径分隔符</p>
</blockquote>
</li>
<li><p>path.extname<br>返回文件扩展名,如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">path.extname(<span class="string">'foo/bar.js'</span>); <span class="comment">//=>".js"</span></span><br></pre></td></tr></table></figure>
</li>
</ul>
<blockquote>
<p>官方文档:<a href="http://nodejs.org/api/path.html" target="_blank" rel="noopener">http://nodejs.org/api/path.html</a></p>
</blockquote>
<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>目录是一个树状结构,在遍历时一般使用<strong>深度优先+先序遍历</strong>算法</p>
<ul>
<li>深度优先:到达一个节点时,下一步先遍历其子节点,子节点遍历结束后才遍历邻居节点</li>
<li>先序遍历:首次到达某节点就访问该节点,而不是等到返回节点时才访问</li>
</ul>
<h3 id="同步遍历"><a href="#同步遍历" class="headerlink" title="同步遍历"></a>同步遍历</h3><p>掌握必要算法之后,可以简单实现目录遍历,如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">travel</span>(<span class="params">dir, callback</span>)</span>{</span><br><span class="line"> fs.readdirSync(dir).forEach(<span class="function"><span class="keyword">function</span> (<span class="params">file</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> pathname = path.join(dir, file);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span>(fs.statSync(pathname).isDirectory()){</span><br><span class="line"> travel(pathname, callback);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> callback(pathname);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>该函数以某个目录作为遍历起点,遇到子目录先遍历子目录,遇到文件就将文件的绝对路径传给回调函数,回调函数拿到文件路径后就可以进行处理;<br>假设有以下目录:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">- /home/user/</span><br><span class="line"> - foo/</span><br><span class="line"> x.js</span><br><span class="line"> - bar/</span><br><span class="line"> y.js</span><br><span class="line"> z.css</span><br></pre></td></tr></table></figure>
<p>使用以下代码遍历该目录时,得到的输入如下:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">travel('/home/user', function (pathname) {</span><br><span class="line"> console.log(pathname);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">------------------------</span><br><span class="line">/home/user/foo/x.js</span><br><span class="line">/home/user/bar/y.js</span><br><span class="line">/home/user/z.css</span><br></pre></td></tr></table></figure>
<h3 id="异步遍历"><a href="#异步遍历" class="headerlink" title="异步遍历"></a>异步遍历</h3><p>若读取目录/读取状态文件时使用异步API,实现如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">travel</span>(<span class="params">dir, callback, finish</span>) </span>{</span><br><span class="line"> fs.readdir(dir, <span class="function"><span class="keyword">function</span> (<span class="params">err, files</span>) </span>{</span><br><span class="line"> (<span class="function"><span class="keyword">function</span> <span class="title">next</span>(<span class="params">i</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (i < files.length) {</span><br><span class="line"> <span class="keyword">var</span> pathname = path.join(dir, files[i]);</span><br><span class="line"></span><br><span class="line"> fs.stat(pathname, <span class="function"><span class="keyword">function</span> (<span class="params">err, stats</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (stats.isDirectory()) {</span><br><span class="line"> travel(pathname, callback, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> next(i + <span class="number">1</span>);</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> callback(pathname, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> next(i + <span class="number">1</span>);</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> finish && finish();</span><br><span class="line"> }</span><br><span class="line"> }(<span class="number">0</span>));</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这里不详细介绍异步遍历函数的编写技巧,在后续章节中会详细介绍这个。总之我们可以看到异步编程还是蛮复杂的</p>
<h2 id="文本编码"><a href="#文本编码" class="headerlink" title="文本编码"></a>文本编码</h2><p>使用 Node.js 编写前端工具时,经常操作文本文件,故时常涉及文件编码的处理问题。<br>常用的文件编码有<code>GBK</code>和<code>UTF-8</code>编码,<code>UTF-8</code>编码可能带有<code>BOM</code>。再读取不同编码的文本文件时,需要将文件内容转换为JS用的<code>UTF-8</code>编码字符串后才能正常处理。</p>
<h3 id="BOM移除"><a href="#BOM移除" class="headerlink" title="BOM移除"></a>BOM移除</h3><p>BOM(<em>Byte Order Mark</em>),字节顺序标记,出现在文本文件头部,Unicode编码标准中用于标识文件是采用哪种格式的编码,详情参见<a href="https://baike.baidu.com/item/BOM/2790364#viewPageContent" target="_blank" rel="noopener">百度百科</a></p>
<p>在不同Unicode编码下,BOM字符对应格式如下:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line"> Bytes Encoding</span><br><span class="line">----------------------------</span><br><span class="line"> FE FF UTF16BE</span><br><span class="line"> FF FE UTF16LE</span><br><span class="line"> EF BB BF UTF8</span><br></pre></td></tr></table></figure>
<p>某些场景下,在读取文件时不去掉BOM会导致出错:例如将几个JS文件合并成一个文件后,如果文件中间含有BOM字符,就会导致浏览器JS语法错误。以下代码实现了识别和去除UTF-8 BOM的功能</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">readText</span>(<span class="params">pathname</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> bin = fs.readFileSync(pathname);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (bin[<span class="number">0</span>] === <span class="number">0xEF</span> && bin[<span class="number">1</span>] === <span class="number">0xBB</span> && bin[<span class="number">2</span>] === <span class="number">0xBF</span>) {</span><br><span class="line"> bin = bin.slice(<span class="number">3</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> bin.toString(<span class="string">'utf-8'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="GBK转UTF-8"><a href="#GBK转UTF-8" class="headerlink" title="GBK转UTF-8"></a>GBK转UTF-8</h3><p>Node.js 支持在读取文本文件时,或者在Buffer转换为字符串时指定文本编码,但GBK编码不在Node.js自身支持范围内。一般需要借助三方包<code>iconv-lite</code>实现功能,如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> iconv = <span class="built_in">require</span>(<span class="string">'iconv-lite'</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">readGBKText</span>(<span class="params">pathname</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> bin = fs.readFileSync(pathname);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> iconv.decode(bin, <span class="string">'gbk'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="单字节编码"><a href="#单字节编码" class="headerlink" title="单字节编码"></a>单字节编码</h3><p>有时候,我们无法预知需要读取的文件采用哪种编码,因此也就无法指定正确的编码。比如我们要处理的某些CSS文件中,有的用GBK编码,有的用UTF8编码。虽然可以一定程度可以根据文件的字节内容猜测出文本编码,但这里要介绍的是有些局限,但是要简单得多的一种技术。</p>
<p>首先我们知道,如果一个文本文件只包含英文字符,比如<code>Hello World</code>,那无论用GBK编码或是UTF8编码读取这个文件都是没问题的。这是因为在这些编码下,ASCII0~128范围内字符都使用相同的单字节编码。</p>
<p>反过来讲,即使一个文本文件中有中文等字符,如果我们需要处理的字符仅在ASCII0~128范围内,比如除了注释和字符串以外的JS代码,我们就可以统一使用单字节编码来读取文件,不用关心文件的实际编码是GBK还是UTF8。以下示例说明了这种方法。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">1. GBK编码源文件内容:</span><br><span class="line"> var foo = '中文';</span><br><span class="line">2. 对应字节:</span><br><span class="line"> 76 61 72 20 66 6F 6F 20 3D 20 27 D6 D0 CE C4 27 3B</span><br><span class="line">3. 使用单字节编码读取后得到的内容:</span><br><span class="line"> var foo = '{乱码}{乱码}{乱码}{乱码}';</span><br><span class="line">4. 替换内容:</span><br><span class="line"> var bar = '{乱码}{乱码}{乱码}{乱码}';</span><br><span class="line">5. 使用单字节编码保存后对应字节:</span><br><span class="line"> 76 61 72 20 62 61 72 20 3D 20 27 D6 D0 CE C4 27 3B</span><br><span class="line">6. 使用GBK编码读取后得到内容:</span><br><span class="line"> var bar = '中文';</span><br></pre></td></tr></table></figure>
<p>这里的诀窍在于,不管大于0xEF的单个字节在单字节编码下被解析成什么乱码字符,使用同样的单字节编码保存这些乱码字符时,背后对应的字节保持不变。</p>
<p>NodeJS中自带了一种<code>binary</code>编码可以用来实现这个方法,因此在下例中,我们使用这种编码来演示上例对应的代码该怎么写。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">function replace(pathname) {</span><br><span class="line"> var str = fs.readFileSync(pathname, 'binary');</span><br><span class="line"> str = str.replace('foo', 'bar');</span><br><span class="line"> fs.writeFileSync(pathname, str, 'binary');</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>node.js</tag>
</tags>
</entry>
<entry>
<title>vue+SpringBoot前后端分离开发</title>
<url>/2022/02/20/vue-SpringBoot%E5%89%8D%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB%E5%BC%80%E5%8F%91/</url>
<content><![CDATA[<p>本文介绍前后端分离开发的基本环境搭建</p>
<a id="more"></a>
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>最近在弄学校课程的前端图像处理项目,想着正好把vue和前后端分离这方面的东西顺便学一下,于是打算拿这个项目练手。</p>
<h2 id="项目地址"><a href="#项目地址" class="headerlink" title="项目地址"></a>项目地址</h2><p>前后端项目均在GitHub上开源,地址如下:</p>
<blockquote>
<p>前端:<a href="https://github.com/Table-the-cat/webip" target="_blank" rel="noopener">https://github.com/Table-the-cat/webip</a><br>后端:<a href="https://github.com/Table-the-cat/webip-server" target="_blank" rel="noopener">https://github.com/Table-the-cat/webip-server</a></p>
</blockquote>
<h2 id="SpringBoot项目"><a href="#SpringBoot项目" class="headerlink" title="SpringBoot项目"></a>SpringBoot项目</h2><h3 id="搭建SpringBoot项目"><a href="#搭建SpringBoot项目" class="headerlink" title="搭建SpringBoot项目"></a>搭建SpringBoot项目</h3><p>访问网页 <a href="http://start.spring.io/" target="_blank" rel="noopener">http://start.spring.io/</a> ,填写必要信息后下载项目压缩包。</p>
<blockquote>
<p>Project:选择使用Maven还是Gradle来构建项目<br>Language:选择项目使用的语言<br>SpringBoot:选择SpringBoot版本</p>
<p>Project Metadata:</p>
<blockquote>
<p>Group:项目组织唯一的标识符,实际对应项目中的package包<br>Artifect:项目的唯一的标识符,实际对应项目的project name名称,Artifact不可包含大写字母<br>Name:项目名<br>Description:项目描述</p>
</blockquote>
</blockquote>
<p>解压后用IDEA打开项目,IDEA会自动扫描pom文件并开始安装依赖,也可以通过<code>Maven->Lifecycle->install</code>来手动安装依赖。</p>
<h3 id="基本准备"><a href="#基本准备" class="headerlink" title="基本准备"></a>基本准备</h3><p>因为要做前后端分离,在SpringBoot项目里做的自然是纯后端的工作,主要有以下方面的准备:</p>
<ul>
<li>数据库</li>
<li>数据库访问框架(本项目使用MyBatis)</li>
<li>定义与数据库数据对应的实体类(pojo层)</li>
<li>编写mapper文件,包括<code>resource/mapper</code>路径下的xml文件和<code>java/com/table/webip/mapper</code>路径下的java文件(dao层)</li>
<li>编写对应的service和serviceImpl文件(service层)</li>
<li>编写controller文件(controller层)</li>
</ul>
<p>准备完毕之后,需要修改配置文件<code>application.properties</code>,为项目配置数据源和MyBatis:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">## 数据源配置</span><br><span class="line">spring.datasource.url=jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false</span><br><span class="line">spring.datasource.username=root</span><br><span class="line">spring.datasource.password=jiang</span><br><span class="line">spring.datasource.driver-class-name=com.mysql.jdbc.Driver</span><br><span class="line"></span><br><span class="line">## Mybatis 配置</span><br><span class="line"># 配置为 com.pancm.bean 指向实体类包路径。</span><br><span class="line">mybatis.type-aliases-package=com.table.webip</span><br><span class="line"># 配置为 classpath 路径下 mapper 包下,* 代表会扫描所有 xml 文件,如果不配置该项,项目将扫描不到mapper文件。</span><br><span class="line">mybatis.mapper-locations=classpath:mapper/*.xml</span><br><span class="line">#mybatis.configuration.map-underscore-to-camel-case=true </span><br><span class="line">spring.aop.auto=true</span><br></pre></td></tr></table></figure>
<h3 id="试运行"><a href="#试运行" class="headerlink" title="试运行"></a>试运行</h3><p>运行<code>WebipApplication.java</code>文件,启动项目,项目默认运行在8080端口上。</p>
<h2 id="Vue项目"><a href="#Vue项目" class="headerlink" title="Vue项目"></a>Vue项目</h2><h3 id="搭建Vue项目"><a href="#搭建Vue项目" class="headerlink" title="搭建Vue项目"></a>搭建Vue项目</h3><p>首先列出大致步骤:</p>
<ul>
<li>安装node.js</li>
<li>安装webpack</li>
<li>安装vue-cli</li>
<li>初始化Vue项目</li>
<li>试运行</li>
</ul>
<h4 id="安装node-js"><a href="#安装node-js" class="headerlink" title="安装node.js"></a>安装node.js</h4><p>在官网下载安装即可。<br>使用<code>node -v</code>指令检查是否安装成功,输出版本号即为安装成功。</p>
<h4 id="安装webpack"><a href="#安装webpack" class="headerlink" title="安装webpack"></a>安装webpack</h4><p>webpack是一款开源的前端打包工具。<br>使用<code>cnpm install webpack -g</code>指令进行全局安装,安装完成之后输入 <code>webpack -v</code>检查是否安装成功。</p>
<h4 id="安装vue-cli"><a href="#安装vue-cli" class="headerlink" title="安装vue-cli"></a>安装vue-cli</h4><p>使用<code>cnpm install vue-cli -g</code>指令进行全局安装,安装完成之后输入 <code>vue -V</code>(注意<strong>大写</strong>)检查是否安装成功。</p>
<h4 id="初始化Vue项目"><a href="#初始化Vue项目" class="headerlink" title="初始化Vue项目"></a>初始化Vue项目</h4><p>在想创建项目的文件夹中右键,在右键菜单中选择Git Bash Here,打开命令行窗口,输入指令<code>vue init webpack project_name</code>,用project_name处替换为自己的项目名。<br>创建好之后进入该项目文件夹,在项目路径下输入指令<code>npm install</code>安装项目所需依赖。</p>
<h4 id="试运行-1"><a href="#试运行-1" class="headerlink" title="试运行"></a>试运行</h4><p>输入指令<code>npm run dev</code>启动项目,如果此时你的后端SpringBoot项目仍在运行,则该项目应该会运行在8081端口上。访问localhost:8081便可以看见vue自动生成的初始界面。</p>
<h2 id="整合"><a href="#整合" class="headerlink" title="整合"></a>整合</h2><p>当前后端项目运行在8080端口,而前端项目运行在8081端口,前端需要设置代理才能正常向后端发送请求。</p>
<h3 id="main-js"><a href="#main-js" class="headerlink" title="main.js"></a>main.js</h3><p>加入如下代码:</p>
<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 设置全局的axios</span></span><br><span class="line">Vue.prototype.$axios=Axios</span><br><span class="line"><span class="comment">// 设置基本路径</span></span><br><span class="line">Axios.defaults.baseURL=<span class="string">'/api'</span></span><br></pre></td></tr></table></figure>
<h3 id="config-index-js"><a href="#config-index-js" class="headerlink" title="config/index.js"></a>config/index.js</h3><p>将proxyTable的内容替换为如下代码:</p>
<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line">proxyTable: {</span><br><span class="line"> <span class="string">'/api'</span>:{</span><br><span class="line"> target:<span class="string">"http://localhost:8080/"</span>,</span><br><span class="line"> changeOrigin:<span class="literal">true</span>,</span><br><span class="line"> pathRewrite:{</span><br><span class="line"> <span class="string">'^/api'</span>:<span class="string">''</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">},</span><br></pre></td></tr></table></figure>
<p>此时便可以正常向后端发送请求。</p>
]]></content>
<tags>
<tag>vue</tag>
<tag>SpringBoot</tag>
<tag>前后端分离开发</tag>
</tags>
</entry>
<entry>
<title>node.js学习_4</title>
<url>/2020/03/18/node-js%E5%AD%A6%E4%B9%A0-4/</url>
<content><![CDATA[<p>Node.js 学习之四:网络操作</p>
<p>本文参考:<a href="http://nqdeng.github.io/7-days-nodejs" target="_blank" rel="noopener">http://nqdeng.github.io/7-days-nodejs</a></p>
<a id="more"></a>
<h1 id="网络操作"><a href="#网络操作" class="headerlink" title="网络操作"></a>网络操作</h1><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p>Node.js 本来的用途是编写高性能Web服务器。我妈可以使用其内置的<code>http</code>模块简单实现一个HTTP服务器:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"></span><br><span class="line">http.createServer(<span class="function"><span class="keyword">function</span> (<span class="params">request, respons</span>)</span>{</span><br><span class="line"> respons.writeHead(<span class="number">200</span>, {<span class="string">'Content-Type'</span>, <span class="string">'text-plain'</span>});</span><br><span class="line"> respons.end(<span class="string">'Hello World\n'</span>);</span><br><span class="line">}).listen(<span class="number">8124</span>);</span><br></pre></td></tr></table></figure>
<blockquote>
<p>以上程序创建了一个HTTP服务器并监听<code>8124</code>端口,打开浏览器访问该端口<code>http://127.0.0.1:8124/</code> 就能够看到效果</p>
</blockquote>
]]></content>
<tags>
<tag>node.js</tag>
</tags>
</entry>
<entry>
<title>Hello World</title>
<url>/2020/02/12/hello-world/</url>
<content><![CDATA[<p>Welcome to <a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/" target="_blank" rel="noopener">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="noopener">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues" target="_blank" rel="noopener">GitHub</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="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="noopener">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="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="noopener">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="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="noopener">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="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/one-command-deployment.html" target="_blank" rel="noopener">Deployment</a></p>
]]></content>
</entry>
<entry>
<title>从零开始的Flutter开发_Day2</title>
<url>/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-Day2/</url>
<content><![CDATA[<p>主要内容为如何在Flutter中构建布局,参照<a href="https://flutterchina.club/tutorials/layout/" target="_blank" rel="noopener">Flutter中文网</a></p>
<a id="more"></a>
<h1 id="在Flutter中构建布局"><a href="#在Flutter中构建布局" class="headerlink" title="在Flutter中构建布局"></a>在Flutter中构建布局</h1><h2 id="Flutter的布局方法"><a href="#Flutter的布局方法" class="headerlink" title="Flutter的布局方法"></a>Flutter的布局方法</h2><blockquote>
<p><strong>重点</strong></p>
</blockquote>
<pre><code>- Widget 是用于构建 UI 的类
- Widget 用于布局和 UI 元素
- 通过简单的 Widget 构建复杂的 Widget</code></pre><p>Flutter 布局机制的<strong>核心</strong>就是 <em>Widget</em>;<br>在 Flutter 中,几乎所有东西都是一个 Widget;</p>
<blockquote>
<p>您在Flutter应用中看到的图像、图标和文本都是widget。 甚至你看不到的东西也是widget,例如行(row)、列(column)以及用来排列、约束和对齐这些可见widget的网格(grid)</p>
</blockquote>
<h3 id="复杂的widget"><a href="#复杂的widget" class="headerlink" title="复杂的widget"></a>复杂的widget</h3><p>如下图,有三个图标,每个图标下有一个标签:<br><img src="/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-Day2/icons.png" alt="Example"><br>该截图显示布局结构,表示一行包括3列,其中每列包含一个图标(icon)和一个标签</p>
<blockquote>
<p>如果希望显示布局情况,需要以下两步:<br>1.在<code>mian.dart</code>中声明引入包<code>import 'package:flutter/rendering.dart';</code><br>2.在主函数中声明<code>debugPaintSizeEnabled = true;</code></p>
</blockquote>
<p>上述 UI 的 widget 树如下:<br><img src="/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-Day2/layoutTree.png" alt="widgetTree"></p>
<h3 id="Container"><a href="#Container" class="headerlink" title="Container"></a>Container</h3><p>上图很容易理解,但是 Container 的存在可能令人疑惑</p>
<h4 id="Container是什么"><a href="#Container是什么" class="headerlink" title="Container是什么"></a>Container是什么</h4><p><code>Container</code> 可让您创建矩形视觉元素。<code>Container</code> 可以装饰为一个<code>BoxDecoration</code>, 如 background、一个边框、或者一个阴影。 <code>Container</code> 也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, <code>Container</code> 可以使用矩阵在三维空间中对其进行变换</p>
<h4 id="为什么要有Container"><a href="#为什么要有Container" class="headerlink" title="为什么要有Container"></a>为什么要有Container</h4><p>如果要添加<em>填充,边距,边框或背景色</em>,要使用 Container 来设置,<strong>只有 Container 有这些属性</strong></p>
<blockquote>
<p>在这个例子中,每个 Text 放置在 Container 中以添加边距。整个行也被放置在容器中以在行的周围添加填充</p>
</blockquote>
<h2 id="布局一个Widget"><a href="#布局一个Widget" class="headerlink" title="布局一个Widget"></a>布局一个Widget</h2><p>在 Flutter 中,在屏幕上放置文本,图标或图像大致需要以下几个步骤</p>
<h4 id="选择一个widget保存该对象"><a href="#选择一个widget保存该对象" class="headerlink" title="选择一个widget保存该对象"></a>选择一个widget保存该对象</h4><p>从<em>布局 widget</em> 中进行选择,<em>布局 widget</em> 用于排列其它 widget 的 columns、rows、grids 和其它的 layouts。</p>
<h4 id="创建一个widget来容纳可见对象"><a href="#创建一个widget来容纳可见对象" class="headerlink" title="创建一个widget来容纳可见对象"></a>创建一个widget来容纳可见对象</h4><p>例如,创建一个 <strong>Text widget</strong>:</p>
<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="keyword">new</span> Text(<span class="string">'Hello World'</span>, style: <span class="keyword">new</span> TextStyle(fontSize: <span class="number">32.0</span>))</span><br></pre></td></tr></table></figure>
<p>创建一个 <strong>Text widget</strong>:</p>
<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="keyword">new</span> Image.asset(<span class="string">'foo/bar.jpg'</span>, fit: BoxFit.cover)</span><br></pre></td></tr></table></figure>
<p>创建一个 <strong>Text widget</strong>:</p>
<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="keyword">new</span> Icon(Icons.star, color: Colors.red[<span class="number">500</span>])</span><br></pre></td></tr></table></figure>
<h4 id="将可见widget添加到布局widget"><a href="#将可见widget添加到布局widget" class="headerlink" title="将可见widget添加到布局widget"></a>将可见widget添加到布局widget</h4><p><em>布局 widget</em> 都含有<code>child</code>属性(如<em>Container、Padding、Center</em>)或者<code>children</code>属性(如<em>Row、Column、Stack</em>),子 widget 会继承<em>布局 widget</em>的某些属性以实现布局效果</p>
<p>将Text widget添加到Center widget:</p>
<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="keyword">new</span> Center(</span><br><span class="line"> child: <span class="keyword">new</span> Text(<span class="string">'Hello World'</span>, style: <span class="keyword">new</span> TextStyle(fontSize: <span class="number">32.0</span>))</span><br><span class="line"> )</span><br></pre></td></tr></table></figure>
<blockquote>
<p>Center可以将内容水平和垂直居中</p>
</blockquote>
<h4 id="将布局widget添加到页面"><a href="#将布局widget添加到页面" class="headerlink" title="将布局widget添加到页面"></a>将布局widget添加到页面</h4><p><strong>Flutter 本身就是一个 widget</strong>,大部分widget都有一个<code>build()</code>方法。在应用程序的<code>build()</code>方法中创建会在设备上显示的widget。<br>对于Material应用程序,我们可以将<em>Center widget</em>直接添加到<code>body</code>属性中:</p>
<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">_MyHomePageState</span> <span class="keyword">extends</span> <span class="title">State</span><<span class="title">MyHomePage</span>> </span>{</span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> Widget build(BuildContext context) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Scaffold(</span><br><span class="line"> appBar: <span class="keyword">new</span> appBar(</span><br><span class="line"> title: <span class="keyword">new</span> Text(widget.title),</span><br><span class="line"> ),</span><br><span class="line"> body: <span class="keyword">new</span> Center(</span><br><span class="line"> child: <span class="keyword">new</span> Text(<span class="string">'Hello World'</span>, style: <span class="keyword">new</span> TextStyle(fontSize: <span class="number">32.0</span>)),</span><br><span class="line"> ),</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>对于非Material应用程序,我们可以将Center widget添加到应用程序的<code>build()</code>方法中:</p>
<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line"> runApp(<span class="keyword">new</span> MyApp());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyApp</span> <span class="keyword">extends</span> <span class="title">StatelessWidget</span> </span>{</span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> Widget build(BuildContext context) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Container(</span><br><span class="line"> decoration: <span class="keyword">new</span> BoxDecoration(color: Colors.white),</span><br><span class="line"> child: <span class="keyword">new</span> Center(</span><br><span class="line"> <span class="string">'Hello World'</span>,</span><br><span class="line"> textDirection: TextDirection.ltr,</span><br><span class="line"> style: <span class="keyword">new</span> TextStyle(fontSize: <span class="number">40.0</span>, color: Colors.black87)),</span><br><span class="line"> ),</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p><span style="color: red">注意:</span> 默认情况下,<strong>非Material应用程序不包含AppBar,标题或背景颜色</strong>。 如果要在非Material应用程序中使用这些功能,则必须自己构建它们</p>
</blockquote>
<h2 id="垂直-水平放置多个widget"><a href="#垂直-水平放置多个widget" class="headerlink" title="垂直/水平放置多个widget"></a>垂直/水平放置多个widget</h2><p>最常见的布局模式<br>1.使用行 <code>Row</code> <strong>水平</strong>排列widget;<br>2.使用列 <code>Column</code> <strong>垂直</strong>排列widget</p>
<p>将一个 <em>widget 列表</em>添加到<code>Row</code>中以创建行,添加到<code>Column</code>中以创建列</p>
<blockquote>
<p>“添加”到<code>Row</code>/<code>Column</code>中,即将 widget 列表添加至<code>children</code>属性中</p>
</blockquote>
<p>每个孩子本身可以是一个<code>Row</code>或一个<code>Column</code>,依此类推<br>以下示例显示如何在行或列内嵌套行或列:<br><img src="/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-Day2/pavlova-diagram.png" alt="Row/Col Nest"></p>
<h3 id="对齐widgets"><a href="#对齐widgets" class="headerlink" title="对齐widgets"></a>对齐widgets</h3><p>利用<code>mainAxisAlignment</code>和<code>crossAxisAlignment</code>属性来对其行/列的子项,这两个属性在<code>Row</code>/<code>Column</code>中的体现如下图:<br><img src="/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-Day2/row-diagram.png" alt="row">)<img src="/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-Day2/column-diagram.png" alt="col"><br><a href="https://api.flutter.dev/flutter/rendering/MainAxisAlignment-class.html" target="_blank" rel="noopener">MainAxisAlignment</a> 和 <a href="https://api.flutter.dev/flutter/rendering/CrossAxisAlignment-class.html" target="_blank" rel="noopener">CrossAxisAlignment</a> 类提供了很多控制对齐的常量</p>
<h4 id="MainAxisAlignment中的常量"><a href="#MainAxisAlignment中的常量" class="headerlink" title="MainAxisAlignment中的常量"></a>MainAxisAlignment中的常量</h4><ul>
<li>center:子项居中排列,<strong>子项之间没有间隔</strong></li>
<li>end:子项靠近 main axis 的末尾排列</li>
<li>spaceAround:子项间的间隔相等,且第一个子项之前和最后一个子项之后的间隔是子项间间隔的一半</li>
<li>spaceBetween:子项间间隔相等,<strong>第一个子项之前和最后一个子项之后没有间隔</strong></li>
<li>spaceEvenly:在每个子项之间,之前和之后均匀分配空闲的空间</li>
<li>start:子项靠近 main axis 的起始排列</li>
</ul>
<h4 id="CrossAxisAlignment中的常量"><a href="#CrossAxisAlignment中的常量" class="headerlink" title="CrossAxisAlignment中的常量"></a>CrossAxisAlignment中的常量</h4><ul>
<li>center:子项的中线与 cross axis 的<strong>中点</strong>对齐,<em>这是默认的对齐方式</em></li>
<li>start:子项靠近 cross axis 的起始排列</li>
<li>end:子项靠近 cross axis 的末尾排列</li>
<li>stretch:要求子项填满整个 cross axis</li>
</ul>
<h4 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h4><ol>
<li><p>一列三个图片以<code>spaceBetween</code>方式对齐<br>代码如下:</p>
<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line">Widget build(BuildContext context) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> MaterialApp(</span><br><span class="line"> title: <span class="string">'Test'</span>,</span><br><span class="line"> theme: <span class="keyword">new</span> ThemeData(primarySwatch: Colors.blue),</span><br><span class="line"> home: <span class="keyword">new</span> Scaffold(</span><br><span class="line"> appBar: <span class="keyword">new</span> AppBar(title: <span class="keyword">new</span> Text(<span class="string">'Alignment Test'</span>),),</span><br><span class="line"> body: <span class="keyword">new</span> Center(</span><br><span class="line"> child: <span class="keyword">new</span> Column(</span><br><span class="line"> mainAxisAlignment: MainAxisAlignment.spaceBetween,</span><br><span class="line"> children: <Widget>[</span><br><span class="line"> <span class="keyword">new</span> Image.asset(<span class="string">'images/01.jpg'</span>),</span><br><span class="line"> <span class="keyword">new</span> Image.asset(<span class="string">'images/02.jpg'</span>),</span><br><span class="line"> <span class="keyword">new</span> Image.asset(<span class="string">'images/03.jpg'</span>),</span><br><span class="line"> ],</span><br><span class="line"> ),</span><br><span class="line"> ),</span><br><span class="line"> ),</span><br><span class="line"> );</span><br></pre></td></tr></table></figure>
<p>运行结果如图:<br><img src="/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-Day2/result1.jpg" alt="result1"></p>
<blockquote>
<p>可以看到:第一张图之前和最后一张图之后没有留下空间</p>
</blockquote>
</li>
<li><p>一行三个图片以<code>spaceEvenly</code>方式对齐<br>代码与上方大同小异,运行结果如图:<br><img src="/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-Day2/result2.jpg" alt="result2"><br><span style="color: red">注意:</span>如果布局太大而不适合设备,则会如上图所示,在受影响的边缘出现黄黑条纹;<br>解决办法:使用 <em>Expanded widget</em>,将widget的大小设置为适和行或列</p>
<blockquote>
<p>后文会详细提及</p>
</blockquote>
</li>
</ol>
<h3 id="调整widget"><a href="#调整widget" class="headerlink" title="调整widget"></a>调整widget</h3><p>如果想要一个 widget 占据其兄弟 widget 两倍的空间,我们可以将行/列的子项放入<em>Expanded widget<em>中,以控制*</em>沿主轴 (main axis) 方向*<em>的 widget 大小<br>*Expanded widget</em>有一个<code>flex</code>属性(整数),用于确定 widget 的弹性系数,*默认为 1</em></p>
<h4 id="示例-1"><a href="#示例-1" class="headerlink" title="示例"></a>示例</h4><ol>
<li>创建一个由三个widget组成的行,其中中间widget的宽度是其他两个widget的两倍<br>代码:<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="keyword">new</span> Column(</span><br><span class="line"> mainAxisAlignment: MainAxisAlignment.spaceBetween,</span><br><span class="line"> children: <Widget>[</span><br><span class="line"> <span class="keyword">new</span> Expanded(child: <span class="keyword">new</span> Image.asset(<span class="string">'images/EVA_01.jpg'</span>)),</span><br><span class="line"> <span class="keyword">new</span> Expanded(</span><br><span class="line"> flex: <span class="number">2</span>,</span><br><span class="line"> child: <span class="keyword">new</span> Image.asset(<span class="string">'images/EVA_02.jpg'</span>)</span><br><span class="line"> ),</span><br><span class="line"> <span class="keyword">new</span> Expanded(child: <span class="keyword">new</span> Image.asset(<span class="string">'images/EVA_03.jpg'</span>)),</span><br><span class="line"> ],</span><br><span class="line">)</span><br></pre></td></tr></table></figure>
</li>
</ol>
<p>结果如图:<br><img src="/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-Day2/result3.jpg" alt="result3"></p>
<ol start="2">
<li>修复上一节中的示例:<br>将上一节代码中所有的<code>new Image.asset()</code>用<code>new Expanded()</code>包裹,如下:<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="keyword">new</span> Expanded(child: <span class="keyword">new</span> Image.asset(<span class="string">'images/01.jpg'</span>)</span><br></pre></td></tr></table></figure>
默认情况下,每个widget的弹性系数为1,将行的三分之一分配给每个小部件</li>
</ol>
<h3 id="聚集widgets"><a href="#聚集widgets" class="headerlink" title="聚集widgets"></a>聚集widgets</h3><p>默认情况下,行或列沿着其主轴会尽可能占用<strong>尽可能多的空间</strong>,但如果要将孩子紧密聚集在一起,可以将<code>mainAxisSize</code>设置为<code>MainAxisSize.min</code>,如下:</p>
<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> packedRow = <span class="keyword">new</span> Row(</span><br><span class="line"> mainAxisSize: MainAxisSize.min,</span><br><span class="line"> children: [</span><br><span class="line"> <span class="keyword">new</span> Icon(Icons.star, color: Colors.green[<span class="number">500</span>]),</span><br><span class="line"> <span class="keyword">new</span> Icon(Icons.star, color: Colors.green[<span class="number">500</span>]),</span><br><span class="line"> <span class="keyword">new</span> Icon(Icons.star, color: Colors.green[<span class="number">500</span>]),</span><br><span class="line"> <span class="keyword">new</span> Icon(Icons.star, color: Colors.black),</span><br><span class="line"> <span class="keyword">new</span> Icon(Icons.star, color: Colors.black),</span><br><span class="line"> ],</span><br><span class="line"> );</span><br></pre></td></tr></table></figure>
<h3 id="行-列的嵌套"><a href="#行-列的嵌套" class="headerlink" title="行/列的嵌套"></a>行/列的嵌套</h3><p><img src="/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-Day2/nest-result.png" alt="nest1"><br>实现图中红框部分的布局</p>
<ol>
<li><p>构造<em>评分行</em>的widget树:<br><img src="/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-Day2/nest-widget-tree.png" alt="widgetTree"><br>根据该树编写代码如下:</p>
<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> ratingRow = <span class="keyword">new</span> Container(</span><br><span class="line"> padding: <span class="keyword">new</span> EdgeInsets.all(<span class="number">20.0</span>),</span><br><span class="line"> child: <span class="keyword">new</span> Row(</span><br><span class="line"> mainAxisAlignment: MainAxisAlignment.spaceEvenly,</span><br><span class="line"> children: <Widget>[</span><br><span class="line"> <span class="keyword">new</span> Row(</span><br><span class="line"> mainAxisSize: MainAxisSize.min,</span><br><span class="line"> children: [</span><br><span class="line"> <span class="keyword">new</span> Icon(Icons.star, color: Colors.black),</span><br><span class="line"> <span class="keyword">new</span> Icon(Icons.star, color: Colors.black),</span><br><span class="line"> <span class="keyword">new</span> Icon(Icons.star, color: Colors.black),</span><br><span class="line"> <span class="keyword">new</span> Icon(Icons.star, color: Colors.black),</span><br><span class="line"> <span class="keyword">new</span> Icon(Icons.star, color: Colors.black),</span><br><span class="line"> ],</span><br><span class="line"> ),</span><br><span class="line"> <span class="keyword">new</span> Text(</span><br><span class="line"> <span class="string">'114514 Reviews'</span>,</span><br><span class="line"> style: <span class="keyword">new</span> TextStyle(</span><br><span class="line"> color: Colors.black,</span><br><span class="line"> fontWeight: FontWeight.w800,</span><br><span class="line"> fontFamily: <span class="string">'Roboto'</span>,</span><br><span class="line"> letterSpacing: <span class="number">0.5</span>,</span><br><span class="line"> fontSize: <span class="number">20.0</span>,</span><br><span class="line"> ),</span><br><span class="line"> ),</span><br><span class="line"> ],</span><br><span class="line"> ),</span><br><span class="line"> );</span><br></pre></td></tr></table></figure></li>
<li><p>构造评分行下方<em>图标行</em>的widget树:<br><img src="/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-Day2/nest-widget-tree2.png" alt="widgetTree2"><br>根据该树编写代码如下:</p>
<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> descTextStyle = <span class="keyword">new</span> TextStyle(</span><br><span class="line"> color: Colors.black,</span><br><span class="line"> fontWeight: FontWeight.w800,</span><br><span class="line"> fontFamily: <span class="string">'Roboto'</span>,</span><br><span class="line"> letterSpacing: <span class="number">0.5</span>,</span><br><span class="line"> fontSize: <span class="number">18.0</span>,</span><br><span class="line"> height: <span class="number">2.0</span>,</span><br><span class="line"> );<span class="comment">//定义默认Text样式</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> iconRow = DefaultTextStyle.merge(</span><br><span class="line"> <span class="comment">// DefaultTextStyle.merge可以允许您创建一个默认的文本样式</span></span><br><span class="line"> <span class="comment">//该样式会被其所有的子节点继承</span></span><br><span class="line"> style: descTextStyle,</span><br><span class="line"> child: <span class="keyword">new</span> Container(</span><br><span class="line"> padding: <span class="keyword">new</span> EdgeInsets.all(<span class="number">20.0</span>),</span><br><span class="line"> child: <span class="keyword">new</span> Row(</span><br><span class="line"> mainAxisAlignment: MainAxisAlignment.spaceEvenly,</span><br><span class="line"> children: [</span><br><span class="line"> <span class="keyword">new</span> Column(</span><br><span class="line"> children: [</span><br><span class="line"> <span class="keyword">new</span> Icon(Icons.kitchen, color: Colors.green[<span class="number">500</span>]),</span><br><span class="line"> <span class="keyword">new</span> Text(<span class="string">'PREP:'</span>),</span><br><span class="line"> <span class="keyword">new</span> Text(<span class="string">'25 min'</span>),</span><br><span class="line"> ],</span><br><span class="line"> ),</span><br><span class="line"> <span class="keyword">new</span> Column(</span><br><span class="line"> children: [</span><br><span class="line"> <span class="keyword">new</span> Icon(Icons.timer, color: Colors.green[<span class="number">500</span>]),</span><br><span class="line"> <span class="keyword">new</span> Text(<span class="string">'COOK:'</span>),</span><br><span class="line"> <span class="keyword">new</span> Text(<span class="string">'1 hr'</span>),</span><br><span class="line"> ],</span><br><span class="line"> ),</span><br><span class="line"> <span class="keyword">new</span> Column(</span><br><span class="line"> children: [</span><br><span class="line"> <span class="keyword">new</span> Icon(Icons.restaurant, color: Colors.green[<span class="number">500</span>]),</span><br><span class="line"> <span class="keyword">new</span> Text(<span class="string">'FEEDS:'</span>),</span><br><span class="line"> <span class="keyword">new</span> Text(<span class="string">'4-6'</span>),</span><br><span class="line"> ],</span><br><span class="line"> ),</span><br><span class="line"> ],</span><br><span class="line"> ),</span><br><span class="line"> ),</span><br><span class="line"> );</span><br></pre></td></tr></table></figure>
</li>
<li><p>整合<br>代码如下:</p>
<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> MaterialApp(</span><br><span class="line"> title: <span class="string">'Test'</span>,</span><br><span class="line"> theme: <span class="keyword">new</span> ThemeData(primarySwatch: Colors.blue),</span><br><span class="line"> home: <span class="keyword">new</span> Scaffold(</span><br><span class="line"> appBar: <span class="keyword">new</span> AppBar(title: <span class="keyword">new</span> Text(<span class="string">'Alignment Test'</span>),),</span><br><span class="line"> body: <span class="keyword">new</span> Center(</span><br><span class="line"> child: <span class="keyword">new</span> Container(</span><br><span class="line"> child: <span class="keyword">new</span> Column(</span><br><span class="line"> children: <Widget>[</span><br><span class="line"> ratingRow,</span><br><span class="line"> iconRow,</span><br><span class="line"> ],</span><br><span class="line"> ),</span><br><span class="line"> ),</span><br><span class="line"> ),</span><br><span class="line"> ),</span><br><span class="line"> );</span><br></pre></td></tr></table></figure>
<p>效果如图:<br><img src="/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-Day2/nest-result.jpg" alt="nestResult"></p>
</li>
</ol>
<h2 id="常用布局widgets"><a href="#常用布局widgets" class="headerlink" title="常用布局widgets"></a>常用布局widgets</h2><h3 id="标准widgets"><a href="#标准widgets" class="headerlink" title="标准widgets"></a>标准widgets</h3><blockquote>
<p><a href="https://api.flutter.dev/flutter/widgets/widgets-library.html" target="_blank" rel="noopener"> widgets-library</a> 中的 widget</p>
</blockquote>
<h4 id="Container-1"><a href="#Container-1" class="headerlink" title="Container"></a>Container</h4><p>添加 padding, margins, borders, background color, 或将其他装饰添加到widget</p>
<h4 id="GridView"><a href="#GridView" class="headerlink" title="GridView"></a>GridView</h4><h4 id="ListView"><a href="#ListView" class="headerlink" title="ListView"></a>ListView</h4><h4 id="Stack"><a href="#Stack" class="headerlink" title="Stack"></a>Stack</h4>]]></content>
<tags>
<tag>Android</tag>
<tag>Flutter</tag>
</tags>
</entry>
<entry>
<title>从零开始的Flutter开发_sp:FirstApp</title>
<url>/2020/05/04/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91-sp-FirstApp/</url>
<content><![CDATA[<p>按照官方网站的教程,完成了一个Flutter App的编写。先将代码贴出,待学成后尝试分析代码</p>
<a id="more"></a>
<h2 id="源码"><a href="#源码" class="headerlink" title="源码"></a>源码</h2><figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">'package:flutter/material.dart'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:english_words/english_words.dart'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> main() => runApp(MyApp());</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyApp</span> <span class="keyword">extends</span> <span class="title">StatelessWidget</span> </span>{</span><br><span class="line"> <span class="comment">// This widget is the root of your application.</span></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> Widget build(BuildContext context) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> MaterialApp(</span><br><span class="line"> title: <span class="string">'Welcome to Flutter'</span>,</span><br><span class="line"> theme: <span class="keyword">new</span> ThemeData(</span><br><span class="line"> primaryColor: Colors.blue,</span><br><span class="line"> ),</span><br><span class="line"> home: <span class="keyword">new</span> RandomWords(),</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RandomWordsState</span> <span class="keyword">extends</span> <span class="title">State</span><<span class="title">RandomWords</span>> </span>{</span><br><span class="line"> <span class="keyword">final</span> _suggestions = <WordPair>[];</span><br><span class="line"> <span class="keyword">final</span> _saved = <span class="keyword">new</span> <span class="built_in">Set</span><WordPair>();</span><br><span class="line"> <span class="keyword">final</span> _biggerFont = <span class="keyword">const</span> TextStyle(fontSize: <span class="number">18.0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> Widget build(BuildContext context) {</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> implement build</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Scaffold(</span><br><span class="line"> appBar: <span class="keyword">new</span> AppBar(</span><br><span class="line"> title: <span class="keyword">new</span> Text(<span class="string">'Startup Name Generator'</span>),</span><br><span class="line"> actions: <Widget>[</span><br><span class="line"> <span class="keyword">new</span> IconButton(</span><br><span class="line"> icon: <span class="keyword">new</span> Icon(Icons.list),</span><br><span class="line"> onPressed: _pushSaved,</span><br><span class="line"> ),</span><br><span class="line"> ],</span><br><span class="line"> ),</span><br><span class="line"> body: _buildSuggestions(),</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Widget _buildSuggestions() {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ListView.builder(</span><br><span class="line"> padding: <span class="keyword">const</span> EdgeInsets.all(<span class="number">16.0</span>),</span><br><span class="line"> itemBuilder: (context, i) {</span><br><span class="line"> <span class="keyword">if</span>(i.isOdd) <span class="keyword">return</span> <span class="keyword">new</span> Divider();</span><br><span class="line"> <span class="keyword">final</span> index = i ~/ <span class="number">2</span>;<span class="comment">// i/2 向下取整</span></span><br><span class="line"> <span class="keyword">if</span>(index >= _suggestions.length) {</span><br><span class="line"> _suggestions.addAll(generateWordPairs().take(<span class="number">10</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> _buildRow(_suggestions[index]);</span><br><span class="line"> }</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Widget _buildRow(WordPair pair) {</span><br><span class="line"> <span class="keyword">final</span> alreadySaved = _saved.contains(pair);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ListTile(</span><br><span class="line"> title: <span class="keyword">new</span> Text(</span><br><span class="line"> pair.asPascalCase,</span><br><span class="line"> style: _biggerFont,</span><br><span class="line"> ),</span><br><span class="line"> trailing: <span class="keyword">new</span> Icon(</span><br><span class="line"> alreadySaved ? Icons.favorite : Icons.favorite_border,</span><br><span class="line"> color: alreadySaved ? Colors.red : <span class="keyword">null</span>,</span><br><span class="line"> ),</span><br><span class="line"> onTap: () {</span><br><span class="line"> setState(() {</span><br><span class="line"> <span class="keyword">if</span>(alreadySaved) {</span><br><span class="line"> _saved.remove(pair);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> _saved.add(pair);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> },</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">void</span> _pushSaved() {</span><br><span class="line"> Navigator.of(context).push(</span><br><span class="line"> <span class="keyword">new</span> MaterialPageRoute(</span><br><span class="line"> builder: (context) {</span><br><span class="line"> <span class="keyword">final</span> tiles = _saved.map(</span><br><span class="line"> (pair) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ListTile(</span><br><span class="line"> title: <span class="keyword">new</span> Text(</span><br><span class="line"> pair.asPascalCase,</span><br><span class="line"> style: _biggerFont,</span><br><span class="line"> ),</span><br><span class="line"> );</span><br><span class="line"> },</span><br><span class="line"> );</span><br><span class="line"> <span class="keyword">final</span> divided = ListTile</span><br><span class="line"> .divideTiles(</span><br><span class="line"> context: context,</span><br><span class="line"> tiles: tiles,</span><br><span class="line"> )</span><br><span class="line"> .toList();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Scaffold(</span><br><span class="line"> appBar: <span class="keyword">new</span> AppBar(</span><br><span class="line"> title: <span class="keyword">new</span> Text(<span class="string">'Saved Suggestions'</span>),</span><br><span class="line"> ),</span><br><span class="line"> body: <span class="keyword">new</span> ListView(children: divided,),</span><br><span class="line"> );</span><br><span class="line"> },</span><br><span class="line"> ),</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RandomWords</span> <span class="keyword">extends</span> <span class="title">StatefulWidget</span> </span>{</span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> createState() => <span class="keyword">new</span> RandomWordsState();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
]]></content>
<tags>
<tag>Android</tag>
<tag>Flutter</tag>
</tags>
</entry>
<entry>
<title>使用Hexo发布博客添加图片的方法</title>
<url>/2020/05/05/%E4%BD%BF%E7%94%A8Hexo%E5%8F%91%E5%B8%83%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0%E5%9B%BE%E7%89%87%E7%9A%84%E6%96%B9%E6%B3%95/</url>
<content><</span><br><span class="line">or</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>这里的路径是<strong>图片文件在电脑内保存的相对路径</strong>,<code>hexo-asset-image</code>插件的作用就是将图片添加到博客源文件中,并重新设置好路径。在发布时,图片也会上传至GitHub仓库,这时网页便可以从GitHub仓库中链接到图片。</p>
<blockquote>
<p>注意<strong>不要</strong>使用<strong>反斜杠</strong>来划分,虽然在某些 Markdown 编辑器中(比如Typora)使用反斜杠的路径会被识别,但是在网页中它似乎并不能正确解析路径——结果就是图片在本地编辑器中可以显示,但在网页中,无论是 <code>localhost</code>上的预览页面还是真正的博客页面中,都无法显示</p>
</blockquote>
<h3 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h3><p>大致分为三步</p>
<h4 id="修改-congif-yml"><a href="#修改-congif-yml" class="headerlink" title="修改_congif.yml"></a>修改_congif.yml</h4><p>修改主页配置文件<code>_config.yml</code>中的选项</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">post_asset_folder: ture</span><br></pre></td></tr></table></figure>
<blockquote>
<p>该选项默认为<code>false</code>,修改为<code>true</code> </p>
</blockquote>
<h4 id="安装插件"><a href="#安装插件" class="headerlink" title="安装插件"></a>安装插件</h4><p>使用如下指令:</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">npm install https://github.com/CodeFalling/hexo-asset-image --save</span><br></pre></td></tr></table></figure>
<p>安装插件(0.0.5版本)<br><span style="color: red">注意:</span>不要使用<code>npm install hexo-asset-image --save</code>指令安装插件,该指令安装的是1.0.0版本的插件,该版本插件会导致图片加载不成功</p>
<h3 id="插入图片"><a href="#插入图片" class="headerlink" title="插入图片"></a>插入图片</h3><p>现在,在执行指令<code>hexo new postName</code>之后,不仅会生成一个postName.md的文件,还会在<code>_posts</code>文件夹中生成一个与博文同名的文件夹,用于存放这篇博文中需要引用的图片<br>在需要引用图片的地方,使用上文提到的语法插入图片即可</p>
<h2 id="使用图床外链"><a href="#使用图床外链" class="headerlink" title="使用图床外链"></a>使用图床外链</h2><h3 id="原理-1"><a href="#原理-1" class="headerlink" title="原理"></a>原理</h3><p>原理十分简单,先手动将图片上传至互联网,然后在文章中将图片链接进来</p>
<h3 id="使用方法-1"><a href="#使用方法-1" class="headerlink" title="使用方法"></a>使用方法</h3><p>使用方法今后补充</p>
]]></content>
<tags>
<tag>hexo</tag>
</tags>
</entry>
<entry>
<title>从零开始的Flutter开发_Day1</title>
<url>/2020/05/01/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Flutter%E5%BC%80%E5%8F%91_Day1/</url>
<content><![CDATA[<p>课设需要 + 个人兴趣,总之今天开始学习Flutter开发了<br>本章只讲解环境搭建和搭建中可能遇到的问题及其解决方法</p>
<a id="more"></a>
<h1 id="初见"><a href="#初见" class="headerlink" title="初见"></a>初见</h1><h2 id="了解Flutter"><a href="#了解Flutter" class="headerlink" title="了解Flutter"></a>了解Flutter</h2><blockquote>
<p>Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的<br>—— From <a href="https://flutterchina.club/" target="_blank" rel="noopener">Flutter中文网</a></p>
</blockquote>
<h2 id="在Windows上搭建Flutter开发环境"><a href="#在Windows上搭建Flutter开发环境" class="headerlink" title="在Windows上搭建Flutter开发环境"></a>在Windows上搭建Flutter开发环境</h2><h3 id="安装教程"><a href="#安装教程" class="headerlink" title="安装教程"></a>安装教程</h3><p><a href="https://flutterchina.club/setup-windows/" target="_blank" rel="noopener">点击这里</a>查看安装教程</p>
<h3 id="可能遇到的问题"><a href="#可能遇到的问题" class="headerlink" title="可能遇到的问题"></a>可能遇到的问题</h3><ul>
<li>Android Studio无法加载Plugins<br> 解决方法依照博文:<a href="https://blog.csdn.net/weixin_44325428/article/details/85336410?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1" target="_blank" rel="noopener">android studio 无法搜索插件、下载插件解决办法</a>(将pligins文件夹替换为2.x版本的,利用2.x版本依赖下载)</li>
</ul>
<ul>
<li>执行flutter doctor时报错<code>android license status unknown</code><br> 执行命令: <figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">flutter doctor --android-licenses</span><br></pre></td></tr></table></figure>
此时有可能再次报错: <figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line"> Exception <span class="keyword">in</span> thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema</span><br><span class="line"> <span class="built_in">at</span> com.android.repository.api.SchemaModule$SchemaModuleVersion.<init>(SchemaModule.java:<span class="number">156</span>)</span><br><span class="line"> <span class="built_in">at</span> com.android.repository.api.SchemaModule.<init>(SchemaModule.java:<span class="number">75</span>)</span><br><span class="line"> <span class="built_in">at</span> com.android.sdklib.repository.AndroidSdkHandler.<clinit>(AndroidSdkHandler.java:<span class="number">81</span>)</span><br><span class="line"> <span class="built_in">at</span> com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:<span class="number">73</span>)</span><br><span class="line"> <span class="built_in">at</span> com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:<span class="number">48</span>)</span><br><span class="line">Caused by: java.lang.ClassNotFoundException: javax.xml.bind.annotation.XmlSchema</span><br><span class="line"> <span class="built_in">at</span> java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:<span class="number">583</span>)</span><br><span class="line"> <span class="built_in">at</span> java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:<span class="number">178</span>)</span><br><span class="line"> <span class="built_in">at</span> java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:<span class="number">521</span>)</span><br><span class="line"> ... <span class="number">5</span> <span class="built_in">more</span></span><br></pre></td></tr></table></figure>
这是因为计算机中Java版本过高,高版本的Java删除了一些库导致它缺少依赖,网上一般有两种方法解决:<pre><code>1.降低jdk版本
首先,<a href="https://www.oracle.com/java/technologies/javase-jdk8-downloads.html">下载jdk8</a>
下载完成后,安装jdk8,一般安装路径默认为`C:\Program Files\Java`,也可以自行指定路径;
安装完毕后,**修改环境变量**,将`JAVA_HOME`修改为`C:\Program Files\Java\jdk1.8.0_xxx`(或者是自定义的安装路径 `path\jdk1.8.0_xxx`)
在环境变量Path中把`%JAVA_HOME%\bin`移到最前
保存修改,打开CMD,输入指令:
<figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">java -version</span><br></pre></td></tr></table></figure>
查看Java版本是否修改成功;</code></pre></li>
</ul>
<pre><code>2.自己添加jaxb相关依赖
参考:<a href="https://www.wandouip.com/t5i325108/">安装Flutter环境遇到Android license status unknown问题解决</a>
步骤比较繁琐,不想舍弃新版本Java特性的朋友可以试试看;
解决完Java版本问题之后,再次执行`flutter doctor`得到如下输出:
<figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line"> octor summary (to see all details, run flutter doctor -v):</span><br><span class="line">[√] Flutter (Channel stable, v1.<span class="number">12</span>.<span class="number">13</span>+hotfix.<span class="number">9</span>, on Microsoft Windows [Version <span class="number">10</span>.<span class="number">0</span>.<span class="number">18362</span>.<span class="number">778</span>], locale zh-CN)</span><br><span class="line">[!] Android toolchain - develop <span class="keyword">for</span> Android devices (Android SDK version <span class="number">29</span>.<span class="number">0</span>.<span class="number">3</span>)</span><br><span class="line"> ! Some Android licenses <span class="keyword">not</span> accepted. To resolve this, run: flutter doctor --android-licenses</span><br><span class="line">[√] Android Studio (version <span class="number">3</span>.<span class="number">5</span>)</span><br><span class="line">[!] Connected device</span><br><span class="line"> ! No devices available</span><br></pre></td></tr></table></figure>
可以看到`android license status unknown`的报错已经没有了,此时按照提示再次输入指令:
<figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">flutter doctor --android-licenses</span><br></pre></td></tr></table></figure>
得到输出:
<figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">Review licenses that have <span class="keyword">not</span> been accepted (y/N)?</span><br></pre></td></tr></table></figure>
输入`y`检查尚未接受的许可
当提示是否接受许可时,输入`y`进行确认
最终得到:
<figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">All SDK package licenses accepted</span><br></pre></td></tr></table></figure></code></pre><p>至此,Fultter的环境搭建就完成了</p>
]]></content>
<tags>
<tag>Android</tag>
<tag>Flutter</tag>
</tags>
</entry>
<entry>
<title>基于Hexo和GitHub搭建个人博客</title>
<url>/2020/02/20/%E4%BD%BF%E7%94%A8hexo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/</url>
<content><![CDATA[<p>这篇文章将介绍如何使用Hexo与GitHub搭建自己的博客</p>
<a id="more"></a>
<h1 id="Hexo"><a href="#Hexo" class="headerlink" title="Hexo"></a>Hexo</h1><h2 id="什么是Hexo"><a href="#什么是Hexo" class="headerlink" title="什么是Hexo"></a>什么是Hexo</h2><blockquote>
<p>Hexo 是一个快速、简洁且高效的博客框架。——From <a href="https://hexo.io/zh-cn/docs/" target="_blank" rel="noopener">https://hexo.io/zh-cn/docs/</a></p>
</blockquote>
<p>简单来说,使用Hexo可以方便地搭建和管理个人博客</p>
<h2 id="Hexo安装"><a href="#Hexo安装" class="headerlink" title="Hexo安装"></a>Hexo安装</h2><p>在安装Hexo之前,需要先安装 <a href="http://nodejs.cn/download/" target="_blank" rel="noopener" title="Node.js Download">Node.js</a> 和 <a href="https://www.git-scm.com/download/" target="_blank" rel="noopener" title="Git Download">Git</a> </p>
<h4 id="Node-js"><a href="#Node-js" class="headerlink" title="Node.js"></a>Node.js</h4><p>Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,当然这不是重点。重点是Hexo需要使用npm安装,而Node.js使用npm作为包管理工具——所以我们安装Node.js是为了使用npm</p>
<h4 id="Git"><a href="#Git" class="headerlink" title="Git"></a>Git</h4><p>Git是一个开源的分布式版本控制系统,用于部署站点和更新内容</p>
<p>安装完成后,在命令行中输入以下代码来检查是否安装成功</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">node -v</span><br><span class="line">git --version</span><br></pre></td></tr></table></figure>
<p>若输出版本号(如下),说明安装成功</p>
<p><img src="/2020/02/20/%E4%BD%BF%E7%94%A8hexo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/cmdOut.png" alt="安装成功时的输出"></p>
<p>确认安装好 Node.js 之后,在<strong>命令行</strong>中输入以下代码来安装Hexo</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">npm install -g hexo-cli</span><br></pre></td></tr></table></figure>
<h2 id="Hexo基本操作"><a href="#Hexo基本操作" class="headerlink" title="Hexo基本操作"></a>Hexo基本操作</h2><h3 id="init"><a href="#init" class="headerlink" title="init"></a>init</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">hexo init [folder]</span><br></pre></td></tr></table></figure>
<p>新建一个网站。如果没有设置 folder ,Hexo 默认在目前的文件夹建立网站</p>
<h3 id="new"><a href="#new" class="headerlink" title="new"></a>new</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">hexo new [layout] <title></span><br></pre></td></tr></table></figure>
<p>新建一篇文章。如果没有设置 layout 的话,默认使用 _config.yml 中的 default_layout 参数代替</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td>-p, –path</td>
<td>自定义新文章的路径</td>
</tr>
<tr>
<td>-r, –replace</td>
<td>如果存在同名文章,将其替换</td>
</tr>
</tbody></table>
<h3 id="generate"><a href="#generate" class="headerlink" title="generate"></a>generate</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">hexo generate</span><br><span class="line">hexo g</span><br></pre></td></tr></table></figure>
<p>生成静态文件</p>
<h3 id="server"><a href="#server" class="headerlink" title="server"></a>server</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">hexo server</span><br><span class="line">hexo s</span><br></pre></td></tr></table></figure>
<p>启动服务器。默认情况下,访问网址为: <code>http://localhost:4000/</code></p>
<h3 id="deploy"><a href="#deploy" class="headerlink" title="deploy"></a>deploy</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">hexo deploy</span><br><span class="line">hexo d</span><br></pre></td></tr></table></figure>
<p>部署网站,<strong>需要先生成静态文件</strong></p>
<h3 id="clean"><a href="#clean" class="headerlink" title="clean"></a>clean</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">hexo clean</span><br></pre></td></tr></table></figure>
<p>清除缓存文件和已经生成的静态文件</p>
<blockquote>
<p>在发现对站点的更改无法生效时,可以尝试执行该指令后重新部署</p>
</blockquote>
<h1 id="建站"><a href="#建站" class="headerlink" title="建站"></a>建站</h1><h2 id="初始化站点"><a href="#初始化站点" class="headerlink" title="初始化站点"></a>初始化站点</h2><p>安装完 Hexo 后,执行下列命令,Hexo将会在指定文件夹中新建所需要的文件</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">hexo init <站点文件夹地址></span><br><span class="line">cd <站点文件夹地址></span><br><span class="line">npm install</span><br></pre></td></tr></table></figure>
<p>或者你也可以在文件夹中新建一个空的站点文件夹,进入该文件夹后在空白处用<code>shift + 鼠标右键</code>打开快捷菜单栏,选择“在此处打开Powershell窗口”进入命令行工具,直接输入 <code>hexo init</code> 指令</p>
<h2 id="站点文件夹简介"><a href="#站点文件夹简介" class="headerlink" title="站点文件夹简介"></a>站点文件夹简介</h2><p>执行 <code>hexo init</code> 指令后,可以看到站点文件夹的构成如下</p>
<p><img src="/2020/02/20/%E4%BD%BF%E7%94%A8hexo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/folderContents.png" alt="站点文件夹构成"></p>
<h3 id="scaffolds"><a href="#scaffolds" class="headerlink" title="scaffolds"></a>scaffolds</h3><p>模板文件夹。在新建文章时,Hexo 会根据 scaffold 来建立文件</p>
<blockquote>
<p>Hexo的模板是指在新建的文章文件中默认填充的内容。例如,如果您修改scaffold/post.md中的Front-matter内容,那么每次新建一篇文章时都会包含这个修改</p>
</blockquote>