-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
243 lines (208 loc) · 37.1 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>無痕的碎碎念</title>
<link href="/atom.xml" rel="self"/>
<link href="http://wuhenqs.com/"/>
<updated>2017-02-09T11:10:44.000Z</updated>
<id>http://wuhenqs.com/</id>
<author>
<name>Yunxiang Huang</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>Golang 各种输入姿势</title>
<link href="http://wuhenqs.com/2017/01/24/Golang-%E5%90%84%E7%A7%8D%E8%BE%93%E5%85%A5%E5%A7%BF%E5%8A%BF/"/>
<id>http://wuhenqs.com/2017/01/24/Golang-各种输入姿势/</id>
<published>2017-01-24T07:21:22.000Z</published>
<updated>2017-02-09T11:10:44.000Z</updated>
<content type="html"><![CDATA[<p><b>原创文章,转载请标明出处:</b><a href="http://wuhenqs.com" target="_blank">無痕的碎碎念</a><br><b>本文链接地址:</b><a href="http://wuhenqs.com/2017/01/24/Golang-各种输入姿势/" target="_blank">Golang 各种输入姿势</a></p>
<p>最近开始在<a href="https://hackerrank.com" target="_blank" rel="external">Hacker Rank</a>上面刷题玩,因为从开始工作起一直是用的Go语言作为主力开发语言,所以就选择用Go语言来解题了。</p>
<p>刚开始做第一题<a href="https://www.hackerrank.com/challenges/30-hello-world" target="_blank" rel="external">Hello World</a>,突然发现自己还没用Go语言从<code>stdin</code>读取过数据。于是自己摸索加各种地方找了一下资料,打算总结一下Go语言从标准输入读取数据的姿势。</p>
<a id="more"></a>
<h2 id="fmt"><a href="#fmt" class="headerlink" title="fmt"></a><code>fmt</code></h2><p>经常使用<code>fmt.Printf</code>之类的做<ruby>输出<rp> (</rp><rt>tiáo shì</rt><rp>) </rp></ruby>,在处理输入自然是第一个想到<code>fmt</code>包。</p>
<p><code>fmt</code>提供了许多输入函数:</p>
<ul>
<li><code>Scan</code></li>
<li><code>Scanf</code></li>
<li><code>Scanln</code></li>
</ul>
<p>由于只讲从<code>stdin</code>读取,所以其他的<code>Fscanf</code>、<code>Sscanf</code>之类的就省略了,下面逐一来玩一下这三个输入。</p>
<h3 id="Scan"><a href="#Scan" class="headerlink" title="Scan"></a><code>Scan</code></h3><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">Scan</span><span class="params">(a ...<span class="keyword">interface</span>{})</span> <span class="params">(n <span class="keyword">int</span>, err error)</span></span></div></pre></td></tr></table></figure>
<p><code>Scan</code>从标准输入读取数据,以空格作为分隔(换行符也视作空格),依次将输入赋给参数。他会返回读取参数的个数,和读取过程中遇到的错误。</p>
<p>来个例子:</p>
<figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// Code:</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">var</span> i <span class="keyword">int</span></div><div class="line"> <span class="keyword">var</span> d <span class="keyword">float32</span></div><div class="line"> <span class="keyword">var</span> s <span class="keyword">string</span></div><div class="line"> fmt.Scan(&i, &d, &s)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// Input:</span></div><div class="line"><span class="number">1</span> <span class="number">2.2</span></div><div class="line">Three</div></pre></td></tr></table></figure>
<p>上述代码接受上述输入之后,<code>i</code>里面存储的是<code>1</code>,<code>d</code>里面存储的是<code>2.2</code>,<code>s</code>里面存储的是<code>Three</code>。</p>
<p>需要注意的一点是:因为<code>Scan</code>是以空格和换行作为分隔,所以<code>Scan</code>不能将一整行作为一个字符串。<br>例子:</p>
<figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// Code:</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">var</span> s <span class="keyword">string</span></div><div class="line"> fmt.Scan(&s)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// Input:</span></div><div class="line">This is a <span class="keyword">string</span>.</div></pre></td></tr></table></figure>
<p>上述代码接受上述输入之后,<code>s</code>里面存储的是<code>This</code>,而<code>is a string</code>会继续保留在<code>stdin</code>中。</p>
<h3 id="Scanf"><a href="#Scanf" class="headerlink" title="Scanf"></a><code>Scanf</code></h3><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">Scanf</span><span class="params">(format <span class="keyword">string</span>, a ...<span class="keyword">interface</span>{})</span> <span class="params">(n <span class="keyword">int</span>, err error)</span></span></div></pre></td></tr></table></figure>
<p><code>Scanf</code> 和<code>Scan</code>类似,只是可以自己定义输入格式。</p>
<p>来个例子:</p>
<figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// Code:</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">var</span> i <span class="keyword">int</span></div><div class="line"> <span class="keyword">var</span> d <span class="keyword">float32</span></div><div class="line"> <span class="keyword">var</span> s <span class="keyword">string</span></div><div class="line"> fmt.Scanf(<span class="string">"%d %f %s"</span>, &i, &d, &s)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// Input:</span></div><div class="line"><span class="number">1</span> <span class="number">2.2</span></div><div class="line">Three</div></pre></td></tr></table></figure>
<p>上述代码接受上述输入之后,<code>i</code>里面存储的是<code>1</code>,<code>d</code>里面存储的是<code>2.2</code>,<code>s</code>依然是空字符串。因为给定的输入格式是<code>%d %f %s</code>,而<code>Three</code>和<code>2.2</code>之间的是<code>\n</code>。所以<code>Three</code>并没有存储到<code>s</code>中。把输入格式改成<code>%d %f\n%s</code>就可以顺利读取了。</p>
<p><code>Scanf</code>的格式控制非常强大,可以做很多事情,例如指定长度:</p>
<figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// Code:</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">var</span> i, j <span class="keyword">int</span></div><div class="line"> fmt.Scanf(<span class="string">"%3d%2d"</span>, &i, &j)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// Input:</span></div><div class="line"><span class="number">1234567</span></div></pre></td></tr></table></figure>
<p>上述代码接受上述输入之后,<code>i</code>里面存储的是<code>123</code>,<code>j</code>里面存储的是<code>45</code>,而<code>67</code>依然保留在<code>stdin</code>中。</p>
<h3 id="Scanln"><a href="#Scanln" class="headerlink" title="Scanln"></a><code>Scanln</code></h3><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">Scanln</span><span class="params">(a ...<span class="keyword">interface</span>{})</span> <span class="params">(n <span class="keyword">int</span>, err error)</span></span></div></pre></td></tr></table></figure>
<p><code>Scanln</code>和<code>Scan</code>几乎一样,只不过<code>Scanln</code>将换行符作为一次输入的结束。</p>
<p>来个栗子:</p>
<figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// Code:</span></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">var</span> i <span class="keyword">int</span></div><div class="line"> <span class="keyword">var</span> d <span class="keyword">float32</span></div><div class="line"> <span class="keyword">var</span> s <span class="keyword">string</span></div><div class="line"> fmt.Scanln(&i, &d, &s)</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// Input:</span></div><div class="line"><span class="number">1</span> <span class="number">2.2</span></div><div class="line">Three</div></pre></td></tr></table></figure>
<p>上述代码接受上述输入之后,<code>i</code>里面存储的是<code>1</code>,<code>d</code>里面存储的是<code>2.2</code>,<code>s</code>依然是空字符串。因为<code>Scanln</code>将换行符作为输入的结束,所以<code>Three</code>被保留在了<code>stdin</code>中。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p><code>fmt</code>中提供了非常方便而且强大的输入函数,基本可以满足<ruby>各种<rp> (</rp><rt>qí guài</rt><rp>) </rp></ruby>输入需求了。但是目前还没找到方法能一次读取一整行。</p>
<h2 id="bufio"><a href="#bufio" class="headerlink" title="bufio"></a><code>bufio</code></h2><p><code>bufio</code>是在处理各种I/O的时候常用的包之一,他实现了各种带缓冲的I/O。其中有两个和输入有关的结构:</p>
<ul>
<li><code>Reader</code></li>
<li><code>Scanner</code></li>
</ul>
<p>下面来看一下这两货怎么玩.</p>
<h3 id="Reader"><a href="#Reader" class="headerlink" title="Reader"></a><code>Reader</code></h3><p><code>Reader</code>是<code>bufio</code>中提供的一个带缓存的<code>Reader</code>,通过<code>NewReader</code>创建的<code>Reader</code>的默认缓存大小是4096 byte。也可以通过<code>NewReaderSize</code>来创建一个自定义缓存大小的<code>Reader</code>,不过缓存最小是16 byte。</p>
<p>来个例子:</p>
<figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</div><div class="line"> <span class="comment">// Default size.</span></div><div class="line"> reader := bufio.NewReader(os.Stdin)</div><div class="line"> <span class="comment">// Custom size.</span></div><div class="line"> reader = bufio.NewReaderSize(os.Stdin, <span class="number">1024</span>)</div><div class="line">}</div></pre></td></tr></table></figure>
<p>上述代码先后创建了两个<code>Reader</code>,都是对<code>stdin</code>进行操作的。其中第一个<code>Reader</code>的缓存大小是4096 byte,第二个的缓存大小是1024 byte。</p>
<p><code>Reader</code>提供了很多很好用的读取函数:</p>
<ul>
<li><code>Read</code></li>
<li><code>ReadBytes</code></li>
<li><code>ReadSlice</code></li>
<li><code>ReadByte</code></li>
<li><code>ReadRune</code></li>
<li><code>ReadString</code></li>
<li><code>ReadLine</code></li>
</ul>
<p>还有一些和输入没太大关系我就忽略了,在函数列表里面看到了<code>ReadLine</code>,不能更激动,终于可以一次读取一行了,不过还是一个一个来讲一下。</p>
<h4 id="Read"><a href="#Read" class="headerlink" title="Read"></a><code>Read</code></h4><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b *Reader)</span> <span class="title">Read</span><span class="params">(p []<span class="keyword">byte</span>)</span> <span class="params">(n <span class="keyword">int</span>, err error)</span></span></div></pre></td></tr></table></figure>
<p><code>Read</code>函数需要一个byte切片作为参数,一次读取最多将给定的切片读满。返回值是实际读取的字节数,以及读取过程中遇到的错误。</p>
<h4 id="ReadBytes"><a href="#ReadBytes" class="headerlink" title="ReadBytes"></a><code>ReadBytes</code></h4><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b *Reader)</span> <span class="title">ReadBytes</span><span class="params">(delim <span class="keyword">byte</span>)</span> <span class="params">([]<span class="keyword">byte</span>, error)</span></span></div></pre></td></tr></table></figure>
<p><code>ReadBytes</code>函数需要给定一个byte作为参数,然后他会一直读取,直到读取到给定的byte,<code>EOF</code>或者在读取过程中发生错误。</p>
<p>从源码里面看到了官方的一行<ruby>注释<rp> (</rp><rt>tǔ cáo</rt><rp>) </rp></ruby>:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">// For simple uses, a Scanner may be more convenient.</div></pre></td></tr></table></figure>
<p>所以没有什么特殊需求的话,还是推荐使用之后讲的<code>Scanner</code></p>
<h4 id="ReadSlice"><a href="#ReadSlice" class="headerlink" title="ReadSlice"></a><code>ReadSlice</code></h4><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b *Reader)</span> <span class="title">ReadSlice</span><span class="params">(delim <span class="keyword">byte</span>)</span> <span class="params">(line []<span class="keyword">byte</span>, err error)</span></span></div></pre></td></tr></table></figure>
<p><code>ReadSlice</code>函数需要给定一个byte作为参数,然后他会一直读取,直到读取到给定的byte,<code>EOF</code>或者发生错误。</p>
<p><code>ReadSlice</code>和<code>ReadBytes</code>的区别在于:<code>ReadBytes</code>是<strong>有I/O操作的</strong>,而<code>ReadSlice</code><strong>没有I/O操作</strong>。<code>ReadSlice</code>的读取仅限于<strong>已经缓存</strong>的部分,而<code>ReadBytes</code>会一直读取输入和刷新缓存。</p>
<p>所以如果某一次调用<code>ReadSlice</code>把缓存读完了的话,后面再调用是不会返回任何结果的,需要调用<code>ReadBytes</code>或者<code>ReadString</code>触发下一次I/O操作。所以一般情况下直接调用<code>ReadBytes</code>和<code>ReadString</code>相对来说好一些。</p>
<h4 id="ReadByte"><a href="#ReadByte" class="headerlink" title="ReadByte"></a><code>ReadByte</code></h4><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b *Reader)</span> <span class="title">ReadByte</span><span class="params">()</span> <span class="params">(<span class="keyword">byte</span>, error)</span></span></div></pre></td></tr></table></figure>
<p><code>ReadByte</code>比较简单粗暴,就是直接读取一个byte的内容。</p>
<h4 id="ReadRune"><a href="#ReadRune" class="headerlink" title="ReadRune"></a><code>ReadRune</code></h4><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b *Reader)</span> <span class="title">ReadRune</span><span class="params">()</span> <span class="params">(r <span class="keyword">rune</span>, size <span class="keyword">int</span>, err error)</span></span></div></pre></td></tr></table></figure>
<p><code>Rune</code>是Go语言里面的字符类型,可以当做是C/C++里面的<code>char</code>,只不过<code>Rune</code>是UTF-8编码的,本质是<code>int32</code>的别名。</p>
<p><code>ReadRune</code>会读取一个<code>Rune</code>的内容,并且返回这个<code>Rune</code>占用了多少byte。</p>
<h4 id="ReadString"><a href="#ReadString" class="headerlink" title="ReadString"></a><code>ReadString</code></h4><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b *Reader)</span> <span class="title">ReadString</span><span class="params">(delim <span class="keyword">byte</span>)</span> <span class="params">(<span class="keyword">string</span>, error)</span></span></div></pre></td></tr></table></figure>
<p><code>ReadString</code>函数需要给定一个byte作为参数,然后他会一直读取,直到读取到给定的byte,并将读取的内容转换成字符串返回。</p>
<p>从源码上来看,<code>ReadString</code>直接调用了<code>ReadBytes</code>读取数据,然后转换成了字符串。</p>
<h4 id="ReadLine"><a href="#ReadLine" class="headerlink" title="ReadLine"></a><code>ReadLine</code></h4><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b *Reader)</span> <span class="title">ReadLine</span><span class="params">()</span> <span class="params">(line []<span class="keyword">byte</span>, isPrefix <span class="keyword">bool</span>, err error)</span></span></div></pre></td></tr></table></figure>
<p><code>ReadLine</code>会读取一整行的内容,如果这一行的数据很大,超过了缓存的大小,<code>isPrefix</code>会设置为<code>true</code>。</p>
<p>从源码上来看,<code>ReadLine</code>是调用了<code>ReadSlice</code>,所以<code>ReadLine</code>是没有I/O操作的,并且会丢掉换行符(<code>\n</code>或者<code>\r\n</code>)。</p>
<p>由于是调用了<code>ReadSlice</code>,所以不推荐使用<code>ReadLine</code>来读取一整行内容,而是使用<code>ReadBytes('\n')</code>或者<code>ReadString('\n')</code>来代替。</p>
<h4 id="小结-1"><a href="#小结-1" class="headerlink" title="小结"></a>小结</h4><p><code>Reader</code>提供的方法读取到的值都是<code>byte</code>,<code>rune</code>或者<code>string</code>类型的,用<code>Reader</code>来读取<code>int</code>或者<code>float32</code>等类型的话还不如直接用<code>fmt</code>包的<code>Scan</code>方法。</p>
<p>不过总算是能用<code>Reader</code>一次读取一整行了。</p>
<h3 id="Scanner"><a href="#Scanner" class="headerlink" title="Scanner"></a><code>Scanner</code></h3><p><code>Scanner</code>提供了一些接口,能很方便的一次读取一行,非常适合拿来读取文件之类的数据。<code>Scanner</code>只能通过<code>NewScanner</code>来创建,缓存大小最开始会设置为4096 byte,之后如果读取到更大的数据会自己调整,最大可以达到 65536 byte。</p>
<p>来个例子:</p>
<figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">var</span> scanner = bufio.NewScanner(os.Stdin)</div><div class="line">}</div></pre></td></tr></table></figure>
<p>上述代码创建了一个<code>Scanner</code>,用来对<code>stdin</code>做操作。</p>
<p>在<code>Scanner</code>中有一个<code>token</code>的概念,一个<code>token</code>表示一次读取的内容,可以是一整行,也可以是自定义的一段数据,<code>token</code>之间的划分通过<code>SplitFunc</code>来进行,关于<code>SplitFunc</code>的内容包含在了<code>Split</code>函数中了,此处不再赘述。</p>
<p><code>Scanner</code>提供的方法相对<code>Reader</code>来说要少很多,主要有以下几个:</p>
<ul>
<li><code>Split</code></li>
<li><code>Scan</code></li>
<li><code>Bytes</code></li>
<li><code>Text</code></li>
</ul>
<h4 id="Split"><a href="#Split" class="headerlink" title="Split"></a><code>Split</code></h4><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Scanner)</span> <span class="title">Split</span><span class="params">(split SplitFunc)</span></span></div></pre></td></tr></table></figure>
<p><code>Split</code> 是可以传入一个<code>SplitFunc</code>,传入的这个方法是用来帮忙界定“行”的,即你可以自己规定怎么样才算一“行”。默认的<code>SplitFunc</code>就是按行读入。</p>
<h5 id="SplitFunc"><a href="#SplitFunc" class="headerlink" title="SplitFunc"></a><code>SplitFunc</code></h5><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">type</span> SplitFunc <span class="function"><span class="keyword">func</span><span class="params">(data []<span class="keyword">byte</span>, atEOF <span class="keyword">bool</span>)</span> <span class="params">(advance <span class="keyword">int</span>, token []<span class="keyword">byte</span>, err error)</span></span></div></pre></td></tr></table></figure>
<p>一个标准的<code>SplitFunc</code>需要接受两个参数:</p>
<ul>
<li>读取到的原始数据</li>
<li>是否读取到<code>EOF</code>的标识</li>
</ul>
<p><code>SplitFunc</code>根据传入的两个参数进行处理,并返回三个值:</p>
<ul>
<li>本次分割的长度</li>
<li>实际读取到的内容</li>
<li>分割过程中发生的错误</li>
</ul>
<p>其中第一个返回值<strong>不一定</strong>就是<code>len(token)</code>用<code>Scanner</code>默认的<code>SplitFunc</code>来解释一下:</p>
<p><code>Scanner</code>默认使用的<code>SplitFunc</code>以<code>\n</code>作为分隔符,返回分割的长度,实际读取到的内容和分割过程中的错误,其中分割的长度包含了行尾的<code>\n</code>或者<code>\r\n</code>,而实际读取到的内容中不包含,所以<code>len(token)</code>是不等于<code>advance</code>的。而<code>advance</code>的值,可以看作是偏移量。</p>
<p>其次,还有一个需要注意的地方,如果想通过每次返回<code>advance</code>为<code>0</code>的方式来一直读取同一个<code>token</code>的话,会导致程序出现<strong>PANIC</strong>。因为在等下要介绍的<code>Scan</code>函数中做了判定:如果连续<strong>100</strong>次读取到的<code>token</code>不为空,但是<code>advance</code>等于0的话,会直接<strong>PANIC</strong>:</p>
<figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">panic</span>(<span class="string">"bufio.Scan: 100 empty tokens without progressing"</span>)</div></pre></td></tr></table></figure>
<p>当然,<code>advance</code>为负数的话就更不行了。</p>
<h4 id="Scan-1"><a href="#Scan-1" class="headerlink" title="Scan"></a><code>Scan</code></h4><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Scanner)</span> <span class="title">Scan</span><span class="params">()</span> <span class="title">bool</span></span></div></pre></td></tr></table></figure>
<p>从函数前面上看,<code>Scan</code>非常简单,不接受任何参数,只返回一个布尔值来表示是否已经全部读取完毕。但是从源码中可以知道,<code>Scan</code>做了非常多的事情。</p>
<p>每次调用<code>Scan</code>都会读取一个<code>token</code>,并存放在内部。如果缓存大小不够了会自动扩大缓存。然后还有一些错误的处理等。</p>
<h4 id="Bytes"><a href="#Bytes" class="headerlink" title="Bytes"></a><code>Bytes</code></h4><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Scanner)</span> <span class="title">Bytes</span><span class="params">()</span> []<span class="title">byte</span></span></div></pre></td></tr></table></figure>
<p><code>Bytes</code>是将上一次调用<code>Scan</code>所读取到的<code>token</code>以<code>[]byte</code>的形式返回。</p>
<p>所以连续多次调用<code>Bytes</code>会得到一模一样的结果。</p>
<h4 id="Text"><a href="#Text" class="headerlink" title="Text"></a><code>Text</code></h4><figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Scanner)</span> <span class="title">Text</span><span class="params">()</span> <span class="title">string</span></span></div></pre></td></tr></table></figure>
<p><code>Text</code>和<code>Bytes</code>一样,只不过是将<code>token</code>转化成<code>string</code>再返回。</p>
<p>同样的,连续多次调用<code>Text</code>也会得到一模一样的结果。</p>
<h4 id="小结-2"><a href="#小结-2" class="headerlink" title="小结"></a>小结</h4><p><code>Scanner</code>如果能比较好的理解<code>token</code>和<code>SplitFunc</code>的话,用起来的确比<code>Reader</code>要方便很多。不过和<code>Reader</code>一样,获得的数据都是<code>[]byte</code>和<code>string</code>,如果要获取其他类型的数据的话需要再进行一次转换,不过这时候就不如直接用<code>fmt</code>的<code>Scan</code>函数来做了。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>为了用Go语言做一个“Hello World”的大水题我也是够拼的,搞了这么多事情出来。通过这次的折腾,找资料,看源码了解到了许多平常不会注意到的事情:</p>
<p>反正不看源码我是不会知道<code>ReadSlice</code>和<code>ReadString</code>是一次性的 =。=</p>
<p>最后还是贴一发我的“Hello world”的源码:</p>
<figure class="highlight golang"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">package</span> main</div><div class="line"></div><div class="line"><span class="keyword">import</span> (</div><div class="line"> <span class="string">"bufio"</span></div><div class="line"> <span class="string">"fmt"</span></div><div class="line"> <span class="string">"os"</span></div><div class="line">)</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</div><div class="line"> <span class="keyword">var</span> scan = bufio.NewScanner(os.Stdin)</div><div class="line"> <span class="keyword">if</span> scan.Scan() {</div><div class="line"> fmt.Printf(<span class="string">"Hello, World.\n%s"</span>, scan.Text())</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
]]></content>
<summary type="html">
<p><b>原创文章,转载请标明出处:</b><a href="http://wuhenqs.com" target="_blank">無痕的碎碎念</a><br/><b>本文链接地址:</b><a href="http://wuhenqs.com/2017/01/24/Golang-各种输入姿势/" target="_blank">Golang 各种输入姿势</a></p>
<p>最近开始在<a href="https://hackerrank.com">Hacker Rank</a>上面刷题玩,因为从开始工作起一直是用的Go语言作为主力开发语言,所以就选择用Go语言来解题了。</p>
<p>刚开始做第一题<a href="https://www.hackerrank.com/challenges/30-hello-world">Hello World</a>,突然发现自己还没用Go语言从<code>stdin</code>读取过数据。于是自己摸索加各种地方找了一下资料,打算总结一下Go语言从标准输入读取数据的姿势。</p>
</summary>
<category term="go" scheme="http://wuhenqs.com/tags/go/"/>
<category term="input" scheme="http://wuhenqs.com/tags/input/"/>
<category term="stdin" scheme="http://wuhenqs.com/tags/stdin/"/>
<category term="hacker rank" scheme="http://wuhenqs.com/tags/hacker-rank/"/>
<category term="hello world" scheme="http://wuhenqs.com/tags/hello-world/"/>
</entry>
<entry>
<title>Shell Script - Trap</title>
<link href="http://wuhenqs.com/2017/01/18/shell-script-trap/"/>
<id>http://wuhenqs.com/2017/01/18/shell-script-trap/</id>
<published>2017-01-18T02:20:50.000Z</published>
<updated>2017-02-09T11:10:59.000Z</updated>
<content type="html"><![CDATA[<p><b>原创文章,转载请标明出处:</b><a href="http://wuhenqs.com" target="_blank">無痕的碎碎念</a><br><b>本文链接地址:</b><a href="http://wuhenqs.com/2017/01/18/shell-script-trap/" target="_blank">Shell Script - Trap</a></p>
<p>最近写 Shell 脚本的时候发现一个需求:</p>
<ul>
<li>有一条(或组)命令不希望被打断执行,或者不能被打断执行。</li>
</ul>
<p>举个例子来说,有一个时间比较长的追加写入操作,中间在执行过程中如果中断的话会很麻烦,要清理文件或者判断写到什么位置了。这时候如果有个方法能保证这个操作完成,不会因为手抖按了 <code>C-c</code> 被中断掉。<br>在不存在的网站 Google 上找了一下,在 Shell 中有一个捕获系统信号的命令 <code>trap</code> 。<br><a id="more"></a></p>
<h1 id="用法"><a href="#用法" class="headerlink" title="用法"></a>用法</h1><p><code>trap command signals</code><br><code>trap</code> 在捕获到 <code>signals</code> 中定义的信号之后,会执行 <code>command</code> 指定的命令。</p>
<h1 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h1><p>虽然提供了这么一个命令来捕获系统信号,但是在实际使用的时候会发现很多信号捕获不了:</p>
<ul>
<li><code>SIGSTOP</code></li>
<li><code>SIGKILL</code></li>
</ul>
<p>此外,处于 Shell 的交互模式的时候,以下信号也捕获不了:</p>
<ul>
<li><code>SIGTTIN</code></li>
<li><code>SIGTTOU</code></li>
<li><code>SIGTSTP</code></li>
</ul>
<h1 id="解决问题"><a href="#解决问题" class="headerlink" title="解决问题"></a>解决问题</h1><p>上面说了这么多,然而并没有解决问题,虽然捕获了信号,但是还是会打断当前进行的操作。<br><code>trap</code> 在捕获到信号,执行指定的 <code>command</code> 的时候,会忽略掉其他的信号(当然 <code>SIGKILL</code> 和 <code>SIGSTOP</code> 这种没办法)。利用这一点,把需要保证完成的操作作为指定的 <code>command</code> ,然后在设置捕获信号之后马上对自己发出一个可以捕获的信号就可以了。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">assert() {</div><div class="line"> trap '$@; trap - SIGINT' SIGINT</div><div class="line"> kill -INT $$</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>只要把需要执行的命令作为 <code>assert</code> 函数的参数传入就好了。</p>
<h1 id="还有个问题"><a href="#还有个问题" class="headerlink" title="还有个问题"></a>还有个问题</h1><p>这个方法对 <code>C-z</code> 是无效的,不过可以利用它来结束掉这条指令。毕竟执行到一半发现什么奇怪的问题,需要结束这种情况还是难免的。</p>
]]></content>
<summary type="html">
<p><b>原创文章,转载请标明出处:</b><a href="http://wuhenqs.com" target="_blank">無痕的碎碎念</a><br/><b>本文链接地址:</b><a href="http://wuhenqs.com/2017/01/18/shell-script-trap/" target="_blank">Shell Script - Trap</a></p>
<p>最近写 Shell 脚本的时候发现一个需求:</p>
<ul>
<li>有一条(或组)命令不希望被打断执行,或者不能被打断执行。</li>
</ul>
<p>举个例子来说,有一个时间比较长的追加写入操作,中间在执行过程中如果中断的话会很麻烦,要清理文件或者判断写到什么位置了。这时候如果有个方法能保证这个操作完成,不会因为手抖按了 <code>C-c</code> 被中断掉。<br>在不存在的网站 Google 上找了一下,在 Shell 中有一个捕获系统信号的命令 <code>trap</code> 。<br>
</summary>
<category term="shell" scheme="http://wuhenqs.com/tags/shell/"/>
<category term="script" scheme="http://wuhenqs.com/tags/script/"/>
<category term="trap" scheme="http://wuhenqs.com/tags/trap/"/>
</entry>
<entry>
<title>Hello World</title>
<link href="http://wuhenqs.com/2017/01/17/Hello-world/"/>
<id>http://wuhenqs.com/2017/01/17/Hello-world/</id>
<published>2017-01-17T11:18:10.000Z</published>
<updated>2017-02-09T11:06:23.000Z</updated>
<content type="html"><![CDATA[<p><b>原创文章,转载请标明出处:</b><a href="http://wuhenqs.com" target="_blank">無痕的碎碎念</a><br><b>本文链接地址:</b><a href="http://wuhenqs.com/2017/01/17/Hello-world/" target="_blank">Hello World</a></p>
<p>好久没有写博客了,这次在Github上面开个坑,发一些碎碎念。</p>
]]></content>
<summary type="html">
<p><b>原创文章,转载请标明出处:</b><a href="http://wuhenqs.com" target="_blank">無痕的碎碎念</a><br><b>本文链接地址:</b><a href="http://wuhenqs.com/2017/01/17/Hello
</summary>
</entry>
</feed>