<?xml version="1.0" encoding="utf-8"?><rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Fing's Blog</title><link>https://blog.imfing.com/</link><description>这里是 Fing 的博客，分享技术，记录生活……</description><generator>Hugo 0.80.0 https://gohugo.io/</generator><language>zh-CN</language><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><lastBuildDate>Sat, 14 May 2022 06:30:37 +0000</lastBuildDate><atom:link rel="self" type="application/rss+xml" href="https://blog.imfing.com/rss.xml"/><item><title>DIY 搭建个人主页实录（一）</title><link>https://blog.imfing.com/2021/04/rebuild-personal-site-with-hugo-1/</link><guid isPermaLink="true">https://blog.imfing.com/2021/04/rebuild-personal-site-with-hugo-1/</guid><pubDate>Sat, 24 Apr 2021 23:39:00 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>个人主页已经很久没有更新了，去年把博客迁移到 Hugo 之后，就一直想把个人主页也用 Hugo 重新搭建并简化，并稍微详细的记录一下整个搭建的过程。&lt;/p>
&lt;!-- more -->
&lt;p>如果能够跟着我的记录一步步做的话，完成之后可以得到下面的效果：&lt;/p>
&lt;p>&lt;img src="images/site-preview.jpg" alt="image-20210428214652449">&lt;/p>
&lt;h2 id="环境配置">环境配置&lt;/h2>
&lt;p>主要用到的工具是 Hugo + Tailwind CSS：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://gohugo.io/">Hugo&lt;/a>： 编译的速度非常快&lt;/li>
&lt;li>&lt;a href="https://tailwindcss.com/">Tailwind CSS&lt;/a>：好看，并且用起来非常简单&lt;/li>
&lt;/ul>
&lt;p>Windows 用户推荐通过 &lt;a href="https://chocolatey.org/install">Chocolatey&lt;/a> 来安装所需的工具，macOS 用户则可以用 &lt;a href="https://brew.sh/">Homebrew&lt;/a> 安装。关于 Chocolatey，更多可以参考&lt;a href="https://sspai.com/post/55309">这篇文章&lt;/a>。&lt;/p>
&lt;p>需要安装 Hugo 和 &lt;a href="https://nodejs.org/en/">Node.js&lt;/a>：&lt;/p>
&lt;ul>
&lt;li>Windows：&lt;code>$ choco install hugo nodejs&lt;/code>&lt;/li>
&lt;li>macOS：&lt;code>$ brew install hugo node&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="初始化项目">初始化项目&lt;/h2>
&lt;p>首先，需要用 Hugo 新建一个新的空白站点，打开 Powershell 或 Terminal：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">hugo new site homepage
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="images/hugo-new-site.jpg" alt="image-20210417121356475">&lt;/p>
&lt;p>按照提示会生成一个新的目录 &lt;code>homepage&lt;/code>，接下来要在刚刚的目录里面初始化 npm 以及安装一些必要的包：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="nb">cd&lt;/span> homepage
npm init -y
npm install tailwindcss@latest postcss-cli@latest autoprefixer@latest --save
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="images/npm-init-install-tailwind.jpg" alt="image-20210417145928379">&lt;/p>
&lt;p>之后初始化 Tailwind：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">npx tailwindcss init -p
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个命令会创建 &lt;code>tailwind.config.js&lt;/code> 和 &lt;code>postcss.config.js&lt;/code>，从名字上可以判断它们是配置文件&lt;/p>
&lt;p>&lt;img src="images/init-tailwindcss.jpg" alt="image-20210417150114092">&lt;/p>
&lt;p>打开 &lt;code>tailwind.config.js&lt;/code>，修改 &lt;code>purge&lt;/code> 部分为：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="nx">purge&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">content&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;./layouts/**/*.html&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;./content/**/*.md&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;./content/**/*.html&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="images/modify-tailwind-config.jpg" alt="image-20210417150415720">&lt;/p>
&lt;h2 id="hello-hugo">Hello Hugo&lt;/h2>
&lt;p>接着来做一个简单的 Hello Hugo 的网站。运行下面的命令，为的是在 Hugo 项目中新建一些基本的目录和站点文件：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">mkdir -p assets/css layouts/_default
touch layouts/index.html layouts/_default/baseof.html assets/css/main.css
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>主要创建了 &lt;code>layouts&lt;/code> 目录以及基本的页面结构：&lt;/p>
&lt;pre>&lt;code>layouts
├── _default
│   └── baseof.html
└── index.html
&lt;/code>&lt;/pre>&lt;p>在这个目录里面，&lt;code>baseof.html&lt;/code> 是基本的框架，&lt;code>index.html&lt;/code> 是主页的构架。&lt;/p>
&lt;p>先在 &lt;code>layouts/_default/baseof.html&lt;/code> 里添加：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="cp">&amp;lt;!DOCTYPE html&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">html&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">title&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>{{ .Site.Title }}&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">title&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">meta&lt;/span> &lt;span class="na">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;viewport&amp;#34;&lt;/span> &lt;span class="na">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;width=device-width, initial-scale=1, maximum-scale=1&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{ $styles := resources.Get &amp;#34;css/main.css&amp;#34; }}
{{ $styles = $styles | resources.PostCSS (dict &amp;#34;inlineImports&amp;#34; true) }}
{{ if hugo.IsProduction }}
{{ $styles = $styles | minify }}
{{ end }}
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">link&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;{{ $styles.Permalink }}&amp;#34;&lt;/span> &lt;span class="na">rel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;stylesheet&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{ block &amp;#34;main&amp;#34; . }}{{ end }}
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">html&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>之后在 &lt;code>layouts/index.html&lt;/code> 添加：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">{{ define &amp;#34;main&amp;#34; }}
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;container mx-auto mt-5 text-3xl&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Hello Hugo&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{ end }}
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>最后在 &lt;code>assets/css/main.css&lt;/code> 中添上：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="p">@&lt;/span>&lt;span class="k">tailwind&lt;/span> &lt;span class="nt">base&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">@&lt;/span>&lt;span class="k">tailwind&lt;/span> &lt;span class="nt">components&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">@&lt;/span>&lt;span class="k">tailwind&lt;/span> &lt;span class="nt">utilities&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在终端里运行 &lt;code>hugo server&lt;/code> 看看效果&lt;/p>
&lt;p>&lt;img src="images/hello-hugo.jpg" alt="image-20210417153249592">&lt;/p>
&lt;h2 id="构建主页">构建主页&lt;/h2>
&lt;p>接下来看看如何构建一个页面，大致上将页面划分为两个主要部分，最上面是头部的导航栏，其余的内容放在另一个大的容器内&lt;/p>
&lt;p>&lt;img src="images/homepage-layout.jpg" alt="image-20210417162918619">&lt;/p>
&lt;h3 id="导航栏">导航栏&lt;/h3>
&lt;p>给刚刚新建的页面加个导航栏，新建 &lt;code>layouts/partials&lt;/code> 文件夹，这个目录里面之后会放一些小的部件。新建 &lt;code>layout/partials/header.html&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">header&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">nav&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex flex-col sm:flex-row justify-between mb-12 mt-8&amp;#34;&lt;/span> &lt;span class="na">role&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;navigation&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">a&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-lg font-bold&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>{{ .Site.Title }}&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">a&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">nav&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">header&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面主要添加了一个大的可点击的标题，内容为站点 &lt;code>config.toml&lt;/code> 中的 &lt;code>title&lt;/code> 属性。在 &lt;code>index.html&lt;/code> 中通过 Hugo 的 &lt;code>partial&lt;/code> 关键词插入 &lt;code>header.html&lt;/code> 的内容：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">{{ define &amp;#34;main&amp;#34; }}
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;container max-w-2xl mx-auto&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{- partial &amp;#34;header.html&amp;#34; . -}}
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Hello Hugo!&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{ end }}
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="images/navbar-title.jpg" alt="image-20210417165035839">&lt;/p>
&lt;p>之后要实现导航栏右侧的几个按钮，它们本质上是横向的列表。&lt;/p>
&lt;p>&lt;img src="images/navbar-links.jpg" alt="image-20210417165206394">&lt;/p>
&lt;p>修改刚刚的 &lt;code>header.html&lt;/code> ：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">header&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">nav&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex flex-col sm:flex-row justify-between mb-12 mt-8&amp;#34;&lt;/span> &lt;span class="na">role&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;navigation&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">a&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-lg font-bold&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>{{ .Site.Title }}&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">a&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ul&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex space-x-4&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{ range .Site.Menus.main }}
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">li&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">a&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;{{ .URL | absURL }}&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>{{ .Name }}&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">a&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">li&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{ end }}
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">ul&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">nav&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">header&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>{{ range .Site.Menus.main }}&lt;/code> 会在 &lt;code>config.toml&lt;/code> 中查找 &lt;code>[menu]&lt;/code> 配置。在 &lt;code>config.toml&lt;/code> 中，添加一些配置：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="c"># 顶部菜单&lt;/span>
&lt;span class="p">[&lt;/span>&lt;span class="nx">menu&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="p">[[&lt;/span>&lt;span class="nx">menu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">main&lt;/span>&lt;span class="p">]]&lt;/span>
&lt;span class="nx">url&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;/projects&amp;#34;&lt;/span>
&lt;span class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;Projects&amp;#34;&lt;/span>
&lt;span class="nx">weight&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;span class="p">[[&lt;/span>&lt;span class="nx">menu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">main&lt;/span>&lt;span class="p">]]&lt;/span>
&lt;span class="nx">url&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;/about&amp;#34;&lt;/span>
&lt;span class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;About&amp;#34;&lt;/span>
&lt;span class="nx">weight&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们的导航栏基本就完成了，之后只需要更改配置文件即可添加其他的页面链接。&lt;/p>
&lt;p>&lt;img src="images/complete-navbar.jpg" alt="image-20210417170710682">&lt;/p>
&lt;h3 id="简介组件">简介组件&lt;/h3>
&lt;p>接下来制作最上面的这样一个简介的组件，包括了照片、名字、社交网站链接等元素&lt;/p>
&lt;p>&lt;img src="images/profile-component.jpg" alt="image-20210417171520214">&lt;/p>
&lt;p>把 &lt;code>profile.jpeg&lt;/code> 照片放到 &lt;code>static/images&lt;/code> 里面，在 &lt;code>layouts/partials&lt;/code>下创建 &lt;code>profile.html&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex mb-4&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;images/profile.jpeg&amp;#34;&lt;/span> &lt;span class="na">alt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;rounded-full object-cover w-32 mr-8&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>像引入 &lt;code>header.html&lt;/code> 一样，在 &lt;code>index.html&lt;/code> 引入：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">{{ define &amp;#34;main&amp;#34; }}
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;container max-w-2xl mx-auto&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{- partial &amp;#34;header.html&amp;#34; . -}}
{{- partial &amp;#34;profile.html&amp;#34; . -}}
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Hello Hugo!&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{ end }}
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="images/profile-component-image.jpg" alt="image-20210417173920094">&lt;/p>
&lt;p>之后加入右边的部分，需要用一个 &lt;code>div&lt;/code> 容器，里面加入 Harry Porter 的标题和下面的简介：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex mb-4&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;images/profile.jpeg&amp;#34;&lt;/span> &lt;span class="na">alt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;rounded-full w-32 h-32 mr-8&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex-row&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-4xl font-medium&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Harry Porter&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Student at Hogwarts School&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="images/profile-component-titles.jpg" alt="image-20210417203535354">&lt;/p>
&lt;p>之后需要加入一些社交网站的图标，例如 GitHub、LinkedIn、Twitter 等。首先需要找到想要添加网站的 SVG 矢量图标，可以利用 &lt;a href="https://simpleicons.org/">Simple Icons&lt;/a> 或者 &lt;a href="https://www.flaticon.com/">Flaticon&lt;/a>：&lt;/p>
&lt;p>&lt;img src="images/simpleicons.jpg" alt="image-20210417204434238">&lt;/p>
&lt;p>同样，和添加头像步骤一样，新建一个 &lt;code>static/icons&lt;/code> 目录，并将下载的 &lt;code>github.svg&lt;/code> 等图标放进去，再修改 &lt;code>profile.html&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex mb-4&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;images/profile.jpeg&amp;#34;&lt;/span> &lt;span class="na">alt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;rounded-full w-32 h-32 mr-8&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex-row&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-4xl font-medium&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Harry Porter&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Student at Hogwarts School&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex mt-2 mb-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;icons/github.svg&amp;#34;&lt;/span> &lt;span class="na">alt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;w-6 mr-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;icons/instagram.svg&amp;#34;&lt;/span> &lt;span class="na">alt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;w-6 mr-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;icons/linkedin.svg&amp;#34;&lt;/span> &lt;span class="na">alt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;w-6 mr-2&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;font-mono text-sm&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>harry (at) hogwarts (dot) edu&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>效果：&lt;/p>
&lt;p>&lt;img src="images/profile-socials.jpg" alt="image-20210417210420606">&lt;/p>
&lt;h3 id="主体部分">主体部分&lt;/h3>
&lt;p>在完成了这一部分之后，接下来我们需要让主页的主体部分（也就是上面的 &lt;code>Hello Hugo!&lt;/code>）部分能够显示 Markdown 文件的内容。我们需要创建 &lt;code>content/_index.md&lt;/code> 文件，在里面加入任意内容，如 &lt;code>## Hello Markdown&lt;/code>，修改 &lt;code>layouts/index.html&lt;/code>，加入 &lt;code>{{ .Content }}&lt;/code>，这部分将会被替换成 Markdown 渲染后的内容&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">...
{{- partial &amp;#34;header.html&amp;#34; . -}}
{{- partial &amp;#34;profile.html&amp;#34; . -}}
{{ .Content }}
...
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但是... 实际并没有把刚刚 &lt;code>content/_index.md&lt;/code> 中的标题正确的格式渲染出来&lt;/p>
&lt;p>&lt;img src="images/content-markdown-hello.jpg" alt="image-20210417214929408">&lt;/p>
&lt;p>这是因为 Tailwind CSS 默认是把这种标题的格式去掉了，要想重新给标题加上格式，需要额外的步骤。在搜索了一番之后，在官方文档里发现了 &lt;a href="https://tailwindcss.com/docs/plugins#typography">Typography&lt;/a> 的插件。为了能够用这个插件，需要先安装：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">npm install @tailwindcss/typography
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>之后在 &lt;code>tailwind.config.js&lt;/code> 加入：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="p">...&lt;/span>
&lt;span class="nx">plugins&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;@tailwindcss/typography&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>之后修改 &lt;code>layouts/index.html&lt;/code>，给 &lt;code>{{ .Content }}&lt;/code> 用 &lt;code>article&lt;/code> 标签包裹，并加上 &lt;code>prose&lt;/code> class：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">...
{{- partial &amp;#34;header.html&amp;#34; . -}}
{{- partial &amp;#34;profile.html&amp;#34; . -}}
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">article&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;prose&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{ .Content }}
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">article&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
...
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="images/content-markdown-prose.jpg" alt="image-20210417230214785">&lt;/p>
&lt;p>到这一步就基本只需要修改 &lt;code>_index.md&lt;/code> 就可以添加原来 90% 主页的内容了：&lt;/p>
&lt;p>&lt;img src="images/content-markdown-body.jpg" alt="image-20210418132351578">&lt;/p>
&lt;h3 id="添加卡片">添加卡片&lt;/h3>
&lt;p>接下来尝试加入更复杂一些的组件，例如 Tailwind 文档演示的&lt;a href="https://tailwindcss.com/docs/extracting-components">这个卡片&lt;/a>：&lt;/p>
&lt;p>&lt;img src="images/tailwind-card.jpg" alt="image-20210418135552206">&lt;/p>
&lt;p>稍作修改，尝试把这一段加入到 &lt;code>index.html&lt;/code> 里刚刚添加的 &lt;code>&amp;lt;article&amp;gt;&lt;/code> 之后：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">...
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">article&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;max-w-2xl mx-auto bg-white rounded-xl shadow-md overflow-hidden my-8&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;md:flex&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;md:flex-shrink-0&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-48 w-48 object-cover&amp;#34;&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/images/profile.jpeg&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;p-8&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;uppercase tracking-wide text-sm text-indigo-500 font-semibold&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Case study&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">a&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;#&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;block mt-1 text-lg leading-tight font-medium text-black hover:underline&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>The Making of Harry Potter&amp;#39;s Wand&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">a&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;mt-2 text-gray-500&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Harry&amp;#39;s wand was broken in 1997, but was repaired by him after the 1998 Battle of Hogwarts. &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
...
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>效果还可以，只是白色的背景和卡片的背景融为了一体&lt;/p>
&lt;p>&lt;img src="images/project-card-white.jpg" alt="image-20210418142057433">&lt;/p>
&lt;p>为了更好的区分，一种方法是在&lt;code>layouts/_default/baseof.html&lt;/code> 给 &lt;code>&amp;lt;body&amp;gt;&lt;/code> 加上浅灰的背景色：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">...
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">body&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;bg-gray-100&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
...
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="images/project-card-gray.jpg" alt="image-20210418142440034">&lt;/p>
&lt;h3 id="提取卡片组件">提取卡片组件&lt;/h3>
&lt;p>但如果我们想添加若干个上面的卡片的话，就需要将那一大段代码复制若干遍，并且将来想要修改这个卡片的样式，也需要一个一个修改。&lt;/p>
&lt;p>这时候我考虑将卡片提取成组件，每次用的时候只需要提供图片路径、标题文字和介绍文字。可以利用 Hugo 的 &lt;a href="https://gohugo.io/templates/shortcode-templates/">Shortcodes&lt;/a> 实现这一点。创建 &lt;code>layouts/shortcodes/project-card.html&lt;/code> 文件，先添加简单的一行：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">This is project card shortcode.
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在 &lt;code>content/_index.md&lt;/code> 中使用上面的 Shortcode：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-md" data-lang="md">...
&lt;span class="gu">## Projects
&lt;/span>&lt;span class="gu">&lt;/span>
{{&lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nt">project-card&lt;/span> &lt;span class="p">&amp;gt;&lt;/span>}}
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可以看到右边对应的地方出现了 &lt;code>project-card.html&lt;/code> 里的内容&lt;/p>
&lt;p>&lt;img src="images/project-card-shortcode-1.jpg" alt="image-20210418145310473">&lt;/p>
&lt;p>之后把上面的卡片代码复制到 &lt;code>layouts/shortcodes/project-card.html&lt;/code>&lt;img src="images/project-card-shortcode-2.jpg" alt="image-20210418151905227">&lt;/p>
&lt;p>结果发现在不一样的地方显示的卡片效果不一样。导致这个问题的原因是之前引入用来渲染 Markdown 的 &lt;code>@tailwind/typography&lt;/code> 的 &lt;code>prose&lt;/code> 类给这个卡片添加了一些额外的样式，造成文字、图片的边距不太一样了。在&lt;a href="https://github.com/tailwindlabs/tailwindcss-typography/issues/32#issuecomment-663045275">这里&lt;/a>找到了问题的解决方法，修改 &lt;code>tailwind.config.js&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">important&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;html&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">purge&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="p">...&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在 &lt;code>layouts/index.html&lt;/code> 中，给 &lt;code>article&lt;/code> 添加 &lt;code>max-w-none&lt;/code> 样式来取消最大宽度的限制，这样卡片就能填充满整个横向的空间：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">article&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;prose max-w-none&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{ .Content }}
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">article&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>之后在 &lt;code>project-card.html&lt;/code> 中的 &lt;code>img&lt;/code> 上增添 &lt;code>m-0&lt;/code> 属性，表示外边距为 0，在最后的 &lt;code>p&lt;/code> 元素加上 &lt;code>mb-0&lt;/code> 将底部边距设为 0，给 &lt;code>a&lt;/code> 元素添上 &lt;code>no-underline&lt;/code> 来取消下划线效果&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">...
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-48 w-48 object-cover m-0&amp;#34;&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/images/profile.jpeg&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
...
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">a&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;#&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;block mt-1 text-lg leading-tight font-medium text-black hover:underline&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>The Making of Harry Potter&amp;#39;s Wand&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">a&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;mt-2 text-gray-500 mb-0&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Harry&amp;#39;s wand was broken in 1997, but was repaired by him after the 1998 Battle of Hogwarts. &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
...
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="images/project-card-shortcode-enhance.jpg" alt="image-20210418164417408">&lt;/p>
&lt;p>删除 &lt;code>layouts/index.html&lt;/code> 中的卡片部分。接着修改 Shortcode 的模板，让不同的部分能够通过参数来修改。&lt;/p>
&lt;h3 id="给组件添加参数">给组件添加参数&lt;/h3>
&lt;p>参照官方文档的&lt;a href="https://gohugo.io/templates/shortcode-templates/#single-named-example-image">这个例子&lt;/a>，修改 &lt;code>img&lt;/code> 标签的 &lt;code>src&lt;/code> 部分为 &lt;code>{{ .Get &amp;quot;img&amp;quot; }}&lt;/code>，花括号的部分会被替换为 &lt;code>{{&amp;lt; project-card img=&amp;quot;xxx&amp;quot; &amp;gt;}}&lt;/code> 中的 &lt;code>xxx&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">...
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-48 w-48 object-cover m-0&amp;#34;&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;{{.Get &amp;#34;&lt;/span>&lt;span class="na">img&lt;/span>&lt;span class="err">&amp;#34;&lt;/span> &lt;span class="err">}}&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
...
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>修改 &lt;code>content/_index.md&lt;/code> 中的 Shortcode 为：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-md" data-lang="md">{{&lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nt">project-card&lt;/span> &lt;span class="na">img&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;images/profile.jpeg&amp;#34;&lt;/span> &lt;span class="p">&amp;gt;&lt;/span>}}
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>保存之后应该会看到正确显示的图片。接着修改 &lt;code>layouts/shortcodes/project-card.html&lt;/code>，去掉 Case Study 那一行（没啥用），将 &lt;code>a&lt;/code> 中的内容改为 &lt;code>{{ .Get &amp;quot;title&amp;quot; | absURL }}&lt;/code>，将 &lt;code>p&lt;/code> 改为 &lt;code>div&lt;/code> 并改标签内容为 &lt;code>{{ .Inner | markdownify }}&lt;/code> 来展示 Markdown 内容&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;max-w-2xl mx-auto bg-white rounded-xl shadow-md overflow-hidden my-8&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;md:flex&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;md:flex-shrink-0&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">img&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;h-48 w-48 object-cover m-0&amp;#34;&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;{{ .Get &amp;#34;&lt;/span>&lt;span class="na">img&lt;/span>&lt;span class="err">&amp;#34;&lt;/span> &lt;span class="err">|&lt;/span> &lt;span class="na">absURL&lt;/span> &lt;span class="err">}}&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;p-8&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">a&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;#&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;no-underline block mt-1 text-lg leading-tight font-medium text-black hover:underline&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>{{ .Get &amp;#34;title&amp;#34; }}&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">a&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;mt-2 text-gray-500 mb-0&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>{{ .Inner | markdownify }}&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>之后可以在 &lt;code>content/_index.md&lt;/code> 中这样使用：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-md" data-lang="md">{{&lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nt">project-card&lt;/span> &lt;span class="na">img&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;images/profile.jpeg&amp;#34;&lt;/span> &lt;span class="na">title&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;The Making of Harry Potter&amp;#39;s Wand&amp;#34;&lt;/span> &lt;span class="p">&amp;gt;&lt;/span>}}
Harry&amp;#39;s wand was broken in 1997, but was repaired by him after the &lt;span class="ge">*1998 Battle of Hogwarts*&lt;/span>.
{{&lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nt">project-card&lt;/span> &lt;span class="p">&amp;gt;&lt;/span>}}
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Nice，中间的文字部分甚至能用 Markdown&lt;img src="images/project-card-shortcode-param.jpg" alt="image-20210418173015842">&lt;/p>
&lt;p>至此就基本完成了个人主页的搭建，之后想要修改主页的话只需修改 &lt;code>_index.md&lt;/code> 中的内容。&lt;/p>
&lt;h2 id="添加项目页面">添加项目页面&lt;/h2>
&lt;p>接着尝试添加 Projects 页面，新建 &lt;code>layouts/projects/list.html&lt;/code> 模板文件，将 &lt;code>layouts/index.html&lt;/code> 的内容复制过去，去掉 &lt;code>profile&lt;/code> 那一行：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">{{ define &amp;#34;main&amp;#34; }}
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;container max-w-2xl mx-auto&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{- partial &amp;#34;header.html&amp;#34; . -}}
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">article&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;prose max-w-none mb-8&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{ .Content }}
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">article&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
{{ end }}
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>新建 &lt;code>content/projects/_index.md&lt;/code> 文件，可以继续利用 &lt;code>project-card&lt;/code> 组件：&lt;/p>
&lt;p>&lt;img src="images/projects-page.jpg" alt="image-20210418214107045">&lt;/p>
&lt;p>这时候可能你会想给某个项目创建一个页面，用来展示具体的项目细节，点击项目卡片的标题即可进入到这个页面。先创建 &lt;code>layouts/projects/single.html&lt;/code> 文件，接着将 &lt;code>layouts/projects/list.html&lt;/code> 的内容复制到 &lt;code>single.html&lt;/code> 中。创建 &lt;code>content/projects/hello.md&lt;/code>，我们将在其中添加一些内容：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-md" data-lang="md">&lt;span class="gu">## Project Hello
&lt;/span>&lt;span class="gu">&lt;/span>
This is an example
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>接着访问 &lt;code>localhost:1313/projects/hello/&lt;/code>&lt;/p>
&lt;p>&lt;img src="images/project-page-example.jpg" alt="image-20210420223105726">&lt;/p>
&lt;p>对于项目卡片，需要修改 Shortcode 模板来增添额外的参数使得点击标题能够跳转到这个页面。修改 &lt;code>project-card.html&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">...
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">a&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;{{ .Get &amp;#34;&lt;/span>&lt;span class="na">url&lt;/span>&lt;span class="err">&amp;#34;&lt;/span> &lt;span class="err">|&lt;/span> &lt;span class="na">absURL&lt;/span> &lt;span class="err">}}&amp;#34;&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;no-underline block mt-1 text-lg leading-tight font-medium text-black hover:underline&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>{{ .Get &amp;#34;title&amp;#34; }}&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">a&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
...
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在 &lt;code>content/projects/_index.md&lt;/code> 中，给其中一个 &lt;code>project-card&lt;/code> 添加 &lt;code>url=&amp;quot;projects/hello&amp;quot;&lt;/code> 即可。&lt;/p>
&lt;h2 id="优化移动端">优化移动端&lt;/h2>
&lt;p>上面的页面在电脑上浏览的效果还不错，来看看在手机这种屏幕比较窄的设备上怎么样，按 F12 打开 Chrome 开发者工具，在左上角切换移动设备：&lt;/p>
&lt;p>&lt;img src="images/mobile-view.jpg" alt="image-20210419210100766">&lt;/p>
&lt;p>在 iPhone 上的显示效果大致如上，可以看到主要的问题是页面左右两边内边距没有设置好。通过把鼠标放在右边树状的 HTML 标签上，可以确定需要在 &lt;code>body&lt;/code> 下第一个 &lt;code>div&lt;/code> 添加 padding：&lt;code>px-6 md:px-0&lt;/code>。其中 &lt;code>px-6&lt;/code> 表示 X轴方向，也就是左右的 padding 为 6 个单位，后面的 &lt;code>md:px-0&lt;/code> 表示在中等尺寸及更大的屏幕上，则设置左右页边距为 0：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;container max-w-2xl mx-auto px-6 md:px-0&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
...
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="images/mobile-view-margin.jpg" alt="image-20210419211140174">&lt;/p>
&lt;p>项目卡片也需要进一步修改：&lt;/p>
&lt;p>&lt;img src="images/mobile-view-card.jpg" alt="image-20210419211655675">&lt;/p>
&lt;p>将 &lt;code>layouts/shortcodes/project-card.html&lt;/code> 中的 &lt;code>&amp;lt;img&amp;gt;&lt;/code> 标签里的 &lt;code>w-48&lt;/code> 替换为 &lt;code>w-full md:w-48&lt;/code>，表示默认宽度 w(idth) 为 full，也就是填充整个横向部分，在大屏幕宽度则固定为 48 个单位：&lt;/p>
&lt;p>&lt;img src="images/mobile-view-card-enhanced.jpg" alt="image-20210419212159848">&lt;/p>
&lt;h2 id="总结">总结&lt;/h2>
&lt;p>这篇文章记录了：&lt;/p>
&lt;ul>
&lt;li>初始化 Hugo 项目及添加 Tailwind CSS&lt;/li>
&lt;li>搭建基本的主页结构，&lt;/li>
&lt;li>创建单独 Projects 页面&lt;/li>
&lt;li>为手机移动端优化页面样式&lt;/li>
&lt;/ul>
&lt;p>下一篇文章，将介绍如何进一步优化该网站的内容和样式，以及和如何部署网站。&lt;/p>
&lt;p>&lt;strong>相关链接&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://praveenjuge.com/blog/install-tailwind-on-hugo/">Install Tailwind on Hugo [2021]&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tailwindcss.com/">Tailwind CSS&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://gohugo.io/">The world’s fastest framework for building websites | Hugo&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/hugo/">Hugo</category><category domain="https://blog.imfing.com/tags/tailwind/">Tailwind</category><category domain="https://blog.imfing.com/tags/web/">Web</category></item><item><title>在网页中嵌入 Plotly 交互式图表</title><link>https://blog.imfing.com/2021/04/html-webpage-embed-plotly/</link><guid isPermaLink="true">https://blog.imfing.com/2021/04/html-webpage-embed-plotly/</guid><pubDate>Tue, 13 Apr 2021 23:39:00 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>&lt;a href="https://plotly.com/">Plotly&lt;/a> 是一个开源的 Python 可视化图表库，能够生成高质量的可交互式图表。在生成了图表之后，把它放到网站上展示给其他人又是一个头疼的问题。这里分享一下我是如何导出 Plotly 图表并能够灵活的嵌入到任意网站的方法。&lt;/p>
&lt;!-- more -->
&lt;h2 id="创建-plotly-交互图表">创建 Plotly 交互图表&lt;/h2>
&lt;p>接下来将要在 &lt;a href="https://colab.research.google.com/">Google Colab&lt;/a> 演示如何创建一个 Plotly 图表。Google Colab 是一个在线免费的 Jupyter 运行环境，能够省去我们配置 Python 环境的麻烦。&lt;/p>
&lt;p>新建一个 Notebook，我们需要安装一些 Python 的包来使后面的例子能够顺利的运行：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">!pip install geopandas pyshp shapely plotly-geo
!pip install plotly --upgrade
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>接下来以一个&lt;a href="https://plotly.com/python/choropleth-maps/">美国的地图&lt;/a>为例：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="kn">from&lt;/span> &lt;span class="nn">urllib.request&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">urlopen&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;span class="k">with&lt;/span> &lt;span class="n">urlopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;https://raw.githubusercontent.com/plotly/datasets/master/geojson-counties-fips.json&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">counties&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">pandas&lt;/span> &lt;span class="kn">as&lt;/span> &lt;span class="nn">pd&lt;/span>
&lt;span class="n">df&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pd&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read_csv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://raw.githubusercontent.com/plotly/datasets/master/fips-unemp-16.csv&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;fips&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">})&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">plotly.express&lt;/span> &lt;span class="kn">as&lt;/span> &lt;span class="nn">px&lt;/span>
&lt;span class="n">fig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">px&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">choropleth&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">df&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">geojson&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">counties&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">locations&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;fips&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">color&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;unemp&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">color_continuous_scale&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Viridis&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">range_color&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">12&lt;/span>&lt;span class="p">),&lt;/span>
&lt;span class="n">scope&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;usa&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">labels&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;unemp&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="s1">&amp;#39;unemployment rate&amp;#39;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;span class="n">fig&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">update_layout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">margin&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;t&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;l&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s2">&amp;#34;b&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">})&lt;/span>
&lt;span class="n">fig&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">show&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面的代码运行之后可以得到这样的效果：&lt;/p>
&lt;p>&lt;img src="plotly-map.jpg" alt="Create Plotly Map">&lt;/p>
&lt;p>我们将鼠标放在上面，能够看到每个县的数据，用鼠标滚轮也还能放大缩小整个地图。这样子我们就得到了这样一个示例的交互图表。那有没有办法能够将它分享出去呢？&lt;/p>
&lt;h2 id="导出-plotly-图表到-html">导出 Plotly 图表到 HTML&lt;/h2>
&lt;p>接下来要做的事是把刚刚绘制的图表导出成网页的格式，也就是 HTML。参照官方文档里的 &lt;a href="https://plotly.com/python/interactive-html-export/">Interactive HTML Export in Python&lt;/a>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="n">fig&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write_html&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;map.html&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们可以在左边的文件浏览窗口看到刚刚生成的 &lt;code>map.html&lt;/code> 网页文件。但上面的代码生成的 HTML 文件体积通常偏大，因为它把 Plotly 的整个 Javascript 脚本库都集成了进去。我们可以通过设置 &lt;code>include_plotlyjs&lt;/code> 参数来控制它的大小：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="n">fig&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write_html&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;map.html&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">include_plotlyjs&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;cdn&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>通过设置 CDN 的方式引入 Plotly 库，&lt;code>map.html&lt;/code> 的大小缩小至了 4M。&lt;/p>
&lt;p>下面问题来了，将这个 &lt;code>map.html&lt;/code> 直接整个插入到其他网页的代码是比较困难，也会造成整个网页加载速度变慢。&lt;/p>
&lt;p>在下面展示的一篇 WIRED 文章就用到了 Plotly 的交互图表，它是通过了 &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe">iframe&lt;/a> 的方式将图表所在的网页嵌入。&lt;/p>
&lt;p>&lt;img src="plotly_on_wired.jpg" alt="WIRED">&lt;/p>
&lt;p>这种方法通俗来说就是在网页中放一个小浏览器，再将另一个网站的页面加载进来。&lt;/p>
&lt;p>想要实现这个的关键步骤是把我们刚才生成的 Plotly 图表变成一个可访问的网站链接。一个简单又免费的方法就是通过 &lt;a href="https://pages.github.com/">GitHub Pages&lt;/a>。&lt;/p>
&lt;h2 id="上传图表创建-github-pages">上传图表创建 GitHub Pages&lt;/h2>
&lt;p>首先&lt;a href="https://docs.github.com/en/github/getting-started-with-github/create-a-repo">新建一个公共仓库&lt;/a>，之后将刚刚生成的 &lt;code>map.html&lt;/code> 重命名为 &lt;code>index.html&lt;/code> 并上传到刚刚创建的仓库：
&lt;img src="repo-upload.png" alt="">&lt;/p>
&lt;p>选择文件之后直接点击 Commit Changes。之后在 Settings 里面找到 Pages 页面，选择 Source 为 main：&lt;/p>
&lt;p>&lt;img src="pages-1.png" alt="">&lt;/p>
&lt;p>选择 Save 之后，就会有成功的通知，打开那个链接确认能够看到你的 Plotly 图表。&lt;/p>
&lt;p>&lt;img src="pages-2.png" alt="">&lt;/p>
&lt;p>如果需要更新你的图表的话，只需要更新仓库中的 &lt;code>index.html&lt;/code> 文件即可，重新打开上面的链接并刷新就能看到更新后的图表了。&lt;/p>
&lt;h2 id="创建-iframe">创建 iframe&lt;/h2>
&lt;p>接着我们就可以利用上面创建的网址链接：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">iframe&lt;/span> &lt;span class="na">scrolling&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;no&amp;#34;&lt;/span> &lt;span class="na">style&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;border:none;&amp;#34;&lt;/span> &lt;span class="na">seamless&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;seamless&amp;#34;&lt;/span>
&lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;https://mtobeiyf.github.io/plotly-map-demo/&amp;#34;&lt;/span>
&lt;span class="na">height&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;400&amp;#34;&lt;/span> &lt;span class="na">width&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;100%&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">iframe&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>只需将 &lt;code>src&lt;/code> 部分替换成你的链接即可，同时可以通过 &lt;code>height&lt;/code> 和 &lt;code>width&lt;/code> 参数调整长宽。上面的一段 HTML 代码可以放到像 WordPress 的文章中，也可以放在任意的 HTML 或 Markdown 的文章中。&lt;/p>
&lt;p>下面是效果展示，将鼠标放上去就可以和图表交互：&lt;/p>
&lt;iframe scrolling="no" style="border:none;" seamless="seamless"
src="https://mtobeiyf.github.io/plotly-map-demo/" height="400" width="100%">&lt;/iframe>
&lt;p>链接：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://plotly.com/python/embedding-plotly-graphs-in-HTML/">Embedding Graphs in HTML in Python&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://plotly.com/python/choropleth-maps/">Choropleth Maps in Python&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/python/">Python</category><category domain="https://blog.imfing.com/tags/html/">HTML</category><category domain="https://blog.imfing.com/tags/github/">GitHub</category></item><item><title>利用 RSS 高效获取信息</title><link>https://blog.imfing.com/2021/03/effective-rss/</link><guid isPermaLink="true">https://blog.imfing.com/2021/03/effective-rss/</guid><pubDate>Sat, 06 Mar 2021 23:39:00 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>信息碎片化的今天，想要获取优质信息变得非常困难：我们需要耗费很多时间访问不同的网站和手机客户端去追踪即时的资讯，与此同时也会被应用自己的推荐信息流分散注意力。&lt;/p>
&lt;!-- more -->
&lt;h2 id="什么是-rss">什么是 RSS&lt;/h2>
&lt;p>RSS 的全称是 Really Simple Syndication，网站通常会生成 RSS Feed 文件，每当站点发布新的内容时，都会在这个文件中生成新内容的摘要等信息。因此仅需通过访问网站上的这个固定地址的文件，我们就能够得到当前网站的新内容更新信息。&lt;/p>
&lt;p>下面是 &lt;a href="https://www.theverge.com/">The Verge&lt;/a> 的 RSS Feed 的示例：
&lt;img src="RSS-XML.png" alt="">&lt;/p>
&lt;p>RSS 是十几二十年前的东西，曾经一度在 Google 宣布关闭 Google Reader 时被“宣告死亡”。但最近几年，随着社交媒体的普及以及信息的爆炸，RSS 又重新回到一些人的视野。
通过 RSS，我们就能够订阅自己想要的站点更新，将信息聚合，能够更加有效率阅读和处理信息流。&lt;/p>
&lt;h2 id="rss-客户端">RSS 客户端&lt;/h2>
&lt;p>目前有很多方式能够通过 RSS 订阅你想要的资讯，有网页版的、也有手机电脑客户端。这些软件或者网站应用的使用方式都非常简单，只需要把你需要订阅的 RSS 链接加入进去就行了。&lt;/p>
&lt;p>我目前在用的 RSS 客户端是 &lt;a href="https://reederapp.com/">Reeder 5&lt;/a>，最新的版本 5 支持了 iCloud 同步，你就不再需要第三方的网站服务，直接由 Reeder 在你的 Mac、iPad 和 iPhone 之间同步阅读进度及内容。
&lt;img src="Reeder5.png" alt="">&lt;/p>
&lt;p>可惜的是 Reeder 是需要付费的，那么免费的替代品可以考虑开源的 &lt;a href="https://nnw.ranchero.com/">NetNewsWire&lt;/a>，目前也正在测试 iCloud 同步功能。将来等新版本的 Mac 和 iOS 客户端发布，是完全能够用作日常主力的 RSS 阅读器。
&lt;img src="NetNewsWire.png" alt="">&lt;/p>
&lt;p>除了电脑客户端，你也可以选择一些网站管理自己的 RSS 订阅源，并直接在网站里阅读。对于网页端的选择，&lt;a href="https://www.inoreader.com/">Inoreader&lt;/a> 的免费 Plan 就足够大多数人的日常订阅需求。当然，免费随之而来的缺点是：有广告，以及订阅源的刷新不是非常及时，想要更好的体验，就得付费购买更高级的 Plan。
&lt;img src="Inoreader.png" alt="">&lt;/p>
&lt;p>&lt;a href="https://feedbin.com/">Feedbin&lt;/a> 是另一个非常优秀的网页 RSS 阅读器，界面简约，还支持订阅 Twitter，以及提供虚拟邮箱订阅一些通过邮件更新的站点。
&lt;img src="Feedbin.png" alt="">&lt;/p>
&lt;p>值得一提的是，Reeder 和 NetNewsWire 都支持订阅 Feedbin 和 Inoreader，这样你可以在手机平板、网页、电脑端多个设备间同步阅读的进度。不过，在Reeder 5 支持 iCloud 同步内容之后，再使用网页阅读器就略显多余了。&lt;/p>
&lt;h2 id="订阅信息">订阅信息&lt;/h2>
&lt;p>要获取网站的 RSS 链接，最常用的就是直接 Google 网站名称加上 RSS 关键词。一般的个人博客，或者用 WordPress 搭建的资讯站点，都会提供 RSS Feed 的链接，不过像一些大型的商业付费资讯，不一定会有 RSS Feed。&lt;/p>
&lt;p>同样我们可以利用 RSS 订阅 YouTube 频道或播放列表，这样一来我们就不需要点开 YouTube 来追踪我们喜欢的频道的最新视频了。
我们以&lt;a href="https://www.youtube.com/channel/UCCKlp1JI9Yg3-cUjKPdD3mw">小高姐的 Magic Ingredients&lt;/a>频道为例，它的链接是：&lt;/p>
&lt;p>&lt;code>https://www.youtube.com/channel/UCCKlp1JI9Yg3-cUjKPdD3mw&lt;/code>&lt;/p>
&lt;p>其中 channel 后面的一长串是 Channel ID，将那一串粘贴到&lt;/p>
&lt;p>&lt;code>https://www.youtube.com/feeds/videos.xml?channel_id=&lt;/code> 最后面就可以得到 RSS 的订阅链接了：&lt;/p>
&lt;p>&lt;code>https://www.youtube.com/feeds/videos.xml?channel_id=UCCKlp1JI9Yg3-cUjKPdD3mw&lt;/code>。&lt;/p>
&lt;p>下面是在 Reeder 5 上面订阅 YouTube 频道的示例。这里需要注意的是，不同的软件对 YouTube 视频的支持是不一样的，有些可能只能显示标题，没法像 Reeder 一样内嵌视频。
&lt;img src="RSS-YouTube.png" alt="">
同样，也可以通过类似的方法订阅一个播放列表，只需使用下面的地址：&lt;/p>
&lt;p>&lt;code>https://www.youtube.com/feeds/videos.xml?playlist_id=&lt;/code>&lt;/p>
&lt;p>订阅 Reddit 的话，以 &lt;a href="https://www.reddit.com/r/MachineLearning/">r/MachineLearning&lt;/a> 为例，仅需在它的地址后加上 &lt;code>.rss&lt;/code> 即可：&lt;/p>
&lt;p>&lt;code>https://www.reddit.com/r/MachineLearning.rss&lt;/code>&lt;/p>
&lt;p>对于像 Twitter 的社交媒体来说，得指望第三方的服务从 Twitter 抓取内容，然后生成对应的 RSS Feed。之前提到的 Feedbin 支持订阅 Twitter，免费的解决方案可以采用 &lt;a href="https://docs.rsshub.app/social-media.html#twitter-yong-hu-shi-jian-xian">社交媒体 | RSSHub&lt;/a> 提供的接口：&lt;/p>
&lt;p>&lt;code>https://rsshub.app/twitter/user/elonmusk&lt;/code>&lt;/p>
&lt;p>&lt;img src="RSS-Twitter.png" alt="">&lt;/p>
&lt;p>对于这类需没有直接提供 RSS 的信息源，可以看看开源的 &lt;a href="https://docs.rsshub.app/">介绍 | RSSHub&lt;/a> 项目，里面包括了非常多开箱即用的 RSS 订阅链接。&lt;/p>
&lt;p>&lt;strong>写在最后&lt;/strong>：
RSS 只是一种便捷的聚合信息的方式，对于经常关注各种各样新闻、油管频道、博客站点的人来说，通过 RSS 订阅的方式，能够非常便捷的获取及时的更新。当然，RSS 也有诸多限制，但这也不妨碍我们尝试使用 RSS 来管理我们的信息流。&lt;/p>
&lt;p>链接：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://news.ycombinator.com/item?id=26014344">I Still Use RSS | Hacker News&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://zapier.com/blog/how-to-use-rss-feeds/">How to Use RSS Feeds to Boost Your Productivity&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://kill-the-newsletter.com/">Kill the Newsletter!&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.rsshub.app/en/">Introduction | RSSHub&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa?hl=en-US">Feedbro - Chrome Web Store&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://feedbin.com/">Feedbin&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.inoreader.com/">Inoreader - Take back control of your news feed&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E5%B7%A5%E5%85%B7/">工具</category><category domain="https://blog.imfing.com/tags/rss/">RSS</category></item><item><title>Raft 实验 Part C 总结回顾</title><link>https://blog.imfing.com/2020/10/mit-6.824-lab-2-raft-part-c/</link><guid isPermaLink="true">https://blog.imfing.com/2020/10/mit-6.824-lab-2-raft-part-c/</guid><pubDate>Sun, 11 Oct 2020 23:39:00 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>在 &lt;a href="https://pdos.csail.mit.edu/6.824/schedule.html">MIT 6.824&lt;/a> 分布式系统课程中，&lt;a href="https://pdos.csail.mit.edu/6.824/labs/lab-raft.html">第二个实验&lt;/a>是实现分布式共识算法 &lt;a href="https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf">Raft&lt;/a>，分成了 A/B/C 三个部分。之前的 A 和 B 部分实现了 Raft 中的领导选举（Leader election）和日志复制（Log replication），第 C 部分将会实现保存持久状态（Persistent State）。&lt;/p>
&lt;!-- more -->
&lt;p>Part 2A 和 Part 2B 部分基本已经把整个 Raft 的框架搭好了。如果基于Raft的服务器重新启动，则应从中断的位置恢复服务。这就要求 Raft 保持持久状态，使其在重启后仍然有效。论文的 &lt;a href="https://user-images.githubusercontent.com/5097752/93008783-e06d5980-f546-11ea-8225-4790b0528502.png">Figure2&lt;/a> 提到了哪些状态应该保持不变。&lt;/p>
&lt;p>前两部分文章链接：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.imfing.com/2020/09/mit-6.824-lab-2-raft-part-a/">Raft 实验 Part A - MIT 6.824 Lab 2&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.imfing.com/2020/10/mit-6.824-lab-2-raft-part-b/">Raft 实验 Part B - MIT 6.824 Lab 2&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="实验部分">实验部分&lt;/h2>
&lt;h4 id="persistent-state">Persistent State&lt;/h4>
&lt;p>论文图 2 直接告诉了我们 Persistent state on all servers: &lt;code>currentTerm&lt;/code>、&lt;code>votedFor&lt;/code> 和 &lt;code>log[]&lt;/code>。在这个部分里面，我们不需要负责把数据写到磁盘上的文件里，而是只需要通过 &lt;code>Persister&lt;/code> 对象 实现 &lt;code>persist()&lt;/code> 和 &lt;code>readPersist()&lt;/code> 函数保存和恢复状态。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Raft&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">persist&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// Your code here (2C).
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">w&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">bytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Buffer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">e&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">labgob&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewEncoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentTerm&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">votedFor&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">persister&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SaveRaftState&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Bytes&lt;/span>&lt;span class="p">())&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Raft&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">readPersist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="c1">// Your code here (2C).
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">bytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewBuffer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">d&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">labgob&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewDecoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentTerm&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">votedFor&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>基本按照原来注释里的例子写就可以了。如果想实现的更好一些，也可以再定义一个结构体。注意在开头要 &lt;code>import &amp;quot;../labgob&amp;quot;&lt;/code>&lt;/p>
&lt;p>那么现在问题来了，该在哪些地方调用 &lt;code>persist()&lt;/code> 和 &lt;code>readPersist()&lt;/code> 呢？直觉告诉我们当上面提到的三个状态（&lt;code>currentTerm&lt;/code>、&lt;code>votedFor&lt;/code> 和 &lt;code>log[]&lt;/code>）改变了就应该进行持久化。回顾一下之前的实现：&lt;/p>
&lt;ul>
&lt;li>&lt;code>Make()&lt;/code> 会在 Raft 实例被初始化的时候调用，因此这里会用到 &lt;code>readPersist()&lt;/code> 来读取先前保存好的状态&lt;/li>
&lt;li>Leader 在 &lt;code>Start()&lt;/code> 函数里可能会更新自己本地的 log，需要 &lt;code>persist()&lt;/code>&lt;/li>
&lt;li>当 Raft 转变为 Candidate 和 Follower 的时候，&lt;code>currentTerm&lt;/code> 和 &lt;code>votedFor&lt;/code> 都可能发生变化，因此在相应地方加上 &lt;code>persist()&lt;/code>&lt;/li>
&lt;li>&lt;code>RequestVote&lt;/code> 和 &lt;code>AppendEntries&lt;/code> 函数同样也会改变状态，最后也需要 &lt;code>persist()&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>如果以上都完成了，Part C 的前几个基本测试就能够通过。&lt;/p>
&lt;h4 id="back-up-nextindex-optimization">Back up nextIndex Optimization&lt;/h4>
&lt;p>不过还有几个 unreliable 的测试是没法通过的。这里我花了很久的时间去 Debug，因为理论上按 Figure 2 把所有的细节实现好了，那么 Figure 8 的问题是不会再出现的。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">Test &lt;span class="o">(&lt;/span>2C&lt;span class="o">)&lt;/span>: Figure &lt;span class="m">8&lt;/span> &lt;span class="o">(&lt;/span>unreliable&lt;span class="o">)&lt;/span> ...
--- FAIL: TestFigure8Unreliable2C &lt;span class="o">(&lt;/span>41.11s&lt;span class="o">)&lt;/span>
config.go:475: one&lt;span class="o">(&lt;/span>4800&lt;span class="o">)&lt;/span> failed to reach agreement
Test &lt;span class="o">(&lt;/span>2C&lt;span class="o">)&lt;/span>: churn ...
--- FAIL: TestReliableChurn2C &lt;span class="o">(&lt;/span>26.33s&lt;span class="o">)&lt;/span>
config.go:475: one&lt;span class="o">(&lt;/span>7825959370052427039&lt;span class="o">)&lt;/span> failed to reach agreement
Test &lt;span class="o">(&lt;/span>2C&lt;span class="o">)&lt;/span>: unreliable churn ...
--- FAIL: TestUnreliableChurn2C &lt;span class="o">(&lt;/span>26.18s&lt;span class="o">)&lt;/span>
config.go:475: one&lt;span class="o">(&lt;/span>1988810278690071914&lt;span class="o">)&lt;/span> failed to reach agreement
FAIL
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>后来又读了一遍实验要求，发现这么一条 Hint：&lt;/p>
&lt;blockquote>
&lt;p>You will probably need the optimization that backs up nextIndex by more than one entry at a time. Look at the &lt;a href="https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf">extended Raft paper&lt;/a> starting at the bottom of page 7 and top of page 8 (marked by a gray line). The paper is vague about the details; you will need to fill in the gaps, perhaps with the help of the 6.824 Raft lectures.&lt;/p>
&lt;/blockquote>
&lt;p>简而言之就是在之前的实现里面，每次当 &lt;code>AppendEntries&lt;/code> 因为不一致的 log 而返回 &lt;code>false&lt;/code> 时，我们只让 &lt;code>nextIndex&lt;/code> 往回退了一个位置。对于有很多不一致的 entry 的话这样做显然不够 efficient。因此需要优化这一步骤，具体的做法是在 &lt;code>AppendEntriesReply&lt;/code> 结构中增加一个 &lt;code>conflictIndex&lt;/code> 项。&lt;/p>
&lt;p>如果 Follower 没有 &lt;code>prevLogIndex&lt;/code> 那一项，即 Follower log 长度不够，那么 &lt;code>conflictIndex = len(log)&lt;/code>。具体可以看 &lt;a href="https://thesquareplanet.com/blog/students-guide-to-raft/#an-aside-on-optimizations">Student's Guide to Raft&lt;/a>，他同时用了 &lt;code>conflictIndex&lt;/code> 和 &lt;code>conflictTerm&lt;/code>，这里我偷懒了，最好要补上。之后 decrement nextIndex and retry 那一步改写成：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="c1">// If last log index ≥ nextIndex for a follower: send
&lt;/span>&lt;span class="c1">// AppendEntries RPC with log entries starting at nextIndex
&lt;/span>&lt;span class="c1">// • If successful: update nextIndex and matchIndex for follower (§5.3)
&lt;/span>&lt;span class="c1">// • If AppendEntries fails because of log inconsistency:
&lt;/span>&lt;span class="c1">// decrement nextIndex and retry (§5.3)
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="k">if&lt;/span> &lt;span class="nx">reply&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Success&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nextIndex&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">reply&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">conflictIndex&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="测试">测试&lt;/h4>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">➜ raft git:&lt;span class="o">(&lt;/span>lab2&lt;span class="o">)&lt;/span> ✗ go &lt;span class="nb">test&lt;/span> -run 2C
Test &lt;span class="o">(&lt;/span>2C&lt;span class="o">)&lt;/span>: basic persistence ...
labgob warning: Decoding into a non-default variable/field int may not work
... Passed -- 3.4 &lt;span class="m">3&lt;/span> &lt;span class="m">308&lt;/span> &lt;span class="m">64734&lt;/span> &lt;span class="m">6&lt;/span>
Test &lt;span class="o">(&lt;/span>2C&lt;span class="o">)&lt;/span>: more persistence ...
... Passed -- 18.7 &lt;span class="m">5&lt;/span> &lt;span class="m">2364&lt;/span> &lt;span class="m">413576&lt;/span> &lt;span class="m">16&lt;/span>
Test &lt;span class="o">(&lt;/span>2C&lt;span class="o">)&lt;/span>: partitioned leader and one follower crash, leader restarts ...
... Passed -- 2.1 &lt;span class="m">3&lt;/span> &lt;span class="m">82&lt;/span> &lt;span class="m">18519&lt;/span> &lt;span class="m">4&lt;/span>
Test &lt;span class="o">(&lt;/span>2C&lt;span class="o">)&lt;/span>: Figure &lt;span class="m">8&lt;/span> ...
... Passed -- 26.8 &lt;span class="m">5&lt;/span> &lt;span class="m">26948&lt;/span> &lt;span class="m">6112820&lt;/span> &lt;span class="m">14&lt;/span>
Test &lt;span class="o">(&lt;/span>2C&lt;span class="o">)&lt;/span>: unreliable agreement ...
... Passed -- 3.5 &lt;span class="m">5&lt;/span> &lt;span class="m">228&lt;/span> &lt;span class="m">80581&lt;/span> &lt;span class="m">246&lt;/span>
Test &lt;span class="o">(&lt;/span>2C&lt;span class="o">)&lt;/span>: Figure &lt;span class="m">8&lt;/span> &lt;span class="o">(&lt;/span>unreliable&lt;span class="o">)&lt;/span> ...
... Passed -- 27.9 &lt;span class="m">5&lt;/span> &lt;span class="m">3900&lt;/span> &lt;span class="m">21820584&lt;/span> &lt;span class="m">132&lt;/span>
Test &lt;span class="o">(&lt;/span>2C&lt;span class="o">)&lt;/span>: churn ...
... Passed -- 16.2 &lt;span class="m">5&lt;/span> &lt;span class="m">1204&lt;/span> &lt;span class="m">1825170&lt;/span> &lt;span class="m">696&lt;/span>
Test &lt;span class="o">(&lt;/span>2C&lt;span class="o">)&lt;/span>: unreliable churn ...
... Passed -- 16.3 &lt;span class="m">5&lt;/span> &lt;span class="m">1755&lt;/span> &lt;span class="m">1290154&lt;/span> &lt;span class="m">72&lt;/span>
PASS
ok _/src/raft 117.704s
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="总结回顾">总结回顾&lt;/h2>
&lt;p>Raft 简明扼要，比较好懂，但是实现起来要注意的细节非常多，得把 Figure 2 仔仔细细的看完并且实现每一个部分。因为是分布式的系统，Raft 调试起来也会有不小的麻烦，Youtube 上的这节课 &lt;a href="https://youtu.be/UzzcUS2OHqo">Lecture 5: Go, Threads, and Raft&lt;/a> 对调试会提供不少的帮助，后半段视频也提供了 Leader Election 部分思路。&lt;/p>
&lt;p>这门课以前 TA 写的 &lt;a href="https://thesquareplanet.com/blog/students-guide-to-raft/#an-aside-on-optimizations">Student's Guide to Raft&lt;/a> 非常详细的解释了 Raft 实现中的一些注意点，非常有用。我在写完了整个 Lab 之后才看，发现很多坑和理解了很久的部分，这篇文章都有所涉及。甚至对于一些具体的细节也解释的非常清楚，强烈推荐看一下。&lt;/p>
&lt;h4 id="参考">参考&lt;/h4>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://raft.github.io/">The Raft Consensus Algorithm&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://pdos.csail.mit.edu/6.824/labs/lab-raft.html">https://pdos.csail.mit.edu/6.824/labs/lab-raft.html&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://thesquareplanet.com/blog/students-guide-to-raft/">https://thesquareplanet.com/blog/students-guide-to-raft/&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/kophy/6.824/blob/master/src/raft/raft.go">https://github.com/kophy/6.824/blob/master/src/raft/raft.go&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.cnblogs.com/mignet/p/6824_Lab_2_Raft_2C.html">https://www.cnblogs.com/mignet/p/6824_Lab_2_Raft_2C.html&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/go/">Go</category></item><item><title>Raft 实验 Part B - MIT 6.824 Lab 2</title><link>https://blog.imfing.com/2020/10/mit-6.824-lab-2-raft-part-b/</link><guid isPermaLink="true">https://blog.imfing.com/2020/10/mit-6.824-lab-2-raft-part-b/</guid><pubDate>Mon, 05 Oct 2020 18:28:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>在 &lt;a href="https://pdos.csail.mit.edu/6.824/schedule.html">MIT 6.824&lt;/a> 分布式系统课程中，&lt;a href="https://pdos.csail.mit.edu/6.824/labs/lab-raft.html">第二个实验&lt;/a>是实现分布式共识算法 &lt;a href="https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf">Raft&lt;/a>，分成了 A/B/C 三个部分。我之前完成了 Part 2A，即 Raft 中的领导选举（Leader election）。实验 Part 2B 的主要实现日志复制（Log replication）的机制。&lt;/p>
&lt;!-- more -->
&lt;p>在熟悉和完成了 Part 2A 部分之后，Part 2B 的部分只需要在 2A 的基础上完善相应的逻辑，更容易直接上手，不过调试还是一个非常头疼的事情，因此在实现每个部分的时候，写好相应的注释以及输出相关的调试信息是非常重要的。&lt;/p>
&lt;h2 id="实验部分">实验部分&lt;/h2>
&lt;p>在 Raft 的论文中，&lt;a href="https://user-images.githubusercontent.com/5097752/93008783-e06d5980-f546-11ea-8225-4790b0528502.png">Figure2&lt;/a> 很好的描述了整个 Raft 算法的细节。所有的实现都需要按照这个图上的指示来，不能偷懒。&lt;/p>
&lt;h4 id="leader-election">Leader Election&lt;/h4>
&lt;p>先来回顾一下选举机制的实现：&lt;/p>
&lt;ul>
&lt;li>初始化后用 Goroutine 执行 &lt;code>LeaderElection()&lt;/code>，它是一个无限循环，通过 &lt;code>time.Sleep&lt;/code> 实现选举超时检测，如果超时则开始新一轮选举 &lt;code>AttemptElection()&lt;/code>&lt;/li>
&lt;li>在 &lt;code>AttemptElection&lt;/code> 中，将自己设为候选者，并向其它服务器发送 RequestVote RPC，若票数超过一半则转为 Leader，开始 &lt;code>LeaderOperation&lt;/code>&lt;/li>
&lt;li>在 &lt;code>LeaderOperation&lt;/code> 中，定时向其它服务器发送心跳包，心跳包通过 &lt;code>AppendEntries&lt;/code> RPC 处理&lt;/li>
&lt;/ul>
&lt;h4 id="log-replication">Log Replication&lt;/h4>
&lt;ul>
&lt;li>当 Leader 的 &lt;code>Start(command)&lt;/code> 调用之后，立即向它自己的 log 末尾添加一个新 entry，更新各种 index&lt;/li>
&lt;li>Leader 需要不断向其它服务器发送心跳包，直接在心跳包处理逻辑 &lt;code>AppendEntries&lt;/code> 里实现日志复制的逻辑&lt;/li>
&lt;li>当 Follower 在 &lt;code>AppendEntries&lt;/code> 接受到参数之后，需要将它自己和 Leader 的 log 保持完全一致。一种情况是该 Follower 已经有了之前完整 log，只需添加新的 entry；另一种情况则是该 Follower 的 log 不完整或者有错误，则直接返回失败，Leader 检测到失败之后会将 &lt;code>nextIndex[index]&lt;/code> 往前移动一位，如此反复直到 Follower 将所有不一致的 entry 删除并将缺失的 log 添加到末尾&lt;/li>
&lt;li>当 Leader 得知 Follower 同步成功之后，和选举一样，如果超过半数的 &lt;code>matchIndex&lt;/code> 都大于当前的 &lt;code>commitIndex&lt;/code>，表明有新的 entry commit 成功了，需要更新 &lt;code>commitIndex&lt;/code>&lt;/li>
&lt;li>当 &lt;code>commitIndex&lt;/code> 更新后，还需要通知 &lt;code>applyCh&lt;/code>，将 &lt;code>[lastApplied, commitIndex]&lt;/code> 区间里的所有 entry 都以 &lt;code>ApplyMsg&lt;/code> 的结构体传送到 &lt;code>applyCh&lt;/code> 中，最后再更新 &lt;code>lastApplied&lt;/code>&lt;/li>
&lt;/ul>
&lt;h4 id="pseudocode">Pseudocode&lt;/h4>
&lt;p>日志结构体可以直接按照图中的描述，这里我按照文章 5.3 小节加上了 &lt;code>Index&lt;/code> 项保存在日志中的序号。注意日志的序号是从 1 开始，而存储的数组的下标是从 0 开始。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">type&lt;/span> &lt;span class="nx">LogEntry&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">Command&lt;/span> &lt;span class="kd">interface&lt;/span>&lt;span class="p">{}&lt;/span> &lt;span class="c1">// command for state machine
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">Term&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// when entry is received by leader
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">Index&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// index in the LogEntry
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>首先，在 &lt;code>Make()&lt;/code> 函数中，需要初始化 &lt;code>nextIndex&lt;/code> 和 &lt;code>matchIndex&lt;/code> 数组。在 Go 中，初始化一个给定长度的数组并赋值为 0 用 &lt;code>make([]int, len)&lt;/code>。此外，还需要在 Raft 结构中将 &lt;code>applyCh&lt;/code> 保存下来，这样每当 commit 一个新的 log，需要向 &lt;code>applyCh&lt;/code> 发送一个 &lt;code>ApplyMsg&lt;/code> 消息，否则是无法通过测试的。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">func&lt;/span> &lt;span class="nf">Make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">peers&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">labrpc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ClientEnd&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">me&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">persister&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Persister&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">applyCh&lt;/span> &lt;span class="kd">chan&lt;/span> &lt;span class="nx">ApplyMsg&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Raft&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// ...
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">applyCh&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">applyCh&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nextIndex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">peers&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">matchIndex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">peers&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="k">go&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LeaderElection&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="c1">//...
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nx">rf&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>接下来处理 &lt;code>Start()&lt;/code> 函数，该函数尝试将传入 command 写入 Raft 的日志中。注释中提示了该函数应当立即返回，因此这里并不需要非常复杂的逻辑，先更新 &lt;code>term&lt;/code> 和 &lt;code>isLeader&lt;/code>，如果不是 Leader 就直接返回（此时 &lt;code>index&lt;/code> 为 -1），如果当前实例是 Leader，那么直接 append 一个新的 LogEntry，将其序号赋值给 &lt;code>index&lt;/code> 变量，并更新 &lt;code>nextIndex&lt;/code> 和 &lt;code>matchIndex&lt;/code>，然后直接返回。&lt;/p>
&lt;p>虽然这个函数添加了一个新的 LogEntry 到它的日志末尾，但其它的 Raft 实例并没有达成共识，我们之后需要在 &lt;code>AppendEntries&lt;/code> RPC 中实现日志复制的完整逻辑。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Raft&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Start&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">command&lt;/span> &lt;span class="kd">interface&lt;/span>&lt;span class="p">{})&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">bool&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// ... Remember to Lock()
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">term&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentTerm&lt;/span>
&lt;span class="nx">isLeader&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">state&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="nx">Leader&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">isLeader&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">LogEntry&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Command&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">command&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Term&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">term&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Index&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">})&lt;/span>
&lt;span class="nx">index&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nextIndex&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">me&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">index&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">matchIndex&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">me&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">index&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="nx">index&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">term&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isLeader&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>接着来看 &lt;code>AppendEntries&lt;/code> RPC，在 Part 2A 中这个函数被用来处理心跳包，我们只需要在心跳包之后加上关于日志复制的逻辑，基本和 Figure 2 的 Receiver Implementation 的 5 点是一模一样的。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Raft&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">AppendEntries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">args&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">AppendEntriesArgs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">reply&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">AppendEntriesReply&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">defer&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lastReceive&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Now&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="nx">reply&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Term&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentTerm&lt;/span>
&lt;span class="c1">// 1. Reply false if term &amp;lt; currentTerm (§5.1)
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// Switch to Follower if newer term found
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="c1">// 2. Reply false if log doesn&amp;#39;t contain an entry at prevLogIndex
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// whose term matches prevLogTerm (§5.3)
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="c1">// rf.log doesn&amp;#39;t contain entry at PrevLogIndex
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// reply false and return
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="c1">// 3. If an existing entry conflicts with a new one (same index
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// but different terms), delete the existing entry and all that
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// follow it (§5.3)
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="c1">// term at PrevLogIndex disagree with PrevLogTerm
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// reply false and return
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="c1">// 4. Append any new entries not already in the log
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="c1">// 5. If leaderCommit &amp;gt; commitIndex, set commitIndex =
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// min(leaderCommit, index of last new entry)
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="c1">// after updating commitIndex, send ApplyMsg if commitIndex &amp;gt; lastApplied
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// update lastApplied
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="nx">reply&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Success&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面的 &lt;code>AppendEntries&lt;/code> 是用来处理心跳包请求的，发送心跳包的函数需要加上处理逻辑。能够发送心跳包的只有 Leader，因此按照 Figure 2 中 Rules for Server Leader 部分实现。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Raft&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">SendHeartbeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">server&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">prevLog&lt;/span> &lt;span class="nx">LogEntry&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nextIndex&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">prevLog&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">[(&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nextIndex&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nx">entries&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="nx">LogEntry&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">prevLog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Index&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nb">copy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">entries&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">prevLog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Index&lt;/span>&lt;span class="p">:])&lt;/span>
&lt;span class="nx">args&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">AppendEntriesArgs&lt;/span>&lt;span class="p">{&lt;/span>
&lt;span class="nx">Term&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">currentTerm&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">LeaderId&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">me&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">PrevLogIndex&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">prevLog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Index&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">PrevLogTerm&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">prevLog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Term&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">Entries&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">entries&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">LeaderCommit&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">commitIndex&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nx">reply&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">AppendEntriesReply&lt;/span>&lt;span class="p">{}&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="nx">ok&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">sendAppendEntries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">args&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">reply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">defer&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lastReceive&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Now&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="c1">// Convert to Follower if reply.Term is newer
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="c1">// If last log index ≥ nextIndex for a follower: send
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// AppendEntries RPC with log entries starting at nextIndex
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// • If successful: update nextIndex and matchIndex for follower (§5.3)
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// • If AppendEntries fails because of log inconsistency:
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// decrement nextIndex and retry (§5.3)
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="nx">reply&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Success&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// Update nextIndex and matchIndex
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// ...
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="c1">// If there exists an N such that N &amp;gt; commitIndex, a majority
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// of matchIndex[i] ≥ N, and log[N].term == currentTerm:
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// set commitIndex = N (§5.3, §5.4).
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// decrement nextIndex
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">nextIndex&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">prevLog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Index&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在 &lt;code>AttemptElection&lt;/code> 函数中，需要修改 &lt;code>RequestVoteArgs&lt;/code>，将 &lt;code>LastLogIndex&lt;/code> 和 &lt;code>LastLogTerm&lt;/code> 的值放进去，在 Candidate 得到足够的选票后，重新初始化 &lt;code>nextIndex&lt;/code> 和 &lt;code>matchIndex&lt;/code>。&lt;/p>
&lt;p>在 &lt;code>RequestVote&lt;/code> 处理函数中，需要加入判断 Candidate 的 log 是否比接收者的更新，确保 Leader 拥有最新的日志，否则设置 &lt;code>reply.voteGranted=false&lt;/code>。&lt;/p>
&lt;p>每一次更新完 &lt;code>commitIndex&lt;/code> 后，如果 &lt;code>commitIndex&lt;/code> 比 &lt;code>lastApplied&lt;/code> 更新，说明要向 &lt;code>applyCh&lt;/code> 发送最新被 commit 的 entry 信息。在 &lt;code>AppendEntries&lt;/code> 和 &lt;code>SendHeartbeat&lt;/code> 都有可能更新 &lt;code>commitIndex&lt;/code>，因此都需要调用该函数。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Raft&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">sendApplyMsg&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// If commitIndex &amp;gt; lastApplied: increment lastApplied, apply
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// log[lastApplied] to state machine (§5.3)
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">commitIndex&lt;/span> &lt;span class="p">&amp;gt;&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lastApplied&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">entries&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">append&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="nx">LogEntry&lt;/span>&lt;span class="p">{},&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lastApplied&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">commitIndex&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">entries&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nx">LogEntry&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">entry&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">entries&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">msg&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">ApplyMsg&lt;/span>&lt;span class="p">{&lt;/span>
&lt;span class="nx">CommandValid&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">Command&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">entry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Command&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">CommandIndex&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">entry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Index&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">applyCh&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="nx">msg&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lastApplied&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nx">msg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">CommandIndex&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lastApplied&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">msg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">CommandIndex&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}(&lt;/span>&lt;span class="nx">entries&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="测试">测试&lt;/h4>
&lt;p>可以通过 &lt;code>$ go test -run TestBasicAgree2B&lt;/code> 来运行其中的一个测试&lt;/p>
&lt;p>整个 Part 2B&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">➜ raft git:&lt;span class="o">(&lt;/span>lab2&lt;span class="o">)&lt;/span> ✗ go &lt;span class="nb">test&lt;/span> -run 2B
Test &lt;span class="o">(&lt;/span>2B&lt;span class="o">)&lt;/span>: basic agreement ...
... Passed -- 1.0 &lt;span class="m">3&lt;/span> &lt;span class="m">16&lt;/span> &lt;span class="m">4290&lt;/span> &lt;span class="m">3&lt;/span>
Test &lt;span class="o">(&lt;/span>2B&lt;span class="o">)&lt;/span>: RPC byte count ...
... Passed -- 2.6 &lt;span class="m">3&lt;/span> &lt;span class="m">48&lt;/span> &lt;span class="m">113518&lt;/span> &lt;span class="m">11&lt;/span>
Test &lt;span class="o">(&lt;/span>2B&lt;span class="o">)&lt;/span>: agreement despite follower disconnection ...
... Passed -- 5.5 &lt;span class="m">3&lt;/span> &lt;span class="m">96&lt;/span> &lt;span class="m">25251&lt;/span> &lt;span class="m">7&lt;/span>
Test &lt;span class="o">(&lt;/span>2B&lt;span class="o">)&lt;/span>: no agreement &lt;span class="k">if&lt;/span> too many followers disconnect ...
... Passed -- 3.7 &lt;span class="m">5&lt;/span> &lt;span class="m">168&lt;/span> &lt;span class="m">38812&lt;/span> &lt;span class="m">3&lt;/span>
Test &lt;span class="o">(&lt;/span>2B&lt;span class="o">)&lt;/span>: concurrent Start&lt;span class="o">()&lt;/span>s ...
... Passed -- 0.8 &lt;span class="m">3&lt;/span> &lt;span class="m">14&lt;/span> &lt;span class="m">3786&lt;/span> &lt;span class="m">6&lt;/span>
Test &lt;span class="o">(&lt;/span>2B&lt;span class="o">)&lt;/span>: rejoin of partitioned leader ...
... Passed -- 4.6 &lt;span class="m">3&lt;/span> &lt;span class="m">140&lt;/span> &lt;span class="m">34273&lt;/span> &lt;span class="m">4&lt;/span>
Test &lt;span class="o">(&lt;/span>2B&lt;span class="o">)&lt;/span>: leader backs up quickly over incorrect follower logs ...
... Passed -- 38.5 &lt;span class="m">5&lt;/span> &lt;span class="m">2634&lt;/span> &lt;span class="m">2335471&lt;/span> &lt;span class="m">106&lt;/span>
Test &lt;span class="o">(&lt;/span>2B&lt;span class="o">)&lt;/span>: RPC counts aren&lt;span class="err">&amp;#39;&lt;/span>t too high ...
... Passed -- 2.2 &lt;span class="m">3&lt;/span> &lt;span class="m">40&lt;/span> &lt;span class="m">11214&lt;/span> &lt;span class="m">12&lt;/span>
PASS
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="写在最后">写在最后&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>这部分实验调试占了绝大部分时间，用工程里面提供的 &lt;code>DPrintf&lt;/code> 会非常方便&lt;/p>
&lt;/li>
&lt;li>
&lt;p>代码需要严格按照 Figure 2 的逻辑实现，具体细节可以阅读 5.3 节帮助理解&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Go 的 for range 要注意是值还是 index&lt;/p>
&lt;p>最好显式的写成 &lt;code>for _, value := range Collection ...&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>JetBrains 的 &lt;a href="https://www.jetbrains.com/go/">GoLand&lt;/a> 非常好用，学生可以免费获得授权&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://raft.github.io/">The Raft Consensus Algorithm&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://pdos.csail.mit.edu/6.824/labs/lab-raft.html">https://pdos.csail.mit.edu/6.824/labs/lab-raft.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://youtu.be/UzzcUS2OHqo">https://youtu.be/UzzcUS2OHqo&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/go/">Go</category></item><item><title>Raft 实验 Part A - MIT 6.824 Lab 2</title><link>https://blog.imfing.com/2020/09/mit-6.824-lab-2-raft-part-a/</link><guid isPermaLink="true">https://blog.imfing.com/2020/09/mit-6.824-lab-2-raft-part-a/</guid><pubDate>Fri, 11 Sep 2020 18:28:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>在 &lt;a href="https://pdos.csail.mit.edu/6.824/schedule.html">MIT 6.824&lt;/a> 分布式系统课程中，&lt;a href="https://pdos.csail.mit.edu/6.824/labs/lab-raft.html">第二个实验&lt;/a>是实现分布式共识算法 &lt;a href="https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf">Raft&lt;/a>。这个实验分成了 A/B/C 三个部分，这篇将主要围绕 Part 2A 部分的实验介绍一下 Raft 的基本概念和实现 Raft 中的领导选举（Leader election）。&lt;/p>
&lt;!-- more -->
&lt;h2 id="raft">Raft&lt;/h2>
&lt;p>在 Raft 之前，比较著名的共识算法是 &lt;a href="https://en.wikipedia.org/wiki/Paxos_(computer_science)">Paxos&lt;/a>，据说非常难懂。提出 Raft 目的之一就是使其易于理解。&lt;/p>
&lt;p>共识（Concensus）是容错分布式系统中的一个基本问题。共识涉及多个服务器就操作达成一致。一旦他们对某个操作做出决定，该决定就是最终决定。当大多数服务器可用时，典型的共识算法会取得进展。例如，即使 2 台服务器出现故障，包含 5 台服务器的群集也可以继续运行。如果更多服务器发生故障，它们将停止前进（但绝不会返回错误的结果）。&lt;/p>
&lt;p>在分布式系统中，复制服务（replicated service）通常将其状态（即数据）的完整副本存储在多个副本服务器（replica server）上来实现容错功能。即使服务的某些服务器出现故障（崩溃，网络故障或不稳定），剩下的副本服务器也可以使服务继续运行。挑战在于，故障可能导致副本服务器持有不同的数据副本。&lt;/p>
&lt;p>Raft 将客户请求组织成一个序列，称为日志，并确保所有副本服务器看到相同的日志。每个副本均按日志顺序执行客户端请求，并将其应用于服务状态的本地副本。由于所有活动副本都具有相同的日志内容，因此它们都以相同的顺序执行相同的请求，因此继续具有相同的服务状态。如果服务器出现故障但后来又恢复了，Raft 会确保其日志为最新状态。只要至少大多数服务器处于活动状态并且可以相互通信，Raft 就会继续运行。如果没有这样的多数，Raft 将不会取得进展，但是一旦多数能够再次交流，它将从中断的地方继续前进。&lt;/p>
&lt;p>更多的细节可以前往 &lt;a href="https://raft.github.io/raft.pdf">Raft Paper&lt;/a>。&lt;/p>
&lt;h2 id="实验部分">实验部分&lt;/h2>
&lt;p>这里将讨论 &lt;a href="https://pdos.csail.mit.edu/6.824/labs/lab-raft.html">6.824 Lab 2: Raft&lt;/a> 的 Part 2A，这一部分是实现 Raft 中的领导选举机制。因为这是第一部分，所以实现起来会有一些无从下手，而且代码本身是 concurrent 的，所以调试也会比较困难。主要的代码都在 &lt;code>raft/raft.go&lt;/code> 中。&lt;/p>
&lt;p>在 Raft 的论文中，Figure 2 很好的描述了整个 Raft 算法的细节。接下来先梳理一下 Leader Election 的逻辑。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/93008783-e06d5980-f546-11ea-8225-4790b0528502.png" alt="4-Figure2-1">&lt;/p>
&lt;p>首先，一个 server 会有三种状态：Follower、Candidate 和 Leader。最开始的时候，所有的 server 的状态都为 Follower。对于每个 server，会初始化一个随机的选举超时时间（election timeout)，那么当一个 server 在这段时间里面没有收到来自 Candidate 或 Leader 的消息时，便将自己转变为 Candidate 并开始新一时期（Term）选举。Candidate 通过向其它 server 索取选票（Vote），得到半数以上便转变为 Leader。之后便定时向其它服务器发送心跳包（Heartbeat）来防止新的选举。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/93008786-ec591b80-f546-11ea-9e68-82ef25d45d41.png" alt="image">&lt;/p>
&lt;p>代码主要思路：&lt;/p>
&lt;ul>
&lt;li>所有状态都通过 &lt;code>Raft&lt;/code> 结构体管理&lt;/li>
&lt;li>&lt;code>Make()&lt;/code> 函数初始化所有状态变量，并通过 Goroutine 启动 &lt;code>LeaderElection()&lt;/code>，监测选举超时&lt;/li>
&lt;li>如果在规定时间内没有没有接收到其它服务器的消息请求，尝试选举 &lt;code>AttemptElection()&lt;/code>&lt;/li>
&lt;li>在 &lt;code>AttemptElection()&lt;/code> 中，将自己设为候选者，并向其它服务器发送 RequestVote RPC&lt;/li>
&lt;li>如果未得票超过半数，则当前 Term 不会有 Leader，于是选举超时被触发，重新选举&lt;/li>
&lt;li>如果得票超过半数，将自己设置为 Leader，并向其它服务器发送心跳包 Heartbeat&lt;/li>
&lt;li>发送心跳包即定期发送空的 AppendEntries PRC&lt;/li>
&lt;/ul>
&lt;p>首先按照 Figure 2，我们将基本的 State 填入 Raft 结构体中。在实验 Part A 中，主要用到 &lt;code>currentTerm&lt;/code> 和 &lt;code>votedFor&lt;/code>。除此以外，一个 Raft server 还需要管理它当前状态 &lt;code>state&lt;/code> 和 &lt;code>lastReceive&lt;/code>，表示最后一次收到其它服务器消息的时间，以便于判断超时。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Raft&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">mu&lt;/span> &lt;span class="nx">sync&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Mutex&lt;/span> &lt;span class="c1">// Lock to protect shared access to this peer&amp;#39;s state
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">peers&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">labrpc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ClientEnd&lt;/span> &lt;span class="c1">// RPC end points of all peers
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">persister&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Persister&lt;/span> &lt;span class="c1">// Object to hold this peer&amp;#39;s persisted state
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">me&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// this peer&amp;#39;s index into peers[]
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">dead&lt;/span> &lt;span class="kt">int32&lt;/span> &lt;span class="c1">// set by Kill()
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="c1">// Look at the paper&amp;#39;s Figure 2 for a description of what
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// state a Raft server must maintain.
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="nx">state&lt;/span> &lt;span class="nx">RaftState&lt;/span> &lt;span class="c1">// Raft server state
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">lastReceive&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Time&lt;/span> &lt;span class="c1">// last time receive message
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="nx">currentTerm&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// latest term server has seen
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">votedFor&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// candidateId that received vote in current term
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">log&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nx">LogEntry&lt;/span> &lt;span class="c1">// log entries
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="c1">// ...other volatile state
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Raft 状态：Follower、Candidate 和 Leader：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">type&lt;/span> &lt;span class="nx">RaftState&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;span class="kd">const&lt;/span> &lt;span class="p">(&lt;/span>
&lt;span class="nx">Follower&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">iota&lt;/span>
&lt;span class="nx">Candidate&lt;/span>
&lt;span class="nx">Leader&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在 Raft 初始化函数 &lt;code>Make()&lt;/code> 中，&lt;code>state&lt;/code> 先初始化为 &lt;code>Follower&lt;/code>，&lt;code>currentTerm&lt;/code> 为 &lt;code>0&lt;/code>，&lt;code>votedFor&lt;/code> 为 &lt;code>-1&lt;/code>。之后需要用 Goroutine 启动一个选举函数 &lt;code>go rf.LeaderElection()&lt;/code>，超时的话便触发选举 &lt;code>AttemptElection()&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Raft&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">LeaderElection&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">electionTimeout&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">rand&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Intn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">200&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">200&lt;/span>
&lt;span class="nx">startTime&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Now&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Duration&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">electionTimeout&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Millisecond&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lastReceive&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Before&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">startTime&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">state&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="nx">Leader&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// Attempt an election
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">go&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AttemptElection&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在 &lt;code>AttemptElection()&lt;/code> 中，主要逻辑为：&lt;/p>
&lt;ol>
&lt;li>将状态 &lt;code>state&lt;/code> 转换为 &lt;code>Candidate&lt;/code>，将 &lt;code>currentTerm&lt;/code> 增加 &lt;code>1&lt;/code>，&lt;code>votedFor&lt;/code> 设置为 &lt;code>me&lt;/code>&lt;/li>
&lt;li>对 &lt;code>peers&lt;/code> 除自己以外的服务器发送 RequestVote RPC，并统计票数，这一部需要用 Goroutine 异步并行执行&lt;/li>
&lt;/ol>
&lt;p>&lt;code>AttemptElection()&lt;/code> 伪代码（注意除 RPC Call 之外访问资源的地方都需要 Mutex）&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="c1">// AttemptElection pseudocode
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Raft&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">AttemptElection&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// Convert to Candidate
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// Init args and reply
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">numVote&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">peers&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// Send RequestVote RPC and tally votes to all peers
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">server&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">sendRequestVote&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">args&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">reply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="c1">// If Term is newer, conver to Follower
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// If me is not Candidate, return
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">reply&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">VoteGranted&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">numVote&lt;/span>&lt;span class="o">++&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">numVote&lt;/span> &lt;span class="p">&amp;gt;&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">peers&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mi">2&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">state&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="nx">Candidate&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// Convert to Leader
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// Send heartbeat to peers
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="nx">peer&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">peers&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">go&lt;/span> &lt;span class="nf">LeaderOperation&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">peer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}(&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>RequestVote()&lt;/code> 函数用来处理 RequestVote RPC 请求，照 Figure 2 实现即可。&lt;/p>
&lt;p>&lt;code>LeaderOperation()&lt;/code> 就是类似上面的 &lt;code>LeaderElection()&lt;/code>，一个无限循环，定时发送 Heartbeat：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">rf&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Raft&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">LeaderOperation&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">server&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// If state is not Leader, return
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">go&lt;/span> &lt;span class="nx">rf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SendHeartbeat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">HeartbeatInterval&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>发送 Heartbeat 又需要调用 &lt;code>sendAppendEntries()&lt;/code>， &lt;code>AppendEntries()&lt;/code> 用来处理 RPC 请求，具体逻辑和 Figure 2 中一样。&lt;/p>
&lt;h2 id="调试与测试">调试与测试&lt;/h2>
&lt;ol>
&lt;li>注意访问资源的地方都需要互斥锁保护：&lt;code>rf.mu.Lock()&lt;/code> 和 &lt;code>rf.mu.Unlock()&lt;/code>&lt;/li>
&lt;li>RPC Call 之前需要确保 &lt;code>rf.mu.Unlock()&lt;/code>，以防止 RPC Handler 里出现 Deadlock&lt;/li>
&lt;li>在函数中如果有无限循环 &lt;code>for {}&lt;/code>，慎用 &lt;code>defer&lt;/code>，因为 &lt;code>defer&lt;/code> 在 &lt;code>for {}&lt;/code> 外面的函数末尾执行，而不是在 &lt;code>for {}&lt;/code> 里面&lt;/li>
&lt;li>多用 &lt;code>log.Printf()&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>测试 Part 2A：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ go &lt;span class="nb">test&lt;/span> -run 2A
Test &lt;span class="o">(&lt;/span>2A&lt;span class="o">)&lt;/span>: initial election ...
... Passed -- 4.0 &lt;span class="m">3&lt;/span> &lt;span class="m">32&lt;/span> &lt;span class="m">9170&lt;/span> &lt;span class="m">0&lt;/span>
Test &lt;span class="o">(&lt;/span>2A&lt;span class="o">)&lt;/span>: election after network failure ...
... Passed -- 6.1 &lt;span class="m">3&lt;/span> &lt;span class="m">70&lt;/span> &lt;span class="m">13895&lt;/span> &lt;span class="m">0&lt;/span>
PASS
ok raft 10.187s
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://raft.github.io/">The Raft Consensus Algorithm&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://pdos.csail.mit.edu/6.824/labs/lab-raft.html">https://pdos.csail.mit.edu/6.824/labs/lab-raft.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://youtu.be/UzzcUS2OHqo">https://youtu.be/UzzcUS2OHqo&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/go/">Go</category></item><item><title>MapReduce 原理及实现 - MIT 6.824 Lab 1</title><link>https://blog.imfing.com/2020/09/mit-6.824-lab1-map-reduce/</link><guid isPermaLink="true">https://blog.imfing.com/2020/09/mit-6.824-lab1-map-reduce/</guid><pubDate>Sat, 05 Sep 2020 18:28:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>MapReduce 是 Google 的 Jeff Dean 等人在 2004 年提出的大数据处理框架。它将数据拆分为多块，在多个机器上并行处理。原本需要高性能计算资源的数据处理任务，通过 MapReduce 可以放到多个廉价的机器上并行处理。&lt;/p>
&lt;p>在 &lt;a href="https://pdos.csail.mit.edu/6.824/schedule.html">MIT 6.824&lt;/a> 分布式系统课程中，&lt;a href="https://pdos.csail.mit.edu/6.824/labs/lab-mr.html">第一个实验&lt;/a>便是用 Go 实现 MapReduce。&lt;/p>
&lt;!-- more -->
&lt;h2 id="mapreduce-原理">MapReduce 原理&lt;/h2>
&lt;ol>
&lt;li>MapReduce 程序分成一个 &lt;code>master&lt;/code> 和多个 &lt;code>worker&lt;/code>，&lt;code>master&lt;/code> 负责分配和调度任务，而 &lt;code>worker&lt;/code> 负责执行被分配的 &lt;code>Map&lt;/code> 或 &lt;code>Reduce&lt;/code> 任务&lt;/li>
&lt;li>MapReduce 的输入为 &lt;code>M&lt;/code> 个文件或数据块，数据之间没有依赖性；输出为 &lt;code>R&lt;/code> 个 Reduce 结果文件&lt;/li>
&lt;li>在 &lt;code>Map&lt;/code> 任务阶段，每一个 &lt;code>worker&lt;/code> 被分配一个文件，对文件内容执行用户自定义的 &lt;code>Map&lt;/code> 函数，输出若干 &lt;code>(key, value)&lt;/code> 对，并保存至中间文件&lt;/li>
&lt;li>当 &lt;code>Map&lt;/code> 任务全部执行完成后，分配 &lt;code>R&lt;/code> 个 &lt;code>Reduce&lt;/code> 任务，每个 &lt;code>worker&lt;/code> 对应一个 Reduce 任务，收集该 &lt;code>Reduce&lt;/code> 任务对应的所有中间文件，读取 &lt;code>(key, value)&lt;/code> 对，将它们排序后执行 &lt;code>Reduce&lt;/code> 函数，最后将结果输出&lt;/li>
&lt;/ol>
&lt;p>下图描绘了 MapReduce 的过程：&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/92337273-7fd9aa80-f076-11ea-9970-410133f6acc9.png" alt="">&lt;/p>
&lt;p>以计算单词个数为例，它的 Map 和 Reduce 函数可以这么表示：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="n">Example&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">word&lt;/span> &lt;span class="n">count&lt;/span>
&lt;span class="nb">input&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">thousands&lt;/span> &lt;span class="n">of&lt;/span> &lt;span class="n">text&lt;/span> &lt;span class="n">files&lt;/span>
&lt;span class="n">Map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">split&lt;/span> &lt;span class="n">v&lt;/span> &lt;span class="n">into&lt;/span> &lt;span class="n">words&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">each&lt;/span> &lt;span class="n">word&lt;/span> &lt;span class="n">w&lt;/span>
&lt;span class="n">emit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;1&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">Reduce&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">emit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>更多的细节在论文 &lt;a href="http://static.googleusercontent.com/media/research.google.com/en//archive/mapreduce-osdi04.pdf">MapReduce: Simplified Data Processing on Large Cluster&lt;/a> 中，对 MapReduce 有着很详细的解释。&lt;/p>
&lt;h2 id="mapreduce-实现">MapReduce 实现&lt;/h2>
&lt;p>下面将基于 MIT 6.824 的实验部分，讲述一下我是如何实现基本的 MapReduce 的。现在网上大多数的实验版本还停留在几年前，但 2020 Spring 的实验代码结构有了很大的变化，相对更加自由。&lt;/p>
&lt;p>首先直接克隆 &lt;a href="https://pdos.csail.mit.edu/6.824/labs/lab-mr.html">MIT 6.824 的项目&lt;/a>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ git clone git://g.csail.mit.edu/6.824-golabs-2020 6.824
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>项目概览：所有的文件都在 &lt;code>src&lt;/code> 文件夹下面。按照实验页面描述的可以执行已经给好的 sequential 的 MapReduce。示例是执行 &lt;code>wc.go&lt;/code> 的统计单词数的任务。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ &lt;span class="nb">cd&lt;/span> 6.824/src/main
$ go build -buildmode&lt;span class="o">=&lt;/span>plugin ../mrapps/wc.go
$ rm mr-out*
$ go run mrsequential.go wc.so pg*.txt
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个 Lab 是实现一个分布式的 MapReduce，由 &lt;code>master&lt;/code> 和 &lt;code>worker&lt;/code> 组成，它们将通过 RPC 进行通讯。&lt;/p>
&lt;p>在这个 Lab 中，我们只需要修改 &lt;code>src/mr&lt;/code> 目录下的 &lt;code>master.go&lt;/code>、&lt;code>rpc.go&lt;/code> 和 &lt;code>worker.go&lt;/code>。&lt;/p>
&lt;h3 id="需求分析">需求分析&lt;/h3>
&lt;ol>
&lt;li>只有一个 &lt;code>master&lt;/code> 实例，来分配调度 MapReduce 的任务&lt;/li>
&lt;li>&lt;code>worker&lt;/code> 实例需要不断向 &lt;code>master&lt;/code> 请求任务，在完成任务之后向 &lt;code>master&lt;/code> 报告任务完成&lt;/li>
&lt;li>&lt;code>master&lt;/code> 先分配 &lt;code>Map&lt;/code> 任务，在所有 &lt;code>Map&lt;/code> 任务结束后，再分配 &lt;code>reduce&lt;/code> 任务&lt;/li>
&lt;li>&lt;code>master&lt;/code> 在完成所有任务之后退出，此时如果还有 &lt;code>worker&lt;/code> 向 &lt;code>master&lt;/code> 请求任务，则会因为 RPC 请求失败而直接退出&lt;/li>
&lt;li>如果 &lt;code>worker&lt;/code> 意外退出，或者长时间未响应，则 &lt;code>master&lt;/code> 需要重新分配该任务&lt;/li>
&lt;/ol>
&lt;h3 id="实现">实现&lt;/h3>
&lt;p>这个 Lab 的切入口是 &lt;code>worker.go&lt;/code>，我们在 &lt;code>Worker&lt;/code> 函数中，需要不断请求任务，&lt;code>master&lt;/code> 根据当前状态分配任务。因此 &lt;code>worker&lt;/code> 并不需要管理状态，只需要根据 RPC 返回的结果执行相应的任务。&lt;/p>
&lt;p>对于 &lt;code>master&lt;/code>，我们需要所有输入文件名、Reduce 任务数量 &lt;code>R&lt;/code>。因为 &lt;code>Map&lt;/code> 任务的数量等于输入文件的数量 &lt;code>M&lt;/code>，所以可以维护一个长为 &lt;code>M&lt;/code> 的数列来管理所有 &lt;code>Map&lt;/code> 任务的状态。同理也可以用长为 &lt;code>R&lt;/code> 的数列管理 &lt;code>Reduce&lt;/code> 任务。因而主要的 &lt;code>master&lt;/code> 数据结构可以如下构造：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Master&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">files&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>
&lt;span class="nx">nReduce&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;span class="nx">mapTasks&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nx">MapReduceTask&lt;/span>
&lt;span class="nx">reduceTasks&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nx">MapReduceTask&lt;/span>
&lt;span class="nx">mapFinished&lt;/span> &lt;span class="kt">bool&lt;/span>
&lt;span class="nx">reduceFinished&lt;/span> &lt;span class="kt">bool&lt;/span>
&lt;span class="nx">mutex&lt;/span> &lt;span class="nx">sync&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Mutex&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>对于每个任务，自定义了一个 &lt;code>MapReduceTask&lt;/code> 的结构体封装对应的数据。根据上面的需求分析，每个任务需要有：&lt;/p>
&lt;ul>
&lt;li>类型 &lt;code>Type&lt;/code>：Map|Reduce|Wait&lt;/li>
&lt;li>任务的状态 &lt;code>Status&lt;/code>：Unassigned|Assigned|Finished&lt;/li>
&lt;li>该任务在原来任务列表里的 &lt;code>Index&lt;/code>（方便定位和更新对应的任务）&lt;/li>
&lt;li>开始时间 &lt;code>Timestamp&lt;/code>，便于判断任务超时&lt;/li>
&lt;li>&lt;code>Map&lt;/code> 任务文件 &lt;code>MapFile&lt;/code>，以及 Reduce 任务中间文件列表 &lt;code>ReduceFiles&lt;/code>&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">type&lt;/span> &lt;span class="nx">MapReduceTask&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">Type&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="c1">// &amp;#34;Map&amp;#34;, &amp;#34;Reduce&amp;#34;, &amp;#34;Wait&amp;#34;
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">Status&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// &amp;#34;Unassigned&amp;#34;, &amp;#34;Assigned&amp;#34;, &amp;#34;Finished&amp;#34;
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">Index&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// Index of the task
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">Timestamp&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Time&lt;/span> &lt;span class="c1">// Start time
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">MapFile&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="c1">// File for map task
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="nx">ReduceFiles&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="c1">// List of files for reduce task
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>RPC 消息的定义非常简单，&lt;code>worker&lt;/code> 向 &lt;code>master&lt;/code> 请求时有两种情况：&lt;/p>
&lt;ol>
&lt;li>请求新的任务 &lt;code>RequestTask&lt;/code>，不需要额外数据&lt;/li>
&lt;li>任务完成 &lt;code>FinishTask&lt;/code>，需要将完成的任务 &lt;code>Task&lt;/code> 传给 &lt;code>master&lt;/code> 以便更新任务列表&lt;/li>
&lt;/ol>
&lt;p>而 &lt;code>master&lt;/code> 回复时则只需要指定 &lt;code>Task&lt;/code> 以及告诉总的 Reduce 任务数量。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">const&lt;/span> &lt;span class="p">(&lt;/span>
&lt;span class="nx">RequestTask&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">iota&lt;/span>
&lt;span class="nx">FinishTask&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;span class="kd">type&lt;/span> &lt;span class="nx">MapReduceArgs&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">MessageType&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;span class="nx">Task&lt;/span> &lt;span class="nx">MapReduceTask&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="kd">type&lt;/span> &lt;span class="nx">MapReduceReply&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">Task&lt;/span> &lt;span class="nx">MapReduceTask&lt;/span>
&lt;span class="nx">NReduce&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>因此 &lt;code>master&lt;/code> 的逻辑便是：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;code>Master&lt;/code> 结构初始化时，得到所有待处理文件 &lt;code>files&lt;/code> 以及 Reduce 结果数量 &lt;code>nReduce&lt;/code>。&lt;/p>
&lt;p>将待处理文件顺序加入到 &lt;code>mapTasks&lt;/code> 中，并初始化每个任务状态为 &lt;code>Unassigned&lt;/code>。同样的，将 &lt;code>nReduce&lt;/code> 个 &lt;code>Task&lt;/code> 加入 &lt;code>reduceTasks&lt;/code> 中&lt;/p>
&lt;/li>
&lt;li>
&lt;p>定义 RPC 处理函数 &lt;code>WorkerCallHandler(args *MapReduceArgs, reply *MapReduceReply) error&lt;/code>，&lt;code>worker&lt;/code> 的 RPC 请求将由这个函数处理&lt;/p>
&lt;/li>
&lt;li>
&lt;p>判断请求类型 &lt;code>RequestTask/FinishTask&lt;/code>&lt;/p>
&lt;p>如果是请求任务，先通过 &lt;code>mapFinished&lt;/code> 判断是否 Map 阶段已完成，若未完成，则在 &lt;code>mapTasks&lt;/code> 中选择一个 &lt;code>Unassigned&lt;/code> 或者超时的任务；对于 Reduce 阶段，也是一样的。如果所有 Map 任务都已经被分配，并且没有完全结束，则返回一个 &lt;code>Wait&lt;/code> 任务让 &lt;code>worker&lt;/code> 等待&lt;/p>
&lt;p>如果是完成任务，则首先判断 &lt;code>worker&lt;/code> 返回的任务 &lt;code>Timestamp&lt;/code> 是否一致（因为会有任务超时被重新分配，所以时间戳会不一致）。接着将对应的任务标记为 &lt;code>Finished&lt;/code>，并判断是否所有的任务都已经 &lt;code>Finished&lt;/code>。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>需要注意的：&lt;/p>
&lt;ol>
&lt;li>因为可能有多个 &lt;code>worker&lt;/code> 同时请求，所以需要用 &lt;code>Mutex&lt;/code>，确保同时只有一个线程在修改 &lt;code>master&lt;/code> 变量&lt;/li>
&lt;li>因为要求能够处理 &lt;code>worker&lt;/code> 不响应或异常退出的情况，所以 &lt;code>worker&lt;/code> 需要先将中间文件写入临时文件，将临时文件名返回 &lt;code>master&lt;/code>，若成功返回了便在 &lt;code>master&lt;/code> 将其重命名，确保只有成功完成的任务才有合法的文件名。如 Map 的中间结果为 &lt;code>mr-&amp;lt;maptask_id&amp;gt;-&amp;lt;reducetask_id&amp;gt;&lt;/code>，而 Reduce 的结果为 &lt;code>mr-out-&amp;lt;reducetask_id&amp;gt;&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>对于 &lt;code>worker&lt;/code>，只需要根据分配的任务执行相应的函数。具体的 &lt;code>Map&lt;/code> 和 &lt;code>Reduce&lt;/code> 任务可以参考 &lt;code>mrsequential.go&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">func&lt;/span> &lt;span class="nf">Worker&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mapf&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nx">KeyValue&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">reducef&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="kc">true&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">args&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">MapReduceArgs&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">MessageType&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">RequestTask&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="nx">reply&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">MapReduceReply&lt;/span>&lt;span class="p">{}&lt;/span>
&lt;span class="nx">res&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">call&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Master.WorkerCallHandler&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">args&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">reply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nx">res&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">break&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">switch&lt;/span> &lt;span class="nx">reply&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Task&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Type&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">case&lt;/span> &lt;span class="s">&amp;#34;Map&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="nf">doMap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">reply&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">mapf&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">case&lt;/span> &lt;span class="s">&amp;#34;Reduce&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="nf">doReduce&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">reply&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">reducef&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">case&lt;/span> &lt;span class="s">&amp;#34;Wait&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在实现好之后通过 &lt;code>src/main/test-mr.sh&lt;/code> 执行所有的测试：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ sh test-mr.sh
*** Starting wc test.
2020/09/06 19:39:30 rpc.Register: method &lt;span class="s2">&amp;#34;Done&amp;#34;&lt;/span> has &lt;span class="m">1&lt;/span> input parameters&lt;span class="p">;&lt;/span> needs exactly three
--- wc test: PASS
*** Starting indexer test.
2020/09/06 19:39:36 rpc.Register: method &lt;span class="s2">&amp;#34;Done&amp;#34;&lt;/span> has &lt;span class="m">1&lt;/span> input parameters&lt;span class="p">;&lt;/span> needs exactly three
--- indexer test: PASS
*** Starting map parallelism test.
2020/09/06 19:39:39 rpc.Register: method &lt;span class="s2">&amp;#34;Done&amp;#34;&lt;/span> has &lt;span class="m">1&lt;/span> input parameters&lt;span class="p">;&lt;/span> needs exactly three
--- map parallelism test: PASS
*** Starting reduce parallelism test.
2020/09/06 19:39:47 rpc.Register: method &lt;span class="s2">&amp;#34;Done&amp;#34;&lt;/span> has &lt;span class="m">1&lt;/span> input parameters&lt;span class="p">;&lt;/span> needs exactly three
--- reduce parallelism test: PASS
*** Starting crash test.
2020/09/06 19:39:55 rpc.Register: method &lt;span class="s2">&amp;#34;Done&amp;#34;&lt;/span> has &lt;span class="m">1&lt;/span> input parameters&lt;span class="p">;&lt;/span> needs exactly three
--- crash test: PASS
*** PASSED ALL TESTS
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="http://static.googleusercontent.com/media/research.google.com/en//archive/mapreduce-osdi04.pdf">MapReduce: Simplified Data Processing on Large Clusters&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.cs.rutgers.edu/~pxk/417/notes/content/mapreduce.html">https://www.cs.rutgers.edu/~pxk/417/notes/content/mapreduce.html&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/go/">Go</category></item><item><title>使用 Bazel 构建项目</title><link>https://blog.imfing.com/2020/08/bazel-build/</link><guid isPermaLink="true">https://blog.imfing.com/2020/08/bazel-build/</guid><pubDate>Sat, 29 Aug 2020 18:28:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>如何让项目的构建更有效率是工程中不可忽视的问题，特别是在项目的规模不断增大之后。传统的 C/C++ 项目，有诸如 Make、CMake 这样的工具，Java 项目则有 Maven/Gradle。但在真正企业级的大型项目里，可能会同时有 C++、Python、Go 或者 Java 项目，并且互相可能会有依赖关系。&lt;/p>
&lt;p>&lt;a href="https://www.bazel.build/">Bazel&lt;/a> 便是由 Google 开发和开源的项目构建测试工具，支持多种语言多个平台。Bazel 凭借其企业级的背景、远程执行的特性，被许多公司使用。Dropbox 在 2019 年末的&lt;a href="https://dropbox.tech/infrastructure/continuous-integration-and-deployment-with-bazel">博文中&lt;/a>介绍了全面使用 Bazel 作为 CI/CD 工具。这篇文章将会以一个简单的项目为例，介绍 Bazel 的基本使用。&lt;/p>
&lt;!-- more -->
&lt;p>Bazel 的安装相对比较简单，在它的 &lt;a href="https://github.com/bazelbuild/bazel/releases">GitHub Releases 页面&lt;/a>就可以下载对应的二进制安装包。&lt;/p>
&lt;h2 id="构建-c-项目">构建 C++ 项目&lt;/h2>
&lt;p>Bazel 中比较重要的两个文件是：&lt;/p>
&lt;ul>
&lt;li>&lt;code>WORKSPACE&lt;/code>：含有该文件的目录将会被视为根目录&lt;/li>
&lt;li>&lt;code>BUILD&lt;/code>：含有该构建规则文件的目录被视为项目的一个模块&lt;/li>
&lt;/ul>
&lt;p>下面以官方的 examples 为例。首先克隆项目到本地：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ git clone https://github.com/bazelbuild/examples
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>进入 &lt;code>examples/cpp-tutorial/stage1&lt;/code> 目录下，有一个 &lt;code>WORKSPACE&lt;/code> 文件，因为没有用到自定义的一些规则，所以该文件是空的。在 &lt;code>main&lt;/code> 和目录下面有 &lt;code>BUILD&lt;/code> 文件，包含了构建的目标：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;@rules_cc//cc:defs.bzl&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;cc_binary&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">cc_binary&lt;/span>&lt;span class="p">(&lt;/span>
&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;hello-world&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">srcs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hello-world.cc&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>第一行 &lt;code>load&lt;/code> 导入了内置的 &lt;code>cc_binary&lt;/code> 规则，之后的 &lt;code>hello-world&lt;/code> 则实例化了这个规则，通过 &lt;code>srcs&lt;/code> 参数指定了源文件。&lt;/p>
&lt;p>在 &lt;code>stage1&lt;/code> 目录下，通过命令 &lt;code>$ bazel run //main:hello-world&lt;/code> 即可编译并执行 &lt;code>hello-world&lt;/code> 目标。如果是使用 &lt;code>bazel build&lt;/code> 命令的话则只是进行编译，构建的目标则是相对于根目录 &lt;code>//&lt;/code> 的 &lt;code>main&lt;/code> 目录下的 &lt;code>BUILD&lt;/code> 里声明的 &lt;code>hello-world&lt;/code> 目标。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">❯ bazel run //main:hello-world
Starting &lt;span class="nb">local&lt;/span> Bazel server and connecting to it...
INFO: Analyzed target //main:hello-world &lt;span class="o">(&lt;/span>&lt;span class="m">14&lt;/span> packages loaded, &lt;span class="m">47&lt;/span> targets configured&lt;span class="o">)&lt;/span>.
INFO: Found &lt;span class="m">1&lt;/span> target...
Target //main:hello-world up-to-date:
bazel-bin/main/hello-world
INFO: Elapsed time: 19.863s, Critical Path: 0.72s
INFO: &lt;span class="m">2&lt;/span> processes: &lt;span class="m">2&lt;/span> linux-sandbox.
INFO: Build completed successfully, &lt;span class="m">6&lt;/span> total actions
INFO: Build completed successfully, &lt;span class="m">6&lt;/span> total actions
Hello world
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在 &lt;code>stage2&lt;/code> 的例子里，展示了如何使用 &lt;code>cc_library&lt;/code> 库并定义依赖关系。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;@rules_cc//cc:defs.bzl&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;cc_binary&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;cc_library&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">cc_library&lt;/span>&lt;span class="p">(&lt;/span>
&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;hello-greet&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">srcs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hello-greet.cc&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="n">hdrs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hello-greet.h&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;span class="n">cc_binary&lt;/span>&lt;span class="p">(&lt;/span>
&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;hello-world&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">srcs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hello-world.cc&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="n">deps&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="s2">&amp;#34;:hello-greet&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>同样的，在 &lt;code>load&lt;/code> 里引入 &lt;code>cc_binary&lt;/code> 和 &lt;code>cc_library&lt;/code> 两个规则。&lt;code>hello-world&lt;/code> 目标在 &lt;code>deps&lt;/code> 中指定了依赖当前目录下的 &lt;code>hello-greet&lt;/code> 模块。如果需要用到其它目录下的模块，在 &lt;code>stage3&lt;/code> 下面就有例子，&lt;code>deps&lt;/code> 中的模块需要像 &lt;code>&amp;quot;//lib:hello-time&amp;quot;&lt;/code> 这样指定根目录下的子模块。&lt;/p>
&lt;h2 id="构建-python-项目">构建 Python 项目&lt;/h2>
&lt;p>在 Bazel 中使用 Python，需要引入自定义的 Python 规则。Bazel 官方开源了 &lt;code>rules_python&lt;/code>。&lt;/p>
&lt;p>首先确保默认的 Python 在 &lt;code>/usr/bin/python&lt;/code> 路径下，可以输入 &lt;code>which python&lt;/code> 来确认。如果没有的话，需要手动进行一下软链接：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ sudo ln -s /usr/bin/python3 /usr/bin/python
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>接下来，创建一个新的工程目录 &lt;code>project&lt;/code>，首先需要在根目录下创建 &lt;code>WORKSPACE&lt;/code> 文件，包含有 &lt;code>WORKSPACE&lt;/code> 文件的目录将会被 Bazel 视为根目录。为了能够在这个工作区中使用 Python 构建规则，需要添加 Python 构建的规则到 &lt;code>WORKSPACE&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;@bazel_tools//tools/build_defs/repo:http.bzl&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;http_archive&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">http_archive&lt;/span>&lt;span class="p">(&lt;/span>
&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;rules_python&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/bazelbuild/rules_python/releases/download/0.0.2/rules_python-0.0.2.tar.gz&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">strip_prefix&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;rules_python-0.0.2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">sha256&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;b5668cde8bb6e3515057ef465a35ad712214962f0b3a314e551204266c7be90c&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在根目录下创建 &lt;code>hello/main.py&lt;/code> 文件：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Hello World&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>之后在 &lt;code>hello&lt;/code> 下添加 &lt;code>BUILD&lt;/code> 文件：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;@rules_python//python:defs.bzl&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;py_binary&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">py_binary&lt;/span>&lt;span class="p">(&lt;/span>
&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;main&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">srcs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;main.py&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>同样，运行 &lt;code>$ bazel run //hello:main&lt;/code>，便可以执行 &lt;code>main.py&lt;/code>。如果脚本需要参数的话，可以在中间用 &lt;code>--&lt;/code> 分隔，并紧跟在后面指定参数： &lt;code>$ bazel run //hello:main -- &amp;lt;Arguments&amp;gt;&lt;/code>&lt;/p>
&lt;h2 id="远程执行">远程执行&lt;/h2>
&lt;p>远程执行也是 Bazel 的一个亮点。远程执行的好处是可以利用云平台的多个机器进行分布式构建，并且缓存编译，这样可以大大加快构建速度。关于远程执行，具体参考官方的 &lt;a href="https://docs.bazel.build/versions/master/remote-execution.html">Remote execution overview&lt;/a>。好像要自己搭。&lt;/p>
&lt;p>参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/bazelbuild/bazel">https://github.com/bazelbuild/bazel&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://dropbox.tech/infrastructure/continuous-integration-and-deployment-with-bazel">https://dropbox.tech/infrastructure/continuous-integration-and-deployment-with-bazel&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.bazel.build/versions/master/bazel-overview.html">https://docs.bazel.build/versions/master/bazel-overview.html&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/bazel/">Bazel</category></item><item><title>Python 执行 Bash 命令得到实时输出</title><link>https://blog.imfing.com/2020/08/python-execute-bash/</link><guid isPermaLink="true">https://blog.imfing.com/2020/08/python-execute-bash/</guid><pubDate>Sat, 01 Aug 2020 18:28:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>有些时候，我们需要在 Python 里执行一些 Bash / Shell 命令，比如调用外部的构建工具、Git 或者是其它程序。这时候，用 Python 自带的 &lt;a href="https://docs.python.org/3/library/subprocess.html">subprocess&lt;/a> 模块可以很方便的实现。在实际使用中，真正让人头大的有两点：如何执行像脚本一样运行多行 Shell 命令；以及如何实时得到命令的输出。&lt;/p>
&lt;!-- more -->
&lt;p>对于非常简单的 Shell 命令，只需要使用 &lt;a href="https://docs.python.org/3/library/subprocess.html#subprocess.Popen">subprocess.Popen&lt;/a> 即可：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="kn">import&lt;/span> &lt;span class="nn">subprocess&lt;/span>
&lt;span class="n">process&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Popen&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s1">&amp;#39;echo&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Hello World&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="n">stdout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PIPE&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">stderr&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PIPE&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">stdout&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stderr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">communicate&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面的代码很好懂，即使用 &lt;code>Popen&lt;/code> 开启一个新的进程执行命令，并将 &lt;code>stdout&lt;/code> 和 &lt;code>stderr&lt;/code> 通过 &lt;code>PIPE&lt;/code> 记录。这个方式非常简单，适用于大多数情况。&lt;/p>
&lt;p>但如果你的命令需要很长时间才能执行完成，会需要实时的输出。最简单的方式便是不将 &lt;code>stdout&lt;/code> 和 &lt;code>stderr&lt;/code> 通过 &lt;code>PIPE&lt;/code> 记录，如此一来便无法得到 &lt;code>stdout&lt;/code> 中的内容；此外，还可以通过 &lt;a href="https://en.wikipedia.org/wiki/Tee_(command)">tee&lt;/a> 命令配合 &lt;code>2&amp;gt;&amp;amp;1&lt;/code>，比如 &lt;code>echo hello 2&amp;gt;&amp;amp;1 | tee dev/tty&lt;/code>，这个方法可行但是对于每一行 Shell 命令都得使用。&lt;/p>
&lt;p>经过了一番探索，最后找到了一个既可以执行多行 Shell 命令，并且能够得到实时输出的方法：&lt;/p>
&lt;p>首先用 &lt;code>Popen&lt;/code> 打开 &lt;code>/bin/bash&lt;/code> 实例，将我们需要执行的 Shell 指令通过 &lt;code>stdin&lt;/code> 传入，这样子做的好处是可以让 &lt;code>bash&lt;/code> 逐行执行每一条命令。之后在一个 &lt;code>while&lt;/code> 循环中，持续地读子进程 &lt;code>stdout&lt;/code> 的内容，通过 &lt;code>proc.poll()&lt;/code> 判断子进程是否终止来终止循环，最后通过 &lt;code>proc.wait()&lt;/code> 得到子进程退出的代码，如果非 0 的话表示异常退出，进而选择下一步的操作。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="n">proc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Popen&lt;/span>&lt;span class="p">(&lt;/span>
&lt;span class="s2">&amp;#34;/bin/bash&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">stdin&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PIPE&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">stdout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PIPE&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">shell&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">True&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;span class="n">proc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdin&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">commands&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">())&lt;/span>
&lt;span class="n">proc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdin&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">flush&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="n">proc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdin&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">close&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="c1"># Real time stdout of subprocess&lt;/span>
&lt;span class="n">stdout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;span class="k">while&lt;/span> &lt;span class="bp">True&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">line&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">proc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">readline&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="n">stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">proc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">poll&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="bp">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="k">break&lt;/span>
&lt;span class="c1"># Print outputs&lt;/span>
&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="c1"># Wait for child process and get return code&lt;/span>
&lt;span class="n">return_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">proc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样便可以像 &lt;a href="https://docs.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow">GitHub Action&lt;/a> 一样 run 多行的命令了&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nt">say_hello&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>- &lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;span class="sd"> echo &amp;#34;Hello ${{ github.event.inputs.name }}!&amp;#34;
&lt;/span>&lt;span class="sd"> echo &amp;#34;- in ${{ github.event.inputs.home }}!&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://stackoverflow.com/questions/803265/getting-realtime-output-using-subprocess">https://stackoverflow.com/questions/803265/getting-realtime-output-using-subprocess&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://janakiev.com/blog/python-shell-commands/">https://janakiev.com/blog/python-shell-commands/&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/python/">Python</category><category domain="https://blog.imfing.com/tags/bash/">Bash</category></item><item><title>支持向量机 (SVM) 的解析与推导</title><link>https://blog.imfing.com/2020/06/svm-explained/</link><guid isPermaLink="true">https://blog.imfing.com/2020/06/svm-explained/</guid><pubDate>Sun, 28 Jun 2020 13:40:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>两年前在面某司 AI Lab 的时候，就有被问到支持向量机（support vector machines, SVM）的推导。正好上一学期在 Machine Learning 的课上又学到，这里便记录一下 SVM 的基本推导过程。&lt;/p>
&lt;!-- more -->
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>在开始之前先推荐一下李航的《统计学习方法》以及周志华的《机器学习》。这两本书可以结合起来看，对理解许多机器学习的原理有非常大的帮助。SVM 的推导中有大量的定理以及数学公式，看着确实挺吓人，但是只要一步步地弄懂，就也不是非常的难。这篇文章花费了挺长的时间来写，参考了上面提到的两本书，以及尽量简单的将每一步解释清楚，不过还是有一些数学的细节以及严谨证明略过了，因为也不在本文的讨论范围之内。&lt;/p>
&lt;p>这里将讨论最基本的二分类的支持向量机（support vector machines, SVM），考虑训练样本集 $D={ (\vec{x_1},y_1),...,(\vec{x_m},y_m), y_i \in {-1,+1} }$。将从最基本的超平面划分训练样本开始，导出 SVM 的基本问题，再利用对偶问题求解，最后讨论 SVM 的核技巧，以及如何在 Python 中使用 SVM。&lt;/p>
&lt;p>&lt;img src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/SVM_margin.png/300px-SVM_margin.png" alt="svm">&lt;/p>
&lt;h2 id="超平面">超平面&lt;/h2>
&lt;p>超平面（Hyper Plane）是在样本空间中的一个平面。在我们熟悉的二维平面上，一条直线的方程可以表示为 $ax+by+c=0$，推广到多维的情况之后，超平面可以用向量的形式表示为 $\vec{w}\cdot\vec{x}+b=0$，记为 $(\vec{w}, b)$ 的形式，其中 $\vec{w}$ 是法向量，而 $b$ 为位移项。&lt;/p>
&lt;p>考虑任意一点 $\vec{x}$ 到超平面之间的距离，可以表示为：&lt;/p>
&lt;p>$$
\gamma=\frac{\vert\vec{w}\cdot\vec{x}+b\vert}{\Vert \vec{w} \Vert}
$$&lt;/p>
&lt;p>超平面能将样本空间划分成两部分。假设超平面 $(\vec{w}, b)$ 将所有样本正确分类，即对于任意的 $(\vec{x_i},y_i)\in D$，若 $y_i=+1$，那么 $\vec{w}\cdot\vec{x}+b &amp;gt; 0$，若 $y_i=-1$，那么 $\vec{w}\cdot\vec{x}+b &amp;lt; 0$。像这样的决策函数可以表示为：&lt;/p>
&lt;p>$$
y_i=\text{sign}(\vec{w}\cdot\vec{x_i}+b)
$$&lt;/p>
&lt;p>那么，在一个空间中，会存在多个超平面能够将训练样本分开，那么究竟该努力去找哪一个超平面呢？&lt;/p>
&lt;p>&lt;img src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b5/Svm_separating_hyperplanes_%28SVG%29.svg/1920px-Svm_separating_hyperplanes_%28SVG%29.svg.png" height=250px />&lt;/p>
&lt;h2 id="最大间隔">最大间隔&lt;/h2>
&lt;p>对于 $(\vec{x_i}, y_i)$，我们可以将上面的距离公式的分子部分代入 $y_i$ 改写成：&lt;/p>
&lt;p>$$
\gamma_i=\frac{y_i(\vec{w}\cdot\vec{x}+b)}{\Vert \vec{w} \Vert}
$$&lt;/p>
&lt;p>定义间隔（margin）$\gamma$ 为所有样本中离超平面最近的那个，即 $\gamma=\min \gamma_i$，那么 $\gamma_i \ge \gamma$ 总是成立的，代入上式可得：&lt;/p>
&lt;p>$$
\frac{y_i(\vec{w}\cdot\vec{x}+b)}{\Vert \vec{w} \Vert} \ge \gamma
$$&lt;/p>
&lt;p>因此，SVM 的问题就变成了找到“最大间隔”（maximum margin）的超平面，使得 $\gamma$ 最大，即：&lt;/p>
&lt;p>$$
\begin{array}{l}\max\limits_{(\vec{w},b)} \ \min\limits_{i} \frac{1}{\Vert\vec{w}\Vert}\vert\vec{w}\cdot\vec{x_i}+b\vert \\ \text{s.t.} \quad y_i(\vec{w}\cdot\vec{x_i}+b) &amp;gt; 0, \quad i=1,2,...,m \end{array}
$$&lt;/p>
&lt;p>因为间隔始终大于 0，即 $\vert\vec{w}\cdot\vec{x_i}+b\vert &amp;gt; 0$，则可以通过缩放 $(\vec{w}, b)$ 使得 $\min\limits_i \vert\vec{w}\cdot\vec{x_i}+b\vert =1$，那么上面的问题就变成 $\max\frac{1}{\Vert\vec{w}\Vert} $，即 $\min\Vert\vec{w}\Vert$，在其它书里通常会被等价成 $\min\frac{1}{2}\Vert\vec{w}\Vert^2$，于是上式可以重写成：&lt;/p>
&lt;p>$$
\begin{array}{l}
\min\ \frac{1}{2}\Vert\vec{w}\Vert^2 \\
\text{s.t.} \quad y_i(\vec{w}\cdot\vec{x_i}+b) \ge 1, \quad i=1,2,...,m \end{array}
$$&lt;/p>
&lt;p>是一个凸二次规划（convex quadratic programming）问题，这样就能够利用上现有的求解优化问题的方法。&lt;/p>
&lt;h2 id="对偶问题">对偶问题&lt;/h2>
&lt;p>首先考虑一个最优化问题：&lt;/p>
&lt;p>$$
\begin{array}{l}\min f(\vec{z}) \\ \text{s.t.} \quad h_i(\vec{z})\le 0 \end{array}
$$&lt;/p>
&lt;p>引入拉格朗日函数 $\mathcal{L}(\vec{z},\vec{\alpha})$，其中 $\vec{\alpha}=(\alpha_1,...,\alpha_m)$，$\alpha_i \ge 0$ 为拉格朗日乘子，则有：&lt;/p>
&lt;p>$$
\mathcal{L}(\vec{z},\vec{\alpha})=f(\vec{z})+\sum \alpha_i h_i(\vec{z})
$$&lt;/p>
&lt;p>那么上面的最优化问题等价于：&lt;/p>
&lt;p>$$
\min\limits_{\vec{z}}\ \max\limits_{\vec{\alpha}\ge 0 } \ \mathcal{L} (\vec{z}, \vec{\alpha})
$$&lt;/p>
&lt;p>称为原始问题（primal problem）。下面来证明其等价于一开始的优化问题，主要思路是将拉格朗日函数展开代入，再利用 $\vec{\alpha}\ge0$的条件消去第二项：&lt;/p>
&lt;p>$$
\begin{aligned}
&amp;amp; \min\limits_{\vec{z}}\ \max\limits_{\vec{\alpha}\ge 0 } \ \mathcal{L} (\vec{z}, \vec{\alpha}) \\ = &amp;amp;\min\limits_{\vec{z}}\left( f(\vec{z})+\max\limits_{\vec{\alpha}\ge 0}\sum \alpha_i h_i(\vec{z})\right) \\ =&amp;amp; \min\limits_{\vec{z}}\left( f(\vec{z})+ \begin{cases}0, &amp;amp;\text{if } h_i(\vec{z})\le 0 \\ \infty, &amp;amp;\text{if } h_i(\vec{z})\gt 0 \end{cases} \right) \\ =&amp;amp;\min\limits_{\vec{z}} f(\vec{z})
\end{aligned}
$$&lt;/p>
&lt;p>将该问题的中的 $\min$ 和 $\max$ 交换位置，即可得到其对偶问题（dual problem）：&lt;/p>
&lt;p>$$
\max\limits_{\vec{\alpha}\ge 0 }\ \min\limits_{\vec{z}} \mathcal{L} (\vec{z}, \vec{\alpha})
$$&lt;/p>
&lt;p>原始问题与对偶问题的关系：&lt;/p>
&lt;p>$$
\max\limits_{\vec{\alpha}\ge 0 }\ \min\limits_{\vec{z}} \mathcal{L} (\vec{z}, \vec{\alpha}) \le \min\limits_{\vec{z}}\ \max\limits_{\vec{\alpha}\ge 0 } \mathcal{L} (\vec{z}, \vec{\alpha})
$$&lt;/p>
&lt;p>在某些条件下，原始问题与对偶问题的最优值相等，这时候可以用解对偶问题代替解原始问题。该条件称为 &lt;a href="https://en.wikipedia.org/wiki/Karush%E2%80%93Kuhn%E2%80%93Tucker_conditions">KKT（Karush-Kunh-Tucker）条件&lt;/a>，具体证明超出了本文的涉及范围，我自己也不会，就先略过。在这个条件下，可以从 SVM 问题的对偶问题求解，进而得到 SVM 的解。&lt;/p>
&lt;h2 id="求解-svm">求解 SVM&lt;/h2>
&lt;p>将 SVM 的问题用拉格朗日函数的形式重写为：&lt;/p>
&lt;div>
$$
\mathcal{L}(\vec{w}, b, \vec{\alpha}) = \underbrace{\frac{1}{2}\Vert\vec{w}\Vert^2}_{f(\vec{w},b)} + \sum \alpha_i \underbrace{\left(1-y_i(\vec{w}\cdot\vec{x_i}-b)\right)}_{h_i(\vec{w},b)\le0}
$$
&lt;/div>
&lt;p>令 $\mathcal{L}(\vec{w}, b, \vec{\alpha})$ 对于 $\vec{w}$ 和 $b$ 的偏导数等于零：&lt;/p>
&lt;div>
$$
\begin{cases}
\frac{\partial \mathcal {L}}{\partial \vec{w}} = \vec{w}-\sum\alpha_i y_i \vec{x_i}=0 \\
\frac{\partial \mathcal {L}}{\partial b} =\sum\alpha_i y_i =0
\end{cases}
$$
&lt;/div>
&lt;p>可得：&lt;/p>
&lt;p>$$
\begin{aligned}
\vec{w}&amp;amp;=\sum\alpha_i y_i \vec{x_i} \\
0 &amp;amp;=\sum\alpha_i y_i
\end{aligned}
$$&lt;/p>
&lt;p>将上式代入 $\mathcal{L}(\vec{w}, b, \vec{\alpha})$ 可得：&lt;/p>
&lt;p>$$
\begin{aligned}
\mathcal{L}(\vec{w}, b, \vec{\alpha}) =&amp;amp; \frac{1}{2}\left(\sum\alpha_i y_i \vec{x_i}\right)\cdot\left(\sum\alpha_i y_i \vec{x_i}\right) \\
&amp;amp;+ \sum\alpha_i\left(1-y_i\left(\left(\sum\alpha_i y_i \vec{x_i}\right)\cdot\vec{x_i}-b)\right)\right) \\
=&amp;amp;\sum_{i=1}^{m}\alpha_i - \frac{1}{2}\sum_{i=1}^{m}\sum_{j=1}^{m}\alpha_i \alpha_j y_i y_j \left(\vec{x_i}\cdot \vec{x_j}\right)
\end{aligned}
$$&lt;/p>
&lt;p>这样，对偶问题就变成：&lt;/p>
&lt;p>$$
\begin{aligned}
&amp;amp;\max\limits_{\vec{\alpha}\ge 0 }\ \min\limits_{\vec{z}} \mathcal{L} (\vec{z}, \vec{\alpha}) \\
\implies &amp;amp; \max\limits_{\vec{\alpha}\ge 0 } \sum_{i=1}^{m}\alpha_i - \frac{1}{2}\sum_{i=1}^{m}\sum_{j=1}^{m}\alpha_i \alpha_j y_i y_j \left(\vec{x_i}\cdot \vec{x_j}\right) \\
&amp;amp;\ \text{s.t.}\quad \sum\alpha_i y_i = 0,\ \alpha_i \ge 0
\end{aligned}
$$&lt;/p>
&lt;p>解出 $\vec{\alpha}$ 后，求出 $\vec{w}$ 与 $b$ 即可得到模型：&lt;/p>
&lt;div>
$$
\begin{aligned}
f(\vec{x})
&amp;=\vec{w}\cdot\vec{x}+b \\
&amp;=\sum_{i=1}^{m}\alpha_i y_i \left(\vec{x_i}\cdot\vec{x}\right)+b
\end{aligned}
$$
&lt;/div>
&lt;p>具体求解过程需要满足上面提到的 KTT 条件：&lt;/p>
&lt;div>
$$
\begin{cases}
\alpha_i\ge 0 \ ; \\
y_i f(\vec{x_i}) - 1\ge 0 \ ; \\
\alpha_i(y_i f(\vec{x_i})-1) = 0 \ .
\end{cases}
$$
&lt;/div>
&lt;p>求解上面的对偶问题的一个著名算法是 &lt;a href="https://en.wikipedia.org/wiki/Sequential_minimal_optimization">SMO (Sequential Minimal Optimization)&lt;/a>，这里不展开叙述。&lt;/p>
&lt;h2 id="软间隔">软间隔&lt;/h2>
&lt;p>上面我们考虑的都是数据可以分隔的情况，那么当数据集并不是完全能被超平面分隔，即有些点不满足约束 $y_i(\vec{w}\cdot\vec{x_i}+b) \ge 1$，那可以设定一个惩罚项：&lt;/p>
&lt;div>
$$
\xi_i=\max\left\{
1-y_i(\vec{w}\cdot\vec{x_i}+b), 0
\right\}
$$
&lt;/div>
那么原来的 SVM 的优化问题就会变成：
$$
\begin{array}{l}
\min\limits_{\vec{w},b}\ \frac{1}{2}\Vert\vec{w}\Vert^2 +
C\sum_{i=1}^{m}\max\left\{1-y_i(\vec{w}\cdot\vec{x_i}+b), 0\right\}
\\
\text{s.t.} \quad 1-y_i(\vec{w}\cdot\vec{x_i}+b) \le 0, \quad i=1,2,...,m
\end{array}
$$
其中的 $\max(1-z, 0)$ 称为“折页损失”（hinge loss）。接着引入“松弛变量”(slack variable) $\xi_i\ge0$，将上式重写为：
$$
\begin{aligned}
&amp;\min\limits_{\vec{w},b}\ \frac{1}{2}\Vert\vec{w}\Vert^2 +
C\sum_{i=1}^{m}\xi_{i}
\\
\text{s.t.}\quad &amp;y_i(\vec{w}\cdot\vec{x_i}+b) \ge 1-\xi_i \\
&amp;\xi_i \ge 0, \ i=1,2,...,m
\end{aligned}
$$
即可得到“软间隔支持向量机”(soft margin SVM)。求解方法与前面是一样的，具体过程可以参考西瓜书。
&lt;h2 id="核方法">核方法&lt;/h2>
&lt;p>当遇到样本是非线性可分时，一般的线性 SVM 就无法很好的分类，这时候可以采用核技巧（kernel trick），将样本映射至高维空间，变成高维空间中线性可分的即可。&lt;/p>
&lt;p>&lt;img src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/cc/Kernel_trick_idea.svg/2880px-Kernel_trick_idea.svg.png" alt="">&lt;/p>
&lt;p>考虑一个映射函数 $\phi(\vec{x})$，将 $d$ 维特征映射至 $m$ 维：
$$
\phi(\vec{x}): \mathbb{R}^d \to \mathbb{R}^m
$$
定义核函数（kernel function）：
$$
K(\vec{x_i}, \vec{x_j})= \phi(\vec{x_i}) \cdot \phi(\vec{x_j})
$$
核技巧的思想是用核函数 $K$ 避免在高维 $m$ 空间中计算映射函数的内积，这样能够极大简化运算。&lt;/p>
&lt;p>根据 &lt;a href="https://en.wikipedia.org/wiki/Mercer%27s_theorem">Mercer's theorem&lt;/a>，函数 $K$ 能够满足成为核函数的充分必要条件是 $K$ 是半正定（positive semidefinite），即&lt;/p>
&lt;p>$$
\sum_{i,j=1}^m c_i c_j K(\vec{x_i}, \vec{x_j}) \geq 0
$$
常用的核函数有多项式核函数（Polynomial function）：&lt;/p>
&lt;p>$$
K(\vec{x_i}, \vec{x_j}) = (\vec{x_i} \cdot \vec{x_j} + 1)^d
$$
以及高斯核函数（Guassian radial basis function），通常称为 RBF Kernel：&lt;/p>
&lt;p>$$
K(\vec{x_i}, \vec{x_j}) = \exp \left(- \frac{\Vert\vec{x_i}-\vec{x_j}\Vert^2}{2\sigma^2}
\right)
$$&lt;/p>
&lt;p>更多的关于核方法的详细推导，可以参考推荐的两本书中的相应章节。&lt;/p>
&lt;h2 id="python-中使用-svm">Python 中使用 SVM&lt;/h2>
&lt;p>在 Python 中可以用 &lt;a href="https://scikit-learn.org/stable/index.html">scikit-learn&lt;/a> 非常方便的使用 &lt;a href="https://scikit-learn.org/stable/modules/svm.html">SVM&lt;/a>。对于二分类或多分类问题，通常使用 &lt;a href="https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html#sklearn.svm.SVC">SVC()&lt;/a>，可以在参数中指定它的 kernel，当 &lt;code>kernel='linear'&lt;/code> 时便退化成了线性支持向量机。一般使用 &lt;code>rbf&lt;/code> 核的 SVM 会有较好的分类效果。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sklearn&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">svm&lt;/span>
&lt;span class="n">X&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">]]&lt;/span>
&lt;span class="n">y&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="n">clf&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">svm&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">SVC&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="n">clf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">X&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://book.douban.com/subject/10590856/">《统计学习方法》李航&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://book.douban.com/subject/26708119/">《机器学习》周志华&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Support_vector_machine">Support vector machine - Wikipedia&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://zhuanlan.zhihu.com/p/31652569">从零推导支持向量机(SVM)&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%AE%97%E6%B3%95/">算法</category><category domain="https://blog.imfing.com/tags/machine-learning/">Machine Learning</category><category domain="https://blog.imfing.com/tags/math/">Math</category></item><item><title>C++ 20 Modules 尝鲜</title><link>https://blog.imfing.com/2020/06/cpp-20-modules-hello-world/</link><guid isPermaLink="true">https://blog.imfing.com/2020/06/cpp-20-modules-hello-world/</guid><pubDate>Mon, 22 Jun 2020 19:14:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>Modules 是 C++ 20 标准里面非常重要的一个新特性，引进了在其它编程语言中常用的 &lt;code>import&lt;/code> 关键词，让 C++ 在几十年前就有的 &lt;code>#include&lt;/code> 基础上支持更现代的模块设计，减轻传统的头文件带来的一些问题，提升编译的速度。于是我尝试了一下用 Modules 写一个简单的 Hello World。&lt;/p>
&lt;!-- more -->
&lt;h2 id="安装-clang-10">安装 Clang 10&lt;/h2>
&lt;p>由于 Modules 是 C++ 20 的标准，因而较老的编译器并不一定能够支持这一特性，推荐最新的 &lt;a href="https://releases.llvm.org/10.0.0/tools/clang/docs/ReleaseNotes.html">Clang 10.0.0&lt;/a>。首先安装必要的运行时与编译库：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ sudo apt install build-essential libncurses5
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可以到 LLVM 的 &lt;a href="https://releases.llvm.org/download.html#10.0.0">下载页面&lt;/a> 选择自己的平台下载，解压后即可在 &lt;code>bin&lt;/code> 目录中找到 Clang 及 LLVM 的相关可执行文件。建议使用 Linux 平台以避免一些使用问题。也可以直接用 &lt;a href="https://apt.llvm.org/">LLVM 提供的安装脚本&lt;/a>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ bash -c &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>wget -O - https://apt.llvm.org/llvm.sh&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="hello-world-module">Hello World Module&lt;/h2>
&lt;p>接着我们创建一个简单的 &lt;code>helloworld.cpp&lt;/code> 文件，实现一个最简单的 &lt;code>helloworld&lt;/code> Module，通过 &lt;code>export&lt;/code> 关键词将函数变量提供给外部程序使用：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="c1">/// helloworld.cpp
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="k">export&lt;/span> &lt;span class="n">module&lt;/span> &lt;span class="n">helloworld&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">export&lt;/span> &lt;span class="k">auto&lt;/span> &lt;span class="nf">hello&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="s">&amp;#34;Hello C++ 20!&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>接着创建 &lt;code>main.cpp&lt;/code>，使用 &lt;code>import&lt;/code> 关键词导入 &lt;code>helloworld&lt;/code> Module：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="c1">/// main.cpp
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;span class="cp">&lt;/span>&lt;span class="n">import&lt;/span> &lt;span class="n">helloworld&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">hello&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>编译分为两步，首先我们需要将用到的模块编译成 BMI（Binary Module Interface），即先把 &lt;code>helloworld.cpp&lt;/code> 模块编译成 &lt;code>.pcm&lt;/code>（precompiled module）文件，再编译 &lt;code>main.cpp&lt;/code>。注意这里需要 &lt;code>-std=c++2a&lt;/code>，用 &lt;code>-std=c++20&lt;/code> 就有问题，迷…… 用 g++10 编译命令似乎更简单，估计等 Clang 对 C++ 20 的标准支持更完善了之后，编译会更方便。&lt;/p>
&lt;p>用 Clang 10 编译和运行：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ clang++ -std&lt;span class="o">=&lt;/span>c++2a -c helloworld.cpp -Xclang -emit-module-interface -o helloworld.pcm
$ clang++ -std&lt;span class="o">=&lt;/span>c++2a -stdlib&lt;span class="o">=&lt;/span>libc++ -fprebuilt-module-path&lt;span class="o">=&lt;/span>. main.cpp helloworld.cpp
$ ./a.out
Hello C++ 20!
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="用-import-代替-include">用 import 代替 #include&lt;/h2>
&lt;p>进一步，我们可以将上面的 &lt;code>#include&lt;/code> 也用 &lt;code>import&lt;/code> 来代替。注意最后需要添加分号 &lt;code>;&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="c1">// main.cpp
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">import&lt;/span> &lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">iostream&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">import&lt;/span> &lt;span class="n">helloworld&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这时候我们在编译的时候需要额外两个 flag：&lt;code>-fimplicit-modules&lt;/code> 和 &lt;code>-fimplicit-module-maps&lt;/code>，具体可以参考 &lt;a href="https://clang.llvm.org/docs/Modules.html#module-maps">Clang 的文档&lt;/a>。现在 Clang 还不支持 &lt;a href="https://clang.llvm.org/docs/Modules.html#semantic-import">Semantic import&lt;/a>，也就是无法使用 &lt;code>import std.io&lt;/code> 语法（MSVC 支持）。这里我们直接用 &lt;code>import&lt;/code> 替换 &lt;code>#include&lt;/code>，去除 &lt;code>#include&lt;/code> 使语法暂时保持一致。&lt;/p>
&lt;p>由于之前的 &lt;code>helloworld&lt;/code> 模块并没有任何变化，因此只需要重新编译一下第二步即可：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ clang++ -std&lt;span class="o">=&lt;/span>c++2a -stdlib&lt;span class="o">=&lt;/span>libc++ -fimplicit-modules -fimplicit-module-maps -fprebuilt-module-path&lt;span class="o">=&lt;/span>. main.cpp helloworld.cpp
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="module-中使用-namespace">Module 中使用 namespace&lt;/h2>
&lt;p>在一个模块里，我们也能 &lt;code>export&lt;/code> 命名空间，下面的例子演示了如何导出和使用 &lt;code>helloworld&lt;/code> 的 &lt;code>namespace&lt;/code>，避免带来全局函数的干扰。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="c1">/// helloworld.cpp
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">module&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">import&lt;/span> &lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">cstdio&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">export&lt;/span> &lt;span class="n">module&lt;/span> &lt;span class="n">helloworld&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">export&lt;/span> &lt;span class="k">namespace&lt;/span> &lt;span class="n">helloworld&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">global_data&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">void&lt;/span> &lt;span class="nf">say_hello&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello Module! Data is %d&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">global_data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="c1">/// main.cpp
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">import&lt;/span> &lt;span class="n">helloworld&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="n">helloworld&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">global_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">123&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">helloworld&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">say_hello&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>编译命令和之前基本相同，只不过因为模块里面用到了 &lt;code>import &amp;lt;cstdio&amp;gt;&lt;/code>，因此在编译模块的时候需要指定 &lt;code>-stdlib=libc++&lt;/code> 以及 &lt;code>-fimplicit-modules&lt;/code> 和 &lt;code>-fimplicit-module-maps&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ clang++ -std&lt;span class="o">=&lt;/span>c++2a -stdlib&lt;span class="o">=&lt;/span>libc++ -fimplicit-modules -fimplicit-module-maps -c helloworld.cpp -Xclang -emit-module-interface -o helloworld.pcm
$ clang++ -std&lt;span class="o">=&lt;/span>c++2a -stdlib&lt;span class="o">=&lt;/span>libc++ -fimplicit-modules -fimplicit-module-maps -fprebuilt-module-path&lt;span class="o">=&lt;/span>. main.cpp helloworld.cpp
$ ./a.out
Hello Module! Data is &lt;span class="m">123&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="module-的规范">Module 的规范&lt;/h2>
&lt;p>一个推荐的 Module 规范结构如下&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="n">module&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;standard library header&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;library not ready for modularization&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;span class="cp">&lt;/span>&lt;span class="p">...&lt;/span>
&lt;span class="k">export&lt;/span> &lt;span class="n">module&lt;/span> &lt;span class="n">top&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">middle&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">bottom&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">import&lt;/span> &lt;span class="n">modularized&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">standard&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">library&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">component&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">import&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">modularized&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">library&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">...&lt;/span>
&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;module internal header&amp;#34; // beware!&lt;/span>&lt;span class="cp">
&lt;/span>&lt;span class="cp">&lt;/span>
&lt;span class="n">non&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">exported&lt;/span> &lt;span class="n">declarations&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">...&lt;/span>
&lt;span class="k">export&lt;/span> &lt;span class="k">namespace&lt;/span> &lt;span class="n">top&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">namespace&lt;/span> &lt;span class="n">middle&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">namespace&lt;/span> &lt;span class="n">bottom&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="n">exported&lt;/span> &lt;span class="n">declarations&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">...&lt;/span>
&lt;span class="p">}}}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="写在后面">写在后面&lt;/h2>
&lt;p>Module 还有一系列的特性，比如将接口（Interface）和实现（Implementation）分离，模块分割（Module Partitions）等等，更多高级的用法可以参考视频：&lt;a href="https://www.youtube.com/watch?v=Kqo-jIq4V3I">Modules the beginner's guide - Daniela Engert - Meeting C++ 2019&lt;/a>。&lt;/p>
&lt;p>但是目前主流的编译器对于 C++ 20 的标准还没有支持完备，因此很多功能也只能先“尝鲜”，等到一段时间后或许这些新的标准会像 C++ 11 一样让 C++ 再次焕发新的活力。&lt;/p>
&lt;p>参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://meetingcpp.com/mcpp/slides/2019/modules-the-beginners-guide-meetingcpp2019.pdf">Modules: The Beginner's Guide&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://quuxplusone.github.io/blog/2019/11/07/modular-hello-world/">Hello World with C++2a modules&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://vector-of-bool.github.io/2019/03/10/modules-1.html">Understanding C++ Modules: Part 1: Hello Modules, and Module Units&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.microsoft.com/en-us/cpp/cpp/modules-cpp?view=vs-2019">Overview of modules in C++ | Microsoft Docs&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.modernescpp.com/index.php/c-20-module-interface-unit-and-module-implementation-unit">C++20: Module Interface Unit and Module Implementation Unit&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.purecpp.org/detail?id=2084">C++20 Modules | PureCPP&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://gracicot.github.io/modules/2018/11/04/modules-are-not-precompiled-headers.html">Modules Are Not Precompiled Headers&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://mariusbancila.ro/blog/2020/05/15/modules-in-clang-11/">Modules in Clang 11&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/c-/">C++</category></item><item><title>Telegram 自动通知脚本运行结果</title><link>https://blog.imfing.com/2020/06/telegram-bot-notify-with-bash/</link><guid isPermaLink="true">https://blog.imfing.com/2020/06/telegram-bot-notify-with-bash/</guid><pubDate>Fri, 19 Jun 2020 18:28:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>很多时候，运行一个耗时的脚本，像是编译或神经网络的拟合，我们并不想一直盯着屏幕到运行完成，希望能在脚本执行完成之后能够自动给我们发送结果，用 Telegram Bot 可以轻松实现自动化的消息提醒。&lt;/p>
&lt;!-- more -->
&lt;p>之前我也试过用 Email 的方式发送脚本运行结果，但需要用到 Python，相较而言更为复杂。&lt;/p>
&lt;h2 id="创建和使用-telegram-bot">创建和使用 Telegram Bot&lt;/h2>
&lt;p>创建 Telegram Bot 非常简单。首先在 Telegram 中查找 &lt;code>BotFather&lt;/code>，输入 &lt;code>/newbot&lt;/code>，按照提示输入名称和 Bot 的唯一用户名之后即可创建成功，随即会得到一串 HTTP API Token，记下这个 Token。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/85213325-f219d900-b32a-11ea-8591-94acd6460af7.jpg" alt="Create Bot">&lt;/p>
&lt;p>前往你刚刚创建的 Bot 的聊天界面，随便输入任何消息。之后在浏览器中打开链接 &lt;code>https://api.telegram.org/bot&amp;lt;TOKEN&amp;gt;/getUpdates&lt;/code>，找到你的 Chat ID。随后在浏览器中打开链接测试给自己的账号发送 &lt;code>Hello World&lt;/code> 的消息：&lt;/p>
&lt;pre>&lt;code>https://api.telegram.org/bot&amp;lt;TOKEN&amp;gt;/sendMessage?chat_id=&amp;lt;CHAT_ID&amp;gt;&amp;amp;text=Hello%20World
&lt;/code>&lt;/pre>&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/85214559-8475a900-b33a-11ea-8800-7fbb5b3fd778.jpg" alt="Hello">&lt;/p>
&lt;h2 id="通过-bash-脚本发送消息">通过 Bash 脚本发送消息&lt;/h2>
&lt;p>上面的例子是一个非常简单的 HTTP 请求，在 Bash 中用 &lt;code>cURL&lt;/code> 可以很便捷的实现&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-zsh" data-lang="zsh">$ curl -s -X POST https://api.telegram.org/bot&amp;lt;TOKEN&amp;gt;/sendMessage &lt;span class="se">\
&lt;/span>&lt;span class="se">&lt;/span> -d &lt;span class="nv">chat_id&lt;/span>&lt;span class="o">=&lt;/span>&amp;lt;CHAT_ID&amp;gt; -d &lt;span class="nv">text&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Hello World&amp;#34;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这时候就可以创建一个简单的 Bash 脚本来自动发送消息了，创建如下的 &lt;code>send.sh&lt;/code>，只需 &lt;code>$ bash send.sh&lt;/code> 即可自动发送消息。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="cp">#!/bin/bash
&lt;/span>&lt;span class="cp">&lt;/span>
&lt;span class="nv">TOKEN&lt;/span>&lt;span class="o">=&lt;/span>&amp;lt;TOKEN&amp;gt;
&lt;span class="nv">CHAT_ID&lt;/span>&lt;span class="o">=&lt;/span>&amp;lt;CHAT_ID&amp;gt;
&lt;span class="nv">MESSAGE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Hello World&amp;#34;&lt;/span>
&lt;span class="nv">URL&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://api.telegram.org/bot&lt;/span>&lt;span class="nv">$TOKEN&lt;/span>&lt;span class="s2">/sendMessage&amp;#34;&lt;/span>
curl -s -X POST &lt;span class="nv">$URL&lt;/span> -d &lt;span class="nv">chat_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nv">$CHAT_ID&lt;/span> -d &lt;span class="nv">text&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$MESSAGE&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>同样的，也可以发送文件：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="nv">CAP&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Run Script&amp;#34;&lt;/span>
&lt;span class="nv">FILE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;log.txt&amp;#34;&lt;/span>
&lt;span class="nv">URL_FILE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://api.telegram.org/bot&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">TOKEN&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/sendDocument&amp;#34;&lt;/span>
curl -s -X POST &lt;span class="nv">$URL_FILE&lt;/span> -F &lt;span class="nv">chat_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nv">$CHAT_ID&lt;/span> -F &lt;span class="nv">caption&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$CAP&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;span class="se">&lt;/span> -F &lt;span class="nv">document&lt;/span>&lt;span class="o">=&lt;/span>@&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$FILE&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &amp;gt; /dev/null
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="通用的消息提醒脚本">通用的消息提醒脚本&lt;/h2>
&lt;p>进一步，我想创建一个脚本 &lt;code>run.sh&lt;/code> 的通用脚本，运行 &lt;code>$ bash run.sh cmd.sh &lt;/code> 即可执行 &lt;code>cmd.sh&lt;/code> 中的命令并在结束之后将结果发送到 Telegram&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="cp">#!/bin/bash
&lt;/span>&lt;span class="cp">&lt;/span>
&lt;span class="nv">HOST&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sb">`&lt;/span>hostname&lt;span class="sb">`&lt;/span>
&lt;span class="nv">DATETIME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sb">`&lt;/span>date &lt;span class="s2">&amp;#34;+%Y%m%d-%H%M%S&amp;#34;&lt;/span>&lt;span class="sb">`&lt;/span>
&lt;span class="c1"># Telegram bot settings&lt;/span>
&lt;span class="nv">TOKEN&lt;/span>&lt;span class="o">=&lt;/span>&amp;lt;TOKEN&amp;gt;
&lt;span class="nv">CHAT_ID&lt;/span>&lt;span class="o">=&lt;/span>&amp;lt;CHAT_ID&amp;gt;
&lt;span class="c1"># URLs for sending messages and files&lt;/span>
&lt;span class="nv">URL_MESSAGE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://api.telegram.org/bot&lt;/span>&lt;span class="nv">$TOKEN&lt;/span>&lt;span class="s2">/sendMessage&amp;#34;&lt;/span>
&lt;span class="nv">URL_FILE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://api.telegram.org/bot&lt;/span>&lt;span class="nv">$TOKEN&lt;/span>&lt;span class="s2">/sendDocument&amp;#34;&lt;/span>
&lt;span class="c1"># Log settings&lt;/span>
&lt;span class="nv">THIS_PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sb">`&lt;/span>dirname &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$0&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="sb">`&lt;/span>
&lt;span class="nv">LOG_FILE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/workspace/logs/&lt;/span>&lt;span class="nv">$DATETIME&lt;/span>&lt;span class="s2">.txt&amp;#34;&lt;/span>
&lt;span class="c1"># Run the script&lt;/span>
&lt;span class="nv">SCRIPT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;`dirname &amp;#34;&lt;/span>&lt;span class="nv">$0&lt;/span>&lt;span class="s2">&amp;#34;`/&lt;/span>&lt;span class="nv">$1&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;span class="o">[&lt;/span> -f &lt;span class="nv">$SCRIPT&lt;/span> &lt;span class="o">]&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$SCRIPT&lt;/span>&lt;span class="s2"> does not exist!&amp;#34;&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">exit&lt;/span> 1&lt;span class="p">;&lt;/span> &lt;span class="o">}&lt;/span>
&lt;span class="nb">printf&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$DATETIME&lt;/span>&lt;span class="s2">\n&amp;#34;&lt;/span> &amp;gt;&amp;gt; &lt;span class="nv">$LOG_FILE&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> cat &lt;span class="nv">$SCRIPT&lt;/span> &amp;gt;&amp;gt; &lt;span class="nv">$LOG_FILE&lt;/span>
bash &lt;span class="nv">$SCRIPT&lt;/span> 2&amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="p">|&lt;/span> tee -a &lt;span class="nv">$LOG_FILE&lt;/span>
&lt;span class="c1"># Send log&lt;/span>
&lt;span class="nv">CAP&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Run on &lt;/span>&lt;span class="nv">$HOST&lt;/span>&lt;span class="s2"> at &lt;/span>&lt;span class="nv">$DATETIME&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
curl -s -X POST &lt;span class="nv">$URL_FILE&lt;/span> -F &lt;span class="nv">chat_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nv">$CHAT_ID&lt;/span> -F &lt;span class="nv">caption&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$CAP&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> -F &lt;span class="nv">document&lt;/span>&lt;span class="o">=&lt;/span>@&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$LOG_FILE&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &amp;gt; /dev/null
&lt;span class="c1"># Send message&lt;/span>
&lt;span class="c1">#curl -s -X POST $URL_MESSAGE -d chat_id=$CHAT_ID -d text=&amp;#34;$MESSAGE&amp;#34;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其中，最重要的是 &lt;code>bash $SCRIPT 2&amp;gt;&amp;amp;1 | tee -a $LOG_FILE&lt;/code>，即将脚本的输出同时显示在终端并且保存到文件 &lt;code>$LOG_FILE&lt;/code> 中。&lt;/p>
&lt;p>此外，还可以集成 &lt;a href="https://seashells.io/">Seashells&lt;/a>，将脚本运行结果实时输出到在线终端中，对于训练神经网络之类的任务能够更方便的查看。之后便可将 Seashells 的 URL 通过 Telegram 发送给自己，就可以&lt;strong>实时&lt;/strong>查看脚本运行进度。&lt;/p>
&lt;h2 id="写在后面">写在后面&lt;/h2>
&lt;p>上面展示了如何在 Bash 脚本中使用，同样也可以在 Python 中使用，GitHub 上已经有了现成的 wrapper：&lt;a href="https://github.com/python-telegram-bot/python-telegram-bot">python-telegram-bot&lt;/a>，能够直接 import。&lt;/p>
&lt;p>Telegram Bot 还有更多玩法，不仅仅是用在发送脚本的消息提醒，比如结合 &lt;a href="https://dev.botframework.com/">Microsoft Bot Framework&lt;/a> 创建了对话式聊天机器人等。&lt;/p>
&lt;p>参考：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://telegram.org/blog/bot-revolution">https://telegram.org/blog/bot-revolution&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://core.telegram.org/bots">https://core.telegram.org/bots&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://medium.com/@jasonlimberis/server-runscript-telegram-alert-bot-8a1691297a52">https://medium.com/@jasonlimberis/server-runscript-telegram-alert-bot-8a1691297a52&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://seashells.io/">https://seashells.io/&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/linux/">Linux</category><category domain="https://blog.imfing.com/tags/bash/">Bash</category></item><item><title>随时随地 SSH 连接家中电脑</title><link>https://blog.imfing.com/2020/06/remote-ssh-to-home-reverse-proxy/</link><guid isPermaLink="true">https://blog.imfing.com/2020/06/remote-ssh-to-home-reverse-proxy/</guid><pubDate>Thu, 18 Jun 2020 18:28:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>记录一下如何让家里的电脑 / 服务器能够随时被 SSH 远程登陆访问。话说以前本科的实验室 GPU 服务器只能在内网访问，宿舍离实验室又很远，炎炎烈日，我只能动一动手配置反向代理，让我能在凉爽的宿舍里 SSH 到我实验室的服务器上炼丹。&lt;/p>
&lt;!-- more -->
&lt;h2 id="开启-ssh-server">开启 SSH Server&lt;/h2>
&lt;p>在非服务器版的 Ubuntu 上，默认是没有 SSH 服务器的，因此需要安装和启用 &lt;code>openssh-server&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ sudo apt update
$ sudo apt install openssh-server
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>安装好之后默认会启动 SSH Server，可以通过命令查看服务状态，显示 &lt;code>Active&lt;/code> 应该就是正常启动了&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ sudo service ssh status
● ssh.service - OpenBSD Secure Shell server
Loaded: loaded &lt;span class="o">(&lt;/span>/lib/systemd/system/ssh.service&lt;span class="p">;&lt;/span> enabled&lt;span class="p">;&lt;/span> vendor preset: enabled&lt;span class="o">)&lt;/span>
Active: active &lt;span class="o">(&lt;/span>running&lt;span class="o">)&lt;/span> since Thu 2020-06-18 18:15:48 EDT&lt;span class="p">;&lt;/span> &lt;span class="m">1&lt;/span> day 22h ago
Main PID: &lt;span class="m">17916&lt;/span> &lt;span class="o">(&lt;/span>sshd&lt;span class="o">)&lt;/span>
Tasks: &lt;span class="m">1&lt;/span> &lt;span class="o">(&lt;/span>limit: 4915&lt;span class="o">)&lt;/span>
CGroup: /system.slice/ssh.service
└─17916 /usr/sbin/sshd -D
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>还需要在防火墙中允许 SSH 的连接&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ sudo ufw allow ssh
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>之后我们需要知道本机在内网中的 IP 地址&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ ip a
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们会看到有几张不同的网卡，找到你使用的网卡下面诸如 &lt;code>inet 192.168.1.27/24&lt;/code> 的一串地址，说明子网的地址即为 &lt;code>192.168.1.27&lt;/code>&lt;/p>
&lt;p>此时，可以测试一下 SSH 服务器是否设置成功了，在另一台连接了同一个路由器的电脑中用 SSH 连接： &lt;code>$ ssh USER@192.168.1.27&lt;/code>，不出意外的话已经可以 SSH 进去了。下一步我们需要让它能被其它不在同一网络下的设备访问到。&lt;/p>
&lt;h2 id="配置路由器端口转发">配置路由器端口转发&lt;/h2>
&lt;p>如果你的运营商提供了公网 IP，比如 &lt;code>68.175.xxx.xx&lt;/code>，那之后的问题就好办了，我们只需要在路由器上配置端口转发（Port Forwarding），这样就能够 &lt;code>$ ssh USER@68.175.xxx.xx&lt;/code> 来访问 Ubuntu 电脑了。&lt;/p>
&lt;p>因路由器的不同，需要在路由器中找到端口转发的设置项，将路由器接收到的 TCP / UDP 的 22 端口转发到子网 &lt;code>192.168.1.27&lt;/code> 的 22 端口，也就是将 SSH 的连接直接转到 Ubuntu 机器上。示意图如下：&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/85211795-b1b25f00-b31a-11ea-98ea-22149a901d3e.png" alt="">&lt;/p>
&lt;p>配置好了之后，将另一台电脑连接其它网络（如手机热点），通过 &lt;code>$ ssh USER@68.175.xxx.xx&lt;/code>，如果遇到 &lt;code>ssh connection&lt;/code> 的错误，可以重启 SSH 服务 &lt;code>$ sudo service ssh restart &lt;/code>&lt;/p>
&lt;h2 id="配置-frp-反向代理">配置 frp 反向代理&lt;/h2>
&lt;p>如果没有公网 IP，或者路由器不支持端口转发，或者像我以前在实验室一样，没有路由器的权限，这时候可以考虑用开源的 &lt;a href="https://github.com/fatedier/frp">frp&lt;/a> 配置反向代理。&lt;/p>
&lt;p>和之前不一样的是，我们需要一个拥有公网 IP 的云服务器&lt;/p>
&lt;p>在服务器端配置 frp 的 server 部分，然后在 Ubuntu 的电脑上配置 frp 的 client 部分，这样就在 Ubuntu 电脑和云服务器之间建立了连接。之后，其它电脑可以通过配置的端口 SSH 访问云服务器，frp 就会将连接转发到 Ubuntu 电脑上的相应端口。详细的配置这里不展开，&lt;a href="https://github.com/fatedier/frp/blob/master/README_zh.md">frp 中文文档&lt;/a> 有非常详细的说明。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/85212101-79605000-b31d-11ea-9ab8-77f0e9a7d0dc.png" alt="">&lt;/p>
&lt;h2 id="其它">其它&lt;/h2>
&lt;p>在 Ubuntu 下，&lt;a href="https://ubuntu.com/server/docs/tools-byobu">byobu&lt;/a> 是非常好用的终端会话管理工具，万一 SSH 断开，你还可以重新连接并恢复之前的会话。&lt;/p>
&lt;p>可以参考 &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-byobu-for-terminal-management-on-ubuntu-16-04">How To Install and Use Byobu for Terminal Management on Ubuntu 16.04&lt;/a> 的使用介绍。&lt;/p>
&lt;p>&lt;img src="https://assets.digitalocean.com/articles/byobu/f2aYlVF.png" alt="byobu">&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E5%B7%A5%E5%85%B7/">工具</category><category domain="https://blog.imfing.com/tags/linux/">Linux</category></item><item><title>Ubuntu 美化与终端配置</title><link>https://blog.imfing.com/2020/06/ubuntu-18-04-theme-terminal-setup/</link><guid isPermaLink="true">https://blog.imfing.com/2020/06/ubuntu-18-04-theme-terminal-setup/</guid><pubDate>Fri, 12 Jun 2020 18:28:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>最近又需要用 Ubuntu 笔电作为主力开发机，于是又将系统配置了一遍。俗话说：“好看是第一生产力”，下面记录了我的主题、终端等工具的配置过程。&lt;/p>
&lt;!-- more -->
&lt;h2 id="系统设置与美化">系统设置与美化&lt;/h2>
&lt;h3 id="主题调整">主题调整&lt;/h3>
&lt;p>原生的 Ubuntu 18.04 Gnome 界面的风格我个人不是非常喜欢，个人偏向于更加扁平的风格。以前在 Ubuntu 16.04 的时候，会使用 &lt;a href="https://github.com/anmoljagetia/Flatabulous">Flatabulous&lt;/a>。但 Ubuntu 18.04 将默认的桌面环境从 Unity 换到了 GNOME，导致这个主题没法使用。好在有 &lt;a href="https://www.gnome-look.org/">Gnome Look&lt;/a> 这个网站，上面有非常多的主题可供挑选。&lt;/p>
&lt;p>首先我们需要安装 &lt;code>gnome-tweaks&lt;/code> 工具：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">sudo apt install gnome-tweaks
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>主题的话我目前用了 &lt;a href="https://www.gnome-look.org/p/1381832/">Mcata&lt;/a>，一款仿 macOS Catalina 的主题。图标使用了非常精致好看的 &lt;a href="https://www.gnome-look.org/s/Gnome/p/1166289/">Papirus&lt;/a> 。鼠标指针采用了 &lt;a href="https://www.gnome-look.org/p/1355701/">McMojave cursors&lt;/a>。这些主题的下载和使用也非常简单，对于主题只需下载后解压到 &lt;code>~/.themes&lt;/code> 目录下，图标和鼠标指针需解压到 &lt;code>~/.icons&lt;/code> 目录下，之后即可在 &lt;code>gnome-tweaks&lt;/code> 工具中修改。&lt;/p>
&lt;h3 id="字体设置">字体设置&lt;/h3>
&lt;p>系统字体使用 &lt;a href="https://www.google.com/get/noto/">Google Noto&lt;/a> 系列的字体，可以通过 &lt;code>apt&lt;/code> 便捷的安装&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">sudo apt install fonts-noto
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>安装完成后即可在 &lt;code>gnome-tweaks&lt;/code> 中将系统界面的字体设置为 &lt;code>Noto Sans Regular&lt;/code>，需要中文显示支持就设置为思源黑体系列 &lt;code>Noto Sans CJK&lt;/code>。&lt;/p>
&lt;p>编程等宽字体可以使用上面安装好的 &lt;code>Noto Sans Mono&lt;/code> 系列，当然我个人用的是 &lt;a href="https://github.com/ryanoasis/nerd-fonts/blob/master/patched-fonts/Meslo">Meslo Nerd Font&lt;/a> 中的 &lt;a href="https://github.com/ryanoasis/nerd-fonts/blob/master/patched-fonts/Meslo/M/Regular/complete/Meslo%20LG%20M%20Regular%20Nerd%20Font%20Complete%20Mono.ttf">MesloLGM NF&lt;/a>，支持 Powerline 的符号显示。&lt;/p>
&lt;h3 id="添加中文支持">添加中文支持&lt;/h3>
&lt;p>假如你的系统是英文版的话，如果有中文输入的需求，首先在设置中找到语言选项，添加中文为第二语言，之后再在输入法设置里面添加汉语拼音。&lt;/p>
&lt;h2 id="终端配置">终端配置&lt;/h2>
&lt;h3 id="安装-zsh">安装 zsh&lt;/h3>
&lt;p>首先安装 &lt;a href="https://www.zsh.org/">zsh&lt;/a> 并通过 &lt;code>chsh&lt;/code> 命令将其设置为默认的 Shell 环境。zsh 是和 Bash 一样最流行的终端命令解释器，已经成为了 &lt;a href="https://support.apple.com/en-us/HT208050">macOS 10.15 Catalina&lt;/a> 中默认的 Shell 了。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="c1"># 安装 zsh&lt;/span>
sudo apt install zsh
&lt;span class="c1"># 确认 zsh 成功安装&lt;/span>
which zsh
&lt;span class="c1"># 设置为默认 Shell&lt;/span>
chsh -s /usr/bin/zsh
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="安装-oh-my-zsh">安装 Oh My Zsh&lt;/h3>
&lt;p>&lt;a href="https://ohmyz.sh/">Oh My Zsh&lt;/a> 是一个开源的“开箱即用”的 zsh 配置管理框架，安装之后可以很方便的为 zsh 安装主题、插件等，一行命令即可安装：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">sh -c &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="https://ohmyz.sh/img/themes/kolo.jpg" alt="https://ohmyz.sh/img/themes/kolo.jpg">&lt;/p>
&lt;h3 id="配置-zsh-主题">配置 Zsh 主题&lt;/h3>
&lt;p>安装好 zsh 之后，继续设置其主题为 &lt;a href="https://github.com/romkatv/powerlevel10k">Powerlevel10k&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">git clone --depth&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> https://github.com/romkatv/powerlevel10k.git &lt;span class="si">${&lt;/span>&lt;span class="nv">ZSH_CUSTOM&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="p">~/.oh-my-zsh/custom&lt;/span>&lt;span class="si">}&lt;/span>/themes/powerlevel10k
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>接着在 &lt;code>~/.zshrc&lt;/code> 中设置 &lt;code>ZSH_THEME=&amp;quot;powerlevel10k/powerlevel10k&amp;quot;&lt;/code>，重启 zsh 即可看见设置向导，按照提示可以选择自己喜欢的样式。&lt;/p>
&lt;h3 id="自动补全与语法高亮">自动补全与语法高亮&lt;/h3>
&lt;p>Oh My Zsh 比较常用的两个插件是 &lt;a href="https://github.com/zsh-users/zsh-autosuggestions">zsh-autosuggestions&lt;/a> 与 &lt;a href="https://github.com/zsh-users/zsh-syntax-highlighting">zsh-syntax-highlighting&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">git clone https://github.com/zsh-users/zsh-autosuggestions &lt;span class="si">${&lt;/span>&lt;span class="nv">ZSH_CUSTOM&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="p">~/.oh-my-zsh/custom&lt;/span>&lt;span class="si">}&lt;/span>/plugins/zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git &lt;span class="si">${&lt;/span>&lt;span class="nv">ZSH_CUSTOM&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="p">~/.oh-my-zsh/custom&lt;/span>&lt;span class="si">}&lt;/span>/plugins/zsh-syntax-highlighting
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>安装完成后在 &lt;code>~/.zshrc&lt;/code> 中设置 &lt;code>plugins=(git zsh-autosuggestions zsh-syntax-highlighting)&lt;/code>，重启终端后即可查看到效果。&lt;/p>
&lt;h3 id="配置颜色主题">配置颜色主题&lt;/h3>
&lt;p>Gnome 的 Terminal 可以在 &lt;a href="https://github.com/Mayccoll/Gogh">Gogh&lt;/a> 这个项目里找到合适的颜色主题，我选用了 OneDark&lt;/p>
&lt;h3 id="安装-terminator">安装 Terminator&lt;/h3>
&lt;p>&lt;a href="https://terminator-gtk3.readthedocs.io/en/latest/">Terminator&lt;/a> 是一个非常好用的终端管理软件，支持标签页以及像 &lt;a href="https://github.com/tmux/tmux/wiki">tmux&lt;/a> 一样分屏。如果需要开启多个终端、或者是连接多个远程服务器，Terminator 会是非常好用的工具。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">sudo apt install terminator
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="编辑器配置">编辑器配置&lt;/h2>
&lt;h3 id="安装-visual-studio-code">安装 Visual Studio Code&lt;/h3>
&lt;p>到 &lt;a href="https://code.visualstudio.com/">Visual Studio Code&lt;/a> 官网下载安装 &lt;code>.deb&lt;/code> 安装包，双击即可安装。VS Code 配合其丰富的插件，做到了&lt;strong>开箱即用&lt;/strong>。同时在 &lt;a href="https://code.visualstudio.com/docs/remote/remote-overview">Remote Development&lt;/a> 的辅助下，能够便捷的在服务器、容器中进行开发。&lt;/p>
&lt;h3 id="安装-vim">安装 Vim&lt;/h3>
&lt;p>Ubuntu 是没有默认安装 Vim 的，因此需要手动安装&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">sudo apt install vim
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>安装完成后可以编辑 &lt;code>~/.vimrc&lt;/code> 文件来修改 Vim 的配置。还可以安装 &lt;a href="https://github.com/junegunn/vim-plug">vim-plug&lt;/a> 给 Vim 添加插件支持&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">curl -fLo ~/.vim/autoload/plug.vim --create-dirs &lt;span class="se">\
&lt;/span>&lt;span class="se">&lt;/span> https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="安装-neovim">安装 NeoVim&lt;/h3>
&lt;p>如果没有管理员权限，或者需要更现代的编辑器体验，不妨试试 &lt;a href="https://neovim.io/">NeoVim&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="c1"># 下载 NeoVim&lt;/span>
wget --quiet https://github.com/neovim/neovim/releases/download/nightly/nvim.appimage --output-document nvim
&lt;span class="c1"># 添加权限&lt;/span>
chmod +x nvim
&lt;span class="c1"># 移动到系统路径&lt;/span>
&lt;span class="c1"># 若没有权限，也可以直接添加到 PATH&lt;/span>
sudo chown root:root nvim
sudo mv nvim /usr/bin
&lt;span class="c1"># 添加配置文件目录&lt;/span>
&lt;span class="c1"># 配置文件将保存在 init.vim 中&lt;/span>
mkdir -p ~/.config/nvim
&lt;span class="c1"># 安装 vim-plug&lt;/span>
sh -c &lt;span class="s1">&amp;#39;curl -fLo &amp;#34;${XDG_DATA_HOME:-$HOME/.local/share}&amp;#34;/nvim/site/autoload/plug.vim --create-dirs \
&lt;/span>&lt;span class="s1"> https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim&amp;#39;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="配置-vim--neovim">配置 Vim / NeoVim&lt;/h3>
&lt;p>这个之后打算再写一篇文章。在线的 &lt;a href="https://vimconfig.com/">VimConfig&lt;/a> 可以帮助简单的调整 Vim 的设置。下面贴上我的配置，仅供参考&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt"> 10
&lt;/span>&lt;span class="lnt"> 11
&lt;/span>&lt;span class="lnt"> 12
&lt;/span>&lt;span class="lnt"> 13
&lt;/span>&lt;span class="lnt"> 14
&lt;/span>&lt;span class="lnt"> 15
&lt;/span>&lt;span class="lnt"> 16
&lt;/span>&lt;span class="lnt"> 17
&lt;/span>&lt;span class="lnt"> 18
&lt;/span>&lt;span class="lnt"> 19
&lt;/span>&lt;span class="lnt"> 20
&lt;/span>&lt;span class="lnt"> 21
&lt;/span>&lt;span class="lnt"> 22
&lt;/span>&lt;span class="lnt"> 23
&lt;/span>&lt;span class="lnt"> 24
&lt;/span>&lt;span class="lnt"> 25
&lt;/span>&lt;span class="lnt"> 26
&lt;/span>&lt;span class="lnt"> 27
&lt;/span>&lt;span class="lnt"> 28
&lt;/span>&lt;span class="lnt"> 29
&lt;/span>&lt;span class="lnt"> 30
&lt;/span>&lt;span class="lnt"> 31
&lt;/span>&lt;span class="lnt"> 32
&lt;/span>&lt;span class="lnt"> 33
&lt;/span>&lt;span class="lnt"> 34
&lt;/span>&lt;span class="lnt"> 35
&lt;/span>&lt;span class="lnt"> 36
&lt;/span>&lt;span class="lnt"> 37
&lt;/span>&lt;span class="lnt"> 38
&lt;/span>&lt;span class="lnt"> 39
&lt;/span>&lt;span class="lnt"> 40
&lt;/span>&lt;span class="lnt"> 41
&lt;/span>&lt;span class="lnt"> 42
&lt;/span>&lt;span class="lnt"> 43
&lt;/span>&lt;span class="lnt"> 44
&lt;/span>&lt;span class="lnt"> 45
&lt;/span>&lt;span class="lnt"> 46
&lt;/span>&lt;span class="lnt"> 47
&lt;/span>&lt;span class="lnt"> 48
&lt;/span>&lt;span class="lnt"> 49
&lt;/span>&lt;span class="lnt"> 50
&lt;/span>&lt;span class="lnt"> 51
&lt;/span>&lt;span class="lnt"> 52
&lt;/span>&lt;span class="lnt"> 53
&lt;/span>&lt;span class="lnt"> 54
&lt;/span>&lt;span class="lnt"> 55
&lt;/span>&lt;span class="lnt"> 56
&lt;/span>&lt;span class="lnt"> 57
&lt;/span>&lt;span class="lnt"> 58
&lt;/span>&lt;span class="lnt"> 59
&lt;/span>&lt;span class="lnt"> 60
&lt;/span>&lt;span class="lnt"> 61
&lt;/span>&lt;span class="lnt"> 62
&lt;/span>&lt;span class="lnt"> 63
&lt;/span>&lt;span class="lnt"> 64
&lt;/span>&lt;span class="lnt"> 65
&lt;/span>&lt;span class="lnt"> 66
&lt;/span>&lt;span class="lnt"> 67
&lt;/span>&lt;span class="lnt"> 68
&lt;/span>&lt;span class="lnt"> 69
&lt;/span>&lt;span class="lnt"> 70
&lt;/span>&lt;span class="lnt"> 71
&lt;/span>&lt;span class="lnt"> 72
&lt;/span>&lt;span class="lnt"> 73
&lt;/span>&lt;span class="lnt"> 74
&lt;/span>&lt;span class="lnt"> 75
&lt;/span>&lt;span class="lnt"> 76
&lt;/span>&lt;span class="lnt"> 77
&lt;/span>&lt;span class="lnt"> 78
&lt;/span>&lt;span class="lnt"> 79
&lt;/span>&lt;span class="lnt"> 80
&lt;/span>&lt;span class="lnt"> 81
&lt;/span>&lt;span class="lnt"> 82
&lt;/span>&lt;span class="lnt"> 83
&lt;/span>&lt;span class="lnt"> 84
&lt;/span>&lt;span class="lnt"> 85
&lt;/span>&lt;span class="lnt"> 86
&lt;/span>&lt;span class="lnt"> 87
&lt;/span>&lt;span class="lnt"> 88
&lt;/span>&lt;span class="lnt"> 89
&lt;/span>&lt;span class="lnt"> 90
&lt;/span>&lt;span class="lnt"> 91
&lt;/span>&lt;span class="lnt"> 92
&lt;/span>&lt;span class="lnt"> 93
&lt;/span>&lt;span class="lnt"> 94
&lt;/span>&lt;span class="lnt"> 95
&lt;/span>&lt;span class="lnt"> 96
&lt;/span>&lt;span class="lnt"> 97
&lt;/span>&lt;span class="lnt"> 98
&lt;/span>&lt;span class="lnt"> 99
&lt;/span>&lt;span class="lnt">100
&lt;/span>&lt;span class="lnt">101
&lt;/span>&lt;span class="lnt">102
&lt;/span>&lt;span class="lnt">103
&lt;/span>&lt;span class="lnt">104
&lt;/span>&lt;span class="lnt">105
&lt;/span>&lt;span class="lnt">106
&lt;/span>&lt;span class="lnt">107
&lt;/span>&lt;span class="lnt">108
&lt;/span>&lt;span class="lnt">109
&lt;/span>&lt;span class="lnt">110
&lt;/span>&lt;span class="lnt">111
&lt;/span>&lt;span class="lnt">112
&lt;/span>&lt;span class="lnt">113
&lt;/span>&lt;span class="lnt">114
&lt;/span>&lt;span class="lnt">115
&lt;/span>&lt;span class="lnt">116
&lt;/span>&lt;span class="lnt">117
&lt;/span>&lt;span class="lnt">118
&lt;/span>&lt;span class="lnt">119
&lt;/span>&lt;span class="lnt">120
&lt;/span>&lt;span class="lnt">121
&lt;/span>&lt;span class="lnt">122
&lt;/span>&lt;span class="lnt">123
&lt;/span>&lt;span class="lnt">124
&lt;/span>&lt;span class="lnt">125
&lt;/span>&lt;span class="lnt">126
&lt;/span>&lt;span class="lnt">127
&lt;/span>&lt;span class="lnt">128
&lt;/span>&lt;span class="lnt">129
&lt;/span>&lt;span class="lnt">130
&lt;/span>&lt;span class="lnt">131
&lt;/span>&lt;span class="lnt">132
&lt;/span>&lt;span class="lnt">133
&lt;/span>&lt;span class="lnt">134
&lt;/span>&lt;span class="lnt">135
&lt;/span>&lt;span class="lnt">136
&lt;/span>&lt;span class="lnt">137
&lt;/span>&lt;span class="lnt">138
&lt;/span>&lt;span class="lnt">139
&lt;/span>&lt;span class="lnt">140
&lt;/span>&lt;span class="lnt">141
&lt;/span>&lt;span class="lnt">142
&lt;/span>&lt;span class="lnt">143
&lt;/span>&lt;span class="lnt">144
&lt;/span>&lt;span class="lnt">145
&lt;/span>&lt;span class="lnt">146
&lt;/span>&lt;span class="lnt">147
&lt;/span>&lt;span class="lnt">148
&lt;/span>&lt;span class="lnt">149
&lt;/span>&lt;span class="lnt">150
&lt;/span>&lt;span class="lnt">151
&lt;/span>&lt;span class="lnt">152
&lt;/span>&lt;span class="lnt">153
&lt;/span>&lt;span class="lnt">154
&lt;/span>&lt;span class="lnt">155
&lt;/span>&lt;span class="lnt">156
&lt;/span>&lt;span class="lnt">157
&lt;/span>&lt;span class="lnt">158
&lt;/span>&lt;span class="lnt">159
&lt;/span>&lt;span class="lnt">160
&lt;/span>&lt;span class="lnt">161
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-viml" data-lang="viml">&lt;span class="c">&amp;#34;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34; &amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34; __ _ _ _ __ ___ _ __ ___ &amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34; \ \ / / | &amp;#39;_ ` _ \| &amp;#39;__/ __| &amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34; \ V /| | | | | | | | | (__ &amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34; \_/ |_|_| |_| |_|_| \___| &amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34; &amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34; &amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">langmenu&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="nx">en_US&lt;/span> &lt;span class="c">&amp;#34; set to English menu&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="k">let&lt;/span> $&lt;span class="nx">LANG&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s1">&amp;#39;en_US&amp;#39;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">nocompatible&lt;/span> &lt;span class="c">&amp;#34; use Vim&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">filetype&lt;/span> &lt;span class="nx">on&lt;/span> &lt;span class="c">&amp;#34; filetype&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">filetype&lt;/span> &lt;span class="nx">plugin&lt;/span> &lt;span class="nx">indent&lt;/span> &lt;span class="nx">on&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34;=====================================================&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;&amp;#34;&amp;#34; Vim-plug settings&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;=====================================================&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">call&lt;/span> &lt;span class="nx">plug&lt;/span>#&lt;span class="nx">begin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;~/.vim/plugged&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">Plug&lt;/span> &lt;span class="s1">&amp;#39;scrooloose/nerdtree&amp;#39;&lt;/span> &lt;span class="c">&amp;#34; nerdtree&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">Plug&lt;/span> &lt;span class="s1">&amp;#39;itchyny/lightline.vim&amp;#39;&lt;/span> &lt;span class="c">&amp;#34; status bar&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">Plug&lt;/span> &lt;span class="s1">&amp;#39;tpope/vim-fugitive&amp;#39;&lt;/span> &lt;span class="c">&amp;#34; git plugin&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">Plug&lt;/span> &lt;span class="s1">&amp;#39;sheerun/vim-polyglot&amp;#39;&lt;/span> &lt;span class="c">&amp;#34; language support&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">Plug&lt;/span> &lt;span class="s1">&amp;#39;mhinz/vim-startify&amp;#39;&lt;/span> &lt;span class="c">&amp;#34; start screen&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">Plug&lt;/span> &lt;span class="s1">&amp;#39;joshdick/onedark.vim&amp;#39;&lt;/span> &lt;span class="c">&amp;#34; color schemes&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;Plug &amp;#39;flazz/vim-colorschemes&amp;#39;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34;Plug &amp;#39;neoclide/coc.nvim&amp;#39;, {&amp;#39;branch&amp;#39;: &amp;#39;release&amp;#39;}&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">call&lt;/span> &lt;span class="nx">plug&lt;/span>#&lt;span class="nx">end&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34;=====================================================&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;&amp;#34; General settings&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;=====================================================&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">syntax&lt;/span> &lt;span class="nx">on&lt;/span> &lt;span class="c">&amp;#34; syntax highlighting&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">background&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="nb">dark&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">colorscheme&lt;/span> &lt;span class="nx">onedark&lt;/span> &lt;span class="c">&amp;#34; color scheme&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">termguicolors&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">cursorline&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">encoding&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="nx">utf&lt;/span>&lt;span class="m">-8&lt;/span> &lt;span class="c">&amp;#34; file encoding&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">number&lt;/span> &lt;span class="c">&amp;#34; show line numbers&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">ruler&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">ttyfast&lt;/span> &lt;span class="c">&amp;#34; terminal acceleration&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">tabstop&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="m">4&lt;/span> &lt;span class="c">&amp;#34; 4 whitespaces for tabs visual presentation&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">shiftwidth&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="m">4&lt;/span> &lt;span class="c">&amp;#34; shift lines by 4 spaces&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">smarttab&lt;/span> &lt;span class="c">&amp;#34; set tabs for a shifttabs logic&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">expandtab&lt;/span> &lt;span class="c">&amp;#34; expand tabs into spaces&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">autoindent&lt;/span> &lt;span class="nx">smartindent&lt;/span> &lt;span class="c">&amp;#34; indent when moving to the next line while writing code&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">showmatch&lt;/span> &lt;span class="c">&amp;#34; shows matching part of bracket pairs (), [], {}&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">matchpairs&lt;/span>&lt;span class="p">+=&amp;lt;&lt;/span>:&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="c">&amp;#34; highlight match pairs&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">magic&lt;/span> &lt;span class="c">&amp;#34; regular expression&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">title&lt;/span> &lt;span class="c">&amp;#34; display title&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">nobackup&lt;/span> &lt;span class="c">&amp;#34; no backup files&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">nowritebackup&lt;/span> &lt;span class="c">&amp;#34; only in case you don&amp;#39;t want a backup file while editing&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">noswapfile&lt;/span> &lt;span class="c">&amp;#34; no swap files&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">backspace&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="nx">indent&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nx">eol&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nx">start&lt;/span> &lt;span class="c">&amp;#34; fix common backspace problems&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">nowrap&lt;/span> &lt;span class="c">&amp;#34; line wrap&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">history&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="m">1000&lt;/span> &lt;span class="c">&amp;#34; history&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">autoread&lt;/span> &lt;span class="c">&amp;#34; reload files when changed on disk, i.e. via `git checkout`&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">shortmess&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="nx">atI&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">noshowmode&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">showcmd&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">selection&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="nx">inclusive&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">selectmode&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="nx">mouse&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">scrolloff&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="m">10&lt;/span> &lt;span class="c">&amp;#34; let 10 lines before/after cursor during scroll&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">clipboard&lt;/span>&lt;span class="p">+=&lt;/span>&lt;span class="nx">unnamed&lt;/span> &lt;span class="c">&amp;#34; use system clipboard&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">laststatus&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">exrc&lt;/span> &lt;span class="c">&amp;#34; enable usage of additional .vimrc files from working directory&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">secure&lt;/span> &lt;span class="c">&amp;#34; prohibit .vimrc files to execute shell, create files, etc...&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">pastetoggle&lt;/span>&lt;span class="p">=&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="c">&amp;#34; toggle paste mode&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34;=====================================================&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;&amp;#34; Search settings&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;=====================================================&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">incsearch&lt;/span> &lt;span class="c">&amp;#34; incremental search&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">hlsearch&lt;/span> &lt;span class="c">&amp;#34; highlight search results&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">ignorecase&lt;/span> &lt;span class="c">&amp;#34; ignore search cases&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">set&lt;/span> &lt;span class="nx">smartcase&lt;/span> &lt;span class="c">&amp;#34; unless there is capital&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34;=====================================================&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;&amp;#34; Plugin settings&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;=====================================================&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="k">let&lt;/span> &lt;span class="nx">g&lt;/span>:&lt;span class="nx">lightline&lt;/span> &lt;span class="p">=&lt;/span> {&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span> \ &lt;span class="s1">&amp;#39;colorscheme&amp;#39;&lt;/span>: &lt;span class="s1">&amp;#39;onedark&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span> \ }&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34; Some key remapping&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">map&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">n&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> :&lt;span class="nx">NERDTreeToggle&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">CR&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34;=====================================================&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;&amp;#34; Keybindings&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;=====================================================&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">inoremap&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">a&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">Home&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">inoremap&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">End&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">inoremap&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">ESC&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&amp;#34;&lt;span class="p">+&lt;/span>&lt;span class="nx">pa&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34; Quick Esc &lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">inoremap&lt;/span> &lt;span class="nx">jj&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">Esc&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">inoremap&lt;/span> &lt;span class="nx">jk&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">Esc&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34; Easy window navigation&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">nnoremap&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">J&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">W&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">j&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">nnoremap&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">K&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">W&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">k&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">nnoremap&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">L&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">W&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">l&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">nnoremap&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">H&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">W&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">h&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34; Quick yanking to the end of the line&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">nnoremap&lt;/span> &lt;span class="nx">Y&lt;/span> &lt;span class="nx">y&lt;/span>$&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34; Jump to matching pairs easily, with Tab&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">nnoremap&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">Tab&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> %&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">vnoremap&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">Tab&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> %&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34; Folding&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">nnoremap&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">Space&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="nx">za&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">vnoremap&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">Space&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="nx">za&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34; Map ; to : and save a million keystrokes&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34; ex mode commands made easy 用于快速进入命令行&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">nnoremap&lt;/span> ; :&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34; Shift+H goto head of the line, Shift+L goto end of the line&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">nnoremap&lt;/span> &lt;span class="nx">H&lt;/span> ^&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">nnoremap&lt;/span> &lt;span class="nx">L&lt;/span> $&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34; Tab&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">nnoremap&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">Left&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> :&lt;span class="nx">tabprevious&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">CR&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="nx">nnoremap&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="nx">Right&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> :&lt;span class="nx">tabnext&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nx">CR&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">
&lt;/span>&lt;span class="c">&amp;#34; Auto indent pasted text&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;nnoremap p p=`]&amp;lt;C-o&amp;gt;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="c">&amp;#34;nnoremap P P=`]&amp;lt;C-o&amp;gt;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E5%B7%A5%E5%85%B7/">工具</category><category domain="https://blog.imfing.com/tags/ubuntu/">Ubuntu</category><category domain="https://blog.imfing.com/tags/bash/">Bash</category></item><item><title>Go + gRPC 使用初探</title><link>https://blog.imfing.com/2020/06/go-grpc-tutorial/</link><guid isPermaLink="true">https://blog.imfing.com/2020/06/go-grpc-tutorial/</guid><pubDate>Tue, 02 Jun 2020 19:28:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>&lt;a href="https://grpc.io/">gRPC&lt;/a> 是一个高性能、开源和通用的 RPC 框架，基于 &lt;a href="https://en.wikipedia.org/wiki/HTTP/2">HTTP/2&lt;/a> 传输协议，使用 &lt;a href="https://en.wikipedia.org/wiki/Protocol_Buffers">Protocol Buffers&lt;/a> 作为接口描述语言，支持众多的编程语言。
在这篇文章里，我记录了如何从零开始，在 Windows 下，用 Docker 和 Go 搭建一个简单的 gRPC 示例。&lt;/p>
&lt;!-- more -->
&lt;h2 id="关于-grpc">关于 gRPC&lt;/h2>
&lt;p>gRPC 最初在2015年由Google开发。它使用 HTTP / 2 进行传输，并提供诸如身份验证，双向流和流控制，阻塞或非阻塞绑定以及取消和超时等功能。它为多种语言生成跨平台的客户端和服务器绑定。最常见的使用场景包括在微服务中连接服务风格的架构，并将移动设备，浏览器客户端连接到后端服务。&lt;/p>
&lt;p>在 gRPC 里，客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法，使得创建分布式应用和服务变得更加容易。同时，只需要用 Protocol Buffers 定义接口与数据格式，gRPC 就可以生成各个语言下的请求与回复的函数定义，极大的简化了不同平台之间服务与客户端的开发流程。&lt;/p>
&lt;p>&lt;img src="https://grpc.io/img/landing-2.svg" alt="">&lt;/p>
&lt;h3 id="protocol-buffers">Protocol Buffers&lt;/h3>
&lt;p>&lt;a href="https://developers.google.com/protocol-buffers">Protocol Buffers&lt;/a> 是与语言无关，与平台无关的可扩展机制，用于对结构化数据进行序列化（例如 XML），但更小，更快，更简单。 您定义要一次构造数据的方式，然后可以使用生成的特殊源代码轻松地使用各种语言在各种数据流中写入和读取结构化数据。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-proto" data-lang="proto">&lt;span class="kd">message&lt;/span> &lt;span class="nc">Person&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span> &lt;span class="kt">int32&lt;/span> &lt;span class="n">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">has_ponycopter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="err">
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面定义了一个简单的 Person 消息数据，按顺序分别有三个字段，因此编号了 &lt;code>1, 2, 3&lt;/code>。&lt;a href="https://developers.google.com/protocol-buffers/docs/proto3">proto3 官方文档&lt;/a> 提供了更详细的介绍与使用示例。&lt;/p>
&lt;h3 id="grpc-vs-rest">gRPC vs REST&lt;/h3>
&lt;p>主要的不同是 gRPC 使用了 Protobuf 作为数据传输格式，因为是二进制，所以体积更小，消耗资源更低。而 REST 一般用的是 JSON 格式，便于直观理解，但数据本身体积很大，而且解析需要更多计算资源。此外，gRPC 默认使用 HTTP/2，而很多 REST 采用的还是 HTTP/1。更多详细的不同可以参考 &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/grpc/comparison?view=aspnetcore-3.1#high-level-comparison">Compare gRPC services with HTTP APIs&lt;/a>&lt;/p>
&lt;h2 id="环境设置">环境设置&lt;/h2>
&lt;p>为了避免配置环境的麻烦，推荐使用 &lt;a href="https://www.docker.com/">Docker&lt;/a> 与 &lt;a href="https://code.visualstudio.com/docs/remote/containers">Visual Studio Code Remote - Containers&lt;/a>，这样一来可以更便捷地在不同的容器环境中进行开发。
在 Windows 下，安装 &lt;a href="https://www.docker.com/products/docker-desktop">Docker Desktop&lt;/a> 来启动 Docker 服务，我使用了 &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/wsl2-index">WSL 2&lt;/a> 作为其引擎，更多关于 WSL 2 的可以参考我之前的文章 &lt;a href="https://blog.imfing.com/upgrade-to-windows-wsl-2">升级到 WSL 2 开发环境&lt;/a>。由于使用了 WSL 2，为了更快的文件访问速度，我选择把整个项目文件夹放在 WSL 2 下的文件夹内： &lt;code>$ mkdir ~/grpc-go&lt;/code>。
用到了 VS Code &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers">Remote Container&lt;/a> 的插件来使 VS Code 能够支持在容器环境内开发。
为了使得开发用到的容器环境具有一致性，需要使用 &lt;a href="https://code.visualstudio.com/docs/remote/containers#_creating-a-devcontainerjson-file">devcontainer.json&lt;/a> 文件，可以参考 &lt;a href="https://github.com/microsoft/vscode-dev-containers/tree/master/containers/go">vscode-dev-containers/containers/go/&lt;/a> 下面的示例配置。
先在 &lt;code>~/grpc-go&lt;/code> 下创建 &lt;code>.devcontainer&lt;/code> 目录，并新建 &lt;a href="https://github.com/microsoft/vscode-remote-try-go/blob/master/.devcontainer/Dockerfile">Dockerfile&lt;/a> 和 &lt;a href="https://github.com/microsoft/vscode-remote-try-go/blob/master/.devcontainer/devcontainer.json">devcontainer.json&lt;/a> 配置，直接复制 &lt;a href="https://github.com/microsoft/vscode-dev-containers/tree/master/containers/go">vscode-dev-containers/containers/go/&lt;/a> 中的配置即可，后面需要对 &lt;code>devcontainer.json&lt;/code> 做一些修改。创建完成后的 &lt;code>grpc-go&lt;/code> 目录如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-sh" data-lang="sh">➜ grpc-go tree -a
.
└── .devcontainer
├── Dockerfile
└── devcontainer.json
&lt;span class="m">1&lt;/span> directory, &lt;span class="m">2&lt;/span> files
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>为了更方便的使用容器中的 &lt;code>GOPATH&lt;/code>，在 &lt;code>devcontainer.json&lt;/code> 中添加如下两行，将 WSL 中的 &lt;code>grpc-go&lt;/code> 挂载到容器中的 &lt;code>/go/src/github.com/fing/&lt;/code> 目录下，方便后面模块的导入。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="s2">&amp;#34;workspaceMount&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s2">&amp;#34;source=${localWorkspaceFolder},target=/go/src/github.com/fing/${localWorkspaceFolderBasename},type=bind,consistency=cached&amp;#34;&lt;/span>&lt;span class="err">,&lt;/span>
&lt;span class="s2">&amp;#34;workspaceFolder&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="s2">&amp;#34;/go/src/github.com/fing/${localWorkspaceFolderBasename}&amp;#34;&lt;/span>&lt;span class="err">,&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在配置好 &lt;code>grpc-go/.devcontainer/&lt;/code> 之后，按 &lt;code>F1&lt;/code> 打开 VS Code 的命令界面，选择 &lt;code>Remote-Containers: Open Folder in Containers...&lt;/code>，之后在打开的 Windows 文件选择框地址栏中输入 &lt;code>\\wsl$&lt;/code> 即可选择 WSL 中的文件夹打开。&lt;/p>
&lt;h2 id="你好-grpc">你好 gRPC&lt;/h2>
&lt;p>在这个例子中将用 gRPC 创建一个简单的 greet 接口以及用 Go 实现服务和客户端。&lt;/p>
&lt;h3 id="安装-protocol-buffer-编译器">安装 Protocol Buffer 编译器&lt;/h3>
&lt;p>因为这个容器的镜像是基于 Debian 的，因此可以使用熟悉的 &lt;code>apt install&lt;/code> 来安装 &lt;code>protobuf-compiler&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="c1"># Install protobuf compiler&lt;/span>
apt update &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> apt install -y protobuf-compiler
protoc --version &lt;span class="c1"># Ensure compiler version is 3+&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;a href="https://grpc.io/docs/protoc-installation/">gRPC – Protocol Buffer Compiler Installation&lt;/a>&lt;/p>
&lt;h3 id="安装-go-模块">安装 Go 模块&lt;/h3>
&lt;p>需要 gRPC 以及 protobuf 的 Go 插件：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="c1"># Install go plugins&lt;/span>
go get -u google.golang.org/grpc
go get -u github.com/golang/protobuf/&lt;span class="o">{&lt;/span>proto, protoc-gen-go&lt;span class="o">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="测试-protoc-编译">测试 protoc 编译&lt;/h3>
&lt;p>首先创建 protobuf 文件 &lt;code>grpc-go/greet/greetpb/greet.proto&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-proto" data-lang="proto">&lt;span class="n">syntax&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">&amp;#34;proto3&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="kn">package&lt;/span> &lt;span class="nn">greet&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="k">option&lt;/span> &lt;span class="n">go_package&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;greet/greetpb&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="kd">service&lt;/span> &lt;span class="n">GreetService&lt;/span> &lt;span class="p">{}&lt;/span>&lt;span class="err">
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里定义了了一个简单 service：&lt;code>GreetService&lt;/code>，暂时将具体定义留白。
使用 &lt;code>protoc&lt;/code> 测试编译：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-sh" data-lang="sh">protoc greet/greetpb/greet.proto --go_out&lt;span class="o">=&lt;/span>&lt;span class="nv">plugins&lt;/span>&lt;span class="o">=&lt;/span>grpc:.
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>编译完成后能在 &lt;code>greetpb&lt;/code> 目录下看到生成好的 &lt;code>greet.pb.go&lt;/code> 文件。&lt;/p>
&lt;h3 id="添加基本的-server">添加基本的 Server&lt;/h3>
&lt;p>首先在 &lt;code>greet&lt;/code> 目录下新建 &lt;code>greet_server&lt;/code> 文件夹并添加 &lt;code>greet/greet_server/server.go&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello world!&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>测试运行 &lt;code>go run greet/greet_server/server.go&lt;/code>，能看到 &lt;code>Hello World!&lt;/code> 的输出。
接下来添加一下基本的 Server 框架。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;log&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;net&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;github.com/fing/grpc-go/greet/greetpb&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;google.golang.org/grpc&amp;#34;&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;span class="kd">type&lt;/span> &lt;span class="nx">server&lt;/span> &lt;span class="kd">struct&lt;/span>&lt;span class="p">{}&lt;/span>
&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello world Server&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">lis&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">net&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Listen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;tcp&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;:50051&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatalf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;failed to listen: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nx">s&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">grpc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewServer&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">RegisterGreetServiceServer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">{})&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Serve&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">lis&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatalf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;failed to serve: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>要构建服务器，我们：&lt;/p>
&lt;ol>
&lt;li>指定要用于侦听客户端请求的端口 &lt;code>lis, err := net.Listen(&amp;quot;tcp&amp;quot;, &amp;quot;:50051&amp;quot;)&lt;/code>&lt;/li>
&lt;li>创建 gRPC 服务器的实例 &lt;code>grpc.NewServer()&lt;/code>&lt;/li>
&lt;li>在 gRPC 服务器上注册我们的服务实现 &lt;code>greetpb.RegisterGreetServiceServer(s, &amp;amp;server{})&lt;/code>&lt;/li>
&lt;li>调用服务器实例 &lt;code>Server()&lt;/code> 来启动侦听&lt;/li>
&lt;/ol>
&lt;h3 id="添加基本的-client">添加基本的 Client&lt;/h3>
&lt;p>同样的，创建 &lt;code>client.go&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;log&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;github.com/fing/grpc-go/greet/greetpb&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;google.golang.org/grpc&amp;#34;&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello I&amp;#39;m client&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">conn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">grpc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Dial&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;localhost:50051&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">grpc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithInsecure&lt;/span>&lt;span class="p">())&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatalf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;did not connect: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">defer&lt;/span> &lt;span class="nx">conn&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="nx">c&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewGreetServiceClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">conn&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Created client: %f&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">c&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在这里，我们：&lt;/p>
&lt;ol>
&lt;li>使用 &lt;code>grpc.Dial()&lt;/code> 创建 gRPC channel&lt;/li>
&lt;li>创建 &lt;code>GreetService&lt;/code> 的客户端 &lt;code>greetpb.NewGreetServiceClient()&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>测试 Server 和 Client&lt;/strong>：我们可以用 &lt;code>go run&lt;/code> 测试运行刚刚创建的 &lt;code>server.go&lt;/code> 和 &lt;code>client.go&lt;/code>，尽管我们并没有实现 &lt;code>GreetService&lt;/code> 的具体细节，但这样可以测试是否能成功连接 gRPC 的服务器和客户端。&lt;/p>
&lt;h2 id="实现-unary-call">实现 Unary Call&lt;/h2>
&lt;p>Unary Call 顾名思义，就是简单的发送请求与回应。&lt;/p>
&lt;h3 id="定义-requestresponse-消息">定义 Request/Response 消息&lt;/h3>
&lt;p>在protobuf 文件 &lt;code>grpc-go/greet/greetpb/greet.proto&lt;/code> 中添加不同的 message 以及声明 &lt;code>GreetService&lt;/code> 中 RPC 函数 &lt;code>Greet()&lt;/code>，使得它发送 &lt;code>GreetingRequest&lt;/code>，并等待接收 &lt;code>GreetingResponse&lt;/code> 的消息。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-proto" data-lang="proto">&lt;span class="n">syntax&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">&amp;#34;proto3&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="kn">package&lt;/span> &lt;span class="nn">greet&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="k">option&lt;/span> &lt;span class="n">go_package&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;greet/greetpb&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="kd">message&lt;/span> &lt;span class="nc">Greeting&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">first_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">last_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="kd">message&lt;/span> &lt;span class="nc">GreetingRequest&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span> &lt;span class="n">Greeting&lt;/span> &lt;span class="n">greeting&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="kd">message&lt;/span> &lt;span class="nc">GreetingResponse&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="kd">service&lt;/span> &lt;span class="n">GreetService&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span> &lt;span class="c1">// Unary
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">rpc&lt;/span> &lt;span class="n">Greet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">GreetingRequest&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">returns&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">GreetingResponse&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{};&lt;/span>&lt;span class="err">
&lt;/span>&lt;span class="err">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="err">
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>别忘了再次使用 &lt;code>protoc&lt;/code> 编译我们修改过的 &lt;code>.proto&lt;/code> 文件。&lt;/p>
&lt;h3 id="实现-rpc-函数">实现 RPC 函数&lt;/h3>
&lt;p>之后要在 &lt;code>server.go&lt;/code> 中实现 &lt;code>Greet()&lt;/code> 函数，我们可以从生成好的 &lt;code>greet.pb.go&lt;/code> 中找到这个接口的定义&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">UnimplementedGreetServiceServer&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Greet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">GreetingRequest&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">GreetingResponse&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="o">...&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>将其复制到 &lt;code>server.go&lt;/code> 中，将具体的变量名放进去&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Greet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">req&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">GreetingRequest&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">GreetingResponse&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Greet function was invoked with %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">firstName&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">GetGreeting&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nf">GetFirstName&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="nx">result&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="s">&amp;#34;Hello &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">firstName&lt;/span>
&lt;span class="nx">res&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">GreetingResponse&lt;/span>&lt;span class="p">{&lt;/span>
&lt;span class="nx">Result&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">result&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>之后可以在 &lt;code>client.go&lt;/code> 里调用 &lt;code>Greet()&lt;/code> 了。可以在 &lt;code>greet.pb.go&lt;/code> 文件里找到函数的定义&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="nf">Greet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">in&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">GreetingRequest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">opts&lt;/span> &lt;span class="o">...&lt;/span>&lt;span class="nx">grpc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">CallOption&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在 &lt;code>client.go&lt;/code> 的 &lt;code>main()&lt;/code> 函数中创建 Request&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="nx">req&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">GreetingRequest&lt;/span>&lt;span class="p">{&lt;/span>
&lt;span class="nx">Greeting&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Greeting&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">FirstName&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;James&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">LastName&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Bond&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>即可像下面一样调用 client 的 &lt;code>Greet()&lt;/code> 并且传递 Request 信息&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Greet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Background&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>之后再分别运行 &lt;code>server.go&lt;/code> 和 &lt;code>client.go&lt;/code> 即可看到 RPC 调用结果&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">root@d90ef922fdfd:/go/src/github.com/fing/grpc-go# go run greet/greet_server/server.go
Hello world Server
Greet &lt;span class="k">function&lt;/span> was invoked with greeting:&lt;span class="o">{&lt;/span>first_name:&lt;span class="s2">&amp;#34;James&amp;#34;&lt;/span> last_name:&lt;span class="s2">&amp;#34;Bond&amp;#34;&lt;/span>&lt;span class="o">}&lt;/span>
root@d90ef922fdfd:/go/src/github.com/fing/grpc-go# go run greet/greet_client/client.go
Hello I&lt;span class="err">&amp;#39;&lt;/span>m client
2020/06/04 04:25:24 Response from Greet: Hello James
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="附录">附录&lt;/h2>
&lt;p>下面附上完整的代码，主要参考了官方的 &lt;a href="https://github.com/grpc/grpc-go/tree/master/examples/helloworld">Helloword 示例&lt;/a>&lt;/p>
&lt;p>client.go&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;span class="s">&amp;#34;context&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;log&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;github.com/fing/grpc-go/greet/greetpb&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;google.golang.org/grpc&amp;#34;&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello I&amp;#39;m client&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">conn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">grpc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Dial&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;localhost:50051&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">grpc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithInsecure&lt;/span>&lt;span class="p">())&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatalf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;did not connect: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">defer&lt;/span> &lt;span class="nx">conn&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="nx">c&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewGreetServiceClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">conn&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">req&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">GreetingRequest&lt;/span>&lt;span class="p">{&lt;/span>
&lt;span class="nx">Greeting&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Greeting&lt;/span>&lt;span class="p">{&lt;/span>
&lt;span class="nx">FirstName&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;James&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">LastName&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Bond&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nx">res&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Greet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Background&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatalf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;could not greet: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Response from Greet: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Result&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>server.go&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;span class="s">&amp;#34;context&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;log&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;net&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;github.com/fing/grpc-go/greet/greetpb&amp;#34;&lt;/span>
&lt;span class="s">&amp;#34;google.golang.org/grpc&amp;#34;&lt;/span>
&lt;span class="p">)&lt;/span>
&lt;span class="kd">type&lt;/span> &lt;span class="nx">server&lt;/span> &lt;span class="kd">struct&lt;/span>&lt;span class="p">{}&lt;/span>
&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Greet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">req&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">GreetingRequest&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">GreetingResponse&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Greet function was invoked with %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">firstName&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">GetGreeting&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nf">GetFirstName&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="nx">result&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="s">&amp;#34;Hello &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">firstName&lt;/span>
&lt;span class="nx">res&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">GreetingResponse&lt;/span>&lt;span class="p">{&lt;/span>
&lt;span class="nx">Result&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">result&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello world Server&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nx">lis&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">net&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Listen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;tcp&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;:50051&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatalf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;failed to listen: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nx">s&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">grpc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewServer&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="nx">greetpb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">RegisterGreetServiceServer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">{})&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Serve&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">lis&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatalf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;failed to serve: %v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>参考链接：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://grpc.io/docs/what-is-grpc/introduction/">https://grpc.io/docs/what-is-grpc/introduction/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://grpc.io/docs/languages/go/basics/">https://grpc.io/docs/languages/go/basics/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.digitalocean.com/community/tutorials/understanding-the-gopath">https://www.digitalocean.com/community/tutorials/understanding-the-gopath&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/grpc/grpc-go/tree/master/examples/helloworld">https://github.com/grpc/grpc-go/tree/master/examples/helloworld&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/go/">Go</category></item><item><title>升级到 WSL 2 开发环境</title><link>https://blog.imfing.com/2020/05/upgrade-to-windows-wsl-2/</link><guid isPermaLink="true">https://blog.imfing.com/2020/05/upgrade-to-windows-wsl-2/</guid><pubDate>Wed, 27 May 2020 19:31:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>微软在今天正式向公众推出了 &lt;a href="https://blogs.windows.com/windowsexperience/2020/05/27/how-to-get-the-windows-10-may-2020-update/">Windows 10 May 2020 Update&lt;/a>（Version 2004），这个大版本的更新中一个比较重要的特性便是 Windows Subsystem for Linux 2（WSL 2）。&lt;/p>
&lt;p>&lt;img src="https://devblogs.microsoft.com/commandline/wp-content/uploads/sites/33/2019/05/LogoDesign.png" alt="">&lt;/p>
&lt;!-- more -->
&lt;h2 id="升级到-windows-10-version-2004">升级到 Windows 10 (version 2004)&lt;/h2>
&lt;p>首先需要升级到 Windows 10 version 2004。升级方法可以参考 &lt;a href="https://blogs.windows.com/windowsexperience/2020/05/27/how-to-get-the-windows-10-may-2020-update/">官方博客&lt;/a>，在 Windows 10 的设置页面手动检查更新推送；或者也可以在 &lt;a href="https://www.microsoft.com/en-us/software-download/windows10">Download Windows10&lt;/a> 页面下载升级助手或者 &lt;a href="https://software-download.microsoft.com/download/pr/MediaCreationTool2004.exe">Media Creation Tool&lt;/a>。我就是使用 Media Creation Tool 成功升级的。
对于更新的详细信息，可以参考 &lt;a href="https://www.reddit.com/r/Windows10/comments/grm5yv/windows_10_may_2020_update_version_2004_build/">Windows Blogs&lt;/a> 或 &lt;a href="https://blogs.windows.com/windowsexperience/2020/05/27/whats-new-in-the-windows-10-may-2020-update/">Reddit 上的帖子&lt;/a>，这里不再赘述。&lt;/p>
&lt;h2 id="升级到-wsl-2">升级到 WSL 2&lt;/h2>
&lt;p>确保 Windows 版本为 2004，Build 19041 或更高。详细的文档可以参考官方文档 &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10#update-to-wsl-2">Update to WSL 2&lt;/a>。&lt;/p>
&lt;p>在开始安装之前需要启用一个特性，桌面左下角右击，用管理员模式运行 PowerShell：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="n">dism&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">exe&lt;/span> &lt;span class="p">/&lt;/span>&lt;span class="n">online&lt;/span> &lt;span class="p">/&lt;/span>&lt;span class="nb">enable-feature&lt;/span> &lt;span class="p">/&lt;/span>&lt;span class="n">featurename&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="n">VirtualMachinePlatform&lt;/span> &lt;span class="p">/&lt;/span>&lt;span class="n">all&lt;/span> &lt;span class="p">/&lt;/span>&lt;span class="n">norestart&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>之后便可以将 WSL 2 设置为默认的版本：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="n">wsl&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-set-default-version&lt;/span> &lt;span class="n">2&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如果你之前安装过了 WSL 1，可以用命令 &lt;code>wsl -l -v&lt;/code> 查看所有已安装的发行版，并将它的 WSL 版本切换为 2：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="n">wsl&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-set-version&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">distribution&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">versionNumber&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>当然，也可以像我一样直接卸载之前的版本，需要用到 &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/wsl-config#unregister-and-reinstall-a-distribution">unregister&lt;/a>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="n">wsl&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-unregister&lt;/span> &lt;span class="n">Ubuntu&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>之后再在商店中运行 Ubuntu，便会自动下载最新的 20.04 版本并像使用 WSL 1 一样重新设置用户名和密码。&lt;/p>
&lt;h2 id="wsl-2-与-wsl-1-的异同">WSL 2 与 WSL 1 的异同&lt;/h2>
&lt;p>简而言之，WSL 2 是运行在一个“迷你虚拟机”里 Linux 内核中的，因此是兼容 Linux 系统函数调用的。而 WSL 1 是将系统函数调用“翻译”成了 Windows 原生的函数调用。&lt;a href="https://docs.microsoft.com/en-us/windows/wsl/compare-versions">Comparing WSL 2 and WSL 1&lt;/a> 中有详细的表格比较了两个版本的异同。&lt;/p>
&lt;p>对于大多数使用场景，是推荐升级到 WSL 2 的。WSL 2 的构架虽然使用了虚拟机，但是能够完全支持 Linux 系统函数调用，并且利用 Linux ext4 文件系统，可以极大提升 WSL 的文件读写性能。官方说法是诸如 &lt;code>git clone&lt;/code> 和 &lt;code>npm install&lt;/code> 这样的命令性能会提升好几倍。同时，WSL 2 还&lt;strong>支持 Docker&lt;/strong>，之后会介绍如何启用 Docker。&lt;/p>
&lt;h2 id="docker-in-wsl-2">Docker in WSL 2&lt;/h2>
&lt;p>这里选择在 Windows 上安装最新版本的 &lt;a href="https://www.docker.com/products/docker-desktop">Docker Desktop&lt;/a>。下面两个博文详细地介绍了如何安装：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.hanselman.com/blog/HowToSetUpDockerWithinWindowsSystemForLinuxWSL2OnWindows10.aspx">How to set up Docker within Windows System for Linux (WSL2) on Windows 10&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/blog/2020/05/21/wsl-docker-kubernetes-on-the-windows-desktop/">WSL+Docker: Kubernetes on the Windows Desktop&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>安装好之后在 Docker Desktop 的设置里启用 &lt;code>Use the WSL 2 based Engine&lt;/code>：
&lt;img src="https://user-images.githubusercontent.com/5097752/83206777-99866000-a11f-11ea-8555-2dfdf2a539ea.jpg" alt="Docker">&lt;/p>
&lt;p>之后便可以在 WSL 中看到 Docker 的版本。需要注意的是 WSL 中的 Docker 应该只是挂载进去的二进制文件，在 Docker Desktop 退出之后是看不到的，因此需要保持 Docker Desktop 的运行。
&lt;img src="https://user-images.githubusercontent.com/5097752/83206906-f08c3500-a11f-11ea-9615-bc90f4fe2828.jpg" alt="WSL Docker">&lt;/p>
&lt;p>之后就可以在 Windows 或者在 WSL 下面运行 Docker 命令启动容器了。比如下面我简单的几个命令就能 pull 一个 golang 的环境。省去了在 Windows 亦或是 WSL 里配置环境的麻烦。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83216542-84b5c680-a137-11ea-8d45-72857c482bb0.jpg" alt="Screenshot_2020-05-28_22-52-14">&lt;/p>
&lt;h2 id="vs-code-与-wsl-2">VS Code 与 WSL 2&lt;/h2>
&lt;p>此外，还可以利用 Visual Studio 的 &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack">Remote Development&lt;/a> 插件，可以非常方便的用 VS Code 在 Container 环境里进行开发。具体可以参考 &lt;a href="https://code.visualstudio.com/docs/remote/containers">Developing inside a Container&lt;/a>。
&lt;img src="https://code.visualstudio.com/assets/docs/remote/containers/architecture-containers.png" alt="Remote Container">&lt;/p>
&lt;h2 id="未来的-wsl">未来的 WSL&lt;/h2>
&lt;p>WSL 团队在 &lt;a href="https://devblogs.microsoft.com/commandline/the-windows-subsystem-for-linux-build-2020-summary/">博客中&lt;/a> 表示未来还计划支持 GPU 加速以及运行 Linux GUI 程序。
&lt;img src="https://devblogs.microsoft.com/commandline/wp-content/uploads/sites/33/2020/05/WSLGUIAppsNoName-1536x864.png" alt="">&lt;/p>
&lt;p>参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.hanselman.com/blog/HowToSetUpDockerWithinWindowsSystemForLinuxWSL2OnWindows10.aspx">How to set up Docker within Windows System for Linux (WSL2) on Windows 10&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://kubernetes.io/blog/2020/05/21/wsl-docker-kubernetes-on-the-windows-desktop/">WSL+Docker: Kubernetes on the Windows Desktop&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.microsoft.com/en-us/windows/wsl/compare-versions">https://docs.microsoft.com/en-us/windows/wsl/compare-versions&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.microsoft.com/en-us/windows/wsl/wsl2-faq">https://docs.microsoft.com/en-us/windows/wsl/wsl2-faq&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.microsoft.com/en-us/windows/wsl/wsl-config#configuration-options">https://docs.microsoft.com/en-us/windows/wsl/wsl-config#configuration-options&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10#update-to-wsl-2">https://docs.microsoft.com/en-us/windows/wsl/install-win10#update-to-wsl-2&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://dmego.me/2019/12/21/make-wsl2-as-a-productivity-tool">https://dmego.me/2019/12/21/make-wsl2-as-a-productivity-tool&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E5%B7%A5%E5%85%B7/">工具</category><category domain="https://blog.imfing.com/tags/%E5%B7%A5%E5%85%B7/">工具</category></item><item><title>域名和 DNS 二三事</title><link>https://blog.imfing.com/2020/05/domain-dns-server/</link><guid isPermaLink="true">https://blog.imfing.com/2020/05/domain-dns-server/</guid><pubDate>Sat, 16 May 2020 13:39:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>昨天上午又冲动消费了，买了一个新的域名 &lt;a href="https://fuxin.me">fuxin.me&lt;/a>，简短好记。
其实从最开始打算搭网站到现在，一路也换过不同的域名、服务器以及 DNS 提供商，下面来简要谈谈这些。&lt;/p>
&lt;!-- more -->
&lt;h2 id="域名名称">域名名称&lt;/h2>
&lt;p>一开始的我的域名和服务器是通过&lt;a href="https://cloud.tencent.com/act/campus">腾讯云+校园&lt;/a>的活动入坑的。这个活动在 2015 年时是每月 1 元的服务器加送 &lt;code>.cn&lt;/code> 的域名。所以就注册了一个 &lt;code>fingr8.cn&lt;/code> 的域名（现在已弃用），国内的网站服务器还需要非常费劲的备案。后来随着参与活动的国内学生人数变多，这个活动也变得&lt;strong>越来越抠门&lt;/strong>。&lt;/p>
&lt;p>对于域名名称选择，我个人的想法是这样子的：
第一是尽量选择顶级域名 &lt;code>.com&lt;/code>，更常见以及价格更实惠。像 &lt;code>.cn&lt;/code> 这种域名太具有地理标签了，不是非常适合用作个人域名。此外 &lt;code>.me&lt;/code> 也非常适合用于个人页面。一般常见的技术主页还会用 &lt;code>.dev&lt;/code>、&lt;code>.io&lt;/code> 等作为结尾。
第二是域名需简短好记。需要在“短”和“好记”之间达到平衡。像我之前的域名 &lt;code>fingr8.cn&lt;/code>，它虽然短，但域名中混杂了英文 + 数字，在一定程度上不利于其他人的记忆。我现在使用的域名 &lt;a href="https://imfing.com">imfing.com&lt;/a> 的前缀意为 &lt;code>I'm Fing&lt;/code>，一定程度上也算好记。而新购的 &lt;a href="https://fuxin.me">fuxin.me&lt;/a> 就是非常体现个人色彩的一个域名了。
我推荐搭建个人主页的话可以使用 &lt;code>名字 / 昵称 + .com / .me&lt;/code> 这样的组合。&lt;/p>
&lt;h2 id="域名提供商">域名提供商&lt;/h2>
&lt;p>域名提供商简而言之就是卖给你域名的商家。国内的话诸如腾讯云、阿里云都会提供域名的购买和免费的解析服务。&lt;/p>
&lt;p>&lt;strong>国内提供商托管的域名可以不备案么&lt;/strong>？答案是&lt;strong>可以&lt;/strong>，前提是你域名指向的服务器空间不在国内。国内的主机空间只要被解析，则对应的域名必须要备案。为了省去备案的麻烦，通常不会首选国内域名提供商以及主机空间。&lt;/p>
&lt;p>下面是几个我推荐的域名提供商：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.namesilo.com/">NameSilo&lt;/a> - 综合性价比较高，提供免费的 WHOIS 隐私保护。我的域名就是在这里购买的&lt;/li>
&lt;li>&lt;a href="https://www.namecheap.com/">NameCheap&lt;/a> - 使用 GitHub 学生包可以免费注册一年的 &lt;code>.me&lt;/code> 域名&lt;/li>
&lt;li>&lt;a href="https://domains.google/">Google Domains&lt;/a> - Google 爸爸的域名服务，价格较贵，有各种各样花式的结尾&lt;/li>
&lt;li>&lt;a href="https://www.godaddy.com/">GoDaddy&lt;/a> - 老牌域名商，续费感觉比较坑，不知道现在怎么样了&lt;/li>
&lt;/ul>
&lt;p>域名是可以在不同的提供商之间&lt;strong>转入转出&lt;/strong>的，因此如果你觉得续费坑的话就可以将域名转出到其它提供商。&lt;/p>
&lt;h2 id="dns-解析">DNS 解析&lt;/h2>
&lt;p>当在访问一个域名的时候，浏览器是不知道这个域名指向的服务器 IP 地址的，因此就需要 DNS 服务来解析。一部分的域名提供商都会提供域名解析服务，比如腾讯就是用自家的 DNSPod。
这里肯定要推荐 &lt;a href="https://www.cloudflare.com/">Cloudflare&lt;/a>，不仅提供了&lt;strong>免费的&lt;/strong>域名解析（DNS）和内容分发网络（CDN），还自带安全保护、HTTPS 等。
在购买了域名之后，只需按照 Cloudflare 的提示将 NS（Name Server）的记录指向 Cloudflare 即可。&lt;/p>
&lt;p>在搭建个人网站时，一搬会选择搭建&lt;strong>静态网站&lt;/strong>，即不需要云服务器来存取和提供数据。因此可以利用 &lt;a href="https://netlify.com">Netlify&lt;/a> 或 GitHub Pages 来托管网站的网页文件，再在 DNS 服务商里添加 CNAME 记录（就是一个 Alias），指向对应的网址即可。比如我现在的 &lt;a href="https://fuxin.me">fuxin.me&lt;/a> 其实是指向了 &lt;a href="https://imfing.com">imfing.com&lt;/a> 的。静态网站还可以利用 Cloudflare 的 CDN 来加速用户的访问。
如果你有服务器的话则需要添加 A 记录，指向一个确定的 IP 地址。这个动画生动解释了 DNS 是如何工作的：&lt;a href="https://howdns.works/">How DNS works&lt;/a>。&lt;/p>
&lt;h2 id="云服务器提供商">云服务器提供商&lt;/h2>
&lt;p>下面列举一些常用的云服务器提供商，想搭建梯子或者简单 Web 应用的可以参考：&lt;/p>
&lt;ul>
&lt;li>VPS 虚拟主机（适用小型网站及应用）
&lt;ul>
&lt;li>&lt;a href="https://www.digitalocean.com/">Digital Ocean&lt;/a> - GitHub 学生优惠 50 刀。&lt;/li>
&lt;li>&lt;a href="https://aws.amazon.com/lightsail/">Amazon Lightsail&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.linode.com/">Linode&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.vultr.com/">Vultr&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>云计算主机
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/">Amazon AWS&lt;/a> - 免费试用，也可用 GitHub 学生优惠&lt;/li>
&lt;li>&lt;a href="https://azure.microsoft.com/en-us/">Microsoft Azure&lt;/a> - 免费试用，也可用 GitHub 学生优惠&lt;/li>
&lt;li>&lt;a href="https://cloud.google.com/">Google Cloud&lt;/a> - 300$ 的免费试用&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BD%91%E7%AB%99/">网站</category><category domain="https://blog.imfing.com/tags/web/">Web</category></item><item><title>中英文混排的“Social Distancing”</title><link>https://blog.imfing.com/2020/05/chinese-typesetting-space/</link><guid isPermaLink="true">https://blog.imfing.com/2020/05/chinese-typesetting-space/</guid><pubDate>Fri, 15 May 2020 23:39:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>我是从去年开始才注意到中英文混合排版时候的空格问题的。当时正在完成我的毕业论文，就发现中英文之间是需要一定的距离才能够更方便阅读者的阅读。加上最近疫情期间北美强调“Social Distancing”这个概念，遂借来描述一下中英文混排中需要注意添加空格的习惯。&lt;/p>
&lt;!-- more -->
&lt;h2 id="盘古之白">盘古之白&lt;/h2>
&lt;p>之前有在少数派看到 「&lt;a href="https://sspai.com/post/33549">为什么你们就是不能加个空格呢？&lt;/a>」，其中介绍到了 &lt;a href="https://github.com/vinta/pangu.js">pangu.js&lt;/a> 的项目，它提供了 Chrome 插件自动在网页中英文之间插入空格，并且也提供了 Go、JavaScript、Python 等语言下的模块帮助自动格式化中英文片段。&lt;/p>
&lt;blockquote>
&lt;p>漢學家稱這個空白字元為「盤古之白」，因為它劈開了全形字和半形字之間的混沌。另有研究顯示，打字的時候不喜歡在中文和英文之間加空格的人，感情路都走得很辛苦，有七成的比例會在 34 歲的時候跟自己不愛的人結婚，而其餘三成的人最後只能把遺產留給自己的貓。畢竟愛情跟書寫都需要適時地留白。&lt;/p>
&lt;/blockquote>
&lt;h2 id="空格规则">空格规则&lt;/h2>
&lt;p>下面表格总结了在哪些情况下需要使用到空格。当然，这个规则也只是作为参考，比如超链接空格等规则是有待商榷的，以习惯为准。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>搭配&lt;/th>
&lt;th>规则&lt;/th>
&lt;th>示例&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>中文与英文&lt;/td>
&lt;td>需空格&lt;/td>
&lt;td>我使用 Google 搜索引擎&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>中文与数字&lt;/td>
&lt;td>需空格&lt;/td>
&lt;td>今天 20 摄氏度&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>英文与数字&lt;/td>
&lt;td>需空格&lt;/td>
&lt;td>最近更新了 iOS 13.5 版本的系统&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>数字与单位&lt;/td>
&lt;td>需空格&lt;/td>
&lt;td>香蕉 1.99 美元一磅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>数字与百分比&lt;/td>
&lt;td>无需空格&lt;/td>
&lt;td>谷歌浏览器市场占比高达 58.4% 了&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>日期&lt;/td>
&lt;td>无需空格&lt;/td>
&lt;td>今天是2020年1月1日&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>全角标点与字符&lt;/td>
&lt;td>无需空格&lt;/td>
&lt;td>喂！你在干什么？！&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>省略号&lt;/td>
&lt;td>后需空格&lt;/td>
&lt;td>这…… 有问题吗&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>全角括号&lt;/td>
&lt;td>无需空格&lt;/td>
&lt;td>全角括号（示例），禁止(半角无空格)或者 (半角加空格) 写法&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>超链接&lt;/td>
&lt;td>需空格&lt;/td>
&lt;td>参考 &lt;a href="https://www.google.com">这里&lt;/a> 的示例&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="网页工具">网页工具&lt;/h2>
&lt;p>道理都懂，但是之前书写的时候并没有注意到这些。借助于 &lt;a href="https://github.com/vinta/pangu.js">pangu.js&lt;/a>，我写了一个简单的 HTML 网页，用来帮我给之前文章自动化添加空格，并且复制到剪贴板上。
当然，&lt;a href="https://github.com/vinta/pangu.js">pangu.js&lt;/a> 在格式化 Markdown 书写的文字时候，会破坏 Markdown 的一些标记，譬如 &lt;code>**加粗**&lt;/code> 会变成 &lt;code>** 加粗 **&lt;/code>。因此还是平时打字的时候养成随手加空格的习惯比较重要。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="cp">&amp;lt;!DOCTYPE html&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">html&lt;/span> &lt;span class="na">lang&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;en&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">meta&lt;/span> &lt;span class="na">charset&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;UTF-8&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">meta&lt;/span> &lt;span class="na">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;viewport&amp;#34;&lt;/span> &lt;span class="na">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">title&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Text Spacing Tool&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">title&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;https://cdn.jsdelivr.net/npm/pangu@4.0.7/dist/browser/pangu.min.js&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">link&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;https://unpkg.com/@primer/css/dist/primer.css&amp;#34;&lt;/span> &lt;span class="na">rel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;stylesheet&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">style&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">.&lt;/span>&lt;span class="nc">input-container&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">margin-top&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="kt">rem&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">display&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">flex&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">align-items&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">center&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">flex-direction&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">column&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">.&lt;/span>&lt;span class="nc">form-control&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">min-width&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">30&lt;/span>&lt;span class="kt">rem&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">min-height&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">15&lt;/span>&lt;span class="kt">rem&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">margin-bottom&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="kt">rem&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">style&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span> &lt;span class="na">style&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-align: center; margin-top: 3rem;&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Text Spacing Tool&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;input-container&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">label&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Before&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">label&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">textarea&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;form-control&amp;#34;&lt;/span> &lt;span class="na">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;input-textarea&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">textarea&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">label&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>After&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">label&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">textarea&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;form-control&amp;#34;&lt;/span> &lt;span class="na">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;output-textarea&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">textarea&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">inputText&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;input-textarea&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">outputText&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;output-textarea&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nb">window&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;load&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">inputText&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">focus&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="p">})&lt;/span>
&lt;span class="nx">inputText&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;input&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">outputText&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">pangu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">spacing&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">inputText&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">outputText&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">select&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">execCommand&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;copy&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">inputText&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">select&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="p">});&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">html&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.zhihu.com/question/19587406/answer/12298128">中英文混排时中文与英文之间是否要有空格？- 知乎&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/zizhengwu/daft-auto-spacing">为什么我就是能这样娴熟地加上空格呢？&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.biliui.com/Home/Info/detail/id/597.html">如何正确的利用空格&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.thetype.com/2015/05/9230/">用印刷品的态度来做 Web 排版&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E8%AE%BE%E8%AE%A1/">设计</category><category domain="https://blog.imfing.com/tags/web/">Web</category><category domain="https://blog.imfing.com/tags/design/">Design</category></item><item><title>PyTorch 简单分类模型示例</title><link>https://blog.imfing.com/2020/05/pytorch-classifier-example/</link><guid isPermaLink="true">https://blog.imfing.com/2020/05/pytorch-classifier-example/</guid><pubDate>Sat, 09 May 2020 18:09:00 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>之前实习的时候 &lt;a href="https://pytorch.org/">PyTorch&lt;/a> 用的非常多。PyTorch 的优势是非常灵活，语法非常 &lt;em>pythonic&lt;/em>，很接近 &lt;a href="https://numpy.org/">NumPy&lt;/a>。动态图的特性也让调试变得比 &lt;a href="https://www.tensorflow.org/">TensorFlow&lt;/a> 轻松许多。
不过对于作业 / Kaggle 中相对简单的分类任务，&lt;a href="https://keras.io/">Keras&lt;/a> 还是我的第一选择：开箱即用，并且和 &lt;a href="https://scikit-learn.org/stable/">scikit-learn&lt;/a> 有着类似的接口，因此只需少量的代码即可实现模型的训练和迭代。而在 PyTorch 中，很多相似的功能都需要自己手动多写几行代码，对我这个懒人不太友好。
在帮朋友解决问题的时候，发现对 PyTorch 有些生疏，而且官方的例子里并没有具体的例子讲述如何用 NumPy 的数据作为数据集，于是这里便记录一下，方便以后参考。&lt;/p>
&lt;h2 id="导入库">导入库&lt;/h2>
&lt;p>第一步是需要导入各种库&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch.nn&lt;/span> &lt;span class="kn">as&lt;/span> &lt;span class="nn">nn&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch.nn.functional&lt;/span> &lt;span class="kn">as&lt;/span> &lt;span class="nn">F&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch.optim&lt;/span> &lt;span class="kn">as&lt;/span> &lt;span class="nn">optim&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="数据集加载">数据集加载&lt;/h2>
&lt;p>比较 tricky 的就是构建 PyTorch 中的 Dataset 和 DataLoader 了。虽然非常好用，比如可以&lt;a href="https://pytorch.org/tutorials/beginner/data_loading_tutorial.html">自定义实现 DataLoader、Transform 等&lt;/a>，但刚上手的时候容易出错。
在用 &lt;a href="https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html">train_test_split&lt;/a> 将 NumPy 数据集划分好后，第一步需要将它们转换为 PyTorch 中的 Tensor，之后用 &lt;code>TensorDataset&lt;/code> 创建 &lt;code>Dataset&lt;/code> 对象，最后用 &lt;code>DataLoader&lt;/code> 创建数据集加载器，之后在训练中会需要用到。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="c1"># Construct PyTorch dataset&lt;/span>
&lt;span class="n">BATCH_SIZE&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">4&lt;/span>
&lt;span class="n">X_train_tensor&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_numpy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">X_train&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">y_train_tensor&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_numpy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">y_train&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">LongTensor&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">X_train_tensor&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shape&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y_train_tensor&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shape&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">train_dataset&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">utils&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TensorDataset&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">X_train_tensor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y_train_tensor&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">train_loader&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">utils&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">DataLoader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">train_dataset&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">batch_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">BATCH_SIZE&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">shuffle&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">num_workers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="构建模型">构建模型&lt;/h2>
&lt;p>下面定义一个简单的 &lt;code>ConvNet&lt;/code> 类，里面实现了一个两层的 CNN。在 &lt;code>__init__()&lt;/code> 里定义一些层并实现了 &lt;code>forward()&lt;/code> 前向传播。因为 PyTorch 是动态图计算，因此不需要手动定义反向传播的计算过程。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConvNet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Module&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="nb">super&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ConvNet&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">conv1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Conv1d&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">in_channels&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">out_channels&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">kernel_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">padding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">conv2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Conv1d&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">in_channels&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">out_channels&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">32&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">kernel_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">padding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">MaxPool1d&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fc1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Linear&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">32&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="mi">256&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">def&lt;/span> &lt;span class="nf">forward&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">relu&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">conv1&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">relu&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">conv2&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">view&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">32&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="mi">256&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fc1&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">output&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>之后可以用 &lt;a href="https://github.com/sksq96/pytorch-summary">torch-summary&lt;/a> 或 &lt;a href="https://github.com/microsoft/tensorwatch">tensorwatch&lt;/a> 输出模型每一层的参数：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConvNet&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="kn">from&lt;/span> &lt;span class="nn">torchsummary&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">summary&lt;/span>
&lt;span class="n">summary&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">input_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可以得到如下的输出：&lt;/p>
&lt;pre>&lt;code>----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv1d-1 [-1, 16, 1024] 112
MaxPool1d-2 [-1, 16, 512] 0
Conv1d-3 [-1, 32, 512] 1,568
MaxPool1d-4 [-1, 32, 256] 0
Linear-5 [-1, 10] 81,930
================================================================
Total params: 83,610
Trainable params: 83,610
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.01
Forward/backward pass size (MB): 0.38
Params size (MB): 0.32
Estimated Total Size (MB): 0.70
----------------------------------------------------------------
&lt;/code>&lt;/pre>&lt;p>因为是多分类任务，所以使用了 &lt;a href="https://pytorch.org/docs/stable/nn.html#crossentropyloss">torch.nn.CrossEntropyLoss()&lt;/a>，需要注意的是其中包含了 &lt;code>Softmax&lt;/code> 因此我们不需要在网络中手动实现。优化器选择了 &lt;a href="https://pytorch.org/docs/stable/optim.html#torch.optim.Adam">torch.optim.Adam&lt;/a>，并设置学习速率为常见的 &lt;code>0.001&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="c1"># Optimizer and criterion&lt;/span>
&lt;span class="n">criterion&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">CrossEntropyLoss&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="n">optimizer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">optim&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Adam&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parameters&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">lr&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.001&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="训练">训练&lt;/h2>
&lt;p>最后我们就能实现模型训练过程，最外面是循环 &lt;code>EPOCHS&lt;/code> 次，每个 &lt;code>epoch&lt;/code> 中定义累积的 &lt;code>running_loss&lt;/code>，前面定义的 &lt;code>train_loader&lt;/code> 一次能够给出 &lt;code>BATCH_SIZE&lt;/code> 的训练样本，在内循环中就能够得到训练数据并进行前向计算、计算 loss、计算梯度、优化器迭代更新整个模型的参数。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="cp"># Training
&lt;/span>&lt;span class="cp">&lt;/span>
&lt;span class="n">EPOCHS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">epoch&lt;/span> &lt;span class="n">in&lt;/span> &lt;span class="n">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">EPOCHS&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span>
&lt;span class="n">model&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">train&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="n">running_loss&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">0.0&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">batch_idx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span> &lt;span class="n">in&lt;/span> &lt;span class="n">enumerate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">train_loader&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span>
&lt;span class="cp"># get inputs and labels
&lt;/span>&lt;span class="cp">&lt;/span> &lt;span class="n">inputs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">labels&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data&lt;/span>
&lt;span class="cp"># zero the parameter gradients
&lt;/span>&lt;span class="cp">&lt;/span> &lt;span class="n">optimizer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">zero_grad&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="cp"># forward + backward + optimize
&lt;/span>&lt;span class="cp">&lt;/span> &lt;span class="n">outputs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">model&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">inputs&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">loss&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">criterion&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">outputs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">labels&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">loss&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">backward&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="n">optimizer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">step&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="cp"># print statistics
&lt;/span>&lt;span class="cp">&lt;/span> &lt;span class="n">running_loss&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">loss&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">batch_idx&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">1000&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">:&lt;/span>
&lt;span class="n">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="err">&amp;#39;&lt;/span>&lt;span class="n">Epoch&lt;/span> &lt;span class="p">[{}&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="p">{}],&lt;/span> &lt;span class="n">Step&lt;/span> &lt;span class="p">[{}&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="p">{}],&lt;/span> &lt;span class="nl">Loss&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mf">.4f&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="err">&amp;#39;&lt;/span>
&lt;span class="p">.&lt;/span>&lt;span class="n">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">epoch&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">EPOCHS&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">batch_idx&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">train_loader&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">loss&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">()))&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>输出：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-text" data-lang="text">Epoch [1/1], Step [1000/6750], Loss: 2.1528
Epoch [1/1], Step [2000/6750], Loss: 1.2026
Epoch [1/1], Step [3000/6750], Loss: 1.7215
Epoch [1/1], Step [4000/6750], Loss: 1.8674
Epoch [1/1], Step [5000/6750], Loss: 1.6417
Epoch [1/1], Step [6000/6750], Loss: 1.1279
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="gpu-训练">GPU 训练&lt;/h2>
&lt;p>如果要用 GPU 训练，需要指定 device，并且将模型以及每个 Batch 的数据都传到 GPU device 中，在上面的基础上改动或添加以下的代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="n">device&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">device&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;cuda:0&amp;#34;&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cuda&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_available&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;cpu&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">device&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">inputs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">labels&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">device&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">device&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;a href="https://pytorch.org/tutorials/recipes/recipes_index.html#">PyTorch Recipes&lt;/a> 里给出了更多常用的例子，包括 &lt;a href="https://pytorch.org/tutorials/recipes/recipes/saving_and_loading_models_for_inference.html">保存 / 读取模型&lt;/a>、&lt;a href="https://pytorch.org/tutorials/recipes/recipes/tensorboard_with_pytorch.html">使用 Tensorboard&lt;/a> 等，可以进一步参考。&lt;/p>
&lt;h2 id="参考链接">参考链接&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html">https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.kaggle.com/sdelecourt/cnn-with-pytorch-for-mnist">https://www.kaggle.com/sdelecourt/cnn-with-pytorch-for-mnist&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://stanford.edu/~shervine/blog/pytorch-how-to-generate-data-parallel">https://stanford.edu/~shervine/blog/pytorch-how-to-generate-data-parallel&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/deep-learning/">Deep Learning</category></item><item><title>CUDA C++ 实现图像处理</title><link>https://blog.imfing.com/2020/05/cuda-image-processing/</link><guid isPermaLink="true">https://blog.imfing.com/2020/05/cuda-image-processing/</guid><pubDate>Fri, 08 May 2020 20:30:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>最近课程里面有用到 &lt;a href="https://developer.nvidia.com/cuda-toolkit">NVIDIA CUDA&lt;/a> 框架进行并行编程，实现了一些非常基本的图像处理的操作。
使用 CUDA 实现的并行加速能够极大的提升图像处理的效率，这也是为什么近几年的深度学习框架都要依托于 CUDA 进行计算加速。CUDA 本质上是 C/C++ 的拓展，因此对 C/C++ 熟悉的话上手也会很快。&lt;/p>
&lt;h2 id="读取保存图像">读取保存图像&lt;/h2>
&lt;p>C++ 中读取图像和保存图像不像 Python 那样方便简单。一开始我想用 &lt;a href="https://opencv.org/">OpenCV&lt;/a> 来读取/写入图像，但对于我写一个小的示例程序来说可谓麻烦了些，涉及到编译的问题。
后来看到了 &lt;a href="https://github.com/lvandeve/lodepng">lodepng&lt;/a> 这个小巧方便的库，支持 C 和 C++，使用时只需要引用一个头文件就可以。读取和保存文件只需要如下的几行代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;lodepng.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;span class="cp">&lt;/span>
&lt;span class="c1">// Read image
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">image&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">unsigned&lt;/span> &lt;span class="n">error&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">lodepng&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">image&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">input_file&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c1">// Save image
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">out_image&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">image&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="mi">255&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">error&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">lodepng&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">output_file&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">out_image&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>加载好的图片像素有 4 个通道，按照 &lt;code>RGBARGBA...&lt;/code> 的顺序在一维数组中排列的，其中 &lt;code>A&lt;/code> 表示的是透明度，&lt;code>0&lt;/code> 表示全透明，&lt;code>255&lt;/code> 则完全显示当前像素。&lt;/p>
&lt;h2 id="cuda-kernel">CUDA Kernel&lt;/h2>
&lt;p>CUDA 并行部分的代码需要单独用 &lt;code>__global__&lt;/code> 关键词进行修饰，这一部分的代码会在 device 上执行。NVIDIA 自家的博文 &lt;a href="https://devblogs.nvidia.com/even-easier-introduction-cuda/">An Even Easier Introduction to CUDA&lt;/a> 就通俗易懂的介绍了 CUDA 的一些基本概念。&lt;a href="https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#abstract">CUDA C++ Programming Guide&lt;/a> 有更为全面和详细的例子与参考。&lt;/p>
&lt;p>下面是我实现的 RGB 转 Grayscale 的 Kernel 并行代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="c1">// CUDA Kernel
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">__global__&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">RGB2GrayKernel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">input_image&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">output_image&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// input_image size: width*height*Channels
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// output_image size: width*height
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">row&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">blockIdx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">blockDim&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">threadIdx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">col&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">blockIdx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">blockDim&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">threadIdx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">row&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">col&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// Offset in Grayscale image
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">offset&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">row&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">width&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">col&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="c1">// Get RGB values
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">input_image&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">offset&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">CHANNELS&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="n">g&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">input_image&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">offset&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">CHANNELS&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">input_image&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">offset&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">CHANNELS&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="c1">// Convert to grayscale
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">output_image&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">offset&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">0.2126f&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">r&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mf">0.7152f&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">g&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mf">0.0722f&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>通过 CUDA Kernel 全局变量 &lt;code>blockDim&lt;/code>、&lt;code>blockIdx&lt;/code> 以及 &lt;code>threadIdx&lt;/code>，可以得到当前运行 &lt;code>Kernel&lt;/code> 的线程的编号，这样能够方便的访问部分数据。&lt;/p>
&lt;p>在 CPU(host) 部分的 &lt;code>main&lt;/code> 函数中，我们需要将图片数据拷贝到 device 上的 global memory 中，需要用到 &lt;code>cudaMalloc&lt;/code> 以及 &lt;code>cudaMemcpy&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="c1">// Allocate memory for CUDA device
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">size_t&lt;/span> &lt;span class="n">mem_size&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">width&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">height&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="k">sizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">dev_input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">dev_output&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">cudaMalloc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">dev_input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mem_size&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">CHANNELS&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">cudaMemcpy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dev_input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">image&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">mem_size&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">CHANNELS&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cudaMemcpyHostToDevice&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">cudaMalloc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">dev_output&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mem_size&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在 &lt;code>main&lt;/code> 中调用 CUDA Kernel 的时候，需要用到尖括号的表达。下面在一个 Block 里我用了 &lt;code>16x16&lt;/code> 个线程，然后对于 &lt;code>width x height&lt;/code> 的图像，可以得到所需的 Grid 的 Size，使得整个图片能被所有的线程都处理到。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="cp">#define BLOCK_SIZE 16
&lt;/span>&lt;span class="cp">&lt;/span>
&lt;span class="c1">// Invoke CUDA kernel
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">dim3&lt;/span> &lt;span class="nf">dimBlock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BLOCK_SIZE&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BLOCK_SIZE&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">dim3&lt;/span> &lt;span class="nf">dimGrid&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">width&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">dimBlock&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">height&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">dimBlock&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">RGB2GrayKernel&lt;/span>&lt;span class="o">&amp;lt;&amp;lt;&amp;lt;&lt;/span>&lt;span class="n">dimGrid&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dimBlock&lt;/span>&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dev_input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dev_output&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在调用完 Kernel 之后，我们需要将 CUDA device 内存中的数据拷贝到 CPU 上 &lt;code>main&lt;/code> 程序里，这样可以在后续使用 &lt;code>lodepng&lt;/code> 将图片保存。最后不能忘记用 &lt;code>cudaFree&lt;/code> 将分配的 GPU 显存给释放。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="c1">// Copy output from device
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">image_y&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">width&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">height&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="n">cudaMemcpy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">image_y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dev_output&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mem_size&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cudaMemcpyDeviceToHost&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">cudaFree&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dev_input&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">cudaFree&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dev_output&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="timing">Timing&lt;/h2>
&lt;p>使用 CUDA C++ 的接口可以方便的计算运行时间，无论是否是 CUDA Kernel 代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;cuda_runtime.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;span class="cp">&lt;/span>
&lt;span class="c1">// Timing
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">cudaEvent_t&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stop&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">cudaEventCreate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">cudaEventCreate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">stop&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">cudaEventRecord&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c1">//
&lt;/span>&lt;span class="c1">// DO SOMETHING
&lt;/span>&lt;span class="c1">// WITH OR WITHOUT CUDA
&lt;/span>&lt;span class="c1">//
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="c1">// Get elapsed time in ms
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">cudaEventRecord&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stop&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">cudaEventSynchronize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stop&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kt">float&lt;/span> &lt;span class="n">milliseconds&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">cudaEventElapsedTime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">milliseconds&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stop&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="编译">编译&lt;/h2>
&lt;p>编译需要用到 CUDA 库中的 &lt;a href="https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html">NVIDIA CUDA Compiler (NVCC)&lt;/a>，在编译的时候注意 GPU 的架构，需要指定 &lt;code>-arch=compute_35&lt;/code> 参数，具体的列表可以在 &lt;a href="https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#gpu-feature-list">GPU Feature List&lt;/a> 里查看。
下面我写了一个非常简单的 makefile 文件，写了一个 &lt;code>rgb2gray_cuda&lt;/code> 的规则来使用系统中的 CUDA 10 下的 &lt;code>nvcc&lt;/code> 来编译我的 &lt;code>rgb2gray.cu&lt;/code> 源码。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-makefile" data-lang="makefile">&lt;span class="nv">NVCC&lt;/span> &lt;span class="o">?=&lt;/span> /usr/local/cuda-10.1/bin/nvcc
&lt;span class="nv">NVCCFLAGS&lt;/span> &lt;span class="o">?=&lt;/span> -arch&lt;span class="o">=&lt;/span>compute_35
&lt;span class="nf">rgb2gray_cuda&lt;/span>&lt;span class="o">:&lt;/span>
&lt;span class="k">$(&lt;/span>NVCC&lt;span class="k">)&lt;/span> &lt;span class="k">$(&lt;/span>NVCCFLAGS&lt;span class="k">)&lt;/span> -o rgb2gray_cuda.out rgb2gray.cu lodepng.cpp
&lt;span class="nf">clean&lt;/span>&lt;span class="o">:&lt;/span>
rm -f *.out
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>编译时只需要执行 &lt;code>$ make rgb2gray_cuda&lt;/code>，即可生成 &lt;code>rgb2gray_cuda.out&lt;/code> 可执行文件。可以通过 &lt;code>$ make clean&lt;/code> 清除所有编译产生的 &lt;code>.out&lt;/code> 文件。&lt;/p>
&lt;h2 id="histogram-与-atomicadd">Histogram 与 AtomicAdd&lt;/h2>
&lt;p>在做直方图统计的时候，多个线程会同时访问直方图结果的数列，为了避免 race condition，需要用到 CUDA 的 &lt;code>atomicAdd&lt;/code> 函数：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="c1">// CUDA Kernel
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">__global__&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">HistogramKernel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">d_image&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">d_histo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// input_image size: width*height
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">row&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">blockIdx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">blockDim&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">threadIdx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">col&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">blockIdx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">blockDim&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">threadIdx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">row&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">col&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">row&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">width&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">col&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">atomicAdd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">d_histo&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">d_image&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">]],&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>进一步的，可以使用 shared memory 来提升性能。&lt;a href="https://devblogs.nvidia.com/using-shared-memory-cuda-cc/">Using Shared Memory in CUDA C/C++&lt;/a> 中给出了一些介绍及基本的例子。&lt;/p>
&lt;p>附上完整 &lt;code>rgb2gray.cu&lt;/code> 代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt"> 10
&lt;/span>&lt;span class="lnt"> 11
&lt;/span>&lt;span class="lnt"> 12
&lt;/span>&lt;span class="lnt"> 13
&lt;/span>&lt;span class="lnt"> 14
&lt;/span>&lt;span class="lnt"> 15
&lt;/span>&lt;span class="lnt"> 16
&lt;/span>&lt;span class="lnt"> 17
&lt;/span>&lt;span class="lnt"> 18
&lt;/span>&lt;span class="lnt"> 19
&lt;/span>&lt;span class="lnt"> 20
&lt;/span>&lt;span class="lnt"> 21
&lt;/span>&lt;span class="lnt"> 22
&lt;/span>&lt;span class="lnt"> 23
&lt;/span>&lt;span class="lnt"> 24
&lt;/span>&lt;span class="lnt"> 25
&lt;/span>&lt;span class="lnt"> 26
&lt;/span>&lt;span class="lnt"> 27
&lt;/span>&lt;span class="lnt"> 28
&lt;/span>&lt;span class="lnt"> 29
&lt;/span>&lt;span class="lnt"> 30
&lt;/span>&lt;span class="lnt"> 31
&lt;/span>&lt;span class="lnt"> 32
&lt;/span>&lt;span class="lnt"> 33
&lt;/span>&lt;span class="lnt"> 34
&lt;/span>&lt;span class="lnt"> 35
&lt;/span>&lt;span class="lnt"> 36
&lt;/span>&lt;span class="lnt"> 37
&lt;/span>&lt;span class="lnt"> 38
&lt;/span>&lt;span class="lnt"> 39
&lt;/span>&lt;span class="lnt"> 40
&lt;/span>&lt;span class="lnt"> 41
&lt;/span>&lt;span class="lnt"> 42
&lt;/span>&lt;span class="lnt"> 43
&lt;/span>&lt;span class="lnt"> 44
&lt;/span>&lt;span class="lnt"> 45
&lt;/span>&lt;span class="lnt"> 46
&lt;/span>&lt;span class="lnt"> 47
&lt;/span>&lt;span class="lnt"> 48
&lt;/span>&lt;span class="lnt"> 49
&lt;/span>&lt;span class="lnt"> 50
&lt;/span>&lt;span class="lnt"> 51
&lt;/span>&lt;span class="lnt"> 52
&lt;/span>&lt;span class="lnt"> 53
&lt;/span>&lt;span class="lnt"> 54
&lt;/span>&lt;span class="lnt"> 55
&lt;/span>&lt;span class="lnt"> 56
&lt;/span>&lt;span class="lnt"> 57
&lt;/span>&lt;span class="lnt"> 58
&lt;/span>&lt;span class="lnt"> 59
&lt;/span>&lt;span class="lnt"> 60
&lt;/span>&lt;span class="lnt"> 61
&lt;/span>&lt;span class="lnt"> 62
&lt;/span>&lt;span class="lnt"> 63
&lt;/span>&lt;span class="lnt"> 64
&lt;/span>&lt;span class="lnt"> 65
&lt;/span>&lt;span class="lnt"> 66
&lt;/span>&lt;span class="lnt"> 67
&lt;/span>&lt;span class="lnt"> 68
&lt;/span>&lt;span class="lnt"> 69
&lt;/span>&lt;span class="lnt"> 70
&lt;/span>&lt;span class="lnt"> 71
&lt;/span>&lt;span class="lnt"> 72
&lt;/span>&lt;span class="lnt"> 73
&lt;/span>&lt;span class="lnt"> 74
&lt;/span>&lt;span class="lnt"> 75
&lt;/span>&lt;span class="lnt"> 76
&lt;/span>&lt;span class="lnt"> 77
&lt;/span>&lt;span class="lnt"> 78
&lt;/span>&lt;span class="lnt"> 79
&lt;/span>&lt;span class="lnt"> 80
&lt;/span>&lt;span class="lnt"> 81
&lt;/span>&lt;span class="lnt"> 82
&lt;/span>&lt;span class="lnt"> 83
&lt;/span>&lt;span class="lnt"> 84
&lt;/span>&lt;span class="lnt"> 85
&lt;/span>&lt;span class="lnt"> 86
&lt;/span>&lt;span class="lnt"> 87
&lt;/span>&lt;span class="lnt"> 88
&lt;/span>&lt;span class="lnt"> 89
&lt;/span>&lt;span class="lnt"> 90
&lt;/span>&lt;span class="lnt"> 91
&lt;/span>&lt;span class="lnt"> 92
&lt;/span>&lt;span class="lnt"> 93
&lt;/span>&lt;span class="lnt"> 94
&lt;/span>&lt;span class="lnt"> 95
&lt;/span>&lt;span class="lnt"> 96
&lt;/span>&lt;span class="lnt"> 97
&lt;/span>&lt;span class="lnt"> 98
&lt;/span>&lt;span class="lnt"> 99
&lt;/span>&lt;span class="lnt">100
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="cm">/*
&lt;/span>&lt;span class="cm"> * Convert RGB image to grayscale
&lt;/span>&lt;span class="cm"> *
&lt;/span>&lt;span class="cm"> * Load image with lodepng (https://github.com/lvandeve/lodepng)
&lt;/span>&lt;span class="cm"> *
&lt;/span>&lt;span class="cm"> * Sample image: lena.png
&lt;/span>&lt;span class="cm"> *
&lt;/span>&lt;span class="cm"> * Y = 0.2126*R + 0.7152*G + 0.0722*B
&lt;/span>&lt;span class="cm"> *
&lt;/span>&lt;span class="cm"> * */&lt;/span>
&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;string&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;span class="cp">&lt;/span>
&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;cuda_runtime.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;lodepng.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;span class="cp">&lt;/span>
&lt;span class="cp">#define CHANNELS 4 &lt;/span>&lt;span class="c1">// RGBA in PNG
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="cp">#define BLOCK_SIZE 16 &lt;/span>&lt;span class="c1">// Thread block size
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="c1">// CUDA Kernel
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">__global__&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">RGB2GrayKernel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">input_image&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">output_image&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// input_image size: width*height*Channels
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="c1">// output_image size: width*height
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">row&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">blockIdx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">blockDim&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">threadIdx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">col&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">blockIdx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">blockDim&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">threadIdx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">row&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">col&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// Offset in Grayscale image
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">offset&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">row&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">width&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">col&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="c1">// Get RGB values
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">input_image&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">offset&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">CHANNELS&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="n">g&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">input_image&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">offset&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">CHANNELS&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">input_image&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">offset&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">CHANNELS&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="c1">// Convert to grayscale
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">output_image&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">offset&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">0.2126f&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">r&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mf">0.7152f&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">g&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mf">0.0722f&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="cm">/* Main */&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">argc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">argv&lt;/span>&lt;span class="p">[])&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">const&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">input_file&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">argc&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="n">argv&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="s">&amp;#34;lena.png&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="c1">// Variables
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">image&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="c1">// Load image
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kt">unsigned&lt;/span> &lt;span class="n">error&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">lodepng&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">image&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">input_file&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">width&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34; x &amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">height&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="c1">// Allocate memory for CUDA device
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">size_t&lt;/span> &lt;span class="n">mem_size&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">width&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">height&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="k">sizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">dev_input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">dev_output&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">cudaMalloc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">dev_input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mem_size&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">CHANNELS&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">cudaMemcpy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dev_input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">image&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">mem_size&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">CHANNELS&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cudaMemcpyHostToDevice&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">cudaMalloc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">dev_output&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mem_size&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c1">// Invoke CUDA kernel
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">dim3&lt;/span> &lt;span class="n">dimBlock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BLOCK_SIZE&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BLOCK_SIZE&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">dim3&lt;/span> &lt;span class="n">dimGrid&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">width&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">dimBlock&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">height&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">dimBlock&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">RGB2GrayKernel&lt;/span>&lt;span class="o">&amp;lt;&amp;lt;&amp;lt;&lt;/span>&lt;span class="n">dimGrid&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dimBlock&lt;/span>&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dev_input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dev_output&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c1">// Copy output from device
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">image_y&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">width&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">height&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="n">cudaMemcpy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">image_y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dev_output&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mem_size&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cudaMemcpyDeviceToHost&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">cudaFree&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dev_input&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">cudaFree&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dev_output&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c1">// Prepare for output
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">out_image&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">image&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="mi">255&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">size_t&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">height&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="n">size_t&lt;/span> &lt;span class="n">offset&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">CHANNELS&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">out_image&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">offset&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">out_image&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">offset&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">out_image&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">offset&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">image_y&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="c1">// Save processed image
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">output_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">input_file&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">output_file&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="s">&amp;#34;.gray.png&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">error&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">lodepng&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">output_file&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">out_image&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">delete&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">image_y&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>部分参考链接：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="http://cuda-programming.blogspot.com/2013/03/computing-histogram-on-cuda-cuda-code_8.html">http://cuda-programming.blogspot.com/2013/03/computing-histogram-on-cuda-cuda-code_8.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://madsravn.dk/posts/simple-image-processing-with-cuda">http://madsravn.dk/posts/simple-image-processing-with-cuda&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://stackoverflow.com/questions/2392250/understanding-cuda-grid-dimensions-block-dimensions-and-threads-organization-s">https://stackoverflow.com/questions/2392250/understanding-cuda-grid-dimensions-block-dimensions-and-threads-organization-s&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Thread_block_(CUDA_programming)">https://en.wikipedia.org/wiki/Thread_block_(CUDA_programming)&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/cuda/">CUDA</category><category domain="https://blog.imfing.com/tags/c-/">C++</category></item><item><title>从 Blogger 回到 Hexo</title><link>https://blog.imfing.com/2020/05/back-to-hexo/</link><guid isPermaLink="true">https://blog.imfing.com/2020/05/back-to-hexo/</guid><pubDate>Thu, 07 May 2020 18:09:00 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>真是啪啪打脸。在决定把原来用 &lt;a href="https://hexo.io/">Hexo&lt;/a> 搭建的静态博客搬到 &lt;a href="https://blog-fing.blogspot.com/">Google Blogger&lt;/a> 三个多月之后，实在无法忍受 Blogger 糟糕的写作体验，尤其是 Markdown 及 Code，于是就还是回到了 Hexo 的怀抱。
先来说说之前把博客搬到 Blogger 主要原因：图床问题以及跨平台写作的便捷性。那既然打算继续用静态博客，就需要解决这些小问题。&lt;/p>
&lt;h2 id="博客配图管理">博客配图管理&lt;/h2>
&lt;p>使用图床是因为不想让博客的 Git 仓库过于臃肿。对于图床，之前一直是用 &lt;a href="https://github.com/Molunerfinn/PicGo">PicGo&lt;/a> 这样的工具上传到新浪微博图床。但后来在 2019 年 4 月之后新浪微博开始防盗链，之前许多上传了的图片都没法显示，让人很是抓狂。使用其它图床服务的话，也会面临相似的问题。
所以博客中的图片还是得&lt;strong>老实放在 Git 仓库中&lt;/strong>，利用 Hexo 的 &lt;a href="https://hexo.io/docs/asset-folders.html#Post-Asset-Folder">Post Asset Folder&lt;/a> 特性可以比较便捷地管理博文的配图。
此外还可以利用 Issue 中能上传图片的功能将图片上传到 GitHub 的 &lt;a href="https://gist.github.com/vinkla/dca76249ba6b73c5dd66a4e986df4c8d">CDN&lt;/a>。这一点我经常在我 Repo 的 README 中用到。&lt;/p>
&lt;h2 id="跨平台写作及部署">跨平台写作及部署&lt;/h2>
&lt;p>静态博客不像 Blogger 或 WordPress 那样提供了一个网页端的后台可以随时随地写作和管理自己的博文。因此用静态博客写作的最大障碍就是你需要有一台电脑并配置好 NodeJS 和 Hexo 的环境，否则是无法预览写好文章的显示效果。这也是我所看到的许多静态博客博主只有寥寥几篇的博文便停更了。
现在我的解决方案是用 &lt;a href="https://stackedit.io/">StackEdit&lt;/a>，一个基于浏览器的 Markdown 编辑器，可以选择将数据保存在 Google 账户、GitHub、GitLab、Google Drive 等地方并同步。这样随时随地我都能够用 Markdown 写作并预览文章。需要发布文章时就可以直接将 &lt;code>.md&lt;/code> 文件下载到本地亦或直接在 GitHub 上创建并发布。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/81767056-7ca62780-94a5-11ea-8eb1-ee6eee4f7d04.jpg" alt="stackedit io">&lt;/p>
&lt;p>博客的所有源文件都放在了 GitHub 的私有仓库中。部署的话我还是像之前一样用 &lt;a href="https://www.netlify.com/">Netlify&lt;/a>。当我的更新 Push 到 GitHub 仓库里时，Netlify 会自动拉取并构建部署，我只需将自己的博客域名 CNAME 到 Netlify 中的子域名，HTTPS 也会自动支持。
相类似的服务还有 &lt;a href="https://vercel.com/">Vercel&lt;/a> （之前叫做 ZEIT），和 Netlify 如出一辙。&lt;a href="https://pages.github.com/">GitHub Pages&lt;/a> 当然也是很好的部署方案，相对于 Netlify 来说还是稍微麻烦了一些。&lt;/p>
&lt;h2 id="hexo-主题与配置">Hexo 主题与配置&lt;/h2>
&lt;p>之前博客用了”烂大街“的 &lt;a href="https://github.com/theme-next/hexo-theme-next">NexT&lt;/a> 主题。十有八九 Hexo 静态博客都用了这个主题，虽然功能丰富、风格简约，但不免还是有些视觉疲劳。&lt;a href="https://github.com/ppoffice/hexo-theme-icarus">hexo-theme-icarus&lt;/a> 也是一个非常好看并且支持很多自定义配置的主题，此外还有 &lt;a href="https://github.com/ppoffice/hexo-theme-minos">hexo-theme-minos&lt;/a>。
后来我选择了用 &lt;a href="https://github.com/probberechts/hexo-theme-cactus">Cactus&lt;/a>，一个非常简约美观的主题。不需要太多很 fancy 的设置，把重心放到博文本身上来。以后想要迁移到其它的主题也可以的。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/81766637-84190100-94a4-11ea-820f-b89ef09c79b6.jpg" alt="cactus">&lt;/p>
&lt;p>由于我的博文中偶尔会需要用到 LaTeX 数学公式，因此我添加了 &lt;a href="https://www.mathjax.org/">MathJax&lt;/a>，可以参考&lt;a href="https://github.com/mathjax/MathJax-demos-web/blob/master/tex-chtml.html.md">这里&lt;/a>，将 &lt;code>&amp;lt;script&amp;gt;&lt;/code> tag 添加到主题中即可。这样在加载文章时就会自动通过 CDN 加载 MathJax 并渲染页面中的数学公式。唯一需要注意的是数学公式中的一些特殊符号需要 escape 否则会和 Markdown 的语法冲突。
可以启用主题中的 &lt;a href="https://analytics.google.com/analytics/web/">Google Analytics&lt;/a>，方便统计访问者等。博文评论使用了 &lt;a href="https://disqus.com/">Disqus&lt;/a>，也可以在主题中设置。为了更好的支持 SEO，用官方的 &lt;a href="https://github.com/hexojs/hexo-generator-sitemap">hexo-generator-sitemap&lt;/a> 即可添加 sitemap.xml。&lt;/p>
&lt;h2 id="后记">后记&lt;/h2>
&lt;p>从本科大一刚开始决定搭建博客只是觉得好玩和想实践一下搭建网站，到现在陆陆续续写了大概二十多篇文章，心态也发生了很大的转变，更多的是为了记录。不管是不是技术相关的内容，不强求关注和访问量，笔耕不辍，日后再回顾会别有一番趣味。&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BD%91%E7%AB%99/">网站</category><category domain="https://blog.imfing.com/tags/web/">Web</category></item><item><title>Windows 下终端及工具配置</title><link>https://blog.imfing.com/2020/04/windows-terminal-setup/</link><guid isPermaLink="true">https://blog.imfing.com/2020/04/windows-terminal-setup/</guid><pubDate>Fri, 17 Apr 2020 20:08:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>写这篇文章的主要目的是总结一下我目前在 Windows 上使用的命令行终端和 Shell 的配置，让自己拥有有更舒适愉悦的使用体验。&lt;/p>
&lt;!-- more -->
&lt;h2 id="powershell-美化与配置">Powershell 美化与配置&lt;/h2>
&lt;p>在 Windows 下会时常用到 Powershell 来进行一些简单的命令行操作，比如使用 Python、Node 等。
然而系统自带的 Powershell 丑的不忍直视，为了让自己用它的时候拥有更加愉悦的心情，得稍微折腾一下。在 Linux 环境下有大名鼎鼎的 &lt;a href="https://github.com/ohmyzsh/ohmyzsh">oh-my-zsh&lt;/a>，于是 Windows 下便也诞生了 &lt;a href="https://github.com/JanDeDobbeleer/oh-my-posh">oh-my-posh&lt;/a> ，用来美化 Powershell。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83092982-92524a00-a06c-11ea-8764-c59c74f0879b.png" alt="Terminal Preview">&lt;/p>
&lt;p>安装的方法也非常简单，确保你已经安装了 &lt;a href="https://git-scm.com/">Git&lt;/a> 客户端。
首先打开 Powershell 输入以下指令安装 &lt;code>posh-git&lt;/code> 以及 &lt;code>oh-my-posh&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="nb">Install-Module&lt;/span> &lt;span class="n">posh-git&lt;/span> &lt;span class="n">-Scope&lt;/span> &lt;span class="n">CurrentUser&lt;/span>
&lt;span class="nb">Install-Module&lt;/span> &lt;span class="n">oh-my-posh&lt;/span> &lt;span class="n">-Scope&lt;/span> &lt;span class="n">CurrentUser&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>之后运行命令 &lt;code>notepad $PROFILE&lt;/code> 来打开默认的配置文件，并在最后加上：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="nb">Import-Module&lt;/span> &lt;span class="n">posh-git&lt;/span>
&lt;span class="nb">Import-Module&lt;/span> &lt;span class="n">oh-my-posh&lt;/span>
&lt;span class="nb">Set-Theme&lt;/span> &lt;span class="n">Paradox&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样便能启用之前安装的两个插件，以及将主题设置为了 Paradox。oh-my-posh 还支持其它的一些&lt;a href="https://github.com/JanDeDobbeleer/oh-my-posh#themes">主题&lt;/a>。&lt;/p>
&lt;p>除此之外，为了一些特殊的符号的显示正常，我们还需要 Powerline 字体，才可以得到和上图一样的效果。
这里不得不推荐一下微软官方的开源字体 &lt;a href="https://github.com/microsoft/cascadia-code">Cascadia Code&lt;/a>，在 Windows Terminal 中将 &lt;code>fontFace&lt;/code> 设置成 &lt;code>Cascadia Code PL&lt;/code> 即可。
其它的还有 &lt;a href="https://github.com/powerline/fonts">Powerline fonts&lt;/a> 和 &lt;a href="https://github.com/ryanoasis/nerd-fonts">Nerd-fonts&lt;/a>&lt;/p>
&lt;h2 id="windows-terminal-的配置">Windows Terminal 的配置&lt;/h2>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83093046-b4e46300-a06c-11ea-8d1b-71b4c44cc001.png" alt="">&lt;/p>
&lt;p>从去年 Build 大会发布以来，&lt;a href="https://github.com/microsoft/terminal">Windows Terminal&lt;/a> 就一直在 GitHub 上积极的迭代，功能也日渐完善，现在它已经是我在 Windows 环境下的主力终端了。&lt;/p>
&lt;p>目前 Windows Terminal 还没有可视化的设置界面，需要通过手动编辑
配置文件才能修改配置。可以参考它的 &lt;a href="https://github.com/microsoft/terminal/blob/master/doc/cascadia/SettingsSchema.md">SettingSchema&lt;/a> 来增删配置。这里也有一份修改官方的修改教程：&lt;a href="https://github.com/microsoft/terminal/blob/master/doc/user-docs/UsingJsonSettings.md#adding-copy-and-paste-keybindings">Editing Windows Terminal JSON Settings&lt;/a>。&lt;/p>
&lt;p>我将全局的字体修改为了 &lt;a href="https://github.com/microsoft/cascadia-code">&amp;quot;Cascadia Code PL&amp;quot;&lt;/a> 以及将字号调整成了 12。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="s2">&amp;#34;defaults&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="err">//&lt;/span> &lt;span class="err">Put&lt;/span> &lt;span class="err">settings&lt;/span> &lt;span class="err">here&lt;/span> &lt;span class="err">that&lt;/span> &lt;span class="err">you&lt;/span> &lt;span class="err">want&lt;/span> &lt;span class="err">to&lt;/span> &lt;span class="err">apply&lt;/span> &lt;span class="err">to&lt;/span> &lt;span class="err">all&lt;/span> &lt;span class="err">profiles&lt;/span>
&lt;span class="nt">&amp;#34;fontFace&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Cascadia Code PL&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;fontSize&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="mi">12&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我更加喜欢 &amp;quot;One Half Dark&amp;quot; 的主题。内置主题的话可以在 &lt;a href="https://github.com/microsoft/terminal/tree/master/src/tools/ColorTool">ColorTool&lt;/a> 里面的 &lt;a href="https://github.com/microsoft/terminal/tree/master/src/tools/ColorTool/schemes">schemes&lt;/a> 里找到，也可以自己在配置文件的 &lt;code>schemes&lt;/code> 块添加自己的主题，如 &lt;a href="https://draculatheme.com/windows-terminal">dracula&lt;/a>。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="p">{&lt;/span>
&lt;span class="err">//&lt;/span> &lt;span class="err">Make&lt;/span> &lt;span class="err">changes&lt;/span> &lt;span class="err">here&lt;/span> &lt;span class="err">to&lt;/span> &lt;span class="err">the&lt;/span> &lt;span class="err">powershell.exe&lt;/span> &lt;span class="err">profile&lt;/span>
&lt;span class="nt">&amp;#34;guid&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;{61c54bbd-c2c6-5271-96e7-009a87ff44bf}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;PowerShell&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;commandline&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;powershell.exe&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;hidden&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;colorScheme&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;One Half Dark&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;startingDirectory&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;.&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>&lt;span class="err">,&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在 &lt;code>keybindings&lt;/code> 区域可以添加一些常用的快捷键。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="err">//&lt;/span> &lt;span class="err">Add&lt;/span> &lt;span class="err">any&lt;/span> &lt;span class="err">keybinding&lt;/span> &lt;span class="err">overrides&lt;/span> &lt;span class="err">to&lt;/span> &lt;span class="err">this&lt;/span> &lt;span class="err">array.&lt;/span>
&lt;span class="err">//&lt;/span> &lt;span class="err">To&lt;/span> &lt;span class="err">unbind&lt;/span> &lt;span class="err">a&lt;/span> &lt;span class="err">default&lt;/span> &lt;span class="err">keybinding,&lt;/span> &lt;span class="err">set&lt;/span> &lt;span class="err">the&lt;/span> &lt;span class="err">command&lt;/span> &lt;span class="err">to&lt;/span> &lt;span class="s2">&amp;#34;unbound&amp;#34;&lt;/span>
&lt;span class="s2">&amp;#34;keybindings&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;command&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;copy&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;keys&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ctrl+shift+c&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;command&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;paste&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;keys&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ctrl+shift+v&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;command&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;action&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;splitPane&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;split&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;auto&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="nt">&amp;#34;keys&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ctrl+|&amp;#34;&lt;/span>&lt;span class="p">]}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>为了能够更方便的使用，还可以在系统右键添加全局的 &amp;quot;Open Terminal Here&amp;quot; 的菜单。这样一来，在任何文件夹里面可以方便的打开 Terminal 并且定位到那个文件夹下面。可以在 GitHub 上的这个 &lt;a href="https://github.com/microsoft/terminal/issues/1060">Issue&lt;/a> 里找到一些社区用户提供的方法。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83093100-ccbbe700-a06c-11ea-9b77-97e3ee04df29.png" alt="">&lt;/p>
&lt;p>创建一个 &lt;code>reg&lt;/code> 文件，将下面内容中的 &lt;code>&amp;lt;user&amp;gt;&lt;/code> 改为你用户的名字后，保存并执行即可。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bat" data-lang="bat">Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\Directory\Background\shell\wt]
&lt;span class="p">@&lt;/span>=&lt;span class="s2">&amp;#34;Windows Terminal here&amp;#34;&lt;/span>
&lt;span class="s2">&amp;#34;Icon&amp;#34;&lt;/span>=&lt;span class="s2">&amp;#34;C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminal_0.10.781.0_x64__8wekyb3d8bbwe\\WindowsTerminal.exe&amp;#34;&lt;/span>
[HKEY_CLASSES_ROOT\Directory\Background\shell\wt\command]
&lt;span class="p">@&lt;/span>=&lt;span class="s2">&amp;#34;C:\\Users\\&amp;lt;user&amp;gt;\\AppData\\Local\\Microsoft\\WindowsApps\\wt.exe -d .&amp;#34;&lt;/span>
[HKEY_CLASSES_ROOT\Directory\shell\wt]
&lt;span class="p">@&lt;/span>=&lt;span class="s2">&amp;#34;Windows Terminal here&amp;#34;&lt;/span>
&lt;span class="s2">&amp;#34;Icon&amp;#34;&lt;/span>=&lt;span class="s2">&amp;#34;C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminal_0.10.781.0_x64__8wekyb3d8bbwe\\WindowsTerminal.exe&amp;#34;&lt;/span>
[HKEY_CLASSES_ROOT\Directory\shell\wt\command]
&lt;span class="p">@&lt;/span>=&lt;span class="s2">&amp;#34;C:\\Users\\&amp;lt;user&amp;gt;\\AppData\\Local\\Microsoft\\WindowsApps\\wt.exe -d .&amp;#34;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="wsl-的配置">WSL 的配置&lt;/h2>
&lt;p>在不少时候需要用暂时到 Linux 的环境。
众所周知，在 &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10">Windows Subsystem for Linux (WSL)&lt;/a> 问世之后，Linux &amp;quot;真的变成了&amp;quot; Windows 的一个&amp;quot;子系统&amp;quot;了。
之前有使用过 &lt;a href="https://github.com/ohmyzsh/ohmyzsh">oh-my-zsh&lt;/a> 但是 zsh 的启动速度过于感人，就转而改用了开箱即用的 &lt;a href="https://github.com/fish-shell/fish-shell">fish-shell&lt;/a>。&lt;/p>
&lt;p>这里以在 Ubuntu 环境下为例。
安装 fish-shell 非常简单，只需要执行 &lt;code>sudo apt install fish&lt;/code> 即可。
之后继续安装 &lt;a href="https://github.com/oh-my-fish/oh-my-fish">oh-my-fish&lt;/a>：&lt;code>curl -L https://get.oh-my.fish | fish&lt;/code>。
之后可以通过 &lt;code>omf theme&lt;/code> 来查看&lt;a href="https://github.com/oh-my-fish/oh-my-fish/blob/master/docs/Themes.md">主题&lt;/a>，&lt;code>omf install &amp;lt;theme&amp;gt;&lt;/code> 来安装主题。&lt;/p>
&lt;p>想要默认启动 fish shell 的话需要在 &lt;code>~/.bashrc&lt;/code> 文件最后加上&lt;code>fish&lt;/code>。&lt;/p>
&lt;p>参考&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.hanselman.com/blog/NowIsTheTimeToMakeAFreshNewWindowsTerminalProfilesjson.aspx">https://www.hanselman.com/blog/NowIsTheTimeToMakeAFreshNewWindowsTerminalProfilesjson.aspx&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.hanselman.com/blog/WindowsTerminal10IsComingUpdateNowAndSetUpYourSplitPaneHotkeys.aspx">https://www.hanselman.com/blog/WindowsTerminal10IsComingUpdateNowAndSetUpYourSplitPaneHotkeys.aspx&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E5%B7%A5%E5%85%B7/">工具</category><category domain="https://blog.imfing.com/tags/windows/">Windows</category><category domain="https://blog.imfing.com/tags/terminal/">Terminal</category></item><item><title>在 Blogger 中使用 Markdown</title><link>https://blog.imfing.com/2020/04/markdown-in-blogger/</link><guid isPermaLink="true">https://blog.imfing.com/2020/04/markdown-in-blogger/</guid><pubDate>Wed, 15 Apr 2020 20:04:51 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>最近有陆陆续续开始写一些东西，不过 Google Blogger 编辑体验非常非常糟糕。
只好求助 Google 一下有没有能够使用 Markdown 编辑的方法，果然有人分享了如何在 Blogger 里使用 Markdown。
思路其实很简单，借助于 &lt;a href="http://showdownjs.com/">showdown&lt;/a>，动态地将 Mardown 内容转换为用于显示的 HTML。&lt;/p>
&lt;!-- more -->
&lt;p>之前把旧的博客里面的 Markdown 写的博文导入到 Blogger 时费了一番功夫，花了很多时间将 Markdown 转换为 HTML。其实都可以借助现有的工具在 Markdown 和 HTML 之间互相转换：例如 showdown 以及 &lt;a href="https://domchristie.github.io/turndown/">turndown&lt;/a>。&lt;/p>
&lt;p>下面的代码是我基于别人博文，自己进行了一些改动，在 Blogger 的布局里添加一个 HTML/Javascript 的组件即可。
在编辑 Markdown 博文时，需要切换到 HTML 模式，并且开头以 &lt;code>markdown&lt;/code> 起头即可。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nx">script&lt;/span> &lt;span class="nx">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.1/showdown.min.js&amp;#34;&lt;/span>&lt;span class="o">&amp;gt;&amp;lt;&lt;/span>&lt;span class="err">/script&amp;gt;&lt;/span>
&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nx">script&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">showdownOption&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;simplifiedAutoLink&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;strikethrough&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;tables&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s1">&amp;#39;tasklists&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">converter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">showdown&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Converter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">showdownOption&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">posts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelectorAll&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;.post-body&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nb">Array&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">prototype&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">forEach&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">call&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">posts&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">el&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">idx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">el&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">innerHTML&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">indexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;markdown&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">idx&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nx">idx&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="kd">let&lt;/span> &lt;span class="nx">md&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">el&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">innerHTML&lt;/span>
&lt;span class="p">.&lt;/span>&lt;span class="nx">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sr">/\n&amp;amp;amp;gt;/g&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;\n&amp;gt;&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// fix quotes
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="p">.&lt;/span>&lt;span class="nx">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;markdown&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// remove flag
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="nx">el&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">innerHTML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">converter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">makeHtml&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">md&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">.&lt;/span>&lt;span class="nx">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sr">/&amp;amp;amp;amp;/g&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;amp;amp;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// remove redundant escape
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;span class="p">});&lt;/span>
&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="err">/script&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>注意在使用的时候，所有 HTML 的内置标签的尖括号需要 escape 一下，比如 &amp;lt;&amp;gt; 分别需要用 lt 和 gt 来替代。
上面的代码有个小问题，就是 showdown 会在 convert 的时候，将代码块中的 &amp;amp; 符号全部都 escape 了，这样一来网页中代码里的尖括号全部都无法显示。所以在代码的最后我将 escape 之后的 amp 又替换回原本的 &amp;amp; 符号了。这样一来唯一的缺点就是没法在 HTML 里显示，这就得等将来有时间再解决了。&lt;/p>
&lt;p>如果尖括号不 escape 的话，像下面的 JS 代码会被执行:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nx">script&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;hello&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="err">/script&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可以借助在线的工具 &lt;a href="https://www.freeformatter.com/html-escape.html">HTML Escape&lt;/a> 来自动进行转换。
对于其它语言的代码，基本就不需要操心了。&lt;/p>
&lt;p>除此之外，我还用 &lt;a href="https://prismjs.com/">PrismJS&lt;/a> 替换了原来使用的 highlightjs 来进行代码的高亮显示。
我个人比较喜欢暗色的 tomorrow 主题，只需要加载 Prism 的 core js 文件以及使用 autoloader 自动加载对应代码区域指定语言的高亮方式。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">link&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;https://cdn.jsdelivr.net/npm/prismjs@1.20.0/themes/prism-tomorrow.css&amp;#34;&lt;/span> &lt;span class="na">rel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;stylesheet&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">link&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;https://cdn.jsdelivr.net/npm/prismjs@1.20.0/components/prism-core.min.js&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;https://cdn.jsdelivr.net/npm/prismjs@1.20.0/plugins/autoloader/prism-autoloader.min.js&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>此外，还需要给滚动条加一下样式，否则在暗色主题下会很丑&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="p">.&lt;/span>&lt;span class="nc">post-body&lt;/span> &lt;span class="nt">pre&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="nt">class&lt;/span>&lt;span class="o">*=&lt;/span>&lt;span class="s2">&amp;#34;language-&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">font-size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">14&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">line-height&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">1.4&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">background-color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#282c34&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">border-radius&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">6&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">overflow&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">auto&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="c">/* Scroll */&lt;/span>
&lt;span class="nt">pre&lt;/span>&lt;span class="p">::&lt;/span>&lt;span class="nd">-webkit-scrollbar&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">width&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">height&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nt">pre&lt;/span>&lt;span class="p">::&lt;/span>&lt;span class="nd">-webkit-scrollbar-track&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">background-color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">transparent&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">border-radius&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nt">pre&lt;/span>&lt;span class="p">::&lt;/span>&lt;span class="nd">-webkit-scrollbar-thumb&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">background-color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">rgba&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.4&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">border-radius&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nt">blockquote&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">border-left&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="kc">solid&lt;/span> &lt;span class="mh">#dddddd&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">15&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#777777&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">text-align&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">left&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">font-size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">medium&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">font-style&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">normal&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">margin&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="kt">rem&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="kt">rem&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>显示效果：&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83093325-3936e600-a06d-11ea-9970-f8ca2d7e81a9.png" alt="">&lt;/p>
&lt;p>这样就大功告成啦&lt;/p>
&lt;p>参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="http://lausai360.blogspot.com/2018/11">http://lausai360.blogspot.com/2018/11&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/cs905s/md-in-blogger">https://github.com/cs905s/md-in-blogger&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://codepen.io/zkreations/pen/bZRgqd">https://codepen.io/zkreations/pen/bZRgqd&lt;/a>&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BD%91%E7%AB%99/">网站</category><category domain="https://blog.imfing.com/tags/markdown/">Markdown</category><category domain="https://blog.imfing.com/tags/web/">Web</category></item><item><title>Markdown + VuePress 构建个人主页</title><link>https://blog.imfing.com/2020/01/markdown-vuepress/</link><guid isPermaLink="true">https://blog.imfing.com/2020/01/markdown-vuepress/</guid><pubDate>Sun, 19 Jan 2020 19:47:57 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>分享一下自己的个人主页模板，用 Markdown 轻松地编辑与生成个人主页。&lt;br>
&lt;a href="https://github.com/mtobeiyf/vuepress-homepage">&lt;img src="https://user-images.githubusercontent.com/5097752/83093756-0d683000-a06e-11ea-8a53-a3c1aa27bedb.png" alt="VuePress HomePage">&lt;/a>&lt;/p>
&lt;!-- more -->
&lt;h2 id="项目初衷">项目初衷&lt;/h2>
&lt;p>很久之前就有打算做个人主页（Academic Portfolio/Online Resume）。感觉将来在学校申请、找工作等各方面都能够用上。&lt;br>
之前考虑过用纯 HTML 单页面静态文件来实现，但是这样 HTML 文件会变得非常长，之后增删信息也会变得麻烦。&lt;br>
大概在 2018 年 4 月，&lt;a href="https://vuepress.vuejs.org/">VuePress&lt;/a> 出现了，起初它是用来管理项目文档的。它的好处之一是能够在 Markdown 文件中插入 Vue 的组件。可以利用这一点添加自定义的组件，然后用 VuePress 构建静态的网页文件。这是我目前个人觉得非常好的一套管理方式，并在 GitHub 上开源了我自己使用的模板 &lt;a href="https://github.com/mtobeiyf/vuepress-homepage">vuepress-homepage&lt;/a>。之后可以在 &lt;a href="https://pages.github.com/">GitHub Pages&lt;/a> 或者 &lt;a href="https://www.netlify.com/">Netlify&lt;/a> 实现免费的部署。&lt;/p>
&lt;h2 id="项目结构">项目结构&lt;/h2>
&lt;p>整个项目的结构基本就是一个 VuePress 项目的结构，也是很经典的一个 Node 项目结构。所有的文档和配置都在 docs 文件夹中。在 .vuepress 文件夹里面，components 文件夹包含了我们自己实现的 Vue 组件，public 文件夹中是所有图片文件，style 文件夹下是一些独立的 CSS 样式，config.js 包含了 VuePress 的设置。&lt;br>
基本的使用可以参照项目中的每个文件，直接套用我的模板的话无非就是修改 Markdown 文件就行了。关于进一步的具体的使用及配置可以到 &lt;a href="https://vuepress.vuejs.org/guide/getting-started.html">VuePress&lt;/a> 官网查看其教程与配置说明，这里不再赘述。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83093767-1527d480-a06e-11ea-9565-7fb15f3687a5.png" alt="">&lt;/p>
&lt;h2 id="项目部署">项目部署&lt;/h2>
&lt;p>我个人的项目是用了 Netlify 进行部署。Netlify 部署起来非常简单，只要在它的网站上绑定一下 GitHub 的仓库，然后构建指令就是 yarn build，默认输出静态网站到 dist 文件夹。之后 Netlify 会自动 serve 这个文件夹中的静态网页。我们能够在 Netlify 中添加自定义子域名以及 Netlify 会自动添加 HTTPS 支持。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83093863-46a0a000-a06e-11ea-8f00-00f3d9120887.png" alt="">&lt;/p>
&lt;p>可以到 &lt;a href="https://vuepress-homepage.netlify.com/">https://vuepress-homepage.netlify.com/&lt;/a> 查看部署好后的效果。&lt;/p>
&lt;h2 id="组件化的-markdown">组件化的 Markdown&lt;/h2>
&lt;p>这里我具体解释对主页面 Markdown 文件的每部分我是如何设计和实现的。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83093870-4b655400-a06e-11ea-8ef1-da352d1136e9.png" alt="">&lt;/p>
&lt;p>以题图里的页面为例，About Me 以及 News 等部分都是直接 Markdown 输出的文字部分内容。因为整个页面比较简单，是比较自然的自上向下的结构，因此可以将最上面的部分视为一个可拔插的组件。&lt;br>
在 components 下用实现了 ProfileSection.vue 的组件，之后在 Markdown 文件里面就能够直接使用。对于名字、邮箱等参数的传递，我利用了 Markdown 的 frontmatter，本质上是 yaml，像简化版的 json。因为 VuePress 在解析 Markdown 时会有个全局的 frontmatter 变量，这样只需要将所有的参数放在 frontmatter 中，就能避免在正文组件里传递太多的参数。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="nt">pageClass&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">home-page&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="c"># some data for the components&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Harry Potter&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="nt">profile&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/profile.jpg&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="nt">socials&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>- &lt;span class="nt">title&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">github&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nt">icon&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;/icons/github.svg&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nt">link&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">https://github.com/mtobeiyf&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>- &lt;span class="nt">title&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">linkedin&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nt">icon&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;/icons/linkedin-mono.svg&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nt">link&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">https://www.linkedin.com&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>- &lt;span class="nt">title&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">instagram&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nt">icon&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;/icons/instagram-mono.svg&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nt">link&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">https://www.instagram.com&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="nt">cv&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">https://en.wikipedia.org/wiki/Harry_Potter&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="nt">bio&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Student at Hogwarts School&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="nt">email&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">harry (at) hogwarts (dot) edu&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="l">&amp;lt;ProfileSection :frontmatter=&amp;#34;$page.frontmatter&amp;#34; /&amp;gt;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面的 pageClass 可以给当前 Markdown 文件渲染后的元素添加类。这样可以当前 Markdown 文件最后面添加自定义的 CSS 来修改样式。&lt;br>
对于每个 Project 的项目，我也制作了一个自定义的组件。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ProjectCard&lt;/span> &lt;span class="na">image&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/projects/1.png&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
Harry P., Hermione G., &lt;span class="ge">*et al*&lt;/span>
&lt;span class="gs">**The Making of Harry Potter&amp;#39;s Wand**&lt;/span>
Harry&amp;#39;s wand was broken in 1997, but was repaired by him after the 1998 Battle of Hogwarts..
[&lt;span class="nt">[PDF&lt;/span>](&lt;span class="na">https://www.google.com&lt;/span>)] [&lt;span class="nt">[arXiv&lt;/span>](&lt;span class="na">https://arxiv.org&lt;/span>)]
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">ProjectCard&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>就能够渲染成下面的样式，其中包含在自定义组件之间的内容能够被捕捉，并在 .vue 组件中很方便的以 Slot 的形式去使用。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83093878-4f917180-a06e-11ea-821c-f13efad116b5.png" alt="">&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E5%89%8D%E7%AB%AF/">前端</category><category domain="https://blog.imfing.com/tags/markdown/">Markdown</category><category domain="https://blog.imfing.com/tags/vue/">Vue</category></item><item><title>迁移博客平台到 Blogger</title><link>https://blog.imfing.com/2020/01/move-to-blogger/</link><guid isPermaLink="true">https://blog.imfing.com/2020/01/move-to-blogger/</guid><pubDate>Sat, 18 Jan 2020 19:58:06 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>最近尝试将自己的博客搬到了 Google 的 Blogger 平台，这里记录了心路历程以及自己如何调整 Blogger 的。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83093419-684d5780-a06d-11ea-8315-db2d0d2238cb.png" alt="">&lt;/p>
&lt;!-- more -->
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>虽然静态博客简单免费，但是自从去年新浪图床开始对外链进行封禁，感觉我有很大一部分时间是花费在了维护博客上。所以兜兜转转一圈下来，还是想要回到类似 WordPress、Blogger 这样的 CMS 上，把重心转移到内容的记录。&lt;br>
静态博客让人比较难受的，是内容的管理。在大多数情况下想要发布文章需要用到脚本、NodeJS、Git 等工具链，而且插入图片需要稳定的图床服务。此外，想要在多个电脑切换编辑非常的不方便。&lt;br>
所以最近尝试了 Google 家免费的 &lt;a href="https://www.blogger.com/">Blogger&lt;/a> 平台，并把之前的文章转到了这里面。为了和之前的平台有一致的阅读体验，作了如下的一些调整和功能添加。&lt;/p>
&lt;h2 id="代码高亮">代码高亮&lt;/h2>
&lt;p>用 &lt;a href="https://highlightjs.org/">highlightjs&lt;/a> 可以让博客文章显示代码块的高亮。我个人之前更多是会选择 &lt;a href="https://prismjs.com/">Prismjs&lt;/a>，因为许多官方文档用到了它，比如 &lt;a href="https://reactjs.org/tutorial/tutorial.html">React&lt;/a>, &lt;a href="https://vuepress.vuejs.org/">VuePress&lt;/a>。&lt;br>
在 Blogger 中使用 highlightjs 也非常简单。Blogger 支持修改主题的 HTML 源码，添加一些自定义的样式和脚本自然不是问题。当然，还有更简单的方法。&lt;br>
选择 Sidebar 的 Layout，点击 Add a Gadget 按钮，选择 HTML/JavaScript。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83093546-a480b800-a06d-11ea-88d3-e0c30a93ae70.png" alt="">&lt;/p>
&lt;p>之后按照 highlightjs 网站上所给的参考用法，添加如下的 HTML 代码将 highlightjs 引入。这里我个人选择了 dracula 配色的 CSS 样式。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">link&lt;/span> &lt;span class="na">rel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;stylesheet&amp;#34;&lt;/span>
&lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.17.1/build/styles/dracula.min.css&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.17.1/build/highlight.min.js&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">hljs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">initHighlightingOnLoad&lt;/span>&lt;span class="p">();&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在文章的编辑页面，可以在 Compose 和 HTML 模式之间切换。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">pre&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;&lt;/span>&lt;span class="nt">code&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#39;language-cpp&amp;#39;&lt;/span> &lt;span class="na">lang&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#39;cpp&amp;#39;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>#include &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">iostream&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
int main()
{
std::cout &lt;span class="err">&amp;lt;&amp;lt;&lt;/span> &amp;#34;Hello World!&amp;#34;;
}
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">code&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">pre&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>显示为：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;span class="cp">&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Hello World!&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>像我一样嫌输 HTML 麻烦的可以在 Typora 中用 Markdown 写好代码块并注明语言后，选中后点击 &lt;code>Copy as HTML Code&lt;/code>。&lt;/p>
&lt;h2 id="显示数学公式">显示数学公式&lt;/h2>
&lt;p>同样，在上面我们添加的 HTML/JavaScript 的 Gadget 中可以添加额外的 JS 脚本来让 Blogger 支持显示 LaTeX 公式。这里我使用的是 &lt;a href="https://www.mathjax.org/">MathJax&lt;/a>，此外 &lt;a href="https://katex.org/">KaTeX&lt;/a> 也被经常用来渲染网页中的数学公式。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="nx">MathJax&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">tex&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">inlineMath&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[[&lt;/span>&lt;span class="s1">&amp;#39;$&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;$&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;\\(&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;\\)&amp;#39;&lt;/span>&lt;span class="p">]]&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="nx">svg&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">fontCache&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;global&amp;#39;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">};&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text/javascript&amp;#34;&lt;/span> &lt;span class="na">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;MathJax-script&amp;#34;&lt;/span> &lt;span class="na">async&lt;/span>
&lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>效果：&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83093586-b95d4b80-a06d-11ea-8c4f-b891f0235b2b.png" alt="">&lt;/p>
&lt;h2 id="样式微调">样式微调&lt;/h2>
&lt;p>此外，Blogger 默认主题的一些元素的样式也不是很好看，比如 &lt;code>Blockquote&lt;/code> 。好在可以在主题的 Customize 中添加自己的 CSS 来覆盖默认的样式。我添加了下面的 CSS 来让代码块部分和引用部分更好看一些。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="nt">code&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#476582&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">.25&lt;/span>&lt;span class="kt">rem&lt;/span> &lt;span class="mf">.25&lt;/span>&lt;span class="kt">rem&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">margin&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">font-size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">.85&lt;/span>&lt;span class="kt">em&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">background-color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">rgba&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">27&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">31&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">35&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mf">.05&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">border-radius&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nt">pre&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nt">code&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">line-height&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">1.4&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">border-radius&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">6&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">1.25&lt;/span>&lt;span class="kt">rem&lt;/span> &lt;span class="mf">1.5&lt;/span>&lt;span class="kt">rem&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nt">code&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nc">hljs&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">1.25&lt;/span>&lt;span class="kt">rem&lt;/span> &lt;span class="mf">1.5&lt;/span>&lt;span class="kt">rem&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="nt">blockquote&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">border-left&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="kc">solid&lt;/span> &lt;span class="mh">#dddddd&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">15&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#777777&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">text-align&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">left&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">font-size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">medium&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">font-style&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">normal&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">margin&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="kt">rem&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="kt">rem&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>参考：&lt;a href="https://gist.github.com/tuzz/3331384">github.css&lt;/a>&lt;/p>
&lt;h2 id="写在后面">写在后面&lt;/h2>
&lt;p>Blogger 可能也是暂时的选择，不过是目前比较好的选择。&lt;/p>
&lt;p>&lt;strong>优点&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>免费，自带图床和基本的流量访问分析&lt;/li>
&lt;li>主题可定制性高&lt;/li>
&lt;li>爸爸是 Google，基本可以不用担心跑路的问题&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>缺点&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>默认主题少，而且丑&lt;/li>
&lt;li>自带的编辑器不是非常好用&lt;/li>
&lt;li>被墙&lt;/li>
&lt;/ul></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BD%91%E7%AB%99/">网站</category><category domain="https://blog.imfing.com/tags/web/">Web</category></item><item><title>我的 2018 年终盘点</title><link>https://blog.imfing.com/2018/12/2018-summary/</link><guid isPermaLink="true">https://blog.imfing.com/2018/12/2018-summary/</guid><pubDate>Mon, 31 Dec 2018 23:53:40 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>本来想拖到明年跨年一起写，想想还是得赶紧写完。&lt;/p>
&lt;!-- more -->
&lt;blockquote>
&lt;p>“你迷失在地图上每一道短暂的光影”&lt;/p>
&lt;/blockquote>
&lt;p>故事不知道该从哪说起，2018 年似乎开始的有点慢热。再次翻开云相册，去照片中依稀找寻过去的美好光景。后半年的我像只“旅行青蛙”一样，到过许多的地方。其实我有许多故事，那么，你有酒吗？🍻&lt;/p>
&lt;p>不想写成流水账，于是我把想到的一些整理成了问答，当作是回顾。&lt;/p>
&lt;h5 id="上半年都做了什么">上半年都做了什么&lt;/h5>
&lt;p>感觉自己挺废柴的。课也不多，一直在咸鱼状态。白天学驾照，晒黑了两度。结束了本科最后一门考试。&lt;/p>
&lt;h5 id="今年都去过哪些城市">今年都去过哪些城市&lt;/h5>
&lt;p>算上转机的话，国内有：武汉、上海、广州、北京；国外有：温哥华、基洛纳、多伦多、滑铁卢、雅典、罗马、梵蒂冈、巴黎、多哈。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83092201-d47a8c00-a06a-11ea-817e-35816a4838bf.jpg" alt="">&lt;/p>
&lt;h5 id="印象最深刻的是哪里">印象最深刻的是哪里&lt;/h5>
&lt;p>其实挺纠结的，单纯谈印象的话我还是会选罗马，可能先入为主了吧。当时的行程只有一天，罗马的历史感、宗教和艺术，满足了我对欧洲所有的幻想，难怪叫作“永恒之城”。&lt;/p>
&lt;h5 id="为什么会去巴黎呢">为什么会去巴黎呢&lt;/h5>
&lt;p>“塞纳河畔，左岸的咖啡”
说到巴黎，记得高中的时候第一个笔友在巴黎，当时就特别想去法国，还在书店买了一个埃菲尔铁塔的钥匙扣。到了大学，也短暂的学过一段时间的法语，那个钥匙扣依然还在我的桌上。能去巴黎也算是圆梦吧。
只不过时间过去了太久，物是人非，巴黎的笔友也早已没有联系，我也不如当年那般热血。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83092202-d47a8c00-a06a-11ea-9a00-96dceb7f3c89.jpg" width = "60%" height = "60%" alt="" align=center />&lt;/p>
&lt;h5 id="最大胆的事情是什么">最大胆的事情是什么&lt;/h5>
&lt;p>背了一个包，一个人去欧洲。去了之后的想法竟是“活着回来”！
欧洲其实并没有想象的美好，盗窃、诈骗、危险还是随时会有的。要多看一些攻略，规避自己被坑。当然我还挺幸运的，没有被偷抢，遇到了一些 nice 的人。&lt;/p>
&lt;h5 id="最巧合的事情是什么">最巧合的事情是什么&lt;/h5>
&lt;p>在卢浮宫售票处朝两个亚洲面孔的打了个招呼，居然也都是武大的同学。那一刻感觉世界真小。
8 月在滑铁卢和国内导师重聚。一个多月前大家也都还在国内，如今却一同在地球的另一端。还有和同班同学在多伦多浪。
回国之后，某天偶然在校园里碰见一个其它学校的同学（上次见面还是在多伦多）。&lt;/p>
&lt;h5 id="最激动的事情是什么">最激动的事情是什么&lt;/h5>
&lt;p>从基洛纳飞到了多伦多看泰勒的演唱会。第一天晚上买了黄牛票进场，激动 Cry。大概从 09 年左右开始听 Taylor 的歌，错过了她上海的两场演唱会。不过在多伦多圆梦了，还见到了好多国内外的 fans。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83092207-d5132280-a06a-11ea-9d23-6133819535e3.jpg" width = "60%" height = "60%" alt="" align=center />&lt;/p>
&lt;h5 id="自己进步最快的地方">自己进步最快的地方&lt;/h5>
&lt;p>厨艺。在加拿大做了 3 个月的饭，以后一个人生活肯定不会饿死了。而且还掌握了一些拿手的菜，可惜回国没什么机会露一手了。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83092205-d47a8c00-a06a-11ea-9981-a8cf45023f86.jpg" width = "60%" height = "60%" alt="" align=center />&lt;/p>
&lt;h5 id="最惊险的事情是什么">最惊险的事情是什么&lt;/h5>
&lt;p>在温哥华，托福 GRE 连考，中间只隔了 1 天。这还没完，考 GRE 的早上手机坏了。所幸带了电脑，摸到了考场。
还有在雅典，扶梯前面一个小偷在掏前面人的包，在巴黎和一个撬车窗的小偷擦肩而过。&lt;/p>
&lt;h5 id="印象最深的剧集">印象最深的剧集&lt;/h5>
&lt;p>《怪奇物语》的两季，充满了童真和想象，甚至买了 Eggo 来吃。
《黑镜》的四季也是不错的，风格截然相反，对人性和社会的反思。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83092206-d5132280-a06a-11ea-950f-f1df964cee54.jpg" width = "60%" height = "60%" alt="" align=center />&lt;/p>
&lt;h5 id="最牛逼的一件事">最牛逼的一件事&lt;/h5>
&lt;p>托福写作满分 30 分，哈哈哈哈，可以吹一年。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83092198-d3e1f580-a06a-11ea-8fff-d6866810912d.jpg" width = "50%" height = "50%" alt="" align=center />&lt;/p>
&lt;h5 id="以后最想生活的城市">以后最想生活的城市&lt;/h5>
&lt;p>应该会选温哥华。气候、环境、生活方式比较适合...养老。
当然目前更多的还是喜欢国内，生活便捷，更丰富一些。&lt;/p>
&lt;h5 id="旅行有什么感触">旅行有什么感触&lt;/h5>
&lt;p>趁着年轻，有机会一定要多出去走走。我更希望最好能一个人独自去，能够按自己的节奏去自由的体验。一个好的手机/相机，是你旅行时的好伴侣，珍贵的回忆应当及时记录和收藏。
不要对一个地方抱有过高的期望。在去罗马之前，本以为会和雅典差不多，结果超出了我的预期很多。&lt;/p>
&lt;h5 id="自己最大的改变">自己最大的改变&lt;/h5>
&lt;p>感觉自己心态更好了一些，日渐佛系。
特别是在国内这样一个环境中，能更从容的去面对一些事情。因为见过了多种多样不同的人的职业和生活的选择，明白了生活的主动权大多是在自己手上的。当然，自己也要更努力，去追求理想的生活。&lt;/p>
&lt;h5 id="最想感谢的人有哪些">最想感谢的人有哪些&lt;/h5>
&lt;p>首先感谢父母吧，他们给了我足够的自由和支持，我才能够去追寻自己的梦想。还有老师们的指导，以及一些同学给予我生活和学习上的帮助。&lt;/p>
&lt;h5 id="最遗憾的事情">最遗憾的事情&lt;/h5>
&lt;p>虽然实现了很多年前的一些梦想，但其实还有挺多小遗憾的，最大的遗憾应该是没能去成 MSRA。&lt;/p>
&lt;h5 id="2019都有什么期盼">2019都有什么期盼&lt;/h5>
&lt;p>拿到心仪的 offer，踏实的学习，努力提升自己。
也不知道未来会何去何从，总之得做好准备，积极应对。
希望关心过和帮助过我的人都能有更好的运气~
2019 会更好！&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83092209-d5abb900-a06a-11ea-88dd-b2e903a14f36.jpg" alt="">&lt;/p>
&lt;hr>
&lt;p>2018 年 12 月 31 日
北京&lt;/p></description><category domain="https://blog.imfing.com/categories/%E7%94%9F%E6%B4%BB/">生活</category><category domain="https://blog.imfing.com/categories/%E9%9A%8F%E7%AC%94/">随笔</category><category domain="https://blog.imfing.com/tags/%E7%94%9F%E6%B4%BB/">生活</category><category domain="https://blog.imfing.com/tags/%E9%9A%8F%E7%AC%94/">随笔</category></item><item><title>Node + Express + MongoDB 服务端开发小结</title><link>https://blog.imfing.com/2018/12/node-express-mongodb/</link><guid isPermaLink="true">https://blog.imfing.com/2018/12/node-express-mongodb/</guid><pubDate>Sat, 22 Dec 2018 17:54:19 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>在刚过去的一次 Hackathon 里面，写了一个服务端的应用。不过由于时间关系，并没有写完 Orz。不过呢，也趁着这个机会学到了一些新的东西。下面就来具体介绍一下吧～&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83092515-77cba100-a06b-11ea-9c94-2550e158f660.jpg" alt="">&lt;/p>
&lt;!-- more -->
&lt;h2 id="服务端技术栈的选择">服务端技术栈的选择&lt;/h2>
&lt;p>服务端之所以选择 &lt;a href="https://nodejs.org">Node&lt;/a>，显而易见，是因为 JavaScript 圈的生态。之前也有用过 Python+Flask/Django 写过后端，有些地方就不如 Node，比如异步、回调、箭头函数等。&lt;a href="https://expressjs.com/">Express&lt;/a> 相当于 Python 里面的 Flask，能够简化一些服务端的写法，比如初始化服务、路由等。之前在用 Node 做项目的时候，数据库我一直避开了，因为考虑到自己的项目对存取没有太大要求，只需要方便操作就行了。因此我用了 &lt;a href="https://github.com/typicode/lowdb">lowdb&lt;/a>，一个基于 json 的本地数据库。你的数据库不大的话，那么用这样一个方式就可以了，而且读取 json 非常的便捷。此外还用过 SQLite，这是本地的 SQL 数据库，查询需要写 SQL。这次的项目里尝试了一下 &lt;a href="https://www.mongodb.com/">MongoDB&lt;/a>，一个非关系型数据库，基于对象和文档进行存储。&lt;/p>
&lt;h2 id="初始化node及express">初始化Node及Express&lt;/h2>
&lt;p>首先我们 &lt;code>$npm init&lt;/code> 一个空项目出来，添加 express 库 &lt;code>$yarn add express&lt;/code>，新建一个 &lt;code>server.js&lt;/code>，像下面一样添加几行代码，就能实现一个最基本的服务。访问 &lt;code>localhost:3000/&lt;/code> 就能看到 Hello World 的输出。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kr">const&lt;/span> &lt;span class="nx">express&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;express&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">express&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3000&lt;/span>
&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Hello World!&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">listen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">port&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`Listening on port &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">port&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">!`&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="初始化数据库">初始化数据库&lt;/h2>
&lt;p>首先我们需要在服务器或本地安装 MongoDB。我是在 Windows 平台上安装的，安装包体积大约有 500M，网上有很多教程，中间有一个步骤会问你是否需要安装 MongoDB Compass，建议可以安装一下，能够可视化的调试数据库。Windows 上的话需要手动去启动 MongoDB 的服务，服务默认的端口是 27017。在 Node 项目中，我们需要使用相关的库来对数据库进行操作，这里我用了 &lt;a href="https://mongoosejs.com/">Mongoose&lt;/a>。可以很优雅的连接 MongoDB 的数据库。我创建了一个 &lt;code>db.js&lt;/code> 模块，将数据库作为一个 module。可以看到我连接的是 MongoDB 里面的 &lt;code>test&lt;/code> 数据库。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kd">var&lt;/span> &lt;span class="nx">mongoose&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;mongoose&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kd">let&lt;/span> &lt;span class="nx">DB_URL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;mongodb://localhost:27017/test&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="nx">mongoose&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">connect&lt;/span>&lt;span class="p">(&lt;/span>
&lt;span class="nx">DB_URL&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">{&lt;/span> &lt;span class="nx">useNewUrlParser&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span> &lt;span class="p">}&lt;/span>
&lt;span class="p">);&lt;/span>
&lt;span class="nx">mongoose&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">connection&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">on&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;connected&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Mongoose connection open to &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">DB_URL&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">});&lt;/span>
&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">mongoose&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="项目结构">项目结构&lt;/h2>
&lt;p>组件化是很有必要的，特别是当项目逐渐变得复杂的时候。于是我用了这样一个结构，将各个功能之间的耦合程度降低。&lt;code>routes&lt;/code> 文件夹存放不同任务的路由，&lt;code>models&lt;/code> 文件夹存放 MongoDB 中对象的定义，&lt;code>controllers&lt;/code> 里面是对于不同类型数据的增删改查的操作。因此整个目录就像下面这样：&lt;/p>
&lt;pre>&lt;code>├── controllers
│ ├── taskController.js
│ └── userController.js
├── db.js
├── models
│ ├── task.js
│ └── user.js
├── package.json
├── routes
│ ├── task.js
│ └── user.js
├── server.js
└── yarn.lock
&lt;/code>&lt;/pre>&lt;h2 id="数据模型的构建">数据模型的构建&lt;/h2>
&lt;p>因为使用了 MongoDB，根据 Mongoose 提供的对象化的操作接口，需要将数据模型抽象出来。以 &lt;code>post.js&lt;/code> 为例，我们将 post 抽象出来，创建一个 schema，告诉数据库一个 post 都有哪几个部分，每个部分对应的类型。这样的好处是在数据库设计时就能考虑到不同数据之间的关系，并将它们嵌起来，比如 post 中嵌入评论段。MongoDB 的好处就是像对象一样管理数据，减少查询的次数。但值得注意的是，如果关系更复杂的话（之后我就遇到了这个问题），嵌套数据反而很麻烦。否则就得像传统 SQL 设计数据表一样，但会造成很多的信息冗余。这个问题可能是后期还需要进一步探索的。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kd">var&lt;/span> &lt;span class="nx">mongoose&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;mongoose&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">Schema&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">mongoose&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Schema&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kd">let&lt;/span> &lt;span class="nx">PostSchema&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Schema&lt;/span>&lt;span class="p">({&lt;/span>
&lt;span class="nx">id&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nb">String&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">title&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nb">String&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">type&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nb">String&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">author&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nb">String&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">sendTime&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">tags&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nb">String&lt;/span>&lt;span class="p">],&lt;/span>
&lt;span class="nx">content&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nb">String&lt;/span>
&lt;span class="p">});&lt;/span>
&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">schema&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">PostSchema&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">model&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">mongoose&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">model&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Post&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">PostSchema&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">};&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="数据控制器的实现">数据控制器的实现&lt;/h2>
&lt;p>对于不同的请求，我们需要根据请求的内容来对数据库进行存取，并返回对应的值。首先我们将数据库模型实例化，再根据后面路由调用的函数进行相应的操作。具体的查找筛选可以参考 mongoose 的文档。总之数据模型的层级不能太深，否则查找起来非常的难受，就会怀念 SQL。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kd">var&lt;/span> &lt;span class="nx">GroupInstance&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;../models/group&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">model&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">TaskInstance&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;../models/task&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">model&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="c1">// Task index
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="nx">exports&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">index&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;This is task index&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c1">// List all users
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="nx">exports&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">list_all&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">GroupInstance&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">find&lt;/span>&lt;span class="p">({},&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">items&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">tasks&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">items&lt;/span> &lt;span class="p">}));&lt;/span>
&lt;span class="p">};&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="路由的实现">路由的实现&lt;/h2>
&lt;p>这个还是相对比较简单的。需要用到不容的控制器，并定义不同路由所触发的对应控制器的函数。再将其导出成模块供我们主服务使用。像 &lt;code>task.js&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kd">var&lt;/span> &lt;span class="nx">express&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;express&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">router&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">express&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Router&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">taskController&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;../controllers/taskController&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">router&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">taskController&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">router&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/list-all&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">taskController&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">list_all&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">router&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/new&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">taskController&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">router&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="模块的整合">模块的整合&lt;/h2>
&lt;p>到最后，我们就能将所有的模块整合在一起，此时我们的主文件 &lt;code>server.js&lt;/code> 就应该如下。其中我们还使用了 &lt;code>body-parser&lt;/code> 模块，来解析 post 请求时发送的数据。引入不同的 router，并定义他们的前缀，这样就可以通过这样的方式进行请求：&lt;code>localhost:3000/task/new&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="kr">const&lt;/span> &lt;span class="nx">express&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;express&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">bodyParser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;body-parser&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">express&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="kr">const&lt;/span> &lt;span class="nx">port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3000&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="c1">// Parser middleware
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">use&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">bodyParser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">());&lt;/span>
&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">use&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">bodyParser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">urlencoded&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">extended&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&lt;/span> &lt;span class="p">}));&lt;/span>
&lt;span class="c1">// Declare routers
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="kd">var&lt;/span> &lt;span class="nx">indexRouter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./routes/index&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">userRouter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./routes/user&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="kd">var&lt;/span> &lt;span class="nx">taskRouter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./routes/task&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c1">// Use different routers
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">use&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">indexRouter&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">use&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">userRouter&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">use&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/task&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">taskRouter&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="nx">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">listen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">port&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`Listening on port &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">port&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">!`&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="调试热重载">调试热重载&lt;/h2>
&lt;p>我们不可能每次都手动去启动我们的服务，我们可以使用 &lt;code>nodemon&lt;/code> 模块来实现热重载，这样每次修改文件都能自动的启动服务。我们可以在 &lt;code>package.json&lt;/code> 里面这样写：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="s2">&amp;#34;scripts&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;dev&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;nodemon server.js&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样我们就可以用命令 &lt;code>yarn dev&lt;/code> 来启动一个开发版本的服务了。&lt;/p>
&lt;h2 id="总结">总结&lt;/h2>
&lt;p>以上就是我在短短 24 小时不到的时间里面学到的一些 Node 做后端的方法。当然，也遇到了很多的问题，特别是在数据库上。我认为在数据库的构建上需要多花点时间去考虑，MongoDB 有优势，也有一定的局限。Node 依靠 Javascript 的生态圈使其在网页开发上有了无可比拟的优势，在需要快速构建应用的场景，Node 不失为一个很好的选择。&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E5%90%8E%E7%AB%AF/">后端</category><category domain="https://blog.imfing.com/tags/javascript/">JavaScript</category><category domain="https://blog.imfing.com/tags/web/">Web</category></item><item><title>2018 IEEE ICIP 会议见闻</title><link>https://blog.imfing.com/2018/10/icip-2018/</link><guid isPermaLink="true">https://blog.imfing.com/2018/10/icip-2018/</guid><pubDate>Sat, 20 Oct 2018 19:50:17 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>非常幸运的参加了今年的 IEEE ICIP，在此我分享一下在这个国际会议上的所见所闻 🔥🔥🔥&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094208-2ae9c980-a06f-11ea-8a8d-a36c03fe32b6.jpg" alt="">&lt;/p>
&lt;!-- more -->
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>&lt;a href="https://2018.ieeeicip.org/">2018 IEEE International Conference on Image Processing&lt;/a>（简称 ICIP 2018），是图像处理国际会议，10 月 7 号 ——10 号在希腊雅典举办。&lt;/p>
&lt;p>在会议水平和规模上，自然不如 &lt;a href="http://cvpr2018.thecvf.com/">&lt;strong>CVPR&lt;/strong>&lt;/a> 和 &lt;a href="http://iccv2019.thecvf.com/">&lt;strong>ICCV&lt;/strong>&lt;/a>。更不巧的是今年的 &lt;a href="https://eccv2018.org/">&lt;strong>ECCV&lt;/strong>&lt;/a> 正好在 ICIP 的前一个月，因此能来的大佬就少了。当然，这并不妨碍我这种弱鸡去学习一下，看看图像处理领域不同的论文和发展近况。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094209-2ae9c980-a06f-11ea-8cb1-c66a8a5208e5.jpg" alt="">&lt;/p>
&lt;h2 id="举办地">举办地&lt;/h2>
&lt;p>今年举办地在希腊的 &lt;strong>雅典&lt;/strong>。因为在欧洲，所以会议上有很多的都是来自欧洲学校的研究者。当然，这也并不妨碍中国学者们不远万里前来参会，因为雅典和圣托里尼岛也是不错的旅游目的地。&lt;/p>
&lt;p>希腊在 2010 年爆发了经济危机，8 年过去了，希腊渐渐走出了经济衰败的阴影。走在雅典街头，或多或少还能看到一些混乱颓败的踪迹。在会议上，有国内来的老师和我们说有好几个人在 &lt;strong>雅典被偷了钱&lt;/strong>，让我们与会的一些同学非常不安。雅典也算是一个旅游城市，在地铁上广场上餐厅中，都能见到各国的游客，当看见这些游客，会有些许的安心。&lt;/p>
&lt;p>来到希腊，雅典卫城是一个必去的地方。公元前文明的残骸，在这里得以体现。特别是经过了欧洲动荡的中世纪，只有些许断垣得以残存，令人唏嘘。会让人不由的联想到英法联军火烧圆明园，文明的瑰宝被毁于一旦。&lt;/p>
&lt;p>虽说举办地与学术本身并无关联，但 &lt;strong>能够在科研之余领略不同国家的风土人情&lt;/strong>，也是国际会议的重要一部分。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094207-2a513300-a06f-11ea-8944-9d77673b3309.jpg" alt="">&lt;/p>
&lt;h2 id="会议议程">会议议程&lt;/h2>
&lt;p>会议虽说是 4 天，但第一天基本可以忽略不记。7 号这天是 &lt;a href="https://2018.ieeeicip.org/Tutorials.asp">Tutorial&lt;/a> 的环节，就是会议请了一些教授来进行长时间的授课，有不同的主题，应该是关于某一具体领域。当然，这些都是 &lt;strong>额外付费的&lt;/strong>，&lt;strong>贫穷&lt;/strong> 的我并没有注册这些。在 Tutorial 完了之后下午 5 点是报道注册领取会议材料，以及一个欢迎晚宴。因为担心自己的人身安全，很多人 7 点中吃的差不多就离开了会场。&lt;/p>
&lt;p>之后的三天就比较类似了。上午 9 点开始有一个一小时的专题 talk，之后外面进行 Poster 环节，上下午分别两场，不同的会议厅里举行不同专题 Oral 的报告。中间有茶歇，提供饼干等糕点，以及饮料。&lt;/p>
&lt;p>其实整个会议的信息量还是挺大的，&lt;strong>41 个 Lecture Session&lt;/strong>，&lt;strong>86 个 Poster Session&lt;/strong>。外面有大概三四十张不同的 Poster，按类别分好区域，因此你可以挑选自己喜欢的主题去看。&lt;/p>
&lt;p>会议厅里我就去的相对少一些，虽然 Oral 的论文质量可能会相对更高一点，但是因为讲整个 PPT 的时间也会很长，所以我还是选择在外面围观海报，会更有意思一些。&lt;/p>
&lt;p>会议的论文已经在 &lt;a href="https://ieeexplore.ieee.org/xpl/mostRecentIssue.jsp?punumber=8436606">&lt;strong>IEEE Xplore&lt;/strong>&lt;/a> 上有了，可以去检索相应的论文了。下面就挑选一些，我在整个会议里面看到的一些还算有意思的展示吧。&lt;/p>
&lt;h2 id="icip-内容节选">ICIP 内容节选&lt;/h2>
&lt;p>首先是 ICIP 2018 的统计数据，1753 的提交，839 的通过，最后的接收率将近 50%。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094340-7c925400-a06f-11ea-833f-ca5cfda3115a.jpg" alt="ICIP 2018 Statistics">&lt;/p>
&lt;p>我们来看一下不同领域接收率的情况，可以看到 IVIU 的通过率最高 28%&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094386-92077e00-a06f-11ea-8870-2cd6fa996d2e.jpg" alt="">&lt;/p>
&lt;p>这是第一天上午的 Talk，来自南加州大学的一个韩国人讲 CNN。其实讲的是自己的研究内容。不用反向传播，只用正向（类似滤波器设计）去设计卷积网络，当然只能设计一些很简单的了，他说好处是可以抵御对抗样本的攻击。反正我觉得有点扯。&lt;/p>
&lt;p>&lt;img src="hhttps://user-images.githubusercontent.com/5097752/83094465-b95e4b00-a06f-11ea-9b93-e6d2fbe8924d.jpg" alt="">&lt;/p>
&lt;p>之后就碰到了一个学校的了。遥感院的学长，做的还是图像拼接。这一块我不是很熟，但和学长聊了一下这一届的会议。后来中午一起去吃了东西。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094522-da26a080-a06f-11ea-9dc4-9e7f096bf652.jpg" alt="">&lt;/p>
&lt;p>非常热闹的第一天的 Poster Session&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094552-ec084380-a06f-11ea-8774-63c7ae352edd.jpg" alt="">&lt;/p>
&lt;p>下午有 VQA 的 Session，我就围观了一下。这个是电子科大的一个老师提出的一种带权重且考虑到排序的相关系数。和常见的 SRCC 在一些特定情况下对衡量准确度进行了比较，而且因为有权重，因此可以自己设计符合特定任务的标准（比如正序最不好），如下面图中画出的曲线。具体想要拿来用还是得看论文。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094587-02160400-a070-11ea-9fe0-ba546131ce66.jpg" alt="">&lt;/p>
&lt;p>下面是一个屏幕内容的评价。考虑到了 Edge，也就是边缘信息。并且魔改了一下 SSIM，把 Edge 信息加入，提升了一下屏幕内容质量评价的效果。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094652-2e318500-a070-11ea-98f0-48d642dd8f19.jpg" alt="">&lt;/p>
&lt;p>一个日本团队做的，很有意思的一个项目。简而言之，优化图片来提升商品的用户点击率。挺有商业前景的。像在淘宝上，一般我就是看图片好不好看决定点进去看商品详情与否。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094653-2eca1b80-a070-11ea-9358-4de4a12170b4.jpg" alt="">&lt;/p>
&lt;p>下面这个是提取视频中文档边界。用的是数学形态学的方法。可能效率上会更好一些。当然，现在文档边界检测的应用很常见了，如微软的 Office Lens，扫描全能王等。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094654-2f62b200-a070-11ea-9901-96e8de791419.jpg" alt="">&lt;/p>
&lt;p>在 7 号上午认识的一个剑桥的博士，恰巧也是做质量评价方向的。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095119-a304bf00-a070-11ea-8328-d5ab69c9cde9.jpg" alt="">&lt;/p>
&lt;p>做图像增强的。让我想起了数字图像处理课让我们探究的均衡化的方法。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095122-a39d5580-a070-11ea-851b-67a5e1f89607.jpg" alt="">&lt;/p>
&lt;p>一个印度小哥的作品，讲真挺水的。我觉得和我数字图像处理课程设计有的一拼。其实就是根据文本去做图像选择与风格转换，可能之前没什么人做这个问题吧。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095224-c7609b80-a070-11ea-8014-b1f08ed096d2.jpg" alt="">&lt;/p>
&lt;p>做一个课题的韩国小哥，英语表达能力太差，基本听不懂他讲的。好在前一天晚上看了一下论文。答题就是用 Pairwise 的方法去做这个任务，把分类问题变成比较问题。用了一个类似 Focal Loss 的损失函数。可以同时做 Ranking 以及 Classification 的任务。最后的指标也还可以。其实类似的点子之前我也有想到过，但是因为我太菜了，没实现出来 Orz&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095226-c7f93200-a070-11ea-94bb-875991a0967d.jpg" alt="">&lt;/p>
&lt;p>因特尔做的一个工作，用进化算法来自动调节 ISP 的参数。这个挺有意思的，让我想到了，很多时候不同品牌手机用的感光元件可能是同一个型号，但是拍出来照片的质量会有差别，原因就在于 ISP 的调教。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095299-00007500-a071-11ea-8ae5-aff044f0edcb.jpg" alt="">&lt;/p>
&lt;p>又看到了校徽。右边是做 SR 的。看标题是考虑到了区域的影响。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095335-16a6cc00-a071-11ea-936d-64dc07e8725f.jpg" alt="">&lt;/p>
&lt;p>这个任务做的是水底视频中海洋生物的探测。这个 Paper 关注了数据增强的方法，用 GAN 来生成类似的图片。其实应该有类似的工作把 GAN 用到 Data Augmentation。其实我一直对把 GAN 生成的图片作为 Ground Truth 训练图片这个表示一些疑虑。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095363-2aeac900-a071-11ea-975e-d7f5c5259028.jpg" alt="">&lt;/p>
&lt;p>下面这个纯粹只是在中间的图中看到了熟悉的 U-Net、SPP、DeepLab，后两者在物体识别领域用到，前者是在 Conditional Generative Adversarial Network 里面用到了。U-Net 的 Shortpath 可能让其在图像生成上会有更好的表现。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095428-540b5980-a071-11ea-9192-71efea05e1da.jpg" alt="">&lt;/p>
&lt;p>隔壁华科的。做目标追踪的。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095471-671e2980-a071-11ea-8465-4d2257603f67.jpg" alt="">&lt;/p>
&lt;p>在景区门口认识的一个清华电子系小哥帮站的。就是考虑到旋转等变形，先把它变回去。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095496-743b1880-a071-11ea-9581-c33eb6a91cd0.jpg" alt="">&lt;/p>
&lt;p>一个好看的法国小姐姐。Poster 也挺好看。用了 U-Net 和 Patch-GAN，提出了一个深度估计的 Loss，和一堆其它 Loss 比较。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095520-8321cb00-a071-11ea-97b7-9d3218ba18f2.jpg" alt="">&lt;/p>
&lt;p>上海交通大学，图像鉴黄。数据集是 Self-collected 的，哈哈。随后被媒体报道：&lt;a href="https://mp.weixin.qq.com/s/vTtKUagiFD55mcQIeGDxQw">《ICIP2018 | 图像鉴黄做得好，健康上网少烦恼》&lt;/a>，很有意思了。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095534-8fa62380-a071-11ea-8953-2c9b3373119b.jpg" alt="">&lt;/p>
&lt;p>这个做的是车辆类型识别，而且据说已经在法国很多地方部署了。方法不难，就是用了 CNN，比较有借鉴意义的就是裁剪网络来解决过拟合的问题。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095556-9df43f80-a071-11ea-9843-0becf62cdbf1.jpg" alt="">&lt;/p>
&lt;p>下面这个是用小波变换做识别的。他被问的最多的就是：“为什么不用 CNN？”，传统方法在 2018 年的会议上可以说是一股清流了。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095586-afd5e280-a071-11ea-9fd5-1cc513dc143d.jpg" alt="">&lt;/p>
&lt;p>这个课题很有意思，用照片做 PM2.5 的估计。虽然听起来有点扯淡，不过给不用专业仪器来快速估计 PM2.5 提供了一个新思路。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095618-bd8b6800-a071-11ea-8e58-dd027c0b61b9.jpg" alt="">&lt;/p>
&lt;p>雨滴检测问题。用了滑动窗口去代替传统的 Region Proposal（因为雨滴形状色泽等多变），裁剪了 CNN。英国小哥的英式发音很有辨识度。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095633-c8de9380-a071-11ea-8619-9f1b19ff20d4.jpg" alt="">&lt;/p>
&lt;p>这个做的是对抗样本攻击。原理很简单，简而言之就是给图片打补丁来让 CNN 产生错误的分类结果。而这些干扰信息量很小。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095781-14913d00-a072-11ea-9c7e-e89b39747d26.jpg" alt="">&lt;/p>
&lt;p>一个年长的日本教授，激动的给我展示了视频去噪的效果。虽然 Poster 上全是公式，但他讲解的还是挺通俗的。用了 3D-DFT，因此算法会非常慢，主要是求解逆傅里叶很慢，不过效果还是很 OK 的。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095815-28d53a00-a072-11ea-95c3-3d9d8fd9cd7c.jpg" alt="">&lt;/p>
&lt;p>来自挪威大学小哥，是个中国人。之前在 VQA 的相关 Session 经常看到他，一开始以为是日本人来着。最后我们的 Poster 在同一场。也算是缘分了。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095840-38ed1980-a072-11ea-968b-b67786a10f88.jpg" alt="">&lt;/p>
&lt;p>这个意大利小哥，做的是有人脸图片的美学评估。方法和我的基本类似。更巧的是我们最后一天同一班飞机去了罗马。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095868-486c6280-a072-11ea-8323-ed9e0076b30d.jpg" alt="">&lt;/p>
&lt;h2 id="我的展示">我的展示&lt;/h2>
&lt;p>其实轮到我的时候已经是最后一天下午了，人并没有第一天的那么多。&lt;/p>
&lt;p>Poster 做的略微小了一些，比例其实也不应该按 CVPR 的 2:1 来做的。被问到最多的一个问题就是我的 Local View 是怎么来的。反正在现场被各种人问了之后对自己的工作会有更深刻的理解。也会有很多人来提出潜在的改进的方法。&lt;/p>
&lt;p>碰到几个教授问我是博士后还是博士，包括国内来的，他们都挺惊讶我是本科的。还有一个 Session Chair 说他的学生在做这个课题，但一直没有取得什么进展。忘记在 Poster 上留邮箱了。&lt;/p>
&lt;p>印象最深的是一个 ARM 来的小姐姐，和我说应该趁机会看看他们胸牌上面的名字，然后去 LinkedIn 或者直接去 Social 一下，说不定对以后读 Grad 有帮助。其实会议本身就是一个 Social 的平台，我也有机会认识了很多人。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095675-d6941900-a071-11ea-8c13-c63b6af8333c.jpg" alt="">&lt;/p>
&lt;h2 id="感想">感想&lt;/h2>
&lt;p>通过参加这个会议，可以看到各种不同图像处理领域最新的进展和成果。你也可以比较容易的去理解一些新点子和方法，它们其实都是一些非常简单的一些小创意。我们可以尝试去借鉴这些点子，然后把它运用到自己今后的研究工作中去。&lt;/p>
&lt;p>在这个 “人工智能” 一词盛行的今天，在 CNN 占领了半壁江山的图像处理会议，还是要向那些坚持使用传统图像处理 / 数学方法、忠于理论研究的研究工作者表示致敬。&lt;/p>
&lt;p>会议很大部分人都是来社交的，同一个研究领域，不同国家的教授互相认识，汲取新的一些点子。作为学生也能够认识其它学校做同一个领域的同学并互相交流。这些就是会议最大的意义。&lt;/p>
&lt;p>非常怀念会议茶歇的 &lt;strong>希腊酸奶 + 果酱 + 蜂蜜&lt;/strong>。实在是好吃 😋&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83095902-59b56f00-a072-11ea-9659-032130033b29.jpg" alt="">&lt;/p></description><category domain="https://blog.imfing.com/categories/%E7%94%9F%E6%B4%BB/">生活</category><category domain="https://blog.imfing.com/categories/%E9%9A%8F%E7%AC%94/">随笔</category><category domain="https://blog.imfing.com/tags/%E9%9A%8F%E7%AC%94/">随笔</category></item><item><title>我的 GRE 备考 + TOEFL 复习建议</title><link>https://blog.imfing.com/2018/09/gre-toefl/</link><guid isPermaLink="true">https://blog.imfing.com/2018/09/gre-toefl/</guid><pubDate>Tue, 25 Sep 2018 23:26:20 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>GRE 和 TOEFL 托福备考经验 + 一些自己的复习建议。文末附赠我整理的资料大礼包。&lt;/p>
&lt;!-- more -->
&lt;h2 id="gre">GRE&lt;/h2>
&lt;p>上周末考完了 GRE，特此总结一下。当然，在这也分享一下我觉得有用的资料。&lt;/p>
&lt;p>首先来简单介绍一下 GRE 这个考试，GRE 全称是 Graduate Record Examinations，对于大多数专业录取需要的是 GRE General Test 的成绩。General Test，顾名思义，不会涉及和专业背景相关的任何知识。&lt;/p>
&lt;p>GRE 一共分三个不同的部分：&lt;strong>Analytical Writing（写作）&lt;/strong>、&lt;strong>Verbal Reasoning（语文）&lt;/strong>、&lt;strong>Quantitative Reasoning（数学）&lt;/strong>。写作部分需要写两篇，第一篇（Issue）是根据一个话题来写，第二篇（Argument）是去驳斥一个已有的论述，共计 1 个小时。Verbal 和 Quant 每个部分分别 30 和 35 分钟，但会以 VQVQA 或 QVQVQ 的顺序出现（其中有一个加试，不计入总分）。&lt;/p>
&lt;h3 id="词汇">词汇&lt;/h3>
&lt;p>我觉得 GRE 最重要的就是单词了，单词背不好后面Verbal有一半的题根本没有办法做。比较经典的是 &lt;strong>GRE3000词（陈琦再要你命3000）&lt;/strong>，涵盖了GRE常考的单词。网上经常有那种“21天突破GRE单词”这样的说法，我觉得 21 天很难去掌握 3000 个单词的。根据我的经验，3000 个词大约有 80%+ 都是生词，需要循序渐进的去背。相比较用纸质书籍，我&lt;strong>更推荐用手机 APP 去背单词&lt;/strong>，这样可以利用步行、公交车、排队的时间去背。我用的是&lt;a href="https://www.maimemo.com/">&lt;strong>墨墨背单词&lt;/strong>&lt;/a>，这是我用过所有里面记的最牢的一个背单词软件了，记忆曲线非常合理，而且&lt;strong>没有广告&lt;/strong>，不过单词上限需要购买。我大概背了 40 天左右，每天打卡。前 3 周每天 150-200 词，考试前两周每天300+词。每天固定的学习量，如 200 词，包含了新词+复习词汇，这样不会有太大的复习压力。墨墨里面的助记部分有前人总结的 GRE 释义和记忆方法，非常有用。&lt;/p>
&lt;p>此外还有新东方的佛脚词汇，2000 词左右，少部分单词再要你命 3000 没有涉及到。它的注释比较好，按 GRE 考点来注释，可以作为复习和回顾使用。&lt;/p>
&lt;p>3000 词是一个方面，此外还有 &lt;a href="https://gre.magoosh.com/flashcards/vocabulary">Magoosh GRE Flashcards&lt;/a> 的 APP，总共大概有 1000 词，有少部分 3000 词没有涵盖到，可以作为 3000 词的一个补充。&lt;/p>
&lt;p>&lt;strong>词典&lt;/strong>：GRE 考试的标准参考是&lt;strong>韦氏词典&lt;/strong>（&lt;a href="https://www.merriam-webster.com/">Merriam-Webster&lt;/a>），有在线版的可以查，App 也可以免费用。不过是英-英释义，有少量广告。
我用的是手机版&lt;a href="https://www.eudic.net/v4/en/app/eudic">&lt;strong>欧路词典&lt;/strong>&lt;/a>，可以导入第三方扩充词典，因此就可以使用和谐版的韦氏词典、朗文词典等。Mdict 格式的词典文件我会放在我的网盘里。&lt;/p>
&lt;h3 id="verbal">Verbal&lt;/h3>
&lt;p>&lt;strong>在开始练习之前，务必背好 80% 的 3000 词&lt;/strong>&lt;/p>
&lt;p>我非常推荐看&lt;strong>Magoosh 的视频教程&lt;/strong>，把各种题型和方法讲解的非常透彻。国内比较经典的有&lt;strong>陈琦（琦叔）的视频&lt;/strong>。看完这些视频之后，你可以对 Verbal 填空部分各个题型有所了解，掌握他们的基本套路。如果想看书的话，&lt;strong>陈圣元的填空可以看一下&lt;/strong>，但是里面的题目相对比较老，不推荐做。&lt;/p>
&lt;p>Verbal 部分比较注重的就是&lt;strong>逻辑&lt;/strong>，你所选择的词都是要有理有据的，而不是你根据上下文臆测出来的，用 Magoosh 视频里面的说法是叫做&lt;strong>Clue（线索）&lt;/strong>，你需要在题干中找到线索，再根据句子结构判定是反义还是同义，最后得出选项。&lt;/p>
&lt;p>&lt;a href="https://gre.magoosh.com/plans">Magoosh&lt;/a>的题是非常好用的，就是订阅太贵了（150 美元 6 个月），如果几个人一起备考可以分享。&lt;/p>
&lt;h4 id="填空练习题">填空练习题&lt;/h4>
&lt;p>&lt;strong>《陈琦GRE填空基础24套》&lt;/strong> 可以用来巩固 3000 词，题目也不是非常难。24 套做的感觉差不多之后，可以选择性的做一下《陈琦GRE填空强化36套》。我当时时间紧，所以只刷了基础 24 套的80%。接着可以开始做机经题了。&lt;/p>
&lt;p>比较经典的机经题是 &lt;strong>《GRE填空机经1000题难度分级版（第1版）》&lt;/strong>，俗称填空 1100，里面难度有分级。以我的感觉里面的 Hard 部分比真实考试是要略难一些的，而且有些出题思路有点怪。我大概到考前只做了 60%。&lt;/p>
&lt;p>考试之前，推荐做一下&lt;strong>官方150题&lt;/strong>，和真实的题目非常非常接近了。在被上面的题目虐过之后你再去做官方题会觉得非常的简单。&lt;/p>
&lt;h4 id="阅读练习题">阅读练习题&lt;/h4>
&lt;p>经典的入门基础练习题就是 &lt;strong>《陈虎平新GRE阅读36套》&lt;/strong>，俗称 36 套。相对来说比较简单，如果时间不是非常够的话可以跳过。&lt;/p>
&lt;p>进阶的就是&lt;strong>陈琦24套&lt;/strong>。此外比较好的还有陈琦200篇，相当于一个大型题库。机经的话有&lt;strong>考满分200篇&lt;/strong>。&lt;/p>
&lt;p>对于阅读部分的&lt;strong>逻辑单题&lt;/strong>，也有&lt;strong>陈虎平逻辑10套&lt;/strong>供你练习。&lt;/p>
&lt;p>多说一句，阅读部分还是要多练习，我就是花了太多时间在填空部分导致 Verbal 的阅读部分的题目没有准备充分。&lt;/p>
&lt;h3 id="quant">Quant&lt;/h3>
&lt;p>数学部分比较简单，基本只要读懂题目的话就很轻松了。有必要的话还是背一下数学部分的单词。然后做两套题就能上考场了。35 分钟的时间绰绰有余，一般都能拿 169-170。&lt;/p>
&lt;h3 id="aw">AW&lt;/h3>
&lt;p>GRE 作文题都是&lt;strong>有题库的&lt;/strong>，因此可以提前准备。&lt;/p>
&lt;p>Issue 题干一般一两句话。注意问题的问法。发表自己的观点要有例子支撑。&lt;/p>
&lt;p>Argument 一般是篇幅较长的论述。你需要找出 2-3 点来针对性的驳斥。要有理有据，论证要有逻辑。和高中的议论文差不多。&lt;/p>
&lt;p>我提供的资料里面有作文的思路和相应的模板。&lt;/p>
&lt;h3 id="模考">模考&lt;/h3>
&lt;p>官方一共有两套模考题 &lt;strong>PowerPrep&lt;/strong>，俗称 PP2。适合在考试前一周做。现在是&lt;a href="https://www.ets.org/gre/revised_general/prepare/powerprep/">在线的网页版&lt;/a>，可以直接到 GRE 官网注册账号选购（免费）。&lt;/p>
&lt;p>这个据说是&lt;strong>比较接近真实考试的成绩&lt;/strong>（？？？）。反正我两次做的时候都超困，第一套 V147（考试前3天），第二套 V143（前一天），考试 V153。&lt;/p>
&lt;h2 id="toefl">TOEFL&lt;/h2>
&lt;p>托福相对来说我复习的时间就比较少了。不过还是稍微提一下。&lt;/p>
&lt;h3 id="听力">听力&lt;/h3>
&lt;p>托福听力部分我没什么特别的经验，还是&lt;strong>多做模拟题&lt;/strong>。&lt;a href="https://toefl.kmf.com/">考满分&lt;/a>上可以做听力题，掌握一下题型和出题套路就 OK 了。&lt;/p>
&lt;h3 id="阅读">阅读&lt;/h3>
&lt;p>在做过 GRE 的阅读之后，托福的阅读就相对比较简单了。不过还是要做几套题来掌握一下题型和做题的方法。&lt;/p>
&lt;h3 id="口语">口语&lt;/h3>
&lt;p>口语的题型和主题比较固定，因此可以准备的很好。不能太依赖模板，还是要注重复述听力/文章内容以及发表自己的观点。&lt;/p>
&lt;p>这个我有认真准备但是最后考爆了。考场发挥还是很重要的。推荐用考满分网页或 APP 去练习，可以听听别人怎么组织答案的。&lt;/p>
&lt;h3 id="作文">作文&lt;/h3>
&lt;p>第一篇是读文章，听听力，然后写听力里面是怎么驳斥文章的。比较死板，可以准备模板，按部就班写就行。&lt;strong>写 350+ 词&lt;/strong>。&lt;/p>
&lt;p>第二篇和 GRE Issue 差不多，可以按 GRE 的套路写。观点不一定要有 3 个，但注意&lt;strong>一定要有例子&lt;/strong>支撑你的观点。&lt;strong>写450+词&lt;/strong>。&lt;/p>
&lt;p>可以提前准备模板，字数宜多，但注意不要有太多语法错误。&lt;/p>
&lt;h2 id="资料下载">资料下载&lt;/h2>
&lt;p>&lt;a href="https://pan.baidu.com/s/1qnbZy_aVMTDUWGGR9rUBYw">&lt;strong>以上资料下载地址&lt;/strong>&lt;/a>，提取码: vav3&lt;/p>
&lt;p>&lt;a href="http://www-personal.umich.edu/~xiaoqiz/GRE.html">其它参考&lt;/a>&lt;/p></description><category domain="https://blog.imfing.com/categories/%E7%94%9F%E6%B4%BB/">生活</category><category domain="https://blog.imfing.com/categories/%E8%8B%B1%E8%AF%AD/">英语</category><category domain="https://blog.imfing.com/tags/%E8%8B%B1%E8%AF%AD/">英语</category></item><item><title>2018 微软 Build 大会第二日看点</title><link>https://blog.imfing.com/2018/05/build-2018-day2/</link><guid isPermaLink="true">https://blog.imfing.com/2018/05/build-2018-day2/</guid><pubDate>Wed, 09 May 2018 12:40:17 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>第一天的微软 Build 大会主要聚焦于企业和云服务上，那么第二天的主题 Microsoft 365 就是和我们普通消费者和开发者密切关联了。下面一起来看看第二天都有哪些亮点。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668954-ccf14080-a59e-11ea-8101-e6dc4eb8b60d.jpg" alt="">&lt;/p>
&lt;!-- more -->
&lt;h1 id="前言">前言&lt;/h1>
&lt;p>今年的 Build 大会于美国当地时间5月7日举行，一共有三天。第一天主要内容是 CEO 纳德拉讲云服务和企业服务，和我们关系不大，我就提前睡觉了。&lt;/p>
&lt;p>第二天的内容关于 Windows，下面为你带来精彩亮点。&lt;/p>
&lt;h1 id="spotlights">Spotlights&lt;/h1>
&lt;h2 id="microsoft-365">Microsoft 365&lt;/h2>
&lt;p>第一次听到 Microsoft 365 这个说法， 其实就是：&lt;/p>
&lt;p>Microsoft 365 = Windows + Office + EMS (Enterprise Mobility Suite)&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668957-cfec3100-a59e-11ea-8eab-950e6a1ad47e.jpg" alt="">&lt;/p>
&lt;h2 id="手机时间轴">手机时间轴&lt;/h2>
&lt;p>Timeline in Phone，把 Windows 的时间轴体验带到了移动端。这样就能随时随地管理你在 PC 上的工作。&lt;/p>
&lt;p>如果是微软官方的应用的话，比如 Office、Outlook 之类，应该还能够支持直接在手机上进行处理。不仅如此，如果你有多台设备（笔记本、台式机等），使用同一个微软账户，就能在各个设备上同步你的时间轴。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668974-d5497b80-a59e-11ea-9308-2b993a5ff1a8.jpg" alt="">&lt;/p>
&lt;h2 id="你的手机应用">你的手机应用&lt;/h2>
&lt;p>这个功能是用来解决这样一个场景：你在电脑上工作，突然手机来了消息，会打断你的工作。这时候为了不中断你的工作，在你登录了微软账户的手机上可以把通知信息（短信、应用等）直接推送到 PC 上，进而直接在 PC 上处理你的消息。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668993-db3f5c80-a59e-11ea-875e-f1d8ac595614.jpg" alt="">&lt;/p>
&lt;p>同时，它还能够直接管理你手机中的照片等内容。非常方便。&lt;/p>
&lt;h2 id="标签集-sets">标签集 Sets&lt;/h2>
&lt;p>Sets 这个新的特性应该是整场的亮点了。&lt;/p>
&lt;p>&lt;strong>痛点问题：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>“把一个项目中涉及到的网页内容和应用内容整合到一起非常难。”&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669021-e72b1e80-a59e-11ea-9ee2-6a4825cde2fb.jpg" alt="">&lt;/p>
&lt;ul>
&lt;li>“有时候我有几百个文件夹，我会搞不清有哪些东西在这些文件夹里面。”&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669049-ef835980-a59e-11ea-9f18-5db367816b05.jpg" alt="">&lt;/p>
&lt;ul>
&lt;li>“重新把我上次离开的工作所需要的材料再组织起来需要很长的时间。”&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669055-f14d1d00-a59e-11ea-9160-a882a8dcd44d.jpg" alt="">&lt;/p>
&lt;ul>
&lt;li>“如果我暂停做这件事一个礼拜，那之后我就会忘记这个组合。我觉得电脑是可以帮我记住它们的。”&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669064-f27e4a00-a59e-11ea-85ed-5b13e106aa5f.jpg" alt="">&lt;/p>
&lt;p>&lt;strong>解决方案&lt;/strong>&lt;/p>
&lt;p>核心观念：把相关联的内容放在一起&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669085-f90cc180-a59e-11ea-8e21-e59a43c64ed7.jpg" alt="">&lt;/p>
&lt;p>Sets，用标签来管理不同的内容，就像你日常使用浏览器一样。比如，你在 Word/PDF 中打开一个外部链接，就可以直接在新标签页中显示出来。&lt;/p>
&lt;p>当然，你可以把不同的应用组合起来，比如浏览器、PDF 阅读器、Word、PPT 等。应该能够支持大部分传统Win32应用。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669096-fdd17580-a59e-11ea-9bf2-7be775b830e0.jpg" alt="">&lt;/p>
&lt;p>在你关闭了一组标签之后，下次再打开其中的一个应用，会自动提醒你打开相关的页面、应用。因此你可以非常方便的恢复上次的工作。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669122-088c0a80-a59f-11ea-821f-d5a67b4b26bb.jpg" alt="">&lt;/p>
&lt;p>当然，时间轴中是支持标签组的管理。你可以方便的追溯之前打开过的标签组。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669135-0e81eb80-a59f-11ea-994c-a3395c02e6b1.jpg" alt="">&lt;/p>
&lt;h2 id="uwp-xaml-islands">UWP XAML Islands&lt;/h2>
&lt;p>简而言之，原本 UWP 才能够使用的一些界面控件，现在可以直接在 WPF 和 Win32 应用中使用了。&lt;/p>
&lt;p>这样，有些就能够很容易集成进去：Edge 网页容器、手写笔迹区域等。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669176-2194bb80-a59f-11ea-8f4d-82bdf80e52ae.jpg" alt="">&lt;/p>
&lt;p>当然，是支持流畅设计的（Fluent Design）&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669180-235e7f00-a59f-11ea-9158-cff5e6697880.jpg" alt="">&lt;/p>
&lt;h2 id="windows-ui-library">Windows UI Library&lt;/h2>
&lt;p>这个应该是新的 UI 组件库。支持 Win10 周年更新及以上。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669194-2a858d00-a59f-11ea-8c2e-670d8c3d84ac.jpg" alt="">&lt;/p>
&lt;h2 id="net-core-30">.NET Core 3.0&lt;/h2>
&lt;p>加快了桌面端 .NET 和 .NET Core 的统一步伐。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669229-35d8b880-a59f-11ea-91fe-570d368e1c95.jpg" alt="">&lt;/p>
&lt;h2 id="msix格式">MSIX格式&lt;/h2>
&lt;p>为了解决应用分发、更新等问题，推出了一个新的统一的格式。用来统一 UWP 应用和传统 Win32 应用的打包。应该在不久就能看到这种格式的安装包了。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669249-40934d80-a59f-11ea-9c23-476490b38af9.jpg" alt="">&lt;/p>
&lt;h2 id="记事本支持linux换行">记事本支持Linux换行&lt;/h2>
&lt;p>延续几十年的 Bug，现在记事本终于支持Linux的 &lt;code>\n&lt;/code> 换行了（Windows 默认 &lt;code>\r\n&lt;/code>）&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669251-41c47a80-a59f-11ea-90a1-eb0888f58fca.jpg" alt="">&lt;/p>
&lt;h2 id="应用收入提升">应用收入提升&lt;/h2>
&lt;p>对开发者的应用收入分成进行了调整，如果用户在应用商店里搜到了你的应用，那是 85% 的收益；如果是直接跳转到商店的，就是 95%。&lt;/p>
&lt;p>（相比较原来的 70% 好很多了！&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669269-46892e80-a59f-11ea-8fa5-943438ed3383.jpg" alt="">&lt;/p>
&lt;h1 id="总结">总结&lt;/h1>
&lt;p>以上就是主要内容了。总之 Windows 本身以及其对开发者的友好度是在不断提升的。 😸&lt;/p>
&lt;p>Let's wait and see. ✌&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669288-4d17a600-a59f-11ea-9b2a-429399d3bab1.jpg" alt="">&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E8%B5%84%E8%AE%AF/">资讯</category><category domain="https://blog.imfing.com/tags/%E8%B5%84%E8%AE%AF/">资讯</category><category domain="https://blog.imfing.com/tags/%E5%BE%AE%E8%BD%AF/">微软</category></item><item><title>Facebook 开发者大会精彩看点</title><link>https://blog.imfing.com/2018/05/facebook-f8-2018/</link><guid isPermaLink="true">https://blog.imfing.com/2018/05/facebook-f8-2018/</guid><pubDate>Thu, 03 May 2018 20:30:38 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>之前并没有关注 Facebook 的开发者大会，但是今年的 F8 大会着实让人眼前一亮，特别是在人工智能领域。
来看看都有哪些精彩之处~&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668330-c4e4d100-a59d-11ea-9f4f-da486bda0003.jpg" alt="">&lt;/p>
&lt;!-- more -->
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>经历了前段时间的数据泄露时间风波后，Facebook 今年的 F8 开发者大会于 5 月 1 日在加州圣何塞 McEnery 会议中心开幕。&lt;/p>
&lt;p>大会官网：&lt;a href="https://www.f8.com/">https://www.f8.com/&lt;/a>&lt;/p>
&lt;p>大会视频链接：&lt;a href="https://developers.facebook.com/videos">https://developers.facebook.com/videos&lt;/a>&lt;/p>
&lt;p>&lt;strong>温馨提示&lt;/strong>：需要使用科学上网方能观看 😉&lt;/p>
&lt;p>本周末也会有 &lt;a href="https://events.google.com/io/">Google IO 2018&lt;/a> 和 &lt;a href="https://www.microsoft.com/en-us/build">Microsoft Build 2018&lt;/a>，可以关注一下~&lt;/p>
&lt;h2 id="day-1-产品">Day 1 产品&lt;/h2>
&lt;h3 id="安全">安全&lt;/h3>
&lt;p>在经历了之前的风波之后，Mark 一开始就强调了 FB 将会更加重视技术的正确使用。特别是在政治领域的更加严格的验证和筛选。对于广告也启用了工具使得他们更加透明。对于虚假新闻加大打击力度。&lt;/p>
&lt;p>更加注重用户的隐私和数据。推出 “清除历史”（Clear History），这项新的隐私功能将可以让用户删除 Facebook 从某些网站和应用中收集的数据。&lt;/p>
&lt;p>其实在国内，我们使用互联网就是在 “裸奔”。你的隐私其实在你不知不觉使用各种 App 的时候就被泄露了。只不过国内的大环境和监管比较好，所以风险相对来说小很多。&lt;/p>
&lt;h3 id="约会应用">约会应用&lt;/h3>
&lt;p>一个非常有趣的应用，集成在 Facebook 之中。当然，它会更加注重隐私，对你的朋友是不可见的 😂。不然真的得尴尬了。当然，在介绍这个应用的时候还是强调了线下的相约，即找到感兴趣的活动，加入，认识新的人。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668342-c8785800-a59d-11ea-850c-466c98a4454a.jpg" alt="">&lt;/p>
&lt;p>Instagram, WhatsApp, Messenger也有了小幅的更新。&lt;/p>
&lt;h3 id="vrar">VR/AR&lt;/h3>
&lt;p>F8 大会上宣布，Oculus 于今天上市。Oculus Go 正式登陆 23 个国家，它是 Oculus 的第一款 VR 一体机，支持 3 自由度头部追踪。售价 199 美元，比较良心。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668347-ca421b80-a59d-11ea-947c-f263fd2fa0f4.jpg" alt="">&lt;/p>
&lt;p>Facebook 推出了一些新的方法，帮助用户在虚拟现实的世界里帮助用户更好的和好友和家人互动：聚会空间、玩桌面游戏、看电影电视、体育赛事等。还会自动把用户给卡通化，并在 VR 中支持面部表情。非常酷。&lt;/p>
&lt;h2 id="day-2-ai--研究">Day 2 AI &amp;amp; 研究&lt;/h2>
&lt;h3 id="pytorch-10">PyTorch 1.0&lt;/h3>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668382-d5954700-a59d-11ea-9aec-556aa33bc27a.jpg" alt="">&lt;/p>
&lt;p>在 PyTorch 项目一年多以前启动以来，其社区、用户数量在不断增加。越来越多的学者使用 PyTorch 来实现或复现论文的成果。但其不足之处在于企业无法把模型应用到现成的产品之中，Tensorflow 在这一方面就更加成熟一些，这也是为什么众多企业会采用 Tensorflow 来部署模型到生产环境中。&lt;/p>
&lt;p>那么 Facebook 自家使用的是 Caffe2 来部署，其天生的好处是用 C/C++，编译之后运行效率高。但推出之后在学术界一直不温不火。ONNX 是去年 Facebook, Amazon, Microsoft 共同推出的在不同深度学习框架之间互相转换模型的协议和工具。那么 PyTorch 1.0 就是结合了这三者，把 &lt;strong>研究&lt;/strong> -&amp;gt; &lt;strong>部署&lt;/strong> 这个过程变得更加容易。&lt;/p>
&lt;p>为了支持这一特性，PyTorch 做了一个 JIT (Just-In-Time Compiler)，即时编译器。后端应该是和 Caffe2 结合起来了。具体的使用还得等到发布之后才能一探究竟。&lt;/p>
&lt;h3 id="更大型的数据集">更大型的数据集&lt;/h3>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668394-dcbc5500-a59d-11ea-88dd-0e9d76f6cedf.jpg" alt="">&lt;/p>
&lt;p>这就是直接 &lt;strong>开挂&lt;/strong> 了。用 Instagram 和 Facebook 上用户标注的照片来作为训练集，一共是 &lt;strong>35 亿张图片&lt;/strong>。对，3.5 billion。不服不行。&lt;/p>
&lt;p>然后 Facebook 用在 3.5 亿张图片上训练过的模型到 COCO 和 ImageNet 上做 Fine-tune，直接提升了 2 个百分点。&lt;/p>
&lt;h3 id="detecion--densepose">Detecion &amp;amp; DensePose&lt;/h3>
&lt;p>目标检测上肯定还是用的是 Kaiming He 的 Mask-RCNN 了。下图展示了这一问题近期的研究趋势。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668402-e0e87280-a59d-11ea-9791-1c0f8fe0c3b1.jpg" alt="">&lt;/p>
&lt;p>不止如此，在检测人姿态的同时，还同时能够输出深度信息。下面是 DensePose，可以进行实时检测。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668408-e6de5380-a59d-11ea-80aa-84da9471a8d5.jpg" alt="">&lt;/p>
&lt;p>可以预想的是，以上的技术可以为未来的 AR 应用铺路，只需要 2D 摄像头和手机，就可以完成一些复杂的功能。而这一切，都是得益于 AI 技术。&lt;/p>
&lt;p>&lt;strong>开源地址&lt;/strong>&lt;/p>
&lt;p>应该都会有 Pretrained 的模型的。可以在 &lt;a href="https://facebook.ai/developers/tools">https://facebook.ai/developers/tools&lt;/a>找到这些发布的工具。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668422-ef368e80-a59d-11ea-8b30-a713f4a76b11.jpg" alt="">&lt;/p>
&lt;p>&lt;strong>整个框架&lt;/strong>， 包含了视觉 (Vision)、语言 (Language)、推理 (Reasoning) 三大前沿 AI 任务。&lt;/p>
&lt;p>如果需要快速开发产品，直接拿 Facebook 的用就行了。真的强。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668435-f2ca1580-a59d-11ea-94a0-a1a68e3bc7bb.jpg" alt="">&lt;/p>
&lt;h3 id="elf-围棋-ai">ELF 围棋 AI&lt;/h3>
&lt;p>Facebook 开源了其基于强化学习的围棋算法，来对标 Alpha Go。不同的是，这个模型只需要一块 GPU 就可以实时运行，强调了效率。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668450-fb225080-a59d-11ea-94b2-d8bf9ae1260b.jpg" alt="">&lt;/p>
&lt;p>当然，Facebook 还开源了其它基于强化学习和推理的项目：House3D 和 TorchCraft （AI 玩星际争霸）&lt;/p>
&lt;h3 id="vr-应用">VR 应用&lt;/h3>
&lt;p>除了 3D 构建、手势识别，比较好玩的是这个卡通形象生成。这样在 VR 的世界里就会更加生动。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668464-ffe70480-a59d-11ea-9065-bcc7a64472c4.jpg" alt="">&lt;/p>
&lt;h3 id="3d-全景重构建">3D 全景重构建&lt;/h3>
&lt;p>不同于传统的方法，Facebook 提出利用双目摄像头拍摄全景图，自动计算出景深信息，最后合成出 3D 图像。对于 VR 应用中现实世界的构建非常有用。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83668467-01183180-a59e-11ea-81d8-e6df2f57a9cc.jpg" alt="">&lt;/p>
&lt;h2 id="总结">总结&lt;/h2>
&lt;p>本次大会亮点挺多，从产品到研究，从社交、VR 再到 AI，Facebook 的布局也越来越明晰。&lt;/p>
&lt;p>值得一提，&lt;a href="https://events.google.com/io/">Google IO 2018&lt;/a> 将会在 5 月 8-10 号举行，&lt;a href="https://www.microsoft.com/en-us/build">Microsoft Build 2018&lt;/a> 也会在 5 月 7 号开始。不妨关注一下，看看最新的行业应用及走向。&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E8%B5%84%E8%AE%AF/">资讯</category><category domain="https://blog.imfing.com/tags/%E9%9A%8F%E7%AC%94/">随笔</category></item><item><title>Cousera Deep Learning 课程学习心得</title><link>https://blog.imfing.com/2018/04/deep-learning-course/</link><guid isPermaLink="true">https://blog.imfing.com/2018/04/deep-learning-course/</guid><pubDate>Mon, 02 Apr 2018 17:44:02 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>学习完Deep Learning的5项课程之后，记录一下课程的主要内容以及心得体会&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094048-c3337e80-a06e-11ea-8fee-7f9468d941ab.jpg" alt="">&lt;/p>
&lt;!-- more -->
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>人工智能是现在最火的一个领域了，而深度学习与神经网络则是这一领域中运用最广泛的技术了。
为了让更多人能够快速的入门人工智能，Andrew Ng，也就是吴恩达，在 17 年中辞去百度的职务后，迅速创办了 &lt;a href="https://www.deeplearning.ai/">deeplearning.ai&lt;/a> 以及推出了一个新的系列的课程，即 Coursera 上的 &lt;a href="https://www.coursera.org/specializations/deep-learning">Deep Learning Specialization&lt;/a> 专项课程。
网易也和吴恩达联手推出了中文字幕版的&lt;a href="http://mooc.study.163.com/smartSpec/detail/1001319001.htm">学习课程&lt;/a>。网络情况不好或者英语吃力的同学可以看网易的视频。&lt;/p>
&lt;p>其实我是拖了很久，一直到寒假才有时间认真把这一门课程给刷完。虽然从 16 年底就开始接触了 CNN，这一次比较系统的课程给我的收获是非常大的。&lt;/p>
&lt;p>最让我惊喜的就是这门课的作业材料。虽然要花费非常大的时间去完成，但是作业的代码质量非常高，容易上手。而且后期复习起来会更加方便。&lt;/p>
&lt;p>下面的笔记其实是结合我个人情况总结的。我也&lt;strong>推荐大家自己参与这门课的学习，而不是通过收藏/复制他人的笔记&lt;/strong>。&lt;strong>亲自完成作业&lt;/strong>，会有更加深刻的理解。&lt;/p>
&lt;p>按惯例，晒一波证书：&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094050-c3cc1500-a06e-11ea-9032-77acd7d8a016.jpg" alt="">&lt;/p>
&lt;h3 id="为什么要学习这门课">为什么要学习这门课&lt;/h3>
&lt;p>可能很多人都有这个疑问，现在网上关于 AI 的教程、讲座层出不穷，但质量确实良莠不齐。更多的则是一些商业公司打着 AI 的旗号来敛财的一种方式。
当然，国外也有很多优质的入门教程。比如 &lt;a href="http://course.fast.ai/">fast.ai&lt;/a> 课程则是和吴恩达课程的思路相反 —— 先不强调原理，而是通过代码来实现神经网络。这样的好处显而易见，初学者能够更有成就感。
作为想要深入研究 AI 的同学，也应当深入理解神经网络和深度学习背后的一些原理。那么学习吴恩达的这门课就是不二之选。&lt;/p>
&lt;h3 id="预备知识">预备知识&lt;/h3>
&lt;p>很显然，你要会基础的 Python 编程，以及良好的英文能力。还需要一些求导和矩阵运算的知识。&lt;/p>
&lt;p>掌握一些 Python 基本工具的使用也是很有必要的，比如 &lt;a href="http://jupyter.org/">Jupyter Notebook&lt;/a>，&lt;a href="http://www.numpy.org/">Numpy&lt;/a> 之类。&lt;/p>
&lt;h2 id="学习资源">学习资源&lt;/h2>
&lt;p>这门课，已经有很多人写过总结了。&lt;/p>
&lt;p>国内的话比较详细的就是黄海广老师的 &lt;a href="http://www.ai-start.com/">吴恩达深度学习笔记&lt;/a>，目前已经更新了所有的课程。这个 “笔记”，更确切的说是课程的记录和翻译。比较详细，对你之后的复习回顾很有帮助。&lt;/p>
&lt;p>GitHub 上也有很多人总结了，比如 &lt;a href="https://github.com/mbadry1/DeepLearning.ai-Summary">DeepLearning.ai-Summary&lt;/a>，也有单纯搬运了课程材料的（虽然不推荐大家这么做，因为违反了 Coursera 的荣誉准则）：https://github.com/JudasDie/deeplearning.ai&lt;/p>
&lt;p>推特网友 Tess Ferrandez 做了一份非常漂亮的 &lt;strong>手绘&lt;/strong> 笔记：&lt;a href="https://www.slideshare.net/TessFerrandez/notes-from-coursera-deep-learning-courses-by-andrew-ng">notes-from-coursera-deep-learning-courses-by-andrew-ng&lt;/a>，我猜这哥们一定是个学艺术的。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83094051-c3cc1500-a06e-11ea-80df-b30aba654e9b.jpg" alt="">&lt;/p>
&lt;h2 id="第一门课-神经网络和深度学习">第一门课 神经网络和深度学习&lt;/h2>
&lt;h3 id="第一周-深度学习引言">第一周 深度学习引言&lt;/h3>
&lt;h4 id="为什么使用深度学习">为什么使用深度学习&lt;/h4>
&lt;p>深度神经网络的模型对于&lt;strong>大规模的数据&lt;/strong>有着更好的效果，这也是其优于传统的机器学习的方法最重要的特征。所以在有足够&lt;strong>标注数据&lt;/strong>集的时候，使用深度学习的方法会比较合适。如果数据量不大，那么可能使用传统的机器学习算法如 SVM 就 OK 了。&lt;/p>
&lt;h3 id="第二周-神经网络的编程基础">第二周 神经网络的编程基础&lt;/h3>
&lt;h4 id="逻辑回归logistic-regression">逻辑回归（Logistic Regression）&lt;/h4>
&lt;p>这门课从最基础的逻辑回归（Logistic Regression）开始 $\hat {y}=\sigma (w^Tx+b)$，其中 $x$ 是你的输入样本变量，$w^T$ 是权重参数，$b$ 是偏置值，$\sigma$ 是 sigmoid 函数 $\sigma (z)=\frac {1}{1+e^{-z}}$，用于模拟神经元的非线性，我们之后会称作 &lt;strong>激活函数&lt;/strong>，是深度学习核心的元素之一。$\hat {y}$ 则为输出的预测。那么学习的过程则是调整它的两个参数 $w$ 和 $b$。&lt;/p>
&lt;h4 id="损失函数与梯度下降">损失函数与梯度下降&lt;/h4>
&lt;p>为了衡量输出，我们需要定义 &lt;strong>损失函数&lt;/strong>。对于上面的问题，通常使用
$$ L(\hat{y},y)=-ylog(\hat{y})-(1-y)log(1-\hat{y}) $$
接下来需要做的就是使这个损失函数值尽可能小，以达到和标签值一样的值。&lt;/p>
&lt;p>&lt;strong>梯度下降&lt;/strong>法（Gradient Descent），使用来优化凸函数（convex function）的方法，那么更新参数的过程就可以用公式表述：$w:=w-\alpha \frac{\partial J(w,b)}{\partial w}$，其中我们通常称 $\alpha$ 为&lt;strong>学习速率&lt;/strong>（Learning Rate），不能太大，也不能太小。玄学之一。【图】&lt;/p>
&lt;h4 id="计算图">计算图&lt;/h4>
&lt;p>&lt;strong>计算图&lt;/strong>（Computation Graph），实际上并不复杂，就是把复杂的运算分解。如果你打算使用 &lt;strong>TensorFlow&lt;/strong>，那么你必须要了解这个概念。比如 $d=a* b+c$ 这样一个非常简单的运算可以分解成一个乘法算子和加法算子。我们在计算 $d$ 的这个过程就是前向（Forward）计算，当我们需要做梯度下降优化的时候就需要做&lt;strong>反向传播&lt;/strong>（Backpropagation），即从输出的值沿计算图反向计算偏导数更新节点参数。值得注意的是，并非所有的函数（算子）都有导数。整个深度学习的理论是基于在&lt;strong>可微分&lt;/strong>计算上的。因此我们在自己设计&lt;strong>损失函数&lt;/strong>或其它深度学习中的运算时一定要考虑可微的情况。【图】&lt;/p>
&lt;h4 id="向量化">向量化&lt;/h4>
&lt;p>向量化（Vectorization）和计算图一样，也是一个重要的概念。在深度学习的体系里面，我们一定需要有向量化的潜意识。通常我们在以前的编程中会使用 for 循环来完成一些计算，那么在 DL 中，我们需要尽可能把变量组成向量，因为向量在运算的时候可以加速运算，效率比单纯用两层 for 循环会快很多。当然，实现这样的转变其实很多时候不需要我们亲自去实现，Python 中的 numpy 库提供了足够丰富的处理矩阵向量的函数。&lt;/p>
&lt;h3 id="第三周-浅层神经网络">第三周 浅层神经网络&lt;/h3>
&lt;h4 id="浅层神经网络">浅层神经网络&lt;/h4>
&lt;p>现在我们知道了最基本的神经元的设计，就是上面的逻辑回归单元 $\hat {y}=\sigma (w^Tx+b)$。将多个这样的单元堆叠起来，就可以实现一个浅层的神经网络，通常指一两层的。再多一些就会称之为多层感知机，MLP（Multi-Layer Perception），或者直接叫做深度网络了（虽然现在看起来并不 “深”）。【图片】&lt;/p>
&lt;h4 id="激活函数">激活函数&lt;/h4>
&lt;p>激活函数为神经元带来了非线性，这是区别传统的线性方式的地方。目前有很多常见的激活函数：sigmoid、ReLU、tanh、LeakyReLU。sigmoid 最古老，在传统一些的二分类问题会用到。ReLU 在现在的深度网络中会更常用到。它的函数表示也很简单 $relu (x)=max (0,x)$。你可能会问：这函数在 0 处不连续，怎么求它的微分？解决办法就是强行在 0 处定义了一个值 0 作为其导数。使用了 ReLU 之后，大型神经网络在训练上速度会更快。tanh 更多的用在了 RNN 之中。LeakyReLU 会在 GAN 中经常出现。更多奇形怪状的激活函数可以在 &lt;a href="https://en.wikipedia.org/wiki/Activation_function">Activation Function&lt;/a> 中找到。通常在反向传播的时候需要用到激活函数的导数，这里就不具体推导他们了。&lt;/p>
&lt;h4 id="随机初始化">随机初始化&lt;/h4>
&lt;p>在初始化神经网络参数的时候我们也需要注意。如果全部初始化为 0，那么梯度很有可能就会是 0，就会得到恒为零的输出。也不能初始化为同样的数、较大的数。要充分考虑到激活函数及梯度的影响，否则学习过程会很慢。通常让初始化的数服从一个分布。在后面学习中，会接触到 Xavier Initialization 和 He Initialization 的初始化方法，使学习过程更快收敛。&lt;/p>
&lt;h3 id="深层神经网络">深层神经网络&lt;/h3>
&lt;h4 id="深层网络">深层网络&lt;/h4>
&lt;p>将我们的浅层神经网络多叠加几层，就可以得到一个更深的网络，也就是我们常称作的深度神经网络（DNN）。我们将输入和输出层中间的称为隐藏层（Hidden Layer）。其前向传播的过程（Forward Propagation），即计算过程，通常可以用下面的一般公式来表示：&lt;/p>
&lt;pre>&lt;code>Z[l] = W[l]A[l-1] + B[l]
A[l] = g[l](A[l])
&lt;/code>&lt;/pre>&lt;p>其中 &lt;code>l&lt;/code> 代表层，所以 &lt;code>A [l-1]&lt;/code> 为上一层的输出，&lt;code>W [l]&lt;/code> 为当前层的权重，&lt;code>B [l]&lt;/code> 为偏置。&lt;code>g [l]&lt;/code> 为当前层的激活函数，直至输出这一层的输出 &lt;code>A [l]&lt;/code>。&lt;/p>
&lt;p>我们需要会计算深度网络每一层参数的形状，和计算参数的数量。&lt;code>W&lt;/code> 的形状（维度）是 &lt;code>(n [l],n [l-1])&lt;/code>，&lt;code>b&lt;/code> 是 &lt;code>(n [l],1)&lt;/code>。他们的偏导数 &lt;code>dw&lt;/code> 和 &lt;code>db&lt;/code> 和他们的形状是一样的。对于 &lt;code>Z [l],&lt;/code> &lt;code>A [l]&lt;/code>, &lt;code>dZ [l]&lt;/code> 和 &lt;code>dA [l]&lt;/code> 则是 &lt;code>(n [l],m)&lt;/code>。m 是输入个数。&lt;/p>
&lt;h4 id="参数和超参数">参数和超参数&lt;/h4>
&lt;p>参数（Parameters）是指神经网络学习（优化）过程中调整的那些参数，如 &lt;code>W&lt;/code> 和 &lt;code>b&lt;/code>。超参数（Hyperparameters）是指人为设定的一些参数，像学习速率、迭代次数、隐藏层个数 &lt;code>L&lt;/code>、隐藏层单元个数 &lt;code>n&lt;/code>、激活函数的选择等都属于超参数的范畴。深度学习的又一个玄学之处。有时候改变一个参数会有意想不到的效果。&lt;/p>
&lt;h2 id="第二门课-改善深层神经网络超参数调试正则化以及优化">第二门课 改善深层神经网络：超参数调试、正则化以及优化&lt;/h2>
&lt;h3 id="第一周-深度学习的实用层面">第一周 深度学习的实用层面&lt;/h3>
&lt;h4 id="数据集的拆分">数据集的拆分&lt;/h4>
&lt;p>在深度学习中，数据集的拆分是实验的第一步，也是比较重要的一步。因为往往数据集会影响整个网络的训练。在一开始的时候，我和很多人一样分不清&lt;strong>训练（Training）&lt;/strong>、&lt;strong>验证（Validation/Dev）&lt;strong>和&lt;/strong>测试集（Test）&lt;/strong>。&lt;/p>
&lt;p>训练集是给你的模型训练的数据集，也应该是数据量最多的一个。验证集是在训练中验证你模型泛化能力的集合。测试集则是测试你模型能力的数据集，和验证集有些许重合。那么通常来说，你的数据量在 100 到 100 万，通常按 60/20/20 划分，如果大于 100 万了，就 98/1/1 或 99.5/0.25/0.25。当然，这不是绝对的，而是表明在数据量足够大的时候，只需给验证集/测试集 1% 的数据量就足矣，测试集也可以不需要。你自需要保证你的模型见过训练集的数据和标签，没有见过验证/测试集的标签就行。还有很重要的就是你的数据集必须是 &lt;strong>相似或同样的分布&lt;/strong>，如果你的训练集和验证/测试集差别太大，那么这样评估模型是没有什么意义的。就比如我在 MNIST 上训练了手写数字识别，用来检测生活场景下的数字，很明显是不合适的。对于更复杂的分类任务也是一样。【图】&lt;/p>
&lt;h4 id="过拟合与欠拟合">过拟合与欠拟合&lt;/h4>
&lt;p>在我们训练神经网络的时候，通常会碰到一些情况（训练集/测试集误差）：
过拟合（1%，11%）、欠拟合（15%，14%）、同时过拟合和欠拟合（15%，14%）、最好情况（0.5%，1%）&lt;/p>
&lt;p>遇到这些问题，可以尝试不同解决方法。&lt;strong>过拟合&lt;/strong>：更多的数据、正则化、更浅的网络；&lt;strong>欠拟合&lt;/strong>：更深更复杂的网络、训练更长的时间、不同的优化函数。【图】&lt;/p>
&lt;h4 id="正则化">正则化&lt;/h4>
&lt;p>正则化是一种预防过拟合的方法，我们需要在损失里面加入正则项来作为惩罚，限制模型的学习能力。通常有 $L1$ 范数： $\Vert {W}\Vert {_1}=\sum\vert {W [i,j]}\vert$ ，以及 $L2$ 范数：$\Vert {W}\Vert {_2}=\sum\vert {W [i,j]}\vert {^2}$，可以通过 $W^TW$ 得到。对于简单的网络来说，以 $L2$ 正则化举例，其损失函数就会呈现下面的形式&lt;/p>
&lt;p>$$ J(w,b) = \frac{1}{m}\sum(L(y(i),y'(i))) + \frac{\lambda}{2m} \sum \Vert{W[l]}\Vert ^2 $$&lt;/p>
&lt;p>对应的，梯度下降权重更新的过程也会有一些差别。$L2$ 惩罚项通常被称为&lt;strong>权重衰减（weight decay）&lt;/strong>。目前主流的深度学习框架都支持损失函数里加入 &lt;code>weight decay&lt;/code> 的参数。具体正则化为什么能够降低过拟合就不在这里详细展开了。可以参考 Goodfellow 的《深度学习》。&lt;/p>
&lt;h4 id="dropout">Dropout&lt;/h4>
&lt;p>Dropout 是现在神经网络中经常使用的一种方法，通过随机屏蔽一些权重点来防止网络过拟合。在训练中将权重点随机屏蔽后，相当于你在训练一个更小的网络，那么经过多个训练批次（epoch）之后，你就相当于训练了多个子网络，近似 Bagging 集成，会使结果的鲁棒性更好，因为训练过程没有用到全部的参数点，间接降低了模型过拟合的可能。十分需要注意的是：Dropout &lt;strong>只用于训练&lt;/strong>，对于推断（Inference）的过程，我们需要把 Dropout 关掉。像 VGG 之类的网络就使用了 Dropout。【图】&lt;/p>
&lt;h4 id="其它方法">其它方法&lt;/h4>
&lt;p>&lt;strong>数据增强（Data Augmentation）&lt;/strong> 是常用的方法，特别是当你的训练样布不够多时，通过翻转、随机裁剪等方法可以得到近似但又不一样的数据用于训练。
&lt;strong>预先停止（Early Stopping）&lt;/strong> 是指当你的模型在训练集和验证集上表现差异很大的时候，及时停止训练。&lt;strong>模型集合（Model Ensembles）&lt;/strong> 通过训练多个模型，最后取平均预测输出的方法，通常能提升一些指标。但训练多个模型的代价就是花费的时间会很多。&lt;/p>
&lt;p>归一化输入（Normalize Input）也是重要的一种方法，最简单的就是所有像素点的值除以255使所有像素点的值在 &lt;code>[0,1]&lt;/code> 的区间中。在视觉任务中也会经常见到减去图像集的均值和除以方差，这样数据的均值和方差就为 0，这个过程能够让模型更快的拟合。&lt;/p>
&lt;h4 id="权重初始化">权重初始化&lt;/h4>
&lt;p>若选择了不合适的初始化参数，你很有可能会遇到 &lt;strong>梯度消失/爆炸（Vanishing / Exploding gradients）&lt;/strong> 的问题。因此我们需要更合理的权重初始化方法。通常会选择 ReLU + 带方差的初始化。实际中比较常用的两种是 &amp;quot;He Initialization / Xavier Initialization&amp;quot;。&lt;/p>
&lt;h3 id="第二周-优化算法">第二周 优化算法&lt;/h3>
&lt;h4 id="mini-batch梯度下降">Mini-batch梯度下降&lt;/h4>
&lt;p>&lt;strong>Mini-batch梯度下降&lt;/strong>，顾名思义就是将你的训练集划分成分一个一个的小批 mini-batch，因为一次性训练整个训练集在数据量极大的情况下是不现实的。通常 mini-batch 大小的设置会是 2 的次方，因此你会经常见到 &lt;code>32,64,128,256...&lt;/code> 的 batch 大小，你需要确保你的一个 batch 能够被装的进 CPU/GPU 的内存。Batch-size 同样也是一个需要调整的超参数，也不是越大越好，因为可能会让你的 GPU 耗尽显存 orz。&lt;/p>
&lt;p>下面分别是 Batch Gradient Descent (BGD), Stochastic Gradient Descent (SGD) 和 mini-Batch Gradient Descent (mini-BGD) 的伪代码。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="c1"># Batch Gradient Descent (BGD)&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">nb_epoches&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="n">params_grad&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">evaluate_gradient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">loss_function&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">params&lt;/span>
&lt;span class="n">params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">params&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">learning_rate&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">params_grad&lt;/span>
&lt;span class="c1"># Stochastic Gradient Descent (SGD)&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">np_epochs&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shuffle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">example&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">params_grad&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">evaluate_gradient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">loss_function&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">example&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">params&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">params&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">learning_rate&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">params_grad&lt;/span>
&lt;span class="c1"># mini-Batch Gradient Descent (mini-BGD)&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">np_epochs&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shuffle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">batch&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">get_batches&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">batch_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="n">params_grad&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">evaluate_gradient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">loss_function&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">batch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">params&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">params&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">learning_rate&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">params_grad&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="动量梯度下降">动量梯度下降&lt;/h4>
&lt;p>使用带有 momentum 的梯度下降可以加快学习过程，其核心思想是计算梯度的指数加权平均数，并利用该梯度更新&lt;/p>
&lt;h4 id="rmsprop">RMSprop&lt;/h4>
&lt;p>Root mean square prop&lt;/p>
&lt;h4 id="adam">Adam&lt;/h4>
&lt;p>Adaptive momentum esitmation&lt;/p>
&lt;h4 id="学习速率衰减">学习速率衰减&lt;/h4>
&lt;p>Learning rate decay 也是常用的技巧。在一个数据集上训练的时候，一开始我们需要使用较大的学习速率来使 loss 较快下降一段时间。到后面想要继续提高准确率时再调小学习率。通常我们基于训练的次数（epoch）来调整学习率，一个常用的公式如下，你可以在任何 ImageNet 训练的代码中发现这个技巧，通常从 0.1 开始衰减。
$$ lr = \frac{\text{lr_0}}{1+\text{decay_rate}*\text{epoch_num}} $$&lt;/p>
&lt;h3 id="第三周-调参批归一化及框架">第三周 调参、批归一化及框架&lt;/h3>
&lt;h4 id="调参">调参&lt;/h4>
&lt;p>参数调试，英文叫做tuning，是你在优化深度模型中一定会经历的一个过程。抛开模型内部的参数，有一些超参数是我们需要人为干预来设定的，例如：学习速率 Learning Rate、批大小 Mini-batch size、层数 Layers、隐藏单元个数 Hidden Units、优化函数的参数$\beta$、正则化系数等等。&lt;/p>
&lt;h4 id="批归一化">批归一化&lt;/h4>
&lt;p>在现代神经网络中，一个重要的技巧就是 &lt;strong>Batch Normalization&lt;/strong>，在一些框架中会把它称为 BN 层。思路非常简单，在上一层的输出后，先求小批的均值 $\mu_B=\frac{1}{m}\sum_{i=1}^{m} x_i$，接着求 mini-batch 的方差 $\sigma_B^2=\frac{1}{m}\sum_{i=1}^m{(x_i-\mu_B)^2}$，然后归一化 $\hat{x}_i=\frac{x_i-\mu_B}{\sqrt{\sigma_B^2+\epsilon}}$，最后得到输出 $y_i=\gamma\hat{x}_i+\beta$
BN 之所以能够起到效果是和之前归一化输入差不多，保证一层在输入改变之后输出不会受巨大的影响，也起到了一定正则化的作用。&lt;/p>
&lt;p>提一下最近 Kaiming He 的团队又提出了&lt;strong>组归一化（Group Normalization）&lt;/strong>：Wu, Yuxin, and Kaiming He. 2018. &amp;quot;Group Normalization&amp;quot;. &lt;a href="https://arxiv.org/abs/1803.08494">https://arxiv.org/abs/1803.08494&lt;/a>.&lt;/p>
&lt;h4 id="softmax-回归">Softmax 回归&lt;/h4>
&lt;p>在多分类问题的时候通常需要用到 Softmax，本质上是某一类的预测的概率分布。为了得到归一化的概率，首先以 e 为底数做乘方，保证输出大于 0，接着除以所有结果的和，得到归一化概率。
$$ P(y=j|z^{(i)})=\frac{e^{z^{(i)}}}{\sum_{j=0}^{k}e^{z_k^{(i)}}} $$
在训练的时候，相应的损失函数：$L(y,\hat{y})=-\sum{y_i log\hat{y}_i}$，就是一个负的对数和 log_sum&lt;/p>
&lt;h4 id="框架">框架&lt;/h4>
&lt;p>为了更方便的实现深度学习的算法，目前有许多深度学习的框架：&lt;a href="https://www.tensorflow.org/">TensorFlow&lt;/a>、&lt;a href="http://pytorch.org/">PyTorch&lt;/a>、&lt;a href="http://caffe.berkeleyvision.org/">Caffe&lt;/a>/&lt;a href="http://caffe2.ai/">Caffe2&lt;/a>、&lt;a href="https://keras.io/">Keras&lt;/a>、&lt;a href="https://mxnet.incubator.apache.org/">MXNet&lt;/a> 等。不能说是三足鼎立，但也是五花八门层出不穷。Keras 最适合初学者上手，它的 API 非常友好；TensorFlow 一家独大，但对初学者不是很友好，而且 API 非常复杂；PyTorch 更灵活，介于 Keras 和 TensorFlow 之间；Caffe 安装十分麻烦，不过使用起来还是不错的；MXNet 的 &lt;a href="https://mxnet.incubator.apache.org/gluon/index.html">Gluon&lt;/a> 像是 Keras，提供了非常友好的 API。还有其它的框架如 &lt;a href="https://pjreddie.com/darknet/">Darknet&lt;/a>，YOLO 就是基于此，以及百度的 &lt;a href="http://paddlepaddle.org/">PaddlePaddle&lt;/a> 等等。Facebook 最新为物体检测推出的框架 &lt;a href="https://research.fb.com/downloads/detectron/">Detectron&lt;/a>，Mask-RCNN 就基于此开源。&lt;/p>
&lt;p>这门课里主要讲解了 TensorFlow，我认为比较重要的概念是张量（Tensor）、静态图（Graph）和 Session。这里不详细展开。&lt;/p>
&lt;p>框架只不过是工具，能够熟练的掌握一个工具就可以了。在研究领域真正的还是需要快速的实现自己的想法并进行验证和迭代。&lt;/p>
&lt;h2 id="第三门课-结构化机器学习工程">第三门课 结构化机器学习工程&lt;/h2>
&lt;p>TODO&lt;/p>
&lt;h2 id="第四门课-卷积神经网络">第四门课 卷积神经网络&lt;/h2>
&lt;h3 id="第一周-卷积神经网络基础">第一周 卷积神经网络基础&lt;/h3>
&lt;h4 id="边缘检测">边缘检测&lt;/h4>
&lt;p>卷积网络，顾名思义，就是以卷积运算为基础的架构。&lt;/p>
&lt;p>卷积在信号处理中是用来滤波的，在离散序列中 $s(t)=(x*w)(t)=\sum_{a=-\infty}^{\infty} x(a)w(t-a)$，然而在机器学习的任务里，通常我们需要处理的是多维的张量/矩阵，对于图像处理，在二维空间中同样能够定义卷积的运算。&lt;a href="http://cs231n.github.io/convolutional-networks/">CS231n&lt;/a> 的课程上有一个 &lt;a href="http://cs231n.github.io/convolutional-networks/">Demo&lt;/a> 演示了这个卷积的过程。通常，在 CNN 中，并不会把卷积核做翻转的处理。我想本质上是因为卷积核通常是对称的，因此对运算结果没有实质上的影响。
&lt;img src="https://user-images.githubusercontent.com/5097752/83094052-c3cc1500-a06e-11ea-8752-42761aea9d70.jpg" alt="">&lt;/p>
&lt;p>在图像处理的课程中，一个重要的运算就是边缘提取（Edge Detection），通常使用 Sobel 算子或 Canny 算子。通常大部分课程会以边缘检测为例，用来说明卷积神经网络中的卷积运算主要提取了输入图像的边缘信息。下面是一个 x 方向的 Sobel 算子。&lt;/p>
&lt;p>$$ G_x= \begin{bmatrix} +1 &amp;amp; 0 &amp;amp; -1 \\ +2 &amp;amp; 0 &amp;amp; -2 \\ +1 &amp;amp; 0 &amp;amp; -1 \end{bmatrix} $$&lt;/p>
&lt;p>在 CNN 中，我们把这种矩阵称为核（Kernel），这其中的参数并非我们手动设置的，而是需要通过优化来得到的。&lt;/p>
&lt;h4 id="多维卷积运算">多维卷积运算&lt;/h4>
&lt;p>大小为 $(n,n)$ 的矩阵，与大小为 $(f,f)$ 的核进行卷积运算后，不难得到大小变成了 $(n-f+1, n-f+1)$&lt;/p>
&lt;p>&lt;strong>Padding&lt;/strong>（填充）和 &lt;strong>Stride&lt;/strong>（步长）也是常用的参数。Padding 的作用是在原始图像周围填充（通常是 0），这样在卷积完之后就可以得到大小和原始输入一样的结果，否则在进行了几次卷积之后，输出形状太小而包含不了有用的信息。在加上 $p$ 单位的 padding 之后，大小变成了 $(n+2p-f+1, n+2p-f+1)$。而 Stride 调整的是步长，即每次卷积核行进的距离。那么输出的形状就变成了 $(\frac{n+2p-f}{s}+1, \frac{n+2p-f}{s}+1)$，对于可能出现的小数情况，向下取整就行了。&lt;/p>
&lt;p>对于图像，通常是三维的，输入 &lt;code>6x6x3&lt;/code> 与 1 个 &lt;code>3x3x3&lt;/code> 核运算可以得到 &lt;code>4x4x1&lt;/code>。更多的时候，可能我们会用 10 个 &lt;code>3x3x3&lt;/code> 卷积核来拓展输出的维数，就会得到 &lt;code>4x4x10&lt;/code> 的输出。现在，我们就可以计算论文中的卷积层的输出和对应的参数的个数了。&lt;/p>
&lt;h4 id="卷积层">卷积层&lt;/h4>
&lt;p>现在来举个卷积层的例子，输入大小为 &lt;code>6x6x3&lt;/code>，经过 10 个 &lt;code>3x3x3&lt;/code> 的核得到 &lt;code>4x4x10&lt;/code> 输出，加上 &lt;code>10x1&lt;/code> 的偏置 bias 和 RELU 激活函数，最终得到 &lt;code>4x4x10&lt;/code> 的输出。参数数量为 &lt;code>(3x3x3x10) + 10 = 280&lt;/code>&lt;/p>
&lt;h4 id="池化层">池化层&lt;/h4>
&lt;p>&lt;strong>Pooling（池化）&lt;/strong> 层通常是来对前一层卷积后的结果进行降维操作的。通常有 Max Pooling 和 Average Pooling，前者用的多一些。Pooling 层是不含有可优化的参数的，因此有时候 Conv 和 Pool 会合并起来算一层。&lt;/p>
&lt;h4 id="全连接层">全连接层&lt;/h4>
&lt;p>&lt;strong>Fully connected（全连接）&lt;/strong> 通常用在最后降维及输出预测的结果，也是参数最多的一层。比如前一层的输出是 &lt;code>5x5x16&lt;/code>，我需要 &lt;code>5x5x16x120&lt;/code> 的矩阵和它卷积得到 &lt;code>1x1x120&lt;/code> 的输出，即 &lt;code>(120,1)&lt;/code>，实现了全连接层。这样它的参数（算上 bias）就有 &lt;code>5x5x16x120+1=48,001&lt;/code>，是非常大的一个值了。&lt;/p>
&lt;p>若后面再跟一个全连接 &lt;code>(84,1)&lt;/code>，则需要一个 &lt;code>(84,120)&lt;/code> 的矩阵 W 和 &lt;code>(84,1)&lt;/code> 的偏置 b，这样参数就有 &lt;code>84x120+1=10081&lt;/code> 个。&lt;/p>
&lt;p>因此在很多现代的网络中会避免使用全连接，比如 ResNet、GoogLeNet，采用 GAP (Global Average Pooling) 代替全连接层来降维。&lt;/p>
&lt;h3 id="第二周-深度卷积网络实例探究">第二周 深度卷积网络：实例探究&lt;/h3>
&lt;h4 id="经典网络">经典网络&lt;/h4>
&lt;p>&lt;strong>LeNet-5, AlexNet, VGG&lt;/strong> 是比较有代表性的经典的卷积网络。&lt;/p>
&lt;p>&lt;strong>LeNet-5&lt;/strong>: &lt;code>Conv ==&amp;gt; Pool ==&amp;gt; Conv ==&amp;gt; Pool ==&amp;gt; FC ==&amp;gt; FC ==&amp;gt; softmax&lt;/code>&lt;/p>
&lt;p>&lt;strong>AlexNet&lt;/strong>: &lt;code>Conv =&amp;gt; Max-pool =&amp;gt; Conv =&amp;gt; Max-pool =&amp;gt; Conv =&amp;gt; Conv =&amp;gt; Conv =&amp;gt; Max-pool ==&amp;gt; Flatten ==&amp;gt; FC ==&amp;gt; FC ==&amp;gt; Softmax&lt;/code>&lt;/p>
&lt;p>&lt;strong>VGG&lt;/strong>: 和AlexNet 相似，采用了更大的 filter 以及更深的网络层数。https://arxiv.org/abs/1409.1556&lt;/p>
&lt;p>具体的可以参考 CS231n 的 &lt;a href="http://cs231n.github.io/convolutional-networks/#case">case&lt;/a> 以及它的 Lecture9 的 slide。&lt;/p>
&lt;h4 id="残差网络">残差网络&lt;/h4>
&lt;p>核心思想：利用 shortcut/skip connection 结构来增加网络深度。在有了 skip connection 之后，最坏的情况是当前层没有学习到任何的有用参数，输出为 0 的话，后一层信息就直接等于了前一层。这样即使增加网络层数，也不见得会影响性能。&lt;/p>
&lt;p>不过这种解释听上去很有道理，但还是挺玄学的。&lt;/p>
&lt;h4 id="inception">Inception&lt;/h4>
&lt;p>Inception 是一个简洁高效的网络模型。不同于残差网络，其主要组成是称作 Inception Module。&lt;/p>
&lt;p>&lt;strong>Network in Network&lt;/strong>：顾名思义，网络中的网络，核心思想是用 &lt;code>1x1&lt;/code> 的卷积&lt;/p>
&lt;p>&lt;strong>[未完待续]&lt;/strong>&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/">深度学习</category><category domain="https://blog.imfing.com/tags/deep-learning/">Deep Learning</category><category domain="https://blog.imfing.com/tags/notes/">Notes</category></item><item><title>快速部署你的深度学习模型为网页应用</title><link>https://blog.imfing.com/2018/03/deploy-keras-model/</link><guid isPermaLink="true">https://blog.imfing.com/2018/03/deploy-keras-model/</guid><pubDate>Thu, 15 Mar 2018 23:04:13 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>训练好了深度学习模型，该怎么给妹子展示？
本文带你用 Keras 和 Flask 搭建一个简单易用的深度学习图像网页应用&lt;/p>
&lt;p>&lt;a href="https://github.com/mtobeiyf/keras-flask-deploy-webapp">&lt;img src="https://user-images.githubusercontent.com/5097752/83357459-74ba0480-a33a-11ea-90fa-8e5c4b8ac64e.jpg" alt="">&lt;/a>&lt;/p>
&lt;!-- more -->
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>小明参加炼丹不久，自我感觉训练了一个非常牛逼的深度学习模型——&lt;strong>神奇动物分类器&lt;/strong>，于是发了个朋友圈。一条优美的交叉熵损失曲线，是每个炼丹民工的梦想。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357462-771c5e80-a33a-11ea-8a6e-16d4b73a1d54.jpg" width = "300px" alt="" align=center />&lt;/p>
&lt;p>一个好朋友&lt;del>妹子&lt;/del>看到之后说：“好厉害啊，能不能让我试一下”，小明当然选择同意啦。不过这下可难住小明了，总不能麻烦她配置一个深度学习环境吧，那么怎样让她体验到我的模型的厉害呢？&lt;/p>
&lt;p>为了解决小明的烦恼，帮助像他一样的同志更好的展示&lt;del>装逼&lt;/del>，同时弥补现有开源项目丑陋的 UI 的缺点。这个小小项目就诞生了。&lt;/p>
&lt;h2 id="具体步骤">具体步骤&lt;/h2>
&lt;p>&lt;a href="https://github.com/mtobeiyf/keras-flask-deploy-webapp">keras-flask-deploy-webapp&lt;/a>，不要 1 小时！不要 1 小时！真的只要 &lt;strong>10 分钟&lt;/strong>！&lt;/p>
&lt;p>我们来看看搭建这样一个网页应用都需要哪些步骤：&lt;/p>
&lt;ol>
&lt;li>下载&lt;/li>
&lt;li>运行&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357479-97e4b400-a33a-11ea-8bea-db6b76a3ba4e.jpg" width = "200px" alt="" align=center />&lt;/p>
&lt;p>“你这样是找不到女朋友的”&lt;/p>
&lt;p>好，我详细一点！&lt;/p>
&lt;h3 id="下载代码">下载代码&lt;/h3>
&lt;p>用你最心爱的命令行 / 终端来克隆一下我仓库的代码&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ git clone https://github.com/mtobeiyf/keras-flask-deploy-webapp.git
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="安装必要模块">安装必要模块&lt;/h3>
&lt;p>Python 肯定得先装好吧，推荐 Python3。可以参考我之前的文章 &lt;a href="https://blog.imfing.com/2018/01/Python-on-Windows/">Win 下 Python 开发环境配置 &amp;amp; Tips 分享&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ pip install -r requirements.txt
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>喂，说好的十分钟呢？ ——“你网速慢怪我咯？？！”&lt;/p>
&lt;h3 id="运行">运行&lt;/h3>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ python app.py
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这时候用浏览器打开 http://localhost:5000/&lt;/p>
&lt;p>我们来测试一下，给它一张&lt;strong>熊猫&lt;/strong>图片&lt;/p>
&lt;img src="https://s18.postimg.cc/l01x6fn3d/demo1.png" width="600px" alt="" align=center>
&lt;p>哎哟，不错哦&lt;/p>
&lt;h2 id="如何魔改">如何魔改&lt;/h2>
&lt;p>上面是用 Keras 训练好的 ResNet50 的样例，还有更多的预训练好的网络在 &lt;a href="https://keras.io/applications/">Keras 应用&lt;/a>，像 DenseNet, MobilNet, NASNet 等等&lt;/p>
&lt;p>那么我们在用自己的模型的时候要怎么办呢？ 看一下 &lt;a href="https://github.com/mtobeiyf/keras-flask-deploy-webapp/blob/master/app.py#L25">这段代码&lt;/a>&lt;/p>
&lt;p>界面太丑，我想要&lt;strong>萝莉风格&lt;/strong>！ 没问题，页面标题之类的信息去改 &lt;code>index.html&lt;/code>，样式去找 &lt;code>main.css&lt;/code> 就行啦&lt;/p>
&lt;h2 id="部署">部署&lt;/h2>
&lt;p>即使这样，还是没法让朋友圈里面所有的人都体验到。&lt;/p>
&lt;p>这时候，你需要一台&lt;strong>服务器&lt;/strong>&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357497-adf27480-a33a-11ea-8234-1e54e44eef1a.jpg" width = "200px" alt="" align=center />&lt;/p>
&lt;p>记得去这里看如何配置哦~ &lt;a href="https://github.com/mtobeiyf/keras-flask-deploy-webapp">keras-flask-deploy-webapp&lt;/a>，这里不详细说了。&lt;/p>
&lt;p>配置好了之后，其它人可以在手机上尽情的调戏你的模型了&lt;/p>
&lt;img src="https://s18.postimg.cc/5ekln1vvt/demo2.gif" width="600px" alt="" align=center>
&lt;p>大家玩的愉快！&lt;/p>
&lt;p>&amp;lt;img src=&amp;quot;https://user-images.githubusercontent.com/5097752/83357506-bfd41780-a33a-11ea-99b8-ada7ace0f708.jpg width = &amp;quot;150px&amp;quot; alt=&amp;quot;&amp;quot; align=center /&amp;gt;&lt;/p>
&lt;p>别忘了去 star 一下~
&lt;a href="https://github.com/mtobeiyf/keras-flask-deploy-webapp">https://github.com/mtobeiyf/keras-flask-deploy-webapp&lt;/a>&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/">深度学习</category><category domain="https://blog.imfing.com/tags/deep-learning/">Deep Learning</category><category domain="https://blog.imfing.com/tags/web/">Web</category></item><item><title>命令行数据科学工具笔记</title><link>https://blog.imfing.com/2018/01/data-science-at-the-command-line/</link><guid isPermaLink="true">https://blog.imfing.com/2018/01/data-science-at-the-command-line/</guid><pubDate>Tue, 30 Jan 2018 00:48:48 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>《Data Science at the Command Line》阅读笔记。
用命令行工具来提升效率，还有这种操作.jpg
&lt;img src="https://user-images.githubusercontent.com/5097752/83357836-eabf6b00-a33c-11ea-9d48-dc025d469088.jpg" alt="">&lt;/p>
&lt;!-- more -->
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>对于习惯了 Windows 图形化操作界面的人来说，对于命令行 / 终端操作界面可能嗤之以鼻。但不可否认的是使用命令行的效率是大于图形界面的。
无意间看到了这本书，觉得里面的一些命令行操作非常实用，也是之前没有接触过的。故记录书中的一些&lt;strong>通用的技巧&lt;/strong>，在很多 Linux 下面都是同样适用的。
对于涉及到具体的问题，可以诸如 Python 解决的，就跳过了。这些命令不必烂熟于行，但至少&lt;/p>
&lt;p>本书在线阅读的网站是 &lt;a href="https://www.datascienceatthecommandline.com/">https://www.datascienceatthecommandline.com/&lt;/a>
示例和 GitHub 地址是 &lt;a href="https://github.com/jeroenjanssens/data-science-at-the-command-line">https://github.com/jeroenjanssens/data-science-at-the-command-line&lt;/a>&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357838-ebf09800-a33c-11ea-8fc7-ed260f8f167e.jpg" width = "150px" alt="" align=center />&lt;/p>
&lt;h2 id="入门起步">入门起步&lt;/h2>
&lt;p>首先你需要一定的 &lt;strong>GNU/Linux&lt;/strong> 知识，至少会使用 Linux 系统。那么命令行工具的环境大致可以分为四层：&lt;code>命令(Command Line&lt;/code>)-&amp;gt;&lt;code>终端(Terminal)&lt;/code>-&amp;gt;&lt;code>Shell&lt;/code>-&amp;gt;&lt;code>操作系统(Operating System)&lt;/code>。Shell 是解释你输入命令的，一般我们在 Linux 发行版中使用的 Shell 是 bash，当然也有其它的比如大名鼎鼎的 zsh。&lt;/p>
&lt;p>如何执行命令行工具呢？在终端下输入相应的命令就行了，比如 &lt;code>$ pwd&lt;/code> 就会给出当前的地址。&lt;/p>
&lt;p>常见的命令行工具可以分成 5 种：&lt;/p>
&lt;ul>
&lt;li>二进制可执行文件，一般是由其它编译而成的&lt;/li>
&lt;li>Shell 自带命令，比如 &lt;code>cd&lt;/code> 和 &lt;code>help&lt;/code>&lt;/li>
&lt;li>可解释脚本，如 Python 脚本&lt;/li>
&lt;li>Shell 函数，Shell 是自带了一些函数的如 &lt;code>seq&lt;/code>、&lt;code>fac&lt;/code>&lt;/li>
&lt;li>命令别名 (alias)，你可以把一些很长的命令用别名的方式来简化。&lt;/li>
&lt;/ul>
&lt;p>结合使用不同的命令行工具，比如&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ seq &lt;span class="m">100&lt;/span> &lt;span class="p">|&lt;/span> grep &lt;span class="m">3&lt;/span> &lt;span class="p">|&lt;/span> wc -l
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>seq 100&lt;/code> 的作用是输出 1-100 的数，那么 grep 就在输出中找出含有 &lt;code>3&lt;/code> 的项，之后 &lt;code>wc -l&lt;/code> 就是数 &lt;code>grep&lt;/code> 之后输出的行数，那么这里有 19 个。&lt;/p>
&lt;p>重定向输入输出，如 &lt;code>$ seq 10 &amp;gt; output.txt&lt;/code> 就可以把命令行的输出保存到文件中，这在许多调试中是非常有用的。
下面的命令中，echo的 &lt;code>-n&lt;/code> 是不换行，&lt;code>&amp;gt;&amp;gt;&lt;/code> 是指在文件后接着写 (append 的方式)&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ &lt;span class="nb">echo&lt;/span> -n &lt;span class="s2">&amp;#34;Hello&amp;#34;&lt;/span> &amp;gt; hello-world.txt
$ &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34; World&amp;#34;&lt;/span> &amp;gt;&amp;gt; hello-world.txt
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>接下来看看怎么读取文件，一个常用的命令是 &lt;code>cat&lt;/code>，如 &lt;code>$ cat hello-world.txt | wc -w&lt;/code>，其中 &lt;code>-w&lt;/code> 参数是数单词数 (words)，这条的结果应该是 2，因为 &amp;quot;Hello World&amp;quot; 有两个单词。
当然还有其它的方法，这句和上面的输出是一样的 &lt;code>$ &amp;lt; hello-world.txt wc -w&lt;/code>，只不过它将 hello-word.txt 文件直接加载到了 wc 的输入流里面。或者直接使用 &lt;code>wc&lt;/code> 的参数 &lt;code>$ wc -w hello-world.txt&lt;/code>，但此时输出会将文件名附在单词数后面。&lt;/p>
&lt;p>和文件打交道，常用的命令是移动/剪切 &lt;code>mv&lt;/code>，复制 &lt;code>cp&lt;/code>，删除 &lt;code>rm&lt;/code>。下面整理了最常用的一些命令。
&lt;code>$ mv hello.txt ~/book/ch02/data/&lt;/code> 将文件移动到文件夹中；
&lt;code>$ cd data&lt;/code> 切换目录，&lt;code>$ mv hello.txt bye.txt&lt;/code> 重命名；
&lt;code>$ rm bye.txt&lt;/code> 删除文件，&lt;code>$ rm -r book/ch02/data/old&lt;/code> 删除文件夹；
&lt;code>$ cp server.log server.log.bak&lt;/code> 复制文件，&lt;code>$ mkdir logs&lt;/code> 新建文件夹&lt;/p>
&lt;p>在命令行中寻求帮助，对于每个命令行工具，一般是有相应的说明文档的，比如 &lt;code>man cat&lt;/code> 就可以查看 &lt;code>cat&lt;/code> 工具的说明。下面列举了常见的三种查看帮助文档的方式。
&lt;code>$ man cat | head -n 20&lt;/code> 使用 &lt;code>man&lt;/code> 命令查看帮助前 20 行；
&lt;code>$ help cd | head -n 20&lt;/code> 对于一些工具使用 &lt;code>help&lt;/code> 也是一样的。
&lt;code>$ jq --help&lt;/code> 大部分工具都有 &lt;code>--help&lt;/code> 这个参数，也一样可以查看帮助&lt;/p>
&lt;p>&lt;a href="https://www.howtogeek.com/howto/ubuntu/keyboard-shortcuts-for-bash-command-shell-for-ubuntu-debian-suse-redhat-linux-etc/">Bash 常用快捷键&lt;/a>，熟悉了的话能够提高不少效率。&lt;/p>
&lt;h2 id="获取数据">获取数据&lt;/h2>
&lt;h3 id="解压">解压&lt;/h3>
&lt;p>在 Linux 中，我们最常见到的压缩文件后缀名是 &lt;code>.tar.gz&lt;/code>、&lt;code>.zip&lt;/code> 和 &lt;code>.rar&lt;/code> 文件，对应的解压缩命令是 &lt;code>tar&lt;/code>、&lt;code>unzip&lt;/code> 和&lt;code>unrar&lt;/code>，注意在使用他们之前要确保已经正确安装了这些工具。
比如，&lt;code>$ tar -xzvf data/logs.tar.gz&lt;/code>，注意命令的参数是&lt;code>-xzvf&lt;/code>，分别对应解压 archive、gzip 解压、verbose 输出、指定 file。
书中提供了实用的 Bash 脚本来解压不同类型的文件，附上&lt;a href="https://www.datascienceatthecommandline.com/chapter-3-obtaining-data.html#exm:script-unpack">地址&lt;/a>。&lt;/p>
&lt;h3 id="转换-excel-文件">转换 Excel 文件&lt;/h3>
&lt;p>很多情况下，使用 Excel 保存的数据文件会存放在一个 &lt;code>xlsx&lt;/code> 文件中，对于命令行工具来说非常不友好，通用一点的数据格式是 &lt;a href="https://en.wikipedia.org/wiki/Comma-separated_values">CSV&lt;/a> 格式。
这时候 &lt;code>in2csv&lt;/code> 命令就能派上用场了。需要先安装一下 &lt;code>sudo pip install csvkit&lt;/code>。
之后就可以使用命令 &lt;code>$ in2csv data/imdb-250.xlsx &amp;gt; data/imdb-250.csv&lt;/code> 将xlsx文件转换成csv文件了。
使用 &lt;code>$ in2csv imdb-250.xlsx | head | cut -c1-80&lt;/code> 可以查看，但是输出的是原始的逗号分隔文件，非常不友好。为了使显示效果更好，可以使用 &lt;code>csvlook&lt;/code> 的管道。
如 &lt;code>in2csv data/imdb-250.xlsx | head | csvcut -c Title,Year,Rating | csvlook&lt;/code> 的效果会好很多。
另一种处理的方式就是使用 Libre Office 这样的的软件打开，再进行转换。但这样非常不方便，而且你在用服务器的时候使不可能装一个 Office 套件的。&lt;/p>
&lt;h3 id="查询关系数据库">查询关系数据库&lt;/h3>
&lt;p>关系型数据库常见的有 MySQL、PostgreSQL 和 SQLite 等。使用命令行工具 &lt;code>sql2csv&lt;/code> 可以更方便的使用 CSV 处理 SQL 数据库。
如使用 &lt;code>$ sql2csv --db 'sqlite:///data/iris.db' --query 'SELECT * FROM iris '\&amp;gt; 'WHERE sepal_length &amp;gt; 7.5'&lt;/code> 进行 query 查询操作。&lt;/p>
&lt;h3 id="从网上下载数据">从网上下载数据&lt;/h3>
&lt;p>使用最多的是 &lt;code>cURL&lt;/code> 工具。
&lt;code>$ curl -s http://www.gutenberg.org/cache/epub/76/pg76.txt | head -n 10&lt;/code>，参数 &lt;code>-s&lt;/code> 表示 silent 模式。
&lt;code>$ curl http://www.gutenberg.org/cache/epub/76/pg76.txt &amp;gt; data/finn.txt&lt;/code> 将 curl 下载的内容到文件中，此时不能用 &lt;code>-s&lt;/code> 模式。
&lt;code>$ curl -s http://www.gutenberg.org/cache/epub/76/pg76.txt -o data/finn.txt&lt;/code> 使用 &lt;code>-o&lt;/code> 参数指定输出文件，此时可以使用 &lt;code>-s&lt;/code>。
&lt;code>$ curl -u username:password ftp://host/file&lt;/code> 下载 FTP 上的文件
&lt;code>$ curl -L j.mp/locatbbar&lt;/code> 当网址是诸如 &lt;code>http://bit.ly/*&lt;/code> 的自动跳转等短网址时，加上 &lt;code>-L&lt;/code> 参数就可以下载跳转之后的网页内容。&lt;/p>
&lt;h3 id="通过api请求数据">通过API请求数据&lt;/h3>
&lt;p>很多时候我们需要通过一些在线服务提供的 API 来获取数据。比如我们需要知道某地的天气时，就需要通过一些天气服务提供商提供的网络接口来获取数据，通常返回的数据格式时 json 形式的。使用最基础的 curl 就能实现，如&lt;code>$ curl -s http://api.randomuser.me | jq '.'&lt;/code>
当我们需要更高级的操作时则需要其它工具，这在后续会涉及到。&lt;/p>
&lt;h2 id="创建可复用的命令行工具">创建可复用的命令行工具&lt;/h2>
&lt;p>其实就是将命令写进一个脚本文件。这在我们需要重复连续执行繁琐的命令的时候会非常有用。当然这里只能介绍一些基本的脚本编写，更复杂的需要阅读 Shell 脚本编写的相关书籍。&lt;/p>
&lt;h3 id="将单行指令转换为shell脚本">将单行指令转换为Shell脚本&lt;/h3>
&lt;p>通常要经历下面几个步骤：&lt;/p>
&lt;ol>
&lt;li>将单行指令复制粘贴到一个文件中&lt;/li>
&lt;li>给文件添加执行权限&lt;/li>
&lt;li>定义一个 shebang 行(如 &lt;code>#!/bin/sh&lt;/code>)&lt;/li>
&lt;li>去除固定输入部分&lt;/li>
&lt;li>添加参数&lt;/li>
&lt;li>可选的拓展你的路径&lt;/li>
&lt;/ol>
&lt;h4 id="复制单行命令到文件">复制单行命令到文件&lt;/h4>
&lt;p>有一个技巧是可以通过 &lt;code>!!&lt;/code> 来代替上一条执行了的命令。
一般来说我们会将 Bash 脚本命令放到 &lt;code>.sh&lt;/code> 文件中，表明是 shell 脚本。我们就可以通过 &lt;code>$ bash book/ch04/top-words-1.sh&lt;/code> 这样的命令来执行 Shell 脚本了。&lt;/p>
&lt;h4 id="添加权限">添加权限&lt;/h4>
&lt;p>绝大多数情况下，我们使用 &lt;code>vim&lt;/code> 或者 &lt;code>nano&lt;/code> 编辑器创建的 &lt;code>.sh&lt;/code> 文件是没法直接执行的。因为我们还需要给它赋予&lt;strong>可执行权限&lt;/strong>。那么这时候就要用到更改权限的命令 &lt;code>chmod&lt;/code>。在终端中执行 &lt;code>$ chmod u+x top-words-2.sh&lt;/code>，其中 &lt;code>u&lt;/code> 指定是用户（也就是你），&lt;code>+x&lt;/code> 代表增加执行权限。这样你就可以在终端中直接执行 &lt;code>./top-words-2.sh&lt;/code> 来运行这个脚本了。
通过 &lt;code>$ ls -l top-words-2.sh&lt;/code> 来查看文件具体信息，我们可以看到权限部分由原来的 &lt;code>-rw-rw-r--&lt;/code> 变到了 &lt;code>-rwxrw-r--&lt;/code>，增加了 x，执行权限。&lt;/p>
&lt;h4 id="定义-shebang">定义 shebang&lt;/h4>
&lt;p>在 bash 脚本的第一行，我们需要加入 &lt;code>#!/usr/bin/env bash&lt;/code> 来告诉终端是用bash来执行这个脚本。
如果你添加的是 &lt;code>#!/usr/bin/python&lt;/code>，那么就是用 Python 来执行。&lt;/p>
&lt;h4 id="去除固定输入">去除固定输入&lt;/h4>
&lt;p>把 bash 脚本中的固定输入部分删除，这样我们就可以通过终端把我们需要的输入直接给脚本，而无须每次都改动脚本。
&lt;code>$ cat data/finn.txt | top-words-4.sh&lt;/code>，通过管道把数据给 shell 脚本。&lt;/p>
&lt;h4 id="参数化">参数化&lt;/h4>
&lt;p>下面的脚本内容定义了一个变量 &lt;code>NUM_WORDS&lt;/code>，在使用这个变量的时候需要在前面加$符号，这个变量的内容是 &lt;code>$1&lt;/code> 代表着脚本第一个输入参数，比如 &lt;code>./xx.sh 1&lt;/code> 里的 1。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="cp">#!/usr/bin/env bash
&lt;/span>&lt;span class="cp">&lt;/span>&lt;span class="nv">NUM_WORDS&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$1&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
tr &lt;span class="s1">&amp;#39;[:upper:]&amp;#39;&lt;/span> &lt;span class="s1">&amp;#39;[:lower:]&amp;#39;&lt;/span> &lt;span class="p">|&lt;/span> grep -oE &lt;span class="s1">&amp;#39;\w+&amp;#39;&lt;/span> &lt;span class="p">|&lt;/span> sort &lt;span class="p">|&lt;/span>
uniq -c &lt;span class="p">|&lt;/span> sort -nr &lt;span class="p">|&lt;/span> head -n &lt;span class="nv">$NUM_WORDS&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="拓展你的-path">拓展你的 PATH&lt;/h4>
&lt;p>很多情况下，在不同的文件夹中，你也想使用你之前编写好的脚本，这时候把脚本所在路径添加到系统PATH中就能够使你在任何地方访问到。
&lt;code>$ echo $PATH | fold&lt;/code>
想要长期的改变系统的环境变量的话，就需要修改 &lt;code>.bashrc&lt;/code> 或 &lt;code>.profile&lt;/code> 文件了。&lt;/p>
&lt;h3 id="使用-python-创建命令行工具">使用 Python 创建命令行工具&lt;/h3>
&lt;h4 id="迁移脚本">迁移脚本&lt;/h4>
&lt;p>上面的脚本，可以同样使用Python来实现，下面代码给出了实现。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="ch">#!/usr/bin/env python&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;span class="kn">from&lt;/span> &lt;span class="nn">collections&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Counter&lt;/span>
&lt;span class="n">num_words&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argv&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">])&lt;/span>
&lt;span class="n">text&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdin&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">lower&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="n">words&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;\W+&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">cnt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Counter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">words&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">word&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">count&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">cnt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">most_common&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_words&lt;/span>&lt;span class="p">):&lt;/span>
&lt;span class="k">print&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">%7d&lt;/span>&lt;span class="s2"> &lt;/span>&lt;span class="si">%s&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">word&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这时候我们就可以通过 Python 脚本来执行我们所需的功能，将数据流输入到脚本并附上参数 5。&lt;code>$ &amp;lt; data/76.txt top-words.py 5&lt;/code>&lt;/p>
&lt;h4 id="使用标准输入来处理流数据">使用标准输入来处理流数据&lt;/h4>
&lt;p>当输入数据流是类似命令行形式而非文件时，你就不可能一次性全部处理掉，故需要一行一行处理。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="ch">#!/usr/bin/env python&lt;/span>
&lt;span class="kn">from&lt;/span> &lt;span class="nn">sys&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">stdin&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stdout&lt;/span>
&lt;span class="k">while&lt;/span> &lt;span class="bp">True&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">line&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">stdin&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">readline&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="k">break&lt;/span>
&lt;span class="n">stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">%d&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">flush&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="清洗数据">清洗数据&lt;/h2>
&lt;h3 id="对普通文本操作">对普通文本操作&lt;/h3>
&lt;h4 id="过滤行">过滤行&lt;/h4>
&lt;p>主要用到的命令行工具是 &lt;code>sed&lt;/code> 和 &lt;code>awk&lt;/code>&lt;/p>
&lt;p>基于位置：
生成一个 10 行的数据用于演示。&lt;code>$ seq -f &amp;quot;Line %g&amp;quot; 10 | tee data/lines&lt;/code>
打印前三行：&lt;code>$ &amp;lt; lines head -n 3&lt;/code>;&lt;code>$ &amp;lt; lines sed -n '1,3p'&lt;/code>；&lt;code>$ &amp;lt; lines awk 'NR&amp;lt;=3'&lt;/code>
打印末三行：&lt;code>$ &amp;lt; lines tail -n 3&lt;/code>
删除前三行：&lt;code>$ &amp;lt; lines tail -n +4&lt;/code>；&lt;code>$ &amp;lt; lines sed '1,3d'&lt;/code>；&lt;code>$ &amp;lt; lines sed -n '1,3!p'&lt;/code>
删除末三行：&lt;code>$ &amp;lt; lines head -n -3&lt;/code>
打印 4-6 行：&lt;code>$ &amp;lt; lines sed -n '4,6p'&lt;/code>；&lt;code>$ &amp;lt; lines awk '(NR&amp;gt;=4)&amp;amp;&amp;amp;(NR&amp;lt;=6)'&lt;/code>；&lt;code>$ &amp;lt; lines head -n 6 | tail -n 3&lt;/code>
打印奇数行：&lt;code>$ &amp;lt; lines sed -n '1~2p'&lt;/code>；&lt;code>$ &amp;lt; lines awk 'NR%2'&lt;/code>
打印偶数行：&lt;code>$ &amp;lt; lines sed -n '0~2p'&lt;/code>；&lt;code>$ &amp;lt; lines awk '(NR+1)%2'&lt;/code>&lt;/p>
&lt;p>基于模式：
使用 &lt;code>grep&lt;/code> 和正则表达式
&lt;code>$ grep -E '^CHAPTER (.*)\. The' alice.txt&lt;/code>，在 alice.txt 中找出所有以 &amp;quot;The&amp;quot; 开头的章节&lt;/p>
&lt;p>基于随机：
使用 &lt;code>sample&lt;/code> 工具创建数据集的子集
&lt;code>$ seq 1000 | sample -r 1% | jq -c '{line: .}'&lt;/code>
可以指定 sample 的延时，避免数据一下子太快打印出来而产生错误，这在调试你的脚本时非常有用。
&lt;code>$ seq 10000 | sample -r 1% -d 1000 -s 5 | jq -c '{line: .}'&lt;/code>&lt;/p>
&lt;h4 id="提取值">提取值&lt;/h4>
&lt;p>使用 &lt;code>grep&lt;/code> 的输出到 &lt;code>cut&lt;/code> 工具中
&lt;code>$ grep -i chapter alice.txt | cut -d' ' -f3-&lt;/code>&lt;/p>
&lt;h4 id="替换与删除">替换与删除&lt;/h4>
&lt;p>使用 &lt;code>tr&lt;/code> 工具
&lt;code>$ echo 'hello world!' | tr ' ' '_'&lt;/code>，把空格替换成了下划线
&lt;code>$ echo 'hello world!' | tr -d -c '[a-z]'&lt;/code>，删除不是 a-z 的字符
&lt;code>tr&lt;/code> 也可以用来转换大小写
&lt;code>$ echo 'hello world!' | tr '[a-z]' '[A-Z]'&lt;/code>，将小写转换成大写
&lt;code>$ echo 'hello world!' | tr '[:lower:]' '[:upper:]'&lt;/code>，与上面效果相同&lt;/p>
&lt;h3 id="操作-csv">操作 CSV&lt;/h3>
&lt;p>使用了三种工具：&lt;code>body&lt;/code>、&lt;code>header&lt;/code> 和 &lt;code>cols&lt;/code>&lt;/p>
&lt;h3 id="操作-html-和-json">操作 HTML 和 JSON&lt;/h3>
&lt;p>使用了&lt;code>curl&lt;/code>、&lt;code>scrape&lt;/code>、&lt;code>xml2json&lt;/code>、&lt;code>jq&lt;/code>、&lt;code>json2csv&lt;/code>&lt;/p>
&lt;h2 id="管理你的数据工作流">管理你的数据工作流&lt;/h2>
&lt;p>原书使用了 Drake 工具。因为不太通用，故跳过。&lt;/p>
&lt;h2 id="探索数据">探索数据&lt;/h2>
&lt;p>用了 R 语言中大名鼎鼎的 ggplot2 来进行数据可视化。
在 Python 中可以使用 matplotlib 或 plotly 等可视化工具。&lt;/p>
&lt;h2 id="并行管道">并行管道&lt;/h2>
&lt;h3 id="普通循环">普通循环&lt;/h3>
&lt;p>这个还是挺有用的。
使用 &lt;code>bc&lt;/code> 来计算数学表达式。&lt;code>$ echo &amp;quot;4^2&amp;quot; | bc&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ &lt;span class="k">for&lt;/span> i in &lt;span class="o">{&lt;/span>0..100..2&lt;span class="o">}&lt;/span>
&amp;gt; &lt;span class="k">do&lt;/span>
&amp;gt; &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$i&lt;/span>&lt;span class="s2">^2&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> bc
&amp;gt; &lt;span class="k">done&lt;/span> &lt;span class="p">|&lt;/span> tail
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>逐行循环：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ &lt;span class="k">while&lt;/span> &lt;span class="nb">read&lt;/span> line
&amp;gt; &lt;span class="k">do&lt;/span>
&amp;gt; &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;Sending invitation to &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">line&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.&amp;#34;&lt;/span>
&amp;gt; &lt;span class="k">done&lt;/span> &amp;lt; data/emails.txt
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>也可以从标准输入流（即你的控制台输入）中获取数据
&lt;code>$ while read i; do echo &amp;quot;You typed: $i.&amp;quot;; done &amp;lt; /dev/stdin&lt;/code>&lt;/p>
&lt;p>逐个文件循环：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">$ &lt;span class="k">for&lt;/span> filename in *.csv
&amp;gt; &lt;span class="k">do&lt;/span>
&amp;gt; &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;Processing &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">filename&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.&amp;#34;&lt;/span>
&amp;gt; &lt;span class="k">done&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>还可以使用 &lt;code>find&lt;/code> 来更灵活的寻找文件
&lt;code>$ find data -name '*.csv' -exec echo &amp;quot;Processing {}&amp;quot; \;&lt;/code>&lt;/p>
&lt;h3 id="gnu-parallel">GNU Parallel&lt;/h3>
&lt;p>非常好用的一个工具，可以让循环变得简单，并且易于管理。
&lt;code>$ seq 5 | parallel &amp;quot;echo {}^2 | bc&amp;quot;&lt;/code>&lt;/p>
&lt;h2 id="对数据建模">对数据建模&lt;/h2>
&lt;p>降维、聚类、回归、分类，机器学习问题。这里不展开了。
可以利用 python 等更友好的实现这些。&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/linux/">Linux</category><category domain="https://blog.imfing.com/tags/linux/">Linux</category><category domain="https://blog.imfing.com/tags/bash/">Bash</category><category domain="https://blog.imfing.com/tags/notes/">Notes</category></item><item><title>Win 下 Python 开发环境配置 &amp; Tips 分享</title><link>https://blog.imfing.com/2018/01/python-on-windows/</link><guid isPermaLink="true">https://blog.imfing.com/2018/01/python-on-windows/</guid><pubDate>Mon, 29 Jan 2018 01:57:49 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>多年的 PY 经验来告诉你如何在 Windows 下愉快的使用 Python 进行交易。包括但不限于 Python 安装选择、开发环境配置、模块和包管理、学习资源、使用 Linux 子系统中的 Python 等。
&lt;img src="https://user-images.githubusercontent.com/5097752/83357587-3d982300-a33b-11ea-82e9-3b577513afa9.jpg" alt="">&lt;/p>
&lt;!-- more -->
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>随着改革开放和进入二十一世纪，我国已迈入社会主义新时代。（画风突变）近几年来，Python 已经逐渐发展成为最为流行的程序设计语言之一，不仅仅是因为它入门简单，更重要的是它在各种场合的灵活性和可拓展性。目前为止，它的应用领域已经覆盖了 Web、数据科学、人工智能、传统软件等等。
对于大多数有 C 语言或者其它语言编程基础的同学，或者即使你没有任何编程基础，配置好合适的开发环境是入门 Python 的第一步。其实配置 Python 开发环境是非常简单，下面给出一些个人的建议。
&lt;img src="https://user-images.githubusercontent.com/5097752/83357597-4ab51200-a33b-11ea-87ce-255e18f3f39f.jpg" alt="">&lt;/p>
&lt;h2 id="python-环境选择">Python 环境选择&lt;/h2>
&lt;h3 id="版本选择">版本选择&lt;/h3>
&lt;p>可能你遇到的第一个问题就是如何选择 Python 的版本，用 &lt;strong>2&lt;/strong> 还是 &lt;strong>3&lt;/strong>。这其中有很多历史遗留的问题在里面。当然，2018 年了，Python2 要慢慢被淘汰了，所以优先推荐 Python3.x 版本。而且两者的语法几乎没有什么差别，而且已经有人为你考虑好了 &lt;a href="https://mp.weixin.qq.com/s?src=11&amp;amp;timestamp=1517592931&amp;amp;ver=674&amp;amp;signature=Dj*5Sm3tL8lNKZ9Y3SGTxQ9Q*J0ZsKJApBRjduDXNLaRtcwyEKglIO5MQXS1wtaFqCeIhOJ-a24uxH1qgCAa1dh-bKHl9n0dBvrb-MzbS-lKZiwUoFq8ISd2Ibil2P1c&amp;amp;new=1">《在 Python2.7 即将停止支持时，我们为你准备了一份 3.x 迁移指南》&lt;/a>（原文 &lt;a href="https://github.com/arogozhnikov/python3_with_pleasure">Github&lt;/a>)
当然，在 Linux 系统中，尤其是服务器，通常默认安装了 Python2.x 版本，而且两个 Python 版本共存会带来一些问题，因此传统的方式可能更受欢迎。&lt;/p>
&lt;h3 id="选择发行包">选择发行包&lt;/h3>
&lt;p>通常来说，安装 Python 有两种选择。一个是直接安装官网的安装包，另一个则是通过 Conda 来安装。&lt;/p>
&lt;p>&lt;a href="https://www.python.org/">Python 官网&lt;/a>，那么我们在 Download 下面可以看到 Latest 字样，点击右侧的 Python3.x 进入下载页面。拉到最下面 Files 中，选择 &lt;code>Windows x86-64 executable installer&lt;/code>，就可以进行下载了。官网的 Python 呢只包含了最必要的组件。
&lt;img src="https://user-images.githubusercontent.com/5097752/83357598-4d176c00-a33b-11ea-9022-61f0801da6b8.jpg" alt="">&lt;/p>
&lt;p>&lt;a href="https://www.anaconda.com/download/">Anaconda 官网&lt;/a>，Anaconda 指的是一个开源的 Python 发行版本，其包含了 conda、Python 等 180 多个科学包及其依赖项，是一个 &lt;strong>大而全&lt;/strong> 的 Python 版本。包含了 Spyder 集成开发环境和 Jupyter Notebook。
同时 conda 这个工具呢也能够更好的帮你进行包和环境管理，特别是有一些包在 Windows 下安装容易出错。
&lt;img src="https://user-images.githubusercontent.com/5097752/83357599-4e489900-a33b-11ea-9715-3b606c4d9e19.jpg" alt="">&lt;/p>
&lt;p>&lt;strong>如何选择&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>如果你是&lt;strong>初学者&lt;/strong>，不需要太多的功能，那么选择 Python 官网的版本&lt;/li>
&lt;li>如果你即是&lt;strong>初学者&lt;/strong>，但也需要接触&lt;strong>数据科学&lt;/strong>相关内容（数据处理、人工智能等），那么安装 Anaconda 是不二之选&lt;/li>
&lt;li>如果你有&lt;strong>一定基础&lt;/strong>，但想要更多个性话的安装方式，不妨试试 &lt;a href="https://conda.io/miniconda.html">Miniconda&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;em>注意&lt;/em>：Anaconda 官网下载速度很慢，这里有国内镜像地址可以加快下载速度：
&lt;a href="https://mirrors.tuna.tsinghua.edu.cn/help/anaconda/">清华大学镜像站&lt;/a>、&lt;a href="http://mirrors.ustc.edu.cn/help/anaconda.html">中科大镜像站&lt;/a>。选择相应的安装包即可（通常在&lt;strong>最下面&lt;/strong>）。&lt;/p>
&lt;h2 id="配置集成开发环境">配置集成开发环境&lt;/h2>
&lt;p>在 Windows 下，有很多集成开发环境 (IDE) 可以用。虽然用记事本就可以写 Python 程序，但有了 IDE 之后能够大大提高开发效率。接下来介绍几种 Python IDE：&lt;/p>
&lt;ul>
&lt;li>Visual Studio Code + Python 扩展&lt;/li>
&lt;li>Spyder&lt;/li>
&lt;li>PyCharm&lt;/li>
&lt;li>Visual Studio 2017 + Python Tools&lt;/li>
&lt;/ul>
&lt;h3 id="vs-code">VS Code&lt;/h3>
&lt;p>再次安利一遍 VS Code，可以去 &lt;a href="https://code.visualstudio.com/">这里下载&lt;/a>。官网中有比较详细的配置和使用教程：&lt;a href="https://code.visualstudio.com/docs/languages/python">Python in Visual Studio Code&lt;/a>、&lt;a href="https://code.visualstudio.com/docs/python/python-tutorial">Getting Started with Python&lt;/a>
配置过程不复杂，弄好之后能够很方便的在 VS Code 中写代码和断点调试，同时享受 IntelliSense 智能提示的舒爽。
&lt;img src="https://user-images.githubusercontent.com/5097752/83357658-ad0e1280-a33b-11ea-8376-f4b9c8dc0095.jpg" alt="">&lt;/p>
&lt;h3 id="spyder">Spyder&lt;/h3>
&lt;p>Anaconda 自带的集成开发环境 &lt;a href="https://spyder-ide.github.io/">Spyder&lt;/a>，界面类似 MATLAB，非常好上手。十分适用数据科学，当然其它开发也是一样的。
&lt;a href="https://pythonhosted.org/spyder/">文档&lt;/a>
&lt;a href="https://github.com/spyder-ide/spyder">spyder-ide/spyder&lt;/a>
&lt;img src="https://user-images.githubusercontent.com/5097752/83357667-b8613e00-a33b-11ea-90ab-8704bf89821d.jpg" alt="">&lt;/p>
&lt;h3 id="pycharm">PyCharm&lt;/h3>
&lt;p>JetBrains 出品，必属精品。和 JB 家其它产品有着相似的体验。
&lt;a href="https://www.jetbrains.com/pycharm/">官网地址&lt;/a>
初学可以下载社区版，如果需要进行 Web 开发则可以使用其专业版。
&lt;img src="https://user-images.githubusercontent.com/5097752/83357679-cca53b00-a33b-11ea-9585-210737bda98e.jpg" alt="">&lt;/p>
&lt;h3 id="vs2017--python-工具包">VS2017 + Python 工具包&lt;/h3>
&lt;p>&lt;a href="https://www.visualstudio.com/zh-hans/">Visual Studio 2017&lt;/a> 也加入了 Python 的支持。
&lt;a href="https://www.visualstudio.com/zh-hans/vs/python/">Python 工具页&lt;/a>
&lt;a href="https://docs.microsoft.com/zh-cn/visualstudio/python/installing-python-support-in-visual-studio">中文安装文档&lt;/a>
这种方式略微笨重一些，当然如果你经常用 Visual Studio 的话也可以顺便装了。
&lt;img src="https://user-images.githubusercontent.com/5097752/83357694-e181ce80-a33b-11ea-8638-2860c3fa789d.jpg" alt="">&lt;/p>
&lt;h2 id="python-模块和包">Python 模块和包&lt;/h2>
&lt;p>Python 的模块非常丰富，因此你不怎么需要自己造轮子。一些大名鼎鼎的库诸如 Numpy、SciPy、Matplotlib 等。那么如何管理这些包？&lt;/p>
&lt;h3 id="pip">Pip&lt;/h3>
&lt;p>pip是官方的包管理器，你可以方便的通过 &lt;code>pip install &amp;lt;包名&amp;gt;&lt;/code> 来安装，也可以通过 &lt;code>pip list&lt;/code> 来查看已安装的包。官方的包网站是 &lt;a href="https://pypi.python.org/pypi">Pypi&lt;/a>，这上面有所有可以通过 pip 安装的包。&lt;/p>
&lt;h3 id="conda">Conda&lt;/h3>
&lt;p>如果你用的 Anaconda 的话，它提供了 conda 工具来进行包管理。&lt;code>conda install &amp;lt;包名&amp;gt;&lt;/code>
当然有时候在国内因为网络问题安装太慢，那么可以&lt;a href="https://blog.imfing.com/2018/01/python-on-windows/#%E4%BD%BF%E7%94%A8%E5%9B%BD%E5%86%85%E9%95%9C%E5%83%8F%E5%8A%A0%E9%80%9F">使用国内镜像进行加速&lt;/a>。&lt;/p>
&lt;h3 id="第三方包">第三方包&lt;/h3>
&lt;p>在 Win 下，有时候安装包的时候会有奇奇怪怪报错，那么可以在下面的网站下载好编译好的 wheel 文件直接进行安装。下载好后到对应目录直接执行 &lt;code>pip install 文件名.whl&lt;/code>
&lt;a href="https://www.lfd.uci.edu/~gohlke/pythonlibs/">Unofficial Windows Binaries for Python Extension Packages&lt;/a>&lt;/p>
&lt;h2 id="python技巧">Python技巧&lt;/h2>
&lt;h3 id="使用国内镜像加速">使用国内镜像加速&lt;/h3>
&lt;p>通常安装包的时候因为服务器在国外所以速度很慢，那么可以通过切换到国内镜像源来加速 Python 包下载速度。通常有&lt;a href="http://pypi.doubanio.com/">豆瓣&lt;/a>、&lt;a href="http://mirrors.aliyun.com/help/pypi">阿里&lt;/a>、&lt;a href="https://mirrors.tuna.tsinghua.edu.cn/help/pypi/">清华&lt;/a>、&lt;a href="http://mirrors.ustc.edu.cn/help/pypi.html">中科大&lt;/a>。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357726-1db52f00-a33c-11ea-8381-f0ce66085e50.jpg" alt="">
以清华的为例
临时使用：&lt;/p>
&lt;pre>&lt;code>pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package
&lt;/code>&lt;/pre>&lt;p>想要修改为默认的可以参考&lt;a href="https://mirrors.tuna.tsinghua.edu.cn/help/pypi/">清华&lt;/a>的教程。&lt;/p>
&lt;h3 id="创建和管理虚拟环境">创建和管理虚拟环境&lt;/h3>
&lt;p>有时候我们需要运行别人的代码，可能它的包版本和我们不一样，为了创建一个完全的一样的运行环境，可以使用 virtualenv 等工具来创建虚拟环境。&lt;/p>
&lt;h3 id="virtualenv-和-venv">virtualenv 和 venv&lt;/h3>
&lt;p>&lt;a href="https://virtualenv.pypa.io/en/stable/userguide/#usage">官方文档&lt;/a>
&lt;strong>2.x&lt;/strong> 下，首先你需要安装virtualenv，通常是使用 &lt;code>pip install virtualenv&lt;/code>，注意使用管理员运行。Linux 下使用 &lt;code>sudo pip install virtualenv&lt;/code>，这样 virtualenv 就会作为一个系统工具来使用。
&lt;code>$ virtualenv ENV&lt;/code> 会创建一个 ENV 的文件夹，会把系统的 Python 环境拷贝一份到里面。Linux 的话直接 &lt;code>source ENV/bin/activate&lt;/code> 就可以激活了。Windows 的话会在里面生成 activate.bat 文件。&lt;/p>
&lt;p>&lt;strong>3.x&lt;/strong> 版本则可参考&lt;a href="https://docs.python.org/3/library/venv.html">官方文档&lt;/a>，使用 &lt;code>venv&lt;/code> 来创建虚拟环境。与 2.x 有一些差别。
&lt;code>python3 -m venv /path/to/new/virtual/environment&lt;/code>&lt;/p>
&lt;h3 id="conda-1">conda&lt;/h3>
&lt;p>Anaconda 也提供的 conda 工具也可以进行虚拟环境管理。而且可以选择 Python 版本。
&lt;code>conda create --name myenv&lt;/code>
详情请见&lt;a href="https://conda.io/docs/user-guide/tasks/manage-environments.html">官方文档&lt;/a>，这里不展开。和上面的 virtualenv 十分相似。&lt;/p>
&lt;h2 id="学习资源">学习资源&lt;/h2>
&lt;h3 id="在线教程">在线教程&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000">Python教程- 廖雪峰的官方网站&lt;/a>：这个是著名的在线教程（但我并没有看过&lt;/li>
&lt;li>&lt;a href="http://www.runoob.com/python/python-tutorial.html">Python 基础教程| 菜鸟教程&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.pythondoc.com/pythontutorial3/#">Python入门指南&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.gitbook.com/book/lenkimo/byte-of-python-chinese-edition/details">简明 Python 教程&lt;/a>：《A Byte of Python》中文版&lt;/li>
&lt;li>&lt;a href="https://www.imooc.com/learn/177">Python入门-慕课网&lt;/a>：视频教程&lt;/li>
&lt;li>&lt;a href="https://github.com/Yixiaohan/codeparkshare">Python初学者（零基础学习Python、Python入门）书籍、视频、资料、社区推荐&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>等等，有很多，善用搜索引擎&lt;/p>
&lt;h3 id="书籍推荐">书籍推荐&lt;/h3>
&lt;ul>
&lt;li>《笨办法学 Python》&lt;/li>
&lt;li>《Python编程 从入门到实践》&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://img1.doubanio.com/lpic/s27836847.jpg" alt="">&lt;/p>
&lt;p>还有很多，这些书都很容易找到电子版，不需要买纸质。&lt;/p>
&lt;h2 id="在-wsl-中使用-python">在 WSL 中使用 Python&lt;/h2>
&lt;p>在最新的 Windows 更新中，你可以在应用商店中获取适用于 Windows 的 Linux 子系统。这样相当于有了一个 Ubuntu 运行在你的 Windows 中，这样安装一些包之类的也会很方便。
&lt;a href="https://docs.microsoft.com/zh-cn/windows/wsl/install-win10">这里&lt;/a>有详细的安装教程。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357745-36bde000-a33c-11ea-8420-217102d816ba.jpg" alt="">&lt;/p>
&lt;p>之后使用 &lt;code>sudo apt install python&lt;/code> 就可以安装 python 了。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357744-36bde000-a33c-11ea-8f67-bd2fe2e33008.jpg" alt="">&lt;/p>
&lt;p>等想到什么再更新吧~ 开始你的 Python 之旅吧！&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/python/">Python</category><category domain="https://blog.imfing.com/tags/windows/">Windows</category></item><item><title>在 Win 下愉快的使用 VS Code + LaTeX</title><link>https://blog.imfing.com/2018/01/latex-on-win/</link><guid isPermaLink="true">https://blog.imfing.com/2018/01/latex-on-win/</guid><pubDate>Mon, 22 Jan 2018 21:29:45 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>想必大家都有被输入公式折磨过的时候，不妨学点 LaTeX (至今还不会读)。
当我们需要专业的排版的时候，LaTeX 绝对是一个比 Word 更好的选择。本文带你在 Windows 下配置愉快的 LaTeX 工作环境。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357865-1c383680-a33d-11ea-93f2-c936f28da2d7.jpg" alt="latex">&lt;/p>
&lt;!-- more -->
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>咳咳，今年 1 月 10 日正好是高德纳的 80 岁大寿。什么？你木有听说过这个老爷子？去&lt;a href="https://baike.baidu.com/item/%E5%94%90%E7%BA%B3%E5%BE%B7%C2%B7%E5%85%8B%E5%8A%AA%E7%89%B9">百度百科&lt;/a>瞅一眼，或者去他&lt;a href="https://www-cs-faculty.stanford.edu/~knuth/">个人主页&lt;/a>看看。他的代表作就是《计算机程序设计艺术》系列了。有兴趣可以去书店买一波书看看。emmm，扯远了，他还发明了 TeX，起因居然是他对当时的排版技术不满。&lt;/p>
&lt;p>最先接触 TeX 的时候是因为需要在 Markdown 下面输入数学公式，于是接触了 &lt;a href="https://www.mathjax.org/">MathJax&lt;/a>（在浏览器内渲染数学公式）。不得不说，这种生成公式的方式很多情况下比 MathType 之类的可视化编辑器效率要高，但输一些很复杂的公式的时候还是会有些繁琐。一般的 Markdown 编辑器像 &lt;a href="https://typora.io/">Typora&lt;/a> 之类都有 MathJax 的支持了。还有一个非常有用的参考 &lt;a href="https://math.meta.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference/5044">MathJax basic tutorial and quick reference&lt;/a>。&lt;/p>
&lt;p>在线的 LaTeX 工具有 &lt;a href="https://cn.sharelatex.com">ShareLaTeX&lt;/a>，非常好用，但是因为是在线工具，特别是国内网络会不稳定，加上在线编译其实是比较慢的。因此，如果是需要学术写作的话还是老老实实跟我来配置一个稳定快速的本地环境吧。&lt;/p>
&lt;h2 id="windows下配置">Windows下配置&lt;/h2>
&lt;p>用的组合是 MiKTeX + VS Code + LaTeX Workshop 插件&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357867-1e01fa00-a33d-11ea-8860-def60868b0cc.jpg" alt="image">&lt;/p>
&lt;h3 id="安装基础-tex-环境">安装基础 TeX 环境&lt;/h3>
&lt;p>TeXLive or MiKTeX? 我也被这个问题困扰过。后来还是选择了 MiKTeX，因为 TeXLive 虽然大而全，但安装包就动辄上 G 了，我得节约一些硬盘空间。还是选择 &lt;a href="https://miktex.org/">MiKTeX&lt;/a>，大家可以到它的&lt;a href="https://miktex.org/download">下载页面&lt;/a>下载，可以看到只要 191M，非常 nice！&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357869-235f4480-a33d-11ea-97e4-d2517eab0d41.jpg" width = "200px" alt="" align=center />&lt;/p>
&lt;p>下载后直接双击安装就行了。一路回车，安装完成之后就可以开始我们的下一步啦！&lt;/p>
&lt;h3 id="安装-vs-code">安装 VS Code&lt;/h3>
&lt;p>如果你安装好了，那么直接跳过这一段。如果你还没有安装好，那么我得再安利一遍微软爸爸的良心产品。&lt;/p>
&lt;p>作为后起之秀的 Visual Studio Code 已经和老牌编辑器 Sublime Text 不相上下，甩了 Atom 很远了。&lt;/p>
&lt;p>下载猛戳-&amp;gt; &lt;a href="https://code.visualstudio.com/">https://code.visualstudio.com/&lt;/a>&lt;/p>
&lt;h3 id="安装插件-latex-workshop">安装插件 LaTeX Workshop&lt;/h3>
&lt;p>就快要完成啦。在 VS Code 左边最后一个按钮搜索 &lt;a href="https://github.com/James-Yu/LaTeX-Workshop">LaTeX Workshop&lt;/a> 并安装，重新加载之后就可以了。
当然，还需要一点设置才能更加愉快的使用。
默认的编译器是 &lt;code>latexmk&lt;/code>，然而 Windows 下这货没法愉快的使用。这当然没关系，绝大多数学术写作的场合我们需要的是用 &lt;code>pdflatex&lt;/code> 生成 pdf 版本，用 &lt;code>bibtex&lt;/code> 生成引文。
我们可以按照该插件 &lt;a href="https://github.com/James-Yu/LaTeX-Workshop">GitHub&lt;/a> 页面上的指导设置 &lt;code>pdflatex -&amp;gt; bibtex -&amp;gt; pdflatex -&amp;gt; pdflatex&lt;/code> 的工作流，在 VS Code 的文件-&amp;gt;首选项-&amp;gt;设置里面添加上下面的这一段：&lt;/p>
&lt;p>&lt;strong>[更新于 2018.04]&lt;/strong> 上个月 LaTeX Workshop 更新了，采用了 recipe 管理 LaTeX 编译流程。简而言之就是你可以自定义 LaTeX 工作流。插件默认已经帮你写好了一些工具及参数，比如 &lt;code>pdflatex&lt;/code>、&lt;code>latexmk&lt;/code> 等。因此我们可以直接在 &lt;code>recipes&lt;/code> 下面定义流程：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="s2">&amp;#34;latex-workshop.latex.recipes&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;pdflatex&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;tools&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="s2">&amp;#34;pdflatex&amp;#34;&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;latexmk&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;tools&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="s2">&amp;#34;latexmk&amp;#34;&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;pdflatex -&amp;gt; bibtex -&amp;gt; pdflatex*2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;tools&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="s2">&amp;#34;pdflatex&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;bibtex&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;pdflatex&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;pdflatex&amp;#34;&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>为了编辑的需要，我们还可以设置自动换行：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="s2">&amp;#34;[latex]&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;editor.wordWrap&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;on&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="err">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>先点击左下角的按钮，生成一下项目，之后点击右上角的预览可以打开 pdf 的预览啦。就像上面的图里一样。
之后每次修改保存后就会自动重新编译成 pdf 并更新右侧的预览，是不是很方便呢。
如果出现问题，Ctrl+` 打开面板，在问题里面找到对应的问题并进行解决即可。&lt;/p>
&lt;h2 id="其它实用工具">其它实用工具&lt;/h2>
&lt;h3 id="pdf-工具">PDF 工具&lt;/h3>
&lt;p>不得不说，&lt;a href="https://acrobat.adobe.com/cn/zh-Hans/free-trial-download.html">Adobe Acrobat Pro DC&lt;/a> 才是终极的 PDF 解决方案。从生成到编辑。&lt;/p>
&lt;h3 id="插图工具">插图工具&lt;/h3>
&lt;p>最常用的图表绘制工具当然是 Excel 了。那么画出来的图表如何保存成高清图片呢？
如果直接是在 Word 里面，可以直接复制 Excel 的图表，然后 选择性粘贴 - Windows 增强元图格式，就可以插入清晰大图了。
如果是要插入 LaTeX 文件，就需要导出 pdf 格式，那么可以选中图表之后打印，然后将图表高清打印出来，再用 Acrobat 裁剪，就可以插入到 LaTeX 文件之中。
如果是 png 或者 jpeg 文件的图片，直接用 Adobe Acrobat 打开，再保存成 pdf 文件就可以了。&lt;/p>
&lt;p>我用了 &lt;a href="http://www.pdfforge.org/pdfcreator">PDF Creator&lt;/a> 来打印高清图表。当然，有了 Acrobat 之后应该是不太需要这个工具了。&lt;/p>
&lt;h3 id="eps-格式文件转换">EPS 格式文件转换&lt;/h3>
&lt;p>如果是要将 eps 格式插图和我们常用的 png, jpeg 之间进行转换要怎么做呢？
一种方法就是用支持 eps 格式的程序将其打开，比如 Adobe Illustrator，再转成其它格式。Adobe Acrobat 好像也支持这个文件类型，我没有具体试过。&lt;/p>
&lt;p>如何将 png，jpeg 文件转换成 eps 格式呢? 像 Photoshop 这样的软件当然是可以的。除此之外，我还发现 &lt;a href="http://www.imagemagick.org/script/download.php">Image Magick&lt;/a> 这个工具里面的 Convert.exe 可以实现这个功能。
为了更好的压缩比可以试试 &lt;code>$ convert example.png eps3:example.eps&lt;/code>，这样转换出来的 eps 文件不会太大。&lt;/p>
&lt;p>其它教程链接
&lt;a href="http://ddswhu.com/visual-studio-code-latex/">http://ddswhu.com/visual-studio-code-latex/&lt;/a>&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BB%8F%E9%AA%8C/">经验</category><category domain="https://blog.imfing.com/tags/windows/">Windows</category><category domain="https://blog.imfing.com/tags/latex/">LaTeX</category></item><item><title>告别 2017，迎接 2018</title><link>https://blog.imfing.com/2017/12/2017-summary/</link><guid isPermaLink="true">https://blog.imfing.com/2017/12/2017-summary/</guid><pubDate>Sat, 30 Dec 2017 02:17:33 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>记录一下我的 2017，祝各位 2018 新年快乐~
&lt;img src="https://user-images.githubusercontent.com/5097752/83357131-84d0e480-a338-11ea-9372-adea1dff907c.jpg" alt="2017-2018">&lt;/p>
&lt;!-- more -->
&lt;p>别往下看了，你看不完的。略略略&lt;/p>
&lt;p>-----萌萌的分割线-----&lt;/p>
&lt;p>岁末已至。再回首，365 天发生的许多事情恍如昨日。抓住 2017 的尾巴，我一直想写点东西来记录这不寻常的一年，那些人，那些事。顺便谈一谈自己对 2018 的期许。&lt;/p>
&lt;h2 id="上篇">上篇&lt;/h2>
&lt;p>故事就从四月说起。
4 月 8 日，我第一次见到了雷军本人。当然，他这次并非来演唱他的成名曲《Are You OK?》，而是参加“雷军奖学金”的颁奖典礼和创业论坛宣讲，我触动非常大。
成功的企业家们在创业之路上经历了太多艰辛，我们看到的也只是那些少数佼佼者。我对雷军的创业价值观是非常认同的，当然也有校友加成。
想起高三的时候喜欢和同桌打 (chui) 趣 (niu)：“大学毕业我就去创办一家香蕉 (Banana) 公司，然后把苹果收购了。”&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357142-97e3b480-a338-11ea-8af6-cd2a3bffdabb.jpg" width = "50%" height = "50%" alt="Are you ok" align=center />&lt;/p>
&lt;p>随后的4月末，2017编程之美挑战赛正式在我们学校开始宣传。起初抱着吃瓜态度的我被我们微软俱乐部的技术组组长“甩锅”去进行Kickoff和技术分享。好在今年的主题非常有意思，关于 NLP(自然语言处理)和 Bot (聊天机器人)。在讲座的前一天晚上慌张入门 + 赶 PPT 赶到凌晨三点，再一次证明 DDL 才是第一生产力。于是第二天就一直处于共产主义凝视状态。附上讲课链接：&lt;a href="https://blog.imfing.com/2017/04/BOP2017-Course">编程之美 2017 培训——打造你的人工智能&lt;/a>&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357320-b26a5d80-a339-11ea-8e2e-27cc4f2e3e66.jpg" width = "50%" height = "50%" alt="" align=center />&lt;/p>
&lt;p>时间来到五月。俱乐部换届，我成功晋升为“副”主席。算是达成了一个大学的小目标：体验一把&lt;del>学生会&lt;/del>主席。带一个社团，我还真没有太多经验。就这样，一个菜鸟选手开始了他的领导体验。
5 月 12 日，看完一部神作《摔跤吧，爸爸》后，迎来一年一度微软 Build 大会，Project Emma 再一次给像我一样的软粉充值了一波信仰。微软，真的是一家有情怀，推动人类进步的公司。
对了，这个月还上了一门有趣的课，不是微电子工业的发展，而是半导体与集成电路设计，认识了有趣的台湾来的老师，哦不，老哥。没想到 11 月的时候又能在上海重逢，不得不感慨命运的神奇。
这一个月除了思考今天吃什么之外，也经常在思考人生，思考如何度过大学接下来的时光。毕业季了，学长学姐们各奔东西，逍遥快活去了。两年后的自己不知在何方。&lt;/p>
&lt;p>匆匆到来的 6 月，成功被加朵女侠圈粉～ &amp;quot;It's not about deserve, it's about what you believe.&amp;quot;
转眼迎来编程之美预赛的 DDL，和同样是 DDL 选手的队友 Touko 爆肝了一夜的 Tensorflow 最终还是爆炸了。第二天索性扔了个随机结果交差。就像充满玄学的 TF 一样，我们进了初赛。而且非常幸运的是，初赛的时间和考试完美重合。（我还能说什么）&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357326-c01fe300-a339-11ea-987b-e286cf82fd03.jpg" width = "50%" height = "50%" alt="" align=center />&lt;/p>
&lt;p>7 月 5 号，带着大家顶着考试的压力，成功举办了编程之美线下活动——小美见面日。也因此为我们学校赢得多了两个微软夏令营的名额，环比增长 100%。在队友全在期末考试这种不利的局面下，我打算放弃编程之美的初赛了，毕竟晋级的希望看来如此渺茫。好在我考完还有 3 天才到 DDL，加上导师的“怂恿”，于是开启了独自打怪的模式。重操高中搞的 C#，边学边做，三天的极限操作，只能回答上自己名字的“人工智障”聊天机器人——“武大小土豆君”就此诞生。（谨以此纪念逝去的知名夜宵——小土豆）
我把自己写的 Bot 模板公开了 &lt;a href="https://github.com/mtobeiyf/bot-luis-qna">bot-luis-qna&lt;/a>，有需要的可以参考~&lt;/p>
&lt;p>7 月 12 号，在懵逼之中被告知进决赛了。200 进 8，我应该提前去买几注大乐透的。成功把导师也拉过去了，土豪的微软爸爸包四星级酒店哦~
这个月还选了两门暑期公选课。一门是《摄影技术与赏析》，炎炎夏日，背个相机在校园里游荡、尽情拍照。另一门是《基督教与中国文学》，大学里难得上到的文学性质的课，又带有宗教的元素，老师的谈吐我很喜欢。交完最后的报告老师特意发了一条短信来感谢我，很暖。
&lt;img src="https://user-images.githubusercontent.com/5097752/83357348-d3cb4980-a339-11ea-9f23-a520075a89bd.jpg" alt="image">&lt;/p>
&lt;p>之后的主旋律就是电子设计大赛，上半年断断续续的在弄四轴飞行器。在一次次的 trial 和 error 之后，时间越来越紧迫。改硬件、改方案，熬夜调参，7 天刷 5 次夜这种事情也第一次体验。每天醒来第一件事情就是庆幸自己还活着。Hell!
最后进了国赛的复测，本以为胜券在握，最后被评委极限反杀。心寒！&lt;/p>
&lt;p>等 8 月 12 日把电赛全部弄完之后，才能够全身心投入到编程之美决赛中。14 号去北京，15 号决赛。又是一波极限的操作，连夜部署了一个图像的服务。去北京之后才开始做 PPT，众人倒腾到凌晨 5 点。在上台展示之前我觉得自己都没法顺畅的讲下来，但有时候压力越大，爆发力就越强，面对台下一群大佬（只在微博里见过的那种），还是顺利完成了答辩。
最后评奖，如愿以偿，拿到了第二，奖品 Surface Pro 4 的顶配，队友随后也成功拿到了 MSRA 实习的机会。感觉最后颁奖典礼邹欣老师唯一没有黑的就是我们队了，也替我们学校争了口气。附上我当时录的&lt;a href="https://www.bilibili.com/video/av13483087/">开箱视频&lt;/a>。
这一次获奖是整个团队齐心协力完成的，离开了任何一个人都无法走到最后。特别感谢我们的指导老师。&lt;/p>
&lt;blockquote>
&lt;p>Ideas are cheap. What is hard is to implement them and to make them work.&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>Highlight of the year.&lt;/strong>&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83357364-e5145600-a339-11ea-8eed-8cbc7ba45331.jpg" alt="">&lt;/p>
&lt;p>第二年参加微软夏令营~
&lt;img src="https://user-images.githubusercontent.com/5097752/83357374-f2c9db80-a339-11ea-96df-4dcb660a0b80.jpg" alt="image">&lt;/p>
&lt;p>随后 9 月迎来俱乐部的招新。宣传工作可能做的太好了，人数爆炸，超出预期。见到了许多有潜力有热情的新生。
10 月又和俱乐部的小伙伴参加了 Android 的应用比赛。因为时间太赶了，最后交了个半成品。不过合作开发还是乐趣无穷的。
月底参加了 DoraHacks，好像是武汉为数不多的 Hackathon 活动。当然，我是去蹭吃蹭网的。见到了武汉其它高校厉害的同学。
11 月初又去上海复旦参加了 HACKxFDU，成功抽到了 Nvidia 的题，在N家的板子上做深度学习的应用部署。当然，我真实目的是去上海旅游的（逃。上海给我的印象很好。遇到了有趣的人与有趣的事。可能只是擦肩而过，但却难忘。
后来想去参观一下上海的企业，就联系了陈老师。正好他参加一个 ARM 技术论坛，就把我请过去了。特意改签了火车，去长了一波见识。这种机会，在武汉真的太少了。&lt;/p>
&lt;p>11 月中旬在我们学校举办了 Hackathon，虽然准备的过程全程出现 Bug，但最后效果还算不错，诞生了创意很好的项目。算是我在俱乐部办的比较成功的事了。希望一年能比一年好~
&lt;img src="https://user-images.githubusercontent.com/5097752/83357390-04ab7e80-a33a-11ea-98b0-2692b561cac7.jpg" alt="">&lt;/p>
&lt;p>当然这个月女神泰勒也出新专辑了，成功的海淘到了。不禁想起了高中和初中买专辑的痛苦，一晃就这么多年了。Everything has changed.
九月申的加拿大 MITACS 的 Research Intern 也面试完了。没去成多伦多，要不我就可以在明年暑假看泰勒的演唱会了。看来得在一个山沟沟里专心搞我的项目了。
当然，这个学期我也带了一个学期的 17 级小朋友的C语言上机，这届小朋友加油啊，不要浪费我给你们debug这么长时间。&lt;/p>
&lt;p>再回首，2017 有很多很多值得细细回味的。
但还是已经踏进了 2018 的大门了。是时候 Shake if off 了。&lt;/p>
&lt;h2 id="下篇">下篇&lt;/h2>
&lt;p>对 2018 也有太多的期许。
不辜负自己和他人的期望。实现一些小目标，收获自己的小确幸。&lt;/p>
&lt;p>首先把 17 年遗留下来的没有做的事情慢慢完成。
比如要在博客上写的一些文章，记录之前的一些心得。
比如做一些整理，收拾一下自己杂乱疲惫的身心。&lt;/p>
&lt;p>很多计划的事情要接着一件一件落实。
多看一些书修身养性，充盈一下自己的灵魂。
更系统的学一些东西，不浮于表面。&lt;/p>
&lt;p>享受生活，记录生活。
Be diverse and be myself.&lt;/p>
&lt;p>还打算在俱乐部举办的一系列技术活动，予力众人成就更多。这是我觉得有价值和有意义的事情。&lt;/p>
&lt;blockquote>
&lt;p>Try not be a man of success, but a man of value.&lt;/p>
&lt;/blockquote>
&lt;p>多多 Commit，少少 issue 和 bug。&lt;/p>
&lt;p>希望自己能够顺利度过暑假。然后能够实现去微软亚院实习的梦想~（2017 没有趁着编程之美去实在可惜）不枉我粉微软爸爸这么多年。
下半年十分关键。不知道会有怎样的 twist and fate，自己要做好准备。
希望能顺利申到自己的 Dream School。&lt;/p>
&lt;blockquote>
&lt;p>Done is better than perfect.&lt;/p>
&lt;/blockquote>
&lt;p>2018，遇见更好的自己~&lt;/p>
&lt;p>祝大家新年快乐~&lt;/p></description><category domain="https://blog.imfing.com/categories/%E7%94%9F%E6%B4%BB/">生活</category><category domain="https://blog.imfing.com/categories/%E9%9A%8F%E7%AC%94/">随笔</category><category domain="https://blog.imfing.com/tags/%E9%9A%8F%E7%AC%94/">随笔</category></item><item><title>编程之美 2017 培训——打造你的人工智能</title><link>https://blog.imfing.com/2017/04/bop2017-course/</link><guid isPermaLink="true">https://blog.imfing.com/2017/04/bop2017-course/</guid><pubDate>Sun, 23 Apr 2017 19:02:29 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>人工智能的时代已经来临，快来跟着本届编程之美来打造自己的人工智能吧。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669661-ec3c9d80-a59f-11ea-93cb-927ba620fb44.jpg" alt="打造你的人工智能女友">&lt;/p>
&lt;!-- more -->
&lt;p>编程之美 2017 官网：&lt;a href="https://studentclub.msra.cn/bop2017/">https://studentclub.msra.cn/bop2017/&lt;/a>&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669695-f6f73280-a59f-11ea-9dd4-c606e8e5af01.jpg" alt="编程之美2017">&lt;/p>
&lt;p>这次编程之美的主题是人工智能，利用微软的 &lt;strong>Bot Framework&lt;/strong> 和 &lt;strong>Cognitive Services&lt;/strong> 为你的学校打造一个最美 Bot。这次的主题还是非常有意思的。我也趁着这个机会，学习了一下微软的 Bot Framework，体验一下这个框架。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669700-f9598c80-a59f-11ea-8536-6130fccadc65.jpg" alt="大赛命题">&lt;/p>
&lt;p>人工智能可谓是当前最火的一个概念了。那么我们这次呢也是用这样的一个框架和微软的认知服务来实现一个人工智能的服务。为了使这次的课程更加有趣呢，我用了“打造你的人工智能'女友'”这样一个题目。其实并没有这么夸张，想要脱单还是得努力一把的~&lt;/p>
&lt;p>当然，在使用了 Bot Framework 和 LUIS 之后，我们要打造的这个机器人会更加“智能”。&lt;/p>
&lt;p>接下来请听详细分解：&lt;/p>
&lt;p>Github 项目地址：&lt;a href="https://github.com/mtobeiyf/bop2017-course">https://github.com/mtobeiyf/bop2017-course&lt;/a>&lt;/p>
&lt;p>相关资源下载：&lt;/p>
&lt;p>&lt;a href="http://pan.baidu.com/s/1gePmA3p#list/path=%2F">百度网盘&lt;/a>&lt;/p>
&lt;p>密码: q84a&lt;/p>
&lt;p>附上一位老铁的入门文章：&lt;a href="http://blog.csdn.net/huangfuyl/article/details/70636526">http://blog.csdn.net/huangfuyl/article/details/70636526&lt;/a>
我觉得写的很详细，大家不知道怎么操作的可以参考一下。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669701-fa8ab980-a59f-11ea-9942-fd6f384d1d81.jpg" alt="编程之美挑战赛">&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/microsoft/">Microsoft</category><category domain="https://blog.imfing.com/tags/programming/">Programming</category></item><item><title>必应精美壁纸下载（持续更新）</title><link>https://blog.imfing.com/2017/04/bing-wallpapers/</link><guid isPermaLink="true">https://blog.imfing.com/2017/04/bing-wallpapers/</guid><pubDate>Sat, 22 Apr 2017 19:06:03 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>喜欢必应精美壁纸的同学不要错过~ 文末附各个月的下载&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669858-3cb3fb00-a5a0-11ea-9fb2-ea653834bcd5.jpg" alt="必应壁纸">&lt;/p>
&lt;!-- more -->
&lt;p>[更新] 2017 年全年收录啦，2018 要开始了&lt;/p>
&lt;p>我们都知道必应搜索一直都坚持每天更换一张精美的首页背景图片。很多年前就喜欢用必应的图片作为桌面壁纸。&lt;/p>
&lt;p>以前 &lt;a href="https://www.ithome.com/">IT 之家&lt;/a>有个板块叫壁纸之家，会定期更新上一个月的必应每日图片合集，只要做一个伸手党就可以了。 然而去年更新的频率不仅变慢了，后来索性停更了。不得已，现在只能自力更生了。&lt;/p>
&lt;p>此前，必应的每日美图都可以在 &lt;a href="https://www.bing.com/gallery/">Bing Gallery&lt;/a>中找到。几个月前这个网站也停更了。当然，还是有第三方网站一直在做，比如 &lt;a href="https://bingwallpaper.com/">Bing Wallpaper Gallery&lt;/a>&lt;/p>
&lt;p>现在已经养成习惯每个月上必应 Gallery 把上个月所有的每日图片手工扒下来。
最近翻出了从 2015 年 10 月开始的必应壁纸合集，有刚刚过去的 2016 年全年的壁纸打包。2017 年的也会持续更新，敬请关注。&lt;/p>
&lt;p>[2017 年 12 月更新]
又发现了一个很好的网站 &lt;a href="https://bing.ioliu.cn/">bing.ioliu.cn&lt;/a>，是一个国人制作的，非常精美和方便。大家有需要可以到这里下载。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669859-3d4c9180-a5a0-11ea-8969-2d0a0bb94ae7.jpg" alt="">&lt;/p>
&lt;p>&lt;strong>下载地址&lt;/strong>&lt;/p>
&lt;p>&lt;a href="http://pan.baidu.com/s/1i5a931b">百度网盘&lt;/a>&lt;/p>
&lt;p>密码: h9hw&lt;/p></description><category domain="https://blog.imfing.com/categories/%E7%94%9F%E6%B4%BB/">生活</category><category domain="https://blog.imfing.com/categories/%E8%B5%84%E6%BA%90/">资源</category><category domain="https://blog.imfing.com/tags/%E8%B5%84%E6%BA%90/">资源</category></item><item><title>深度学习入坑 — Caffe 的安装</title><link>https://blog.imfing.com/2017/01/caffe-install/</link><guid isPermaLink="true">https://blog.imfing.com/2017/01/caffe-install/</guid><pubDate>Wed, 25 Jan 2017 20:52:11 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>入坑深度学习，接触的第一个框架便是 Caffe，该如何在自己的电脑上搭建 Caffe 的框架呢？
还真有点坑...&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83669989-71c04d80-a5a0-11ea-9262-06197385c931.jpg" alt="Caffe">&lt;/p>
&lt;!-- more -->
&lt;p>原文发布于 &lt;a href="https://www.zhihu.com/question/28540111/answer/142708503">深度学习入门爱好者如何使用Caffe搭建一个简单可训练学习的识别系统？ - Fing的回答 - 知乎&lt;/a>&lt;/p>
&lt;p>注：本文最初写于 2016.12.16，部分内容可能有点过时。&lt;/p>
&lt;p>Caffe，全称 &lt;strong>C&lt;/strong>onvolution &lt;strong>A&lt;/strong>rchitecture &lt;strong>F&lt;/strong>or &lt;strong>F&lt;/strong>eature &lt;strong>E&lt;/strong>xtraction，是一个清晰且快速的深度学习框架。&lt;/p>
&lt;h2 id="配置-caffe">配置 Caffe&lt;/h2>
&lt;p>成功在 Windows 平台和 Ubuntu 平台配置好了 Caffe。下面会讲一下如何在这两个平台配置 Caffe。
一开始在配置 Caffe 的过程中遇到了各种神坑。
强烈推荐使用 &lt;strong>Ubuntu&lt;/strong> 平台！
Caffe 官网地址：&lt;a href="http://caffe.berkeleyvision.org/">Caffe | Deep Learning Framework&lt;/a>
Github 地址：&lt;a href="https://github.com/BVLC/caffe">BVLC/caffe&lt;/a>&lt;/p>
&lt;h3 id="ubuntu-1604-下的配置">Ubuntu 16.04 下的配置&lt;/h3>
&lt;p>官网的教程其实比较详细了，推荐用 Python 接口。
&lt;a href="http://caffe.berkeleyvision.org/install_apt.html">Caffe | Installation: Ubuntu&lt;/a>&lt;/p>
&lt;h3 id="安装各种依赖库">安装各种依赖库&lt;/h3>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">sudo apt-get install libprotobuf-dev libleveldb-dev libsnappy-dev libopencv-dev libhdf5-serial-dev protobuf-compiler
sudo apt-get install --no-install-recommends libboost-all-dev
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">sudo apt-get install libgflags-dev libgoogle-glog-dev liblmdb-dev
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>安装Python&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">sudo apt-get install python-dev
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后把 Caffe 的源代码下载下来: &lt;code>git clone https://github.com/BVLC/caffe.git&lt;/code>
(当然没有安装 Git 的得先安装一下)
下载完成之后,进入 Caffe 文件夹, 进入里面的 Python 文件夹,然后输入&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="k">for&lt;/span> req in &lt;span class="k">$(&lt;/span>cat requirements.txt&lt;span class="k">)&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">do&lt;/span> pip install &lt;span class="nv">$req&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">done&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这一步是安装所有 Python 的依赖包，如果没有安装Pip的话可以 &lt;code>sudo apt install python-pip&lt;/code> 来安装。&lt;/p>
&lt;h3 id="第二部分-编译安装-caffe">第二部分: 编译安装 Caffe&lt;/h3>
&lt;p>到 Caffe 文件夹, 使用模板写个 &lt;code>Makefile.config&lt;/code>。具体就是先复制一下模板, 再改一些内容&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">cp Makefile.config.example Makefile.config
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;ul>
&lt;li>因为我们暂时使用 CPU 模式, 所以在 &lt;code>CPU_ONLY := 1&lt;/code> 前面的 &lt;code>#&lt;/code> 要去掉.&lt;/li>
&lt;li>两个路径要改成这样:( 添加后面的两个 hdf5 的路径, 否则编译时报 hdf5 错误 )&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="c1"># Whatever else you find you need goes here.&lt;/span>
INCLUDE_DIRS :&lt;span class="o">=&lt;/span> &lt;span class="k">$(&lt;/span>PYTHON_INCLUDE&lt;span class="k">)&lt;/span> /usr/local/include /usr/include/hdf5/serial
LIBRARY_DIRS :&lt;span class="o">=&lt;/span> &lt;span class="k">$(&lt;/span>PYTHON_LIB&lt;span class="k">)&lt;/span> /usr/local/lib /usr/lib /usr/lib/x86_64-linux-gnu/hdf5/serial
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>准备好了，我们就可以开始编译。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">make pycaffe
make all
make &lt;span class="nb">test&lt;/span>
make runtest
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>结果显示 &lt;code>ALL TESTS PASSED&lt;/code> 就安装好了, 只需要再加上一个 PYTHONPATH;
另外, 这个 make 默认是用 CPU 单核运算,如果想要快一点, 比如我想使用四核, 在 make 后面加上 &lt;code>-j4&lt;/code> 标签.
如果上面 4 行某一行报错之后想要重试,建议先 &lt;code>make clean&lt;/code> 再重新开始.&lt;/p>
&lt;h3 id="第三部分-设置-python-caffe">第三部分: 设置 Python Caffe&lt;/h3>
&lt;p>去到 Caffe 文件夹里面的 python 文件夹, 把当前路径记录下来 &lt;code>pwd&lt;/code>。 然后输入以下命令(把记下的路径放在相应地方)&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="nb">export&lt;/span> &lt;span class="nv">PYTHONPATH&lt;/span>&lt;span class="o">=&lt;/span>/path/to/caffe/python:&lt;span class="nv">$PYTHONPATH&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这时候应该可以了,试验一下:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">$ python2.7
Python 2.7.12 &lt;span class="o">(&lt;/span>default, Jul &lt;span class="m">1&lt;/span> 2016, 15:12:24&lt;span class="o">)&lt;/span>
&lt;span class="o">[&lt;/span>GCC 5.4.0 20160609&lt;span class="o">]&lt;/span> on linux2
Type &lt;span class="s2">&amp;#34;help&amp;#34;&lt;/span>, &lt;span class="s2">&amp;#34;copyright&amp;#34;&lt;/span>, &lt;span class="s2">&amp;#34;credits&amp;#34;&lt;/span> or &lt;span class="s2">&amp;#34;license&amp;#34;&lt;/span> &lt;span class="k">for&lt;/span> more information.
&amp;gt;&amp;gt;&amp;gt; import caffe
&amp;gt;&amp;gt;&amp;gt;
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>说明安装全部完成!&lt;/p>
&lt;h3 id="一些参考文档">一些参考文档：&lt;/h3>
&lt;p>&lt;a href="https://link.zhihu.com/?target=http%3A//www.linuxidc.com/Linux/2016-09/135034.htm">Ubuntu 16.04上安装Caffe(CPU only)&lt;/a>
&lt;a href="https://link.zhihu.com/?target=http%3A//blog.csdn.net/jnulzl/article/details/52077915">Caffe for Python 官方教程(翻译)&lt;/a>
&lt;a href="https://link.zhihu.com/?target=http%3A//blog.csdn.net/u010417185/article/details/52065472">Ubuntu16.4系统下为Python配置caffe环境 - 爱吃鱼的猫 - 博客频道 - CSDN.NET&lt;/a>&lt;/p>
&lt;h2 id="windows下的配置">Windows下的配置&lt;/h2>
&lt;h3 id="预先准备">预先准备&lt;/h3>
&lt;p>编译环境：&lt;strong>Visual Studio 2013&lt;/strong> &lt;del>Visual Studio 2015&lt;/del>
当时编译不能用 2015，最新的好像是可以了，一切请参考微软 Caffe Github 页面的说明！&lt;/p>
&lt;p>GPU 工具：&lt;a href="https://developer.nvidia.com/compute/cuda/8.0/prod/local_installers/cuda_8.0.44_win10-exe">CUDA 8.0&lt;/a> 下载完后把 &lt;code>-exe&lt;/code> 改为 &lt;code>.exe&lt;/code>，直接安装即可。
&lt;a href="https://www.python.org/ftp/python/2.7.12/python-2.7.12.msi">Python 2.7&lt;/a>
&lt;a href="https://developer.nvidia.com/compute/machine-learning/cudnn/secure/v5.1/prod/8.0/cudnn-8.0-windows10-x64-v5.1-zip">cuDNN x64&lt;/a> 把链接复制到迅雷等下载工具中即可，否则就注册账号，按官网提示下载。
&lt;a href="https://developer.nvidia.com/cudnn">https://developer.nvidia.com/cudnn&lt;/a>&lt;/p>
&lt;p>之后我们到微软在Github上提供的 Caffe 下载页：&lt;a href="https://github.com/Microsoft/caffe">Microsoft/caffe&lt;/a>&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670036-84d31d80-a5a0-11ea-9d52-6516722b7458.jpg" alt="直接下载ZIP文件">&lt;/p>
&lt;p>下载好了 ZIP 文件后，解压到任何位置，将文件夹改名为 &lt;code>caffe&lt;/code>&lt;/p>
&lt;h3 id="编译配置">编译配置&lt;/h3>
&lt;p>1.打开 caffe 文件夹，再打开 windows 文件夹，将其中 &lt;code>CommonSettings.props.example&lt;/code> 文件复制一份，改名为 &lt;code>CommonSettings.props&lt;/code>&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670095-9c120b00-a5a0-11ea-86df-d75c2c27031e.jpg" alt="CommonSettings">&lt;/p>
&lt;p>2.解压 &lt;code>cudnn-8.0-windows10-x64-v5.1.1.zip&lt;/code> 压缩包，得到 &lt;code>cuda&lt;/code> 文件夹，将其复制到 &lt;code>caffe&lt;/code> 文件夹下&lt;/p>
&lt;p>3.接下来我们编辑配置文件。右击 &lt;code>CommonSettings.props&lt;/code>，使用记事本或者其它文本编辑器打开。
将其中 CudaVersion 后面的 7.5 改为 &lt;strong>8.0&lt;/strong>
CuDnn 是指定之前 &lt;code>cuda&lt;/code> 文件夹的路径，我把 caffe 文件夹放在了 &lt;code>D:\Workspace&lt;/code> 下，因此这里填了 &lt;code>D:\Workspace\caffe&lt;/code>&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670113-a3d1af80-a5a0-11ea-9211-c620978c71d7.jpg" alt="CommonSettings">&lt;/p>
&lt;p>4.找到路径 &lt;code>C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\extras\visual_studio_integration\MSBuildExtensions&lt;/code> 文件夹，将其中的所有文件复制到 &lt;code>C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\V120\BuildCustomizations&lt;/code> 中&lt;/p>
&lt;p>5.这时候再打开 Caffe.sln，就可以全部正常加载了。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670118-a8966380-a5a0-11ea-8180-3ef12129bd42.jpg" alt="加载成功">&lt;/p>
&lt;h3 id="开始编译">开始编译&lt;/h3>
&lt;p>1.右击 libcaffe，选择 Properties
&lt;img src="https://user-images.githubusercontent.com/5097752/83670135-b2b86200-a5a0-11ea-9d23-c66fb84b27df.jpg" alt="">
2.属性设置为如图所示&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670152-bba93380-a5a0-11ea-8d46-f79db89874c2.jpg" alt="Release">&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670154-bd72f700-a5a0-11ea-9310-a5f4287b74ee.jpg" alt="Warning">&lt;/p>
&lt;p>3.之后再把 libcaffe 设置为启动项目&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670158-bea42400-a5a0-11ea-88d9-ea18ba622cb5.jpg" alt="启动项目">&lt;/p>
&lt;p>同时改上面编译为 Release&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670244-dda2b600-a5a0-11ea-9655-e4b17fc284f8.jpg" alt="Release">&lt;/p>
&lt;p>4.构建 libcaffe&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670164-bf3cba80-a5a0-11ea-90d9-4902143d8879.jpg" alt="编译">&lt;/p>
&lt;p>这一过程会比较漫长，因为 NuGet 会从网上把所有依赖文件全部下载到本地。大概有 1 个 G 左右。在 Workspace 文件夹下可以看到 &lt;code>NuGetPackages&lt;/code> 文件夹，里面就是编译用到的包。&lt;/p>
&lt;p>注意VS的输出窗口，看看 Build 是不是 Succeeded 了。
编译好了后会出现一个 Build 的文件夹，进 &lt;code>caffe\Build\x64\Release&lt;/code> 看看是否有相关文件&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670190-c95eb900-a5a0-11ea-96da-49384292242a.jpg" alt="Build">&lt;/p>
&lt;p>5.此时，同理可以编译 caffe
也可以直接编译 Solution，把所有 caffe 的组件全部编译好。
&lt;img src="https://user-images.githubusercontent.com/5097752/83670571-628dcf80-a5a1-11ea-8160-8c3a8e3f75cd.jpg" alt="Solution">&lt;/p>
&lt;ol start="6">
&lt;li>在 Release 文件夹里，就可以看到编译好的 caffe 了&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670268-e72c1e00-a5a0-11ea-8752-d30d60a10c5d.jpg" alt="caffe">&lt;/p>
&lt;p>配置好了之后，就可以开始搞事情了~&lt;/p>
&lt;p>参考：
&lt;a href="http://blog.csdn.net/xierhacker/article/details/51834563">Windows+VS2013爆详细Caffe编译安装教程&lt;/a>&lt;/p>
&lt;p>Fing
2016.12.16&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/">深度学习</category><category domain="https://blog.imfing.com/tags/deep-learning/">Deep Learning</category></item><item><title>Arduino 寝室智能门禁（刷卡 + 手机蓝牙）</title><link>https://blog.imfing.com/2016/08/arduino-smart-lock/</link><guid isPermaLink="true">https://blog.imfing.com/2016/08/arduino-smart-lock/</guid><pubDate>Tue, 30 Aug 2016 23:23:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>出门 &lt;strong>忘带钥匙&lt;/strong>，回来又碰巧室友一个都不在，只能在门口干等；躺在床上，有人敲门，又得起身下床开门……
懒癌附体，无药可治。于是在暑假，利用了两天时间，倒腾出来这个智能门禁。且听我慢慢分解。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670667-86e9ac00-a5a1-11ea-92b8-2e346f0c519d.jpg" alt="总览">&lt;/p>
&lt;!-- more -->
&lt;p>左图是门内的效果，右图是门外的校园卡感应器。&lt;/p>
&lt;h2 id="元件清单">元件清单&lt;/h2>
&lt;h3 id="arduino-uno-r3">Arduino Uno R3&lt;/h3>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670675-88b36f80-a5a1-11ea-8f32-fabe1204adc3.jpg" alt="Arduino Uno">&lt;/p>
&lt;p>核心控制部分，其它元件调度的 “大脑”。其实还考虑过 Arduino Nano，体积更小。但是在实际使用的时候出现过电流过小，无法稳定驱动舵机的情况。故为了稳定性，选择使用 Arduino Uno。&lt;/p>
&lt;h3 id="rc522-ic-卡识别模块">RC522 IC 卡识别模块&lt;/h3>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670679-89e49c80-a5a1-11ea-9392-4acb09917dd9.jpg" alt="RC522">
核心模块之二，兼容大多数主流的 MIFARE 标准的 IC 卡。可以通过它来读取校园卡的 UID (唯一标识符) 的值，从而达到身份验证的效果。&lt;/p>
&lt;h3 id="舵机-mg996r">舵机 MG996R&lt;/h3>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670682-8a7d3300-a5a1-11ea-86b5-c5d63d8b1ca7.jpg" alt="舵机">
没有用常见的 SG90 舵机是因为用舵机拉门栓需要挺大的力的。所以用了贵一点的 MG996R，保证扭力足够，能够拉动门栓。&lt;/p>
&lt;h3 id="hc-06-蓝牙模块">HC-06 蓝牙模块&lt;/h3>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670685-8bae6000-a5a1-11ea-84fb-ba0b9a4341a3.jpg" alt="HC-06">
工作在丛机模式下，负责接收数据。&lt;/p>
&lt;h3 id="蜂鸣器等">蜂鸣器等&lt;/h3>
&lt;p>蜂鸣器用来进行提示：刷卡成功 “滴” 一声，失败就 “滴 — 滴” 两声。
细的漆包线：IC 卡识别模块需要用 7 根线和 Arduino 进行连接，而且门缝比较窄，所以没有用普通导线。
数据线：一般买 Arduino 自带。
&lt;strong>热熔胶抢&lt;/strong>：固定走线、固定各种元件用。&lt;/p>
&lt;h2 id="硬件制作">硬件制作&lt;/h2>
&lt;p>第一张图上可以看到，我将所有的元器件固定在了一个洞洞板上，焊了一些排针方便拔插。还可以保证整体性，更加简洁。
当然也可以用杜邦线飞，但杜邦线不稳，容易掉。而且飞线太多也不太好看。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83670688-8c46f680-a5a1-11ea-9bf7-fba0bee87cf8.jpg" alt="布局">&lt;/p>
&lt;h2 id="软件制作">软件制作&lt;/h2>
&lt;p>控制舵机、蜂鸣器是小菜一碟，Arduino 的新手必备技能。下面主要说说蓝牙模块和射频模块的使用。
&lt;strong>蓝牙模块&lt;/strong>
使用方法可以见我另一篇文章中的总结：&lt;a href="http://www.jianshu.com/p/5c220d0d3692">[Arduino] HC-06 蓝牙模块使用小结&lt;/a>
&lt;strong>RC522 模块&lt;/strong>
GitHub 上有功能完整的库和丰富的示例了：&lt;a href="https://github.com/miguelbalboa/rfid">rfid&lt;/a>
在 Readme 中有 RC522 与 Arduino 的接口说明。第三方库的使用自行搜索。
之后就可以在 Arduino 工程文件中直接 &lt;code>#include &amp;lt;MFRC522.h&amp;gt;&lt;/code> 了。&lt;/p>
&lt;p>详细源代码：&lt;a href="https://github.com/mtobeiyf/access-system">GitHub&lt;/a>&lt;/p>
&lt;p>注意：供电的电源一定要好，不然电机很有可能无法驱动。最好用 5V 1.2A 来进行供电&lt;/p>
&lt;p>有更多问题欢迎提问。&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E6%99%BA%E8%83%BD%E7%A1%AC%E4%BB%B6/">智能硬件</category><category domain="https://blog.imfing.com/tags/arduino/">Arduino</category></item><item><title>MATLAB 串口通信与动态绘图</title><link>https://blog.imfing.com/2016/08/matlab-serial-plot/</link><guid isPermaLink="true">https://blog.imfing.com/2016/08/matlab-serial-plot/</guid><pubDate>Tue, 02 Aug 2016 23:38:29 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>有时候在调试一些单片机等外设时，现有的串口软件往往不能胜任我们数据处理的需求。而自己写一个上位机软件实在是小题大做了。
那么 MATLAB 其实时支持从串口读取数据的，再借助 MATLAB 强大的数据处理与分析函数，就能实时可视化传感器等数据了。&lt;/p>
&lt;!-- more -->
&lt;p>MATLAB 基础可以参考我之前的文章 &lt;a href="https://blog.imfing.com/2016/07/MATLAB-Notes/">『MATLAB 学习笔记』导航 &amp;amp; 资源 &amp;amp; 笔记&lt;/a>&lt;/p>
&lt;h2 id="matlab-串口通信">MATLAB 串口通信&lt;/h2>
&lt;p>MATLAB 本身是支持 &lt;a href="http://cn.mathworks.com/help/matlab/serial-port-devices.html">Serial Port Devices&lt;/a>，即串口设备。通过自带的一些函数可以操作串口（读取、写入等）
&lt;a href="http://cn.mathworks.com/help/matlab/matlab_external/getting-started-with-serial-i-o.html">官方英文教程&lt;/a>&lt;/p>
&lt;p>然而在实际使用中，我的和官方给出的例子有一些不同。
下面给出一个完整的串口读取的例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="c">% 新建串口对象&lt;/span>
&lt;span class="n">s1&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="n">serial&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;COM15&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;BaudRate&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">9600&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c">% 尝试代开串口&lt;/span>
&lt;span class="k">try&lt;/span>
&lt;span class="n">fopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s1&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c">% 打开串口对象&lt;/span>
&lt;span class="k">catch&lt;/span> &lt;span class="n">err&lt;/span>
&lt;span class="n">fclose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">instrfind&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c">% 关闭被占用的串口&lt;/span>
&lt;span class="n">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;请确认选择了正确的串口&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c">% 输出错误提示&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="n">a&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="n">fscanf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;%d&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c">% 读取数据&lt;/span>
&lt;span class="n">fclose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s1&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c">% 关闭串口&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="matlab-动态绘图">MATLAB 动态绘图&lt;/h2>
&lt;p>要实现读取一个数据，然后实时更新图像。而不是已知所有点坐标再进行绘图。&lt;/p>
&lt;h3 id="动态曲线">动态曲线&lt;/h3>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="n">h&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">animatedline&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c">% 创建空白动态曲线&lt;/span>
&lt;span class="n">axis&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="mi">0&lt;/span> &lt;span class="mi">40&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="mi">30&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c">% 设置坐标系范围&lt;/span>
&lt;span class="n">x&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">linspace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">40&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">800&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">xlabel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;时间&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">ylabel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;距离&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">k&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nb">length&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">y&lt;/span> &lt;span class="p">=(&lt;/span>&lt;span class="n">fscanf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">arduino&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;%d&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mf">100.0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">addpoints&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">h&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">k&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">drawnow&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可以实现从串口实时读取数据并进行显示。最终效果类似心电图。&lt;/p>
&lt;h3 id="动态点坐标">动态点坐标&lt;/h3>
&lt;p>这个挺头疼的。因为我不需要两点之间连线，而是类似随机的散点。
最终用 clf 的命令解决了问题。代码如下&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="n">n&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">500&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="nb">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="n">n&lt;/span>
&lt;span class="n">clf&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c">% 清空&lt;/span>
&lt;span class="n">a&lt;/span> &lt;span class="p">=(&lt;/span>&lt;span class="n">fscanf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;%d&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mf">100.0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">b&lt;/span> &lt;span class="p">=(&lt;/span>&lt;span class="n">fscanf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;%d&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mf">100.0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">plot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">px&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">py&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;or&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;MarkerSize&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;MarkerFaceColor&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;r&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">axis&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="mi">0&lt;/span> &lt;span class="mi">40&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">40&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;span class="n">drawnow&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>还有其他动态绘图的方法，比如 &lt;code>comet()&lt;/code> 函数、hold on 等用法。可以自行百度。但我之所以不采用这些方法的原因是因为它们的坐标系是会变化的，不固定。&lt;/p>
&lt;p>MATLAB 初学者，若有不足，请指正。&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/matlab/">MATLAB</category></item><item><title>Arduino HC-06 蓝牙模块使用小结</title><link>https://blog.imfing.com/2016/08/arduino-hc06-bluetooth/</link><guid isPermaLink="true">https://blog.imfing.com/2016/08/arduino-hc06-bluetooth/</guid><pubDate>Tue, 02 Aug 2016 07:22:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;h2 id="简介">简介&lt;/h2>
&lt;p>&lt;strong>蓝牙&lt;/strong>，顾名思义，蓝色的牙齿，并不能用来咬人，而是一种无线数据传输标准。
&lt;strong>HC-05&lt;/strong> 和 &lt;strong>HC-06&lt;/strong> 是现在使用较多的两种蓝牙模块。两者之间的区别是 &lt;strong>HC-05&lt;/strong> 是&lt;strong>主从一体机&lt;/strong>，既能向自己的“主子”打小报告，也可以接收下达的命令；而 &lt;strong>HC-06&lt;/strong> 只能工作在&lt;strong>从机模式&lt;/strong>下，那就只能乖乖接收上级的命令啦。
在很多情况下，我们都想当“霸道总裁”，让下级乖乖听话就 OK 了，不需要他多废话。那就用 HC-06 就够了。
下面是 HC-06 的模块示意图：
&lt;img src="https://user-images.githubusercontent.com/5097752/116758154-7de5a280-a9dd-11eb-8939-11095fa667d5.png" alt="示意图">&lt;/p>
&lt;p>可以看到它有 4 个引脚，那么它接线如下：&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/116758216-981f8080-a9dd-11eb-94b6-b97c9b123950.png" alt="接线图">&lt;/p>
&lt;p>在这里，我并没有使用 Arduino 的 0,1 号口，而是使用了两个数字口。这样就不会占用 Arduino 自身的串口。&lt;/p>
&lt;h2 id="at模式">AT模式&lt;/h2>
&lt;p>在按照上面的接线之后，HC-06 会自动进入 AT 模式，在这个模式下，可以通过串口对蓝牙模块进行一些诸如修改名称、密码的操作。
注意：AT 命令一定要大写，中间的“+”&lt;strong>不可省略&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>指令&lt;/th>
&lt;th>返回&lt;/th>
&lt;th>功能&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>AT&lt;/td>
&lt;td>OK&lt;/td>
&lt;td>确认连接&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AT+VERSION&lt;/td>
&lt;td>OKlinvorV1.8&lt;/td>
&lt;td>查看版本&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AT+NAMExxxx&lt;/td>
&lt;td>OKsetname&lt;/td>
&lt;td>设置蓝牙名称&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AT+PINxxxx&lt;/td>
&lt;td>OKsetPIN&lt;/td>
&lt;td>设定密码&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>----&lt;/td>
&lt;td>----&lt;/td>
&lt;td>----&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AT+BAUD1&lt;/td>
&lt;td>OK1200&lt;/td>
&lt;td>波特率设为1200&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AT+BAUD2&lt;/td>
&lt;td>OK2400&lt;/td>
&lt;td>波特率设为2400&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AT+BAUD3&lt;/td>
&lt;td>OK4800&lt;/td>
&lt;td>波特率设为4800&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AT+BAUD4&lt;/td>
&lt;td>OK9600&lt;/td>
&lt;td>波特率设为9600&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AT+BAUD5&lt;/td>
&lt;td>OK19200&lt;/td>
&lt;td>波特率设为19200&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AT+BAUD6&lt;/td>
&lt;td>OK38400&lt;/td>
&lt;td>波特率设为38400&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AT+BAUD7&lt;/td>
&lt;td>OK57600&lt;/td>
&lt;td>波特率设为57600&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AT+BAUD8&lt;/td>
&lt;td>OK115200&lt;/td>
&lt;td>波特率设为115200&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>其中 xxx 替换成自己想要的。
不建议设置更高的波特率，通信会不稳定&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="cm">/*
&lt;/span>&lt;span class="cm"> Name: Bluetooth.ino
&lt;/span>&lt;span class="cm"> Created: 2016/7/30 13:26:47
&lt;/span>&lt;span class="cm"> Author: Fing
&lt;/span>&lt;span class="cm">*/&lt;/span>
&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;SoftwareSerial.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;span class="cp">&lt;/span>&lt;span class="c1">// 使用软件串口，能讲数字口模拟成串口
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">SoftwareSerial&lt;/span> &lt;span class="nf">BT&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">8&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">9&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 新建对象，接收脚为8，发送脚为9
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="kt">char&lt;/span> &lt;span class="n">val&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 存储接收的变量
&lt;/span>&lt;span class="c1">&lt;/span>
&lt;span class="kt">void&lt;/span> &lt;span class="nf">setup&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="n">Serial&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">begin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">9600&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 与电脑的串口连接
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">Serial&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;BT is ready!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">BT&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">begin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">9600&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">//设置波特率
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="kt">void&lt;/span> &lt;span class="nf">loop&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="c1">// 如果串口接收到数据，就输出到蓝牙串口
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">Serial&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">available&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="n">val&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Serial&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="n">BT&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">val&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="c1">// 如果接收到蓝牙模块的数据，输出到屏幕
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">BT&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">available&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="n">val&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">BT&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="n">Serial&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">val&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>连接 Arduino，写入程序。进入串口，输入 AT，看看是不是有 OK 返回。
之后就可以愉快地使用 HC-06 模块了。&lt;/p>
&lt;h2 id="手机连接调试蓝牙">手机连接调试蓝牙&lt;/h2>
&lt;p>通过 SPP 蓝牙助手可以实现连接蓝牙和与蓝牙之间的通信。
我用的是 unWired Lite，可以在 Play 市场上下载到。
有些国内开发者也有相关的作品，自行搜索便可。&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E6%99%BA%E8%83%BD%E7%A1%AC%E4%BB%B6/">智能硬件</category><category domain="https://blog.imfing.com/tags/arduino/">Arduino</category></item><item><title>『MATLAB 学习笔记』导航 &amp; 资源 &amp; 笔记</title><link>https://blog.imfing.com/2016/07/matlab-notes/</link><guid isPermaLink="true">https://blog.imfing.com/2016/07/matlab-notes/</guid><pubDate>Sat, 23 Jul 2016 09:03:20 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>在 Coursera 上学习 MATLAB 课程的过程中，自己把笔记整理总结了一下，一方面方便自己日后的参考，另一方面也提供给大家一些资源和经验。&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83187848-d0974a00-a0fc-11ea-8e22-8d3786410573.jpg" alt="课程">&lt;/p>
&lt;!-- more -->
&lt;h2 id="matlab-程序设计入门">MATLAB 程序设计入门&lt;/h2>
&lt;blockquote>
&lt;p>【前言】这一部分偏重的是基于 MATLAB 的程序设计，即编程的方法，当然也包含了很多 MATLAB 的基础操作。个人觉得，之后对 MATLAB 一些工具的使用在有了 MATLAB 的编程基础的前提下回变得相对的轻松。&lt;/p>
&lt;/blockquote>
&lt;p>我是在 15 年下半年（大一）在 &lt;a href="https://www.coursera.org">Coursera&lt;/a> 学习了这一门课 &lt;a href="https://www.coursera.org/learn/matlab">MATLAB 程序设计入门&lt;/a>，英文课程，总体难度不大，毕竟是针对初学者的课程。但有一些章节的课后作业还是挺有难度的。
下面是我在学习这一课程过程中的笔记，因为是在课余学习的，只记了关键的一些操作与命令。考虑会在后续补上截图与操作。&lt;/p>
&lt;ol>
&lt;li>MATLAB 介绍 (略)&lt;/li>
&lt;li>&lt;a href="https://blog.imfing.com/2016/07/matlab-notes/#%E7%9F%A9%E9%98%B5%E7%9A%84%E6%93%8D%E4%BD%9C">矩阵的操作&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.imfing.com/2016/07/matlab-notes/#%E5%87%BD%E6%95%B0%E4%B8%8E%E8%84%9A%E6%9C%AC">函数与脚本&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.imfing.com/2016/07/matlab-notes/#%E5%88%86%E6%94%AF%E7%BB%93%E6%9E%84">分支结构&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.imfing.com/2016/07/matlab-notes/#%E5%BE%AA%E7%8E%AF%E7%BB%93%E6%9E%84">循环结构&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.imfing.com/2016/07/matlab-notes/#%E5%A4%9A%E6%80%81%E5%87%BD%E6%95%B0">多态函数&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.imfing.com/2016/07/matlab-notes/#%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA%E5%92%8C%E7%BB%98%E5%9B%BE">输入输出和绘图&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.imfing.com/2016/07/matlab-notes/#%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B">数据类型&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.imfing.com/2016/07/matlab-notes/#%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C">文件操作&lt;/a>&lt;/li>
&lt;/ol>
&lt;h3 id="matlab-应用">MATLAB 应用&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="http://www.jianshu.com/p/2fd315a0df8f">MATLAB 串口通信与动态绘图&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>后续不断补充&lt;/p>
&lt;h3 id="mathworks-官方资源">Mathworks 官方资源&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="http://cn.mathworks.com/help/matlab/getting-started-with-matlab.html">在线入门教程 (英文)&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://cn.mathworks.com/help/pdf_doc/matlab/index.html?s_cid=doc_ftr">英文 PDF 文档 (需登录)&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://pan.baidu.com/s/1c1CmDmK">官方 PDF 网盘下载&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://pan.baidu.com/s/1qYQrv7e">MATLAB 百度云下载&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="矩阵的操作">矩阵的操作&lt;/h2>
&lt;h3 id="克隆运算符colon-operator">克隆运算符（Colon Operator）&lt;/h3>
&lt;p>&lt;code>x=1:3:7&lt;/code> 生成数组 &lt;code>1 4 7&lt;/code> 表示 x 从 1 开始逐加 3，到 x&amp;lt;=7，相当于 &lt;code>for (i=1; i&amp;lt;=7; i+=3)&lt;/code> 这样一个循环。
&lt;code>x=1:100&lt;/code>，生成 &lt;code>1 2 3...100&lt;/code> 序列，即中间参数没有指定时默认逐加 1。&lt;/p>
&lt;h3 id="操作矩阵的一部分access-parts-of-a-matrix">操作矩阵的一部分（Access Parts of a Matrix）&lt;/h3>
&lt;p>有如下的一些方法：
假设 x=
1 2 3
4 5 6
7 8 9&lt;/p>
&lt;p>&lt;code>x(2,3)&lt;/code> 矩阵 x 第 2 行，第 3 列的元素 &lt;code>6&lt;/code>
&lt;code>x(end,2)&lt;/code> 矩阵 x 最后一行，第 2 列的元素 &lt;code>8&lt;/code>
&lt;code>x(2,[1 3])&lt;/code> 矩阵 x 第 2 行的第 1 列和第 3 列的元素 &lt;code>4 6&lt;/code>
&lt;code>x(2,1:3)&lt;/code> 第二行的 1,2,3 列元素 &lt;code>4 5 6&lt;/code>
&lt;code>x(:,2)&lt;/code> 第 2 列所有元素
&lt;code>[m,n] = size(x)&lt;/code> 得到 m 为行数，n 为列数
&lt;code>sum(x)&lt;/code> 对逐列求和，输出每列和的行向量 &lt;code>12 15 18&lt;/code>&lt;/p>
&lt;h3 id="矩阵生成matrix-building">矩阵生成（Matrix Building）&lt;/h3>
&lt;p>&lt;strong>指定元素&lt;/strong>&lt;/p>
&lt;p>&lt;code>zeros(5,6)&lt;/code>：5×6 的方阵，元素全为 0
&lt;code>ones(4,2)&lt;/code>：4×2 的方阵，元素全为 1
&lt;code>5*ones(4,2)&lt;/code>：元素全为 5
&lt;code>zeros(4)&lt;/code>：4×4 的方阵
&lt;code>diag(7 3 9 2)&lt;/code>：对角阵，7 3 9 2 位于其主对角线上，其余元素全为 0&lt;/p>
&lt;p>&lt;strong>随机元素&lt;/strong>&lt;/p>
&lt;p>&lt;code>rand(3,4)&lt;/code>,&lt;code>rand(5)&lt;/code>：分别生成 3×4 和 5×5 的矩阵，元素值 0-1
&lt;code>fix(1+rand(5,4)*10)&lt;/code>：fix 为取整
&lt;code>randi(10,4,5)&lt;/code>：生成 1-10 的 4×5 矩阵
&lt;code>randi([5 10],4,5)&lt;/code>：生成 5-10 的 4×5 矩阵
&lt;code>randn(1,1000)&lt;/code>：n 代表 normal，按正态分布生成随机数&lt;/p>
&lt;p>&lt;strong>随机数生成器（Random Generator）&lt;/strong>
每次打开 MATLAB 后 rand 的值便固定了。需要重置随机数。
&lt;code>rng(参数)&lt;/code>，参数部分可以是数字，可以是字符串。&lt;/p>
&lt;hr>
&lt;h2 id="函数与脚本">函数与脚本&lt;/h2>
&lt;h3 id="函数---function">函数 - Function&lt;/h3>
&lt;p>MATLAB 自带了丰富的函数，当然我们也可以自定义函数来实现自己想要的功能。
比如：&lt;code>rand(3,4)&lt;/code> 就能生成 3×4 的数表，每个数在 0~1 之间。
输入 &lt;code>edit&lt;/code> 编辑新文件&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span> &lt;span class="nf">myRand&lt;/span>
&lt;span class="n">a&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nb">rand&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="mi">9&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>保存为 &lt;code>myRand.m&lt;/code>，这样就定义了一个 myRand 函数，用来生成 1-10 之间的数。其中 a 为局部变量，只在其作用域 (myRand) 中有效。
就这样执行 &lt;code>myRand&lt;/code> 结果会被保存在变量 ans 中。&lt;/p>
&lt;p>修改一下第一行：&lt;code>function a = myRand&lt;/code>，本意是想输出 a，运行之后会发现输出了 &lt;code>a=... ans=...&lt;/code> 两个部分。想要只输出 ans，只需在 &lt;code>a=1+rand(3,4)*9&lt;/code> 后加上 &lt;code>;&lt;/code>
输入 &lt;code>b=myRand&lt;/code> 便可将值保存到变量 b 里。
将函数修改一下&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span>&lt;span class="w"> &lt;/span>a&lt;span class="p">=&lt;/span>&lt;span class="nf">myRand&lt;/span>&lt;span class="p">(&lt;/span>low, high&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="n">low&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nb">rand&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">high&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">low&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>就可以输出在low~high之间的随机数表了。若想输出a中的全部数字的和，则可以改成如下&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span>&lt;span class="w"> &lt;/span>[a,s]&lt;span class="p">=&lt;/span>&lt;span class="nf">myRand&lt;/span>&lt;span class="p">(&lt;/span>low, high&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="n">low&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nb">rand&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">high&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">low&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">v&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">(:);&lt;/span>
&lt;span class="n">s&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="n">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后在命令行中输入 &lt;code>[x ss]=myRand(1,10)&lt;/code> 就会分别输出数表 x 及所有数字之和 ss&lt;/p>
&lt;h3 id="function-的定义">function 的定义&lt;/h3>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab"> &lt;span class="n">funtion&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">out&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">arg1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">out&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">arg2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c">...]&lt;/span>
&lt;span class="p">=&lt;/span>&lt;span class="k">function&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">in&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">arg1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">in&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">arg2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c">...)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>不确定函数名是否已定义，可以用 &lt;code>help exist&lt;/code>&lt;/p>
&lt;h3 id="subfunction---定义多个-子-函数">subfunction - 定义多个 (子) 函数&lt;/h3>
&lt;p>举个栗子&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span>&lt;span class="w"> &lt;/span>[a, s] &lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">myRand&lt;/span>&lt;span class="p">(&lt;/span>low, high&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">low&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="nb">rand&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">high&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">low&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">s&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">sumAllElements&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">function&lt;/span>&lt;span class="w"> &lt;/span>summa &lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">sumAllElements&lt;/span>&lt;span class="p">(&lt;/span>M&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">v&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">M&lt;/span>&lt;span class="p">(:);&lt;/span>
&lt;span class="n">summa&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="全局变量">全局变量&lt;/h3>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">global&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">v&lt;/span>&lt;span class="p">=(&lt;/span>&lt;span class="c">...);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>一定要分开写&lt;/strong>
新手尽量少使用，可能会导致难检测的错误。&lt;/p>
&lt;h3 id="function-的优点">function 的优点&lt;/h3>
&lt;ol>
&lt;li>将一个大问题分解&lt;/li>
&lt;li>功能的分解&lt;/li>
&lt;li>代码复用&lt;/li>
&lt;li>普遍性&lt;/li>
&lt;/ol>
&lt;h3 id="script---脚本">Script - 脚本&lt;/h3>
&lt;p>是一个 &lt;code>.m&lt;/code> 文件，用来执行一系列命令、赋值等计算
执行脚本时只需输名字&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="n">fprint&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;This is matlab\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">pause&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">quit&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h2 id="分支结构">分支结构&lt;/h2>
&lt;h3 id="if语句">if语句&lt;/h3>
&lt;pre>&lt;code>function guess_my_number(x)
if x == 2
fprintf('Congrats! You guessed my number!\n');
end
&lt;/code>&lt;/pre>&lt;p>注意每个 if 语句都要以 &lt;strong>end&lt;/strong> 结尾&lt;/p>
&lt;h3 id="if-else语句">if-else语句&lt;/h3>
&lt;pre>&lt;code>function guess_my_number(x)
if x == 2
fprintf('Congrats! You guessed my number!\n');
else
fprintf('Not right, but a good guess.\n');
end
&lt;/code>&lt;/pre>&lt;h3 id="if-elseif-else-语句">if-elseif-else 语句&lt;/h3>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">guess_my_number&lt;/span>&lt;span class="p">(&lt;/span>x&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="k">if&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;Congrats! You guessed my number!\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">elseif&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">42&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;Too small. Try again\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">else&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;Not right, but a good guess.\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>最后一个 else 可以不要，就是一个 if-elseif 结构。&lt;/p>
&lt;h3 id="关系运算符relational-operators">关系运算符（Relational operators）&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">运算符&lt;/th>
&lt;th style="text-align:center">含义&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">==&lt;/td>
&lt;td style="text-align:center">判断是否等于&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">~=&lt;/td>
&lt;td style="text-align:center">是否不等于&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">&amp;gt;&lt;/td>
&lt;td style="text-align:center">是否大于&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">&amp;lt;&lt;/td>
&lt;td style="text-align:center">是否小于&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">&amp;gt;=&lt;/td>
&lt;td style="text-align:center">是否大于等于&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">&amp;lt;=&lt;/td>
&lt;td style="text-align:center">是否小于等于&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>进行判断后返回值为 1 (真) 或 0 (假)&lt;/p>
&lt;p>可以进行一组数的比较&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="p">[&lt;/span>&lt;span class="mi">4&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="mi">7&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">5&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">9&lt;/span> &lt;span class="mi">6&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="nb">ans&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>当然也可以这样，逐个与 4 进行比较：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="p">[&lt;/span>&lt;span class="mi">4&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="mi">7&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">&amp;lt;&lt;/span>&lt;span class="p">=&lt;/span> &lt;span class="mi">4&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>还有很多玩法&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="n">sum&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="mi">14&lt;/span> &lt;span class="mi">9&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="mi">14&lt;/span> &lt;span class="mi">8&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">14&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="逻辑运算符logical-operators">逻辑运算符（Logical operators）&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">运算符&lt;/th>
&lt;th style="text-align:center">含义&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">&amp;amp;&amp;amp;&lt;/td>
&lt;td style="text-align:center">逻辑与&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">||&lt;/td>
&lt;td style="text-align:center">逻辑或&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">~&lt;/td>
&lt;td style="text-align:center">逻辑非&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">输入&lt;/th>
&lt;th style="text-align:center">&lt;/th>
&lt;th style="text-align:center">&amp;amp;&amp;amp;&lt;/th>
&lt;th style="text-align:center">||&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">false&lt;/td>
&lt;td style="text-align:center">false&lt;/td>
&lt;td style="text-align:center">0&lt;/td>
&lt;td style="text-align:center">0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">false&lt;/td>
&lt;td style="text-align:center">true&lt;/td>
&lt;td style="text-align:center">0&lt;/td>
&lt;td style="text-align:center">1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">true&lt;/td>
&lt;td style="text-align:center">false&lt;/td>
&lt;td style="text-align:center">0&lt;/td>
&lt;td style="text-align:center">1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">true&lt;/td>
&lt;td style="text-align:center">true&lt;/td>
&lt;td style="text-align:center">1&lt;/td>
&lt;td style="text-align:center">1&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>非零为真，零为假&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="o">~&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="nb">pi&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="nb">ans&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但对两个矩阵使用 &lt;code>&amp;amp;&amp;amp;&lt;/code> 和 &lt;code>||&lt;/code>，应变为 &lt;code>&amp;amp;&lt;/code> 和 &lt;code>|&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">3&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">9&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nb">pi&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="nb">ans&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>help precedence&lt;/code> 获取关于运算符优先级的帮助&lt;/p>
&lt;h3 id="循环嵌套nested-if-statements">循环嵌套（Nested if-statements）&lt;/h3>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">if&lt;/span> &lt;span class="n">arg_1&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">arg_2&lt;/span>
&lt;span class="n">statement_1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">else&lt;/span>
&lt;span class="n">statement_2&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="k">else&lt;/span>
&lt;span class="n">statement_3&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="多态函数">多态函数&lt;/h2>
&lt;h3 id="多态函数polymorphic-functions">多态函数（Polymorphic functions）&lt;/h3>
&lt;p>&lt;strong>什么是多态&lt;/strong>&lt;/p>
&lt;p>是指函数可以根据输入输出的表达式 &lt;strong>个数&lt;/strong> 和 &lt;strong>类型&lt;/strong> 作出不同的反应。
举个栗子：&lt;code>zeros(5,6)&lt;/code> 生成 5×6 矩阵，&lt;code>zeros(4)&lt;/code> 生成 4×4 的矩阵。
MATLAB 中许多内置的函数是多态的（sqrt, max, size, plot, etc.）&lt;/p>
&lt;p>如何使自己的函数具有多态性？&lt;/p>
&lt;p>&lt;strong>nargin:&lt;/strong> 返回实际的输入参数的个数
&lt;strong>nargout:&lt;/strong> 返回实际要求的输出参数个数&lt;/p>
&lt;p>下面这个函数 &lt;code>multable(n,m)&lt;/code> 生成 n×m 的数阵，我们来使其具有多态性&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span>&lt;span class="w"> &lt;/span>[table summa] &lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">multable&lt;/span>&lt;span class="p">(&lt;/span>n,m&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="k">if&lt;/span> &lt;span class="n">nargin&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;span class="n">m&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="n">table&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">&amp;#39;&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">nargout&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;span class="n">summa&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">table&lt;/span>&lt;span class="p">(:));&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>[table s] = multable(3,4)&lt;/code>，会输出 3×4 的数表和其所有元素和
&lt;code>table = multable(5)&lt;/code>，只会输出 5×5 数表&lt;/p>
&lt;h3 id="健壮性robustness">健壮性（Robustness）&lt;/h3>
&lt;p>一个健壮的函数，会对各种可能出现错误的情况进行处理。上面的函数可以进行如下的优化：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span>&lt;span class="w"> &lt;/span>[table summa] &lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">multable&lt;/span>&lt;span class="p">(&lt;/span>n,m&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="c">% MULTABLE muliplication table.&lt;/span>
&lt;span class="c">% T = MULTABLE(N) return an N-by-N matrix&lt;/span>
&lt;span class="c">% containing the multiplication table for&lt;/span>
&lt;span class="c">% the integers 1 through N.&lt;/span>
&lt;span class="c">% MULTABLE(N,M) returns an N-by-M matrix.&lt;/span>
&lt;span class="c">% Both input arguments must be positive integers.&lt;/span>
&lt;span class="c">% [T SM] = MULTABEL(...) returns the matrix&lt;/span>
&lt;span class="c">% containing the multiplication table in T&lt;/span>
&lt;span class="c">% and the sum of all its elements in SM&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">nargin&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="c">% 没有输入&lt;/span>
&lt;span class="n">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;must have at least one input argument&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">nargin&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;span class="n">m&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">elseif&lt;/span> &lt;span class="o">~&lt;/span>&lt;span class="nb">isscalar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="o">~=&lt;/span> &lt;span class="nb">fix&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c">% 参数不为正整数&lt;/span>
&lt;span class="n">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;m needs to be a positive integer&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="o">~&lt;/span>&lt;span class="nb">isscalar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">~=&lt;/span> &lt;span class="nb">fix&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;n needs to be a positive integer&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="n">table&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">&amp;#39;&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">nargout&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;span class="n">summa&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">table&lt;/span>&lt;span class="p">(:));&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>%&lt;/code> 后为注释，在命令行中输入 &lt;code>help multable&lt;/code>，可以得到最上面一大段注释作为 help 的内容&lt;/p>
&lt;h3 id="持续变量persistent-variables">持续变量（Persistent variables）&lt;/h3>
&lt;p>和全局变量类似，但又有所区别。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span>&lt;span class="w"> &lt;/span>total &lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">accumulate&lt;/span>&lt;span class="p">(&lt;/span>n&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="n">persitent&lt;/span> &lt;span class="n">summa&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="nb">isempty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">summa&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">summa&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">else&lt;/span>
&lt;span class="n">summa&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">summa&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="n">total&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">summa&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>也就是说，再次调用这个函数的时候，summa 的值是接着上次的值的。
重置：&lt;code>clear accumulate&lt;/code>&lt;/p>
&lt;hr>
&lt;h2 id="输入输出和绘图">输入输出和绘图&lt;/h2>
&lt;h3 id="输入--输出inputoutput">输入 &amp;amp; 输出（Input&amp;amp;Output）&lt;/h3>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span> &lt;span class="nf">a&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">one_more&lt;/span>
&lt;span class="n">x&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">input&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;Gimme a number, buddy:&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">a&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面实现了输入一个 x，并将 x+1 赋值给 a，input 括号中的为控制台输出的提示信息。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;This is an output example\\n&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>输出一行信息&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">check_out&lt;/span>&lt;span class="p">(&lt;/span>n,price&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="n">total&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">price&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;%d items at %.2f each\\nTotal = $%5.2f \\n&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">price&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">total&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>fprintf 中的 f 代表 format，即格式化。&lt;/p>
&lt;p>&lt;code>%d&lt;/code>，整数
&lt;code>%.2f&lt;/code>，f 代表 fixed point，小数位为 2 位
&lt;code>%5.2&lt;/code>，共 5 位（包括小数点），小数点后 2 位。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;%4.1f\\n&amp;#39;&lt;/span>&lt;span class="p">,[&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>会输出&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-text" data-lang="text">1.0
2.0
3.0
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="绘图plot">绘图（Plot）&lt;/h3>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="n">a&lt;/span> &lt;span class="p">=(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.^&lt;/span>&lt;span class="mi">2&lt;/span>
&lt;span class="n">plot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>绘图一般是先生成数列，再绘图&lt;/p>
&lt;p>&lt;code>plot(t,b)&lt;/code>，以 t 为 x 轴，b 为 y 轴
&lt;code>figure(2)&lt;/code>，新建第 2 个空白的图像，如果有就切换到这个图像&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="n">x1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mf">0.1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nb">pi&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">y1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">sin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">x2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">pi&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mf">0.1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nb">pi&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">y2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">cos&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x2&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">plot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">y1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">x2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">y2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>自定义图像样式&lt;/strong>
&lt;code>plot(t,b,'m--o')&lt;/code>，最后一个参数中，m 表示 magenta (品红色)，-- 表示虚线，o 表示小圆圈标出每个点。可以在 &lt;code>help plot&lt;/code> 中找到更多相关参数&lt;/p>
&lt;p>&lt;code>hold on&lt;/code> 在当前 figure 上保留画出来的 图像，可以再做图像。与之相对的是 &lt;code>hold off&lt;/code>
&lt;code>plot(x2,y2,'r:')&lt;/code>，红色连续点线，&lt;code>plot(x1,y1,'g*')&lt;/code>，绿色，点用 * 表示
&lt;code>grid&lt;/code>，打开网格
&lt;code>title('xxxx')&lt;/code>，加上标题
&lt;code>xlabel('xxxx')&lt;/code>，x 轴加上标签
&lt;code>ylabel('xxxx')&lt;/code>，y 轴加上标签
&lt;code>legend('sine','consine')&lt;/code>，图例说明
&lt;code>axis([-2 12 -1.5 1.5])&lt;/code>，指定 x 轴从 - 2 到 12，y 轴从 - 1.5 到 1.5
&lt;code>close(1)&lt;/code>，关闭 figure1
&lt;code>close all&lt;/code>，关闭所有图像&lt;/p>
&lt;hr>
&lt;h2 id="数据类型">数据类型&lt;/h2>
&lt;p>&lt;strong>数据类型（Datatypes）&lt;/strong>&lt;/p>
&lt;p>同其他的编程语言一样，MATLAB 也有许多的数据类型。下面我们来看看我们用过哪些数据类型。&lt;/p>
&lt;p>&lt;code>class(arg)&lt;/code> 可以返回 arg 的类型。如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">class&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="nb">ans&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">double&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>whos&lt;/code> 命令可以列出当前工作区域变量的详细信息。&lt;/p>
&lt;h3 id="数字类型numerical-types">数字类型（Numerical Types）&lt;/h3>
&lt;p>&lt;strong>双精度实数（Double）&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>MATLAB 中默认的数据类型&lt;/li>
&lt;li>浮点表示形式，例如：12.34 = 1234 * 10&lt;sup>-2&lt;/sup>&lt;/li>
&lt;li>内存占用：64 bits（8 bytes）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>单精度实数（Single）&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>与双精度类似。&lt;/li>
&lt;li>内存占用：32 bits（4 bytes）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>整数类型（Integer types&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>分为有符号（Signed）、无符号（Unsigned）两类&lt;/li>
&lt;li>内存占用：8, 16, 32, 64 bit long&lt;/li>
&lt;/ul>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">数据类型&lt;/th>
&lt;th style="text-align:center">范围&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">int8&lt;/td>
&lt;td style="text-align:center">-2&lt;sup>7&lt;/sup>~2&lt;sup>7&lt;/sup>-1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">int16&lt;/td>
&lt;td style="text-align:center">-2&lt;sup>15&lt;/sup>~2&lt;sup>15&lt;/sup>-1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">int32&lt;/td>
&lt;td style="text-align:center">-2&lt;sup>31&lt;/sup>~2&lt;sup>31&lt;/sup>-1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">int64&lt;/td>
&lt;td style="text-align:center">-2&lt;sup>63&lt;/sup>~2&lt;sup>63&lt;/sup>-1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">uint8&lt;/td>
&lt;td style="text-align:center">0~2&lt;sup>8&lt;/sup>-1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">uint16&lt;/td>
&lt;td style="text-align:center">0~2&lt;sup>16&lt;/sup>-1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">uint32&lt;/td>
&lt;td style="text-align:center">0~2&lt;sup>32&lt;/sup>-1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">uint64&lt;/td>
&lt;td style="text-align:center">0~2&lt;sup>64&lt;/sup>-1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">single&lt;/td>
&lt;td style="text-align:center">-3.4×10&lt;sup>38&lt;/sup>~3.4×10&lt;sup>38&lt;/sup>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">double&lt;/td>
&lt;td style="text-align:center">-1.79×10&lt;sup>308&lt;/sup>~1.79×10&lt;sup>308&lt;/sup>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="一些实用的函数">一些实用的函数&lt;/h3>
&lt;p>&lt;strong>类型检测&lt;/strong>&lt;/p>
&lt;p>&lt;code>class&lt;/code> 返回类型&lt;/p>
&lt;p>&lt;code>isa&lt;/code> 返回是否是这个类型，例如&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">isa&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;double&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>范围检测&lt;/strong>&lt;/p>
&lt;p>&lt;code>intmax&lt;/code> &lt;code>intmin&lt;/code>&lt;/p>
&lt;p>&lt;code>realmax&lt;/code> &lt;code>realmin&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">intmax&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;uint32&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>类型转换&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="n">int8&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">uint32&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">double&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>需要说明的是，当一个变量重新赋值时，会转换为所赋值的类型。&lt;/p>
&lt;h3 id="字符串strings">字符串（Strings）&lt;/h3>
&lt;p>字符串，顾名思义，是由一串字符组成的。&lt;/p>
&lt;p>在 MATLAB 中，字符串其实是一个向量，其中的元素自然是字符（char），字符又是由 ASCII 码值，可以得到一个打印所有可见字符的例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span> &lt;span class="nf">char_codes&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">ii&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">33&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">126&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;%s&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">char&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ii&lt;/span>&lt;span class="p">));&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>MATLAB 中，有一些常用的操作字符串的函数：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">函数&lt;/th>
&lt;th style="text-align:center">作用&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">findstr&lt;/td>
&lt;td style="text-align:center">找到字符串中的子串&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">num2str&lt;/td>
&lt;td style="text-align:center">将数字转换为字符串&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">str2num&lt;/td>
&lt;td style="text-align:center">将字符串转换为数字&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">strcmp&lt;/td>
&lt;td style="text-align:center">比较两个字符串&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">lower&lt;/td>
&lt;td style="text-align:center">将字符串转换为小写&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">upper&lt;/td>
&lt;td style="text-align:center">将字符串转换为大写&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">sprintf&lt;/td>
&lt;td style="text-align:center">将结果写入字符串中&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>具体使用方法可以参考 MATLAB 自带的 WIKI&lt;/p>
&lt;p>注：&lt;code>strcmp&lt;/code> 在比较时严格区分大小写，若忽略大小写，可以用 &lt;code>strcmpi&lt;/code>&lt;/p>
&lt;p>&lt;code>sprintf&lt;/code> 的用法和 &lt;code>fprintf&lt;/code> 用法基本一致，但区别在于 &lt;code>sprintf&lt;/code> 返回一个字符串&lt;/p>
&lt;h3 id="结构体类型structs">结构体类型（Structs）&lt;/h3>
&lt;p>众所周知，在 MATLAB 中，一个数列只能包含类型相同的一组数据，那我们需要存储类型不同的数据怎么办呢？结构体就能做到。&lt;/p>
&lt;p>&lt;strong>结构体和数组的区别：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>结构体的基本单位是字段 (fields) 而非元素 (elements)&lt;/li>
&lt;li>我们访问结构体时用的是字段名称 (field name) 而非索引 (indices)&lt;/li>
&lt;li>字段可以有不同的数据类型 (甚至是另一个结构体)&lt;/li>
&lt;/ul>
&lt;p>灵活性：字段可以包含子结构体；结构体可以包含数组，同时数组元素也可以是结构体&lt;/p>
&lt;p>下面来实践一下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ssn&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">12345678&lt;/span>
&lt;span class="c">% 以上定义了一个结构体 r&lt;/span>
&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#39;Homer Simpson&amp;#39;&lt;/span>
&lt;span class="c">% 继续添加其它类型的字段&lt;/span>
&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">address&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">street&lt;/span> &lt;span class="p">=&lt;/span> &amp;#34;&lt;span class="mi">742&lt;/span> &lt;span class="n">Evergreen&lt;/span> &lt;span class="n">Terrace&lt;/span>&amp;#34;
&lt;span class="c">% 这里的 r.address 就是一个子结构体&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>一些常用的操作结构体的函数：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">函数&lt;/th>
&lt;th style="text-align:center">作用&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">isfield&lt;/td>
&lt;td style="text-align:center">判断结构体中是否有字段&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">setfield&lt;/td>
&lt;td style="text-align:center">动态添加字段&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">rmfield&lt;/td>
&lt;td style="text-align:center">删除结构体中的一个字段&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">struct&lt;/td>
&lt;td style="text-align:center">快速构造一个结构体&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>结构体数组&lt;/strong>：在已经定义了一个结构体的前提下，可以快速构造结构体数组。已知 &lt;code>account&lt;/code> 为一个结构体&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="o">&amp;gt;&amp;gt;&lt;/span>&lt;span class="n">account&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">number&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">7654321&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">account&lt;/span> &lt;span class="p">=&lt;/span>
&lt;span class="mi">1&lt;/span>×&lt;span class="mi">2&lt;/span> &lt;span class="n">struct&lt;/span> &lt;span class="n">array&lt;/span> &lt;span class="n">with&lt;/span> &lt;span class="n">fields&lt;/span>&lt;span class="p">:&lt;/span>
&lt;span class="n">number&lt;/span>
&lt;span class="n">balance&lt;/span>
&lt;span class="n">owner&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>值得注意的是，在 &lt;code>account(2)&lt;/code> 中，除了赋过值的 &lt;code>number&lt;/code> 字段外，其它字段初始为空&lt;/p>
&lt;p>在一个结构体数组中，如果一个结构体的字段是一个子结构体，那么这两个子结构体不一定要相同。&lt;/p>
&lt;p>注意：在使用 &lt;code>rmfield&lt;/code> 时，应将返回值赋给要删除字段的结构体。&lt;/p>
&lt;h3 id="元胞数组cells">元胞数组（Cells）&lt;/h3>
&lt;p>这种类型也是 MATLAB 独有的，类似于 C 语言中的指针 (Pointer)，但一个元胞 (Cell) 并不直接存内存的地址，而是指向某变量。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="c">%% The Ultimate Legend of Big John 相当于段落文本&lt;/span>
&lt;span class="n">page&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#39;You could find him on the field almost any day.&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">page&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#39;Tall, dark hair, and eyes of steel gray.&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">page&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#39;They say he pulled a Frisbee &amp;#39;&amp;#39;bout half a mile,&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">page&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#39;And when he&amp;#39;&amp;#39;d stick in the corner, you could almost catch a smile&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">page&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#39;On Big John.&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="c">%% 打印输出&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">ii&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nb">length&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">page&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;%s\n&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">page&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="n">ii&lt;/span>&lt;span class="p">});&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可以看出元胞数组的定义是用了一对花括号 &lt;code>{}&lt;/code>，而里面的数字类似于数组的索引。此外，你可以这样构建元胞数组：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="n">p&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">cell&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">}&lt;/span>
&lt;span class="n">p&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#39;Awesome&amp;#39;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>类似于 2×3 的数组。但里面的每个元素都是元胞 (Cell)&lt;/p>
&lt;p>注意：MATLAB 不允许两个元胞指向同一个对象，也就是说，当你令 &lt;code>c2 = c1&lt;/code> 时，虽然表面上 c2 的数据和 c1 是一样的，但 MATLAB 悄悄地将 c1 的数据拷贝到了新的内存地址中，也就是 c2 指向的数据地址和 c1 指向的数据地址是不一样的。&lt;/p>
&lt;p>还有许多实用的函数可以操作元胞数组，有兴趣可以看一下官方文档。&lt;/p>
&lt;h2 id="文件操作">文件操作&lt;/h2>
&lt;p>文件操作（File IO）&lt;/p>
&lt;p>使用文件有诸多优点：
首先它是被一直存储在硬盘中的，它里面储存着信息，文件可以由操作系统轻松的管理，文件同时也可以被复制和移动，可以被不同软件访问。
对于 MATLAB，它不仅可以处理其专有文件格式 (.mat .m)，还可以处理 txt 文本文档、二进制文件，甚至 Excel 文件。&lt;/p>
&lt;p>&lt;code>pwd&lt;/code> 命令可以显示当前所在的目录 (Print Working Directory)
&lt;code>ls&lt;/code> 命令可以列出当前目录下所有文件
&lt;code>cd&lt;/code> 命令可以改变目录 (Change Directory)&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-text" data-lang="text">cd(&amp;#39;Lesson 08&amp;#39;) % 进入当前文件夹下的 Lesson 08 文件夹
cd(&amp;#39;..&amp;#39;) % 返回上级目录
cd(&amp;#39;../..&amp;#39;) % 返回上上级目录
mkdir(&amp;#39;folder&amp;#39;) % 创建新目录
rmdir(&amp;#39;folder&amp;#39;) % 删除空目录
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>save&lt;/code> 命令可以保存当前工作区
&lt;code>load&lt;/code> 命令可以加载当前工作区&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="n">save&lt;/span> &lt;span class="n">my_data_file&lt;/span> &lt;span class="n">data&lt;/span> &lt;span class="n">s&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="c">% 将变量 a 和 s 保存至 my_data_file.mat&lt;/span>
&lt;span class="n">load&lt;/span> &lt;span class="n">my_data_file&lt;/span> &lt;span class="c">% 从 my_data_file.mat 中加载变量&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="excel-文件操作">Excel 文件操作&lt;/h3>
&lt;p>MATLAB 支持读取和写入 Microsoft Excel 文件。需要用到 &lt;code>xlsread&lt;/code> 和 &lt;code>xlswrite&lt;/code> 两个函数。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">num&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">txt&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">raw&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">xlsread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;Nashville_climate.xlsx&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>num&lt;/code> 元素包含了表中所有的数字，&lt;code>txt&lt;/code> 则是包含了文字，&lt;code>raw&lt;/code> 储存了所有&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">temps&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">xlsread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;Nashville_climate.xlsx&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c">% temps 包含有数字&lt;/span>
&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">temps&lt;/span> &lt;span class="n">txt&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">xlsread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;Nashville_climate.xlsx&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="o">~&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">text&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">xlsread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;Nashville_climate.xlsx&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c">% 只输出 text&lt;/span>
&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="o">~&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">~&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">everything&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">xlsread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;Nashville_climate.xlsx&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">xlsread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;Nashville_climate.xlsx&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#39;D15:E17&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="c">% 数字 1 表示工作簿，D15 指定了 D15 这个单元格&lt;/span>
&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">xlsread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;Nashville_climate.xlsx&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#39;D15:E17&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>至于 &lt;code>xlswrite&lt;/code>，可以写入 &lt;code>CSV&lt;/code> 文件。不详细讨论了。&lt;/p>
&lt;h3 id="txt-文件操作">txt 文件操作&lt;/h3>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="n">fid&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">fopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">permission&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">fclose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其中 permission 这个参数可以有很多，如：&lt;code>rt&lt;/code> &lt;code>wt&lt;/code> &lt;code>at&lt;/code> &lt;code>r+t&lt;/code> &lt;code>w+t&lt;/code> &lt;code>a+t&lt;/code>&lt;/p>
&lt;p>下面用一个示例来进行 txt 文件读取操作的演示。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">view_text_file&lt;/span>&lt;span class="p">(&lt;/span>filename&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="n">fid&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">fopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;rt&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">fid&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;span class="n">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;error opening file %s\n&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">filename&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="c">% Read file as a set of strings, one string per line:&lt;/span>
&lt;span class="n">oneline&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">fgets&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">while&lt;/span> &lt;span class="n">ischar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">oneline&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;%s&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">oneline&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c">% display one line&lt;/span>
&lt;span class="n">oneline&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">fgets&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">fclose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>再来看看如何写入 txt 文件。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">write_temp_precip_txt&lt;/span>&lt;span class="p">(&lt;/span>filename&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="n">Title_1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#39;Climate Data for Nashville, TN&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">Title_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#39;(Average highs(F), lows(F), and precip(in)&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">Label_1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#39;High&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">Label_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#39;Low&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">Label_3&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#39;Precip&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">Mo_1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;Jan&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Feb&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;March&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;April&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;May&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;June&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#39;July&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Aug&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Sep&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Oct&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Nov&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;Dec&amp;#39;&lt;/span>&lt;span class="p">};&lt;/span>
&lt;span class="n">Data_1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="mi">46&lt;/span> &lt;span class="mi">28&lt;/span> &lt;span class="mf">3.98&lt;/span>
&lt;span class="mi">51&lt;/span> &lt;span class="mi">31&lt;/span> &lt;span class="mf">3.7&lt;/span>
&lt;span class="mi">61&lt;/span> &lt;span class="mi">39&lt;/span> &lt;span class="mf">4.88&lt;/span>
&lt;span class="mi">70&lt;/span> &lt;span class="mi">47&lt;/span> &lt;span class="mf">3.94&lt;/span>
&lt;span class="mi">78&lt;/span> &lt;span class="mi">57&lt;/span> &lt;span class="mf">5.08&lt;/span>
&lt;span class="mi">85&lt;/span> &lt;span class="mi">65&lt;/span> &lt;span class="mf">4.09&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="n">Data_2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="mi">89&lt;/span> &lt;span class="mi">69&lt;/span> &lt;span class="mf">3.78&lt;/span>
&lt;span class="mi">88&lt;/span> &lt;span class="mi">68&lt;/span> &lt;span class="mf">3.27&lt;/span>
&lt;span class="mi">82&lt;/span> &lt;span class="mi">61&lt;/span> &lt;span class="mf">3.58&lt;/span>
&lt;span class="mi">71&lt;/span> &lt;span class="mi">49&lt;/span> &lt;span class="mf">2.87&lt;/span>
&lt;span class="mi">59&lt;/span> &lt;span class="mi">40&lt;/span> &lt;span class="mf">4.45&lt;/span>
&lt;span class="mi">49&lt;/span> &lt;span class="mi">31&lt;/span> &lt;span class="mf">4.53&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="n">fid&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">fopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;w+t&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">fid&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;error opening file\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;%s\n&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">Title_1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;%s\n&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">Title_2&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39; %s%s%s\n&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">Label_1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">Label_2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">Label_3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">ii&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">6&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;%5s: &amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">Mo_1&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="n">ii&lt;/span>&lt;span class="p">});&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;%5.2f,%5.2f,%5.2f\n&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">Data_1&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ii&lt;/span>&lt;span class="p">,:));&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;\n&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39; %s%s%s\n&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">Label_1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">Label_2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">Label_3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="n">ii&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">6&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;%5s: &amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">Mo_2&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="n">ii&lt;/span>&lt;span class="p">});&lt;/span>
&lt;span class="n">fprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;%5.2f,%5.2f,%5.2f\n&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">Data_2&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ii&lt;/span>&lt;span class="p">,:));&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="n">fclose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="二进制文件操作">二进制文件操作&lt;/h3>
&lt;p>在更多的时候，使用二进制 (binary) 文件进行文件存储和读取具有更高的效率。&lt;/p>
&lt;p>下面就给出操作二进制的例子来看看 MATLAB 是如何存取二进制文件的。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span>&lt;span class="w"> &lt;/span>A &lt;span class="p">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">read_bin_file&lt;/span>&lt;span class="p">(&lt;/span>filename,data_type&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="n">fid&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">fopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;r&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">fid&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;span class="n">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;error opening file %s\n&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="n">A&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">fread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nb">inf&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">data_type&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">fclose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>下面是写入二进制文件&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-matlab" data-lang="matlab">&lt;span class="k">function&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">write_array_bin&lt;/span>&lt;span class="p">(&lt;/span>A,filename&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w">&lt;/span>&lt;span class="n">fid&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">fopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;w+&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="n">fid&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;span class="n">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;error opening file %s\n&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">filename&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">end&lt;/span>
&lt;span class="n">fwrite&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">A&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#39;double&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">fclose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fid&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>小结&lt;/strong>：
MATLAB 提供了对常用文件类型操作的支持，使用文件，我们可以更方便地读入大量数据并进行操作，同时输出易于阅读的数据。&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/matlab/">MATLAB</category><category domain="https://blog.imfing.com/tags/matlab/">MATLAB</category><category domain="https://blog.imfing.com/tags/notes/">Notes</category></item><item><title>我与科比的八年光阴</title><link>https://blog.imfing.com/2016/04/8-years-with-kobe/</link><guid isPermaLink="true">https://blog.imfing.com/2016/04/8-years-with-kobe/</guid><pubDate>Thu, 14 Apr 2016 12:16:55 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>碎碎语：本文写于 2016 年科比退役之战当天夜晚，4 年之后的 2020 年我在简书的草稿箱中发现了这篇未发布的文章，也是在 2020 年的 1 月 26 日，科比因直升机事故意外离开了，唏嘘不已。Mamba Out，感谢有你的篮球世界。&lt;/p>
&lt;h2 id="初识科比">初识科比&lt;/h2>
&lt;p>08 年，还在上小学的我，回家打开电视，中央 5 台正在播 07-08 赛季总决赛湖人对阵凯尔特人的比赛。那是我第一次看 NBA 总决赛，此前从不关心篮球的我饶有兴致地来看这场比赛。因为，这是总决赛第六场，凯尔特人已经遥遥领先，我即将见证一个冠军的诞生。这场比赛，是我真正意义上看科比的第一场比赛。
当然，这场比赛的结局也是我们都知道的，湖人以 92:132 大比分落败，绿军三巨头如愿以偿的拿到了属于他们的总冠军。
依稀记得从下半场开始，胜利的天平就完全倾倒在凯尔特人一边：一边是凯尔特人完美的配合、现场观众一阵又一阵的 “Beat LA”；另一边是科比孤胆英雄般无奈的单打、罚球时被观众的嘘声，一切的一切仿佛注定了这个比赛不属于科比、这个冠军不属于湖人。绿军的团队配合、湖人的溃不成军使我体会到篮球的魅力——篮球不属于个人，篮球属于团队。相信大家都还记得最后还有几分钟时，皮尔斯将饮料倒在教练里弗斯身上的那一幕。
这一场比赛，使我彻底爱上了篮球。这一场比赛，科比在。&lt;/p>
&lt;h2 id="憎恶科比">憎恶科比&lt;/h2>
&lt;p>那一次的总决赛给我留下的印象：科比太独，这样的人不配有冠军。08-09 赛季，我正式关注 NBA。
那时的联盟，有姚明、有火箭。和许许多多人一样，我也有着主队情结。那一年火箭艰难突围，杀入季后赛第二轮，对手便是洛杉矶湖人队。很明显，这不是势均力敌的比赛，然而火箭还是艰难杀入抢七大战。败给湖人，无悔。科比在的湖人，无情的将我喜爱的球队淘汰。我讨厌湖人，讨厌科比。
年轻的魔术在爆冷击败骑士之后，艰难杀入总决赛。彼时的的魔术，有年轻的霍华德，还有特科格鲁、刘易斯等一批角色球员。然而湖人的阵容，更加恐怖，内线有正值当打之年的加索尔和拜纳姆。面对年轻的魔术，他们毫不手软，4:1 的屠杀。我不情愿，但科比拿到了他的第四个总冠军。&lt;/p>
&lt;h2 id="尊敬科比">尊敬科比&lt;/h2>
&lt;p>随后一年的 09-10 赛季，是属于科比的一个赛季。我在电视机前见证了科比 0.5 秒超远三分绝杀热火，记忆犹新。我没有惊叫，而是愣住了半天，完全无法相信自己的眼睛。但科比就是科比，他做到了。我在了解到他曾经种种：3 连冠、81 分……之后，我已不敢想象这个男人有多强大。
当年的总决赛，湖人和凯尔特人再一次会师总决赛。我很希望凯尔特人再赢一次。毕竟岁月无情，当年的三巨头已是迟暮，这或许是他们最后一次杀入总决赛。抢七大战正好在周末，我静候着一个冠军的诞生。然而这次还是湖人，艰难的赢下了比赛。第五冠，已是科比的囊中之物。绿军，也该谢幕了。
我慢慢意识到，这是属于科比的时代，这是属于他的冠军。&lt;/p>
&lt;h2 id="热爱科比">热爱科比&lt;/h2>
&lt;p>这种热爱，我想，更多的是对篮球本身的热爱。我相信对于很多人来说，科比就是篮球的代名词。他做到的几乎就是这项运动不可思议的一切：美如画的后仰跳投、暴力的扣篮、惊天的绝杀、不断打破的记录、一个又一个的冠军……凡是我所能想到的关于这项运动的一切，都有他的存在。看他的比赛，成为一种欣赏，他也让篮球成为一种欣赏。
然而，英雄终有迟暮。
大大小小的伤病，还是把这位铁人给摧垮了。不知从哪日起，科比不再是我们熟悉的那个科比，他不再无所不能：数据下滑、投篮打铁。
12-13 赛季，湖人引进霍华德和纳什，组成新的豪华阵容。本以为湖人又将迎来新的王朝，结果却令人大失所望。那个赛季，随着霍华德的报销，科比扛起了整支球队。强烈的求生欲让他多场高强度比赛。最终在 13 年跟腱撕裂，这是危及职业生涯的伤病。很多人质疑他会不会就此退役。他用行动诠释了什么是真正的坚韧。
然而 13-14 赛季，科比在打了 6 场比赛之后，再次受伤，赛季报销。14-15 赛季，科比强行突破暴扣拉伤肩膀，导致右肩旋转袖肌腱撕裂，赛季报销。
那两年，科比淡出了我们的视线。我们也见证了一批新兴球队和球星的成长。
15-16 赛季，科比归来。然而，37 岁的科比已经不是当年的小飞侠。自己的身体、湖人的糟糕战绩也让他没了斗志。
一个人可以被毁灭，但不可以被打败。科比败给了时间，败给了伤病。
20 年了，也该歇歇了。这一年，我大一。
96 黄金一代，也终于谢幕。沧海桑田，8 年的时光，送走了他们：艾佛森、纳什、雷阿伦、佩贾、费舍尔、华莱士……&lt;/p>
&lt;p>最后一战，60 分。拼尽全力，昔日的黑曼巴仿佛又回来了。然而这却是最后一次了。&lt;/p>
&lt;h2 id="告别科比">告别科比&lt;/h2>
&lt;p>科比陪伴我从小学到大学，整整 8 年，有些人甚至更长。他是引领我进入篮球时间的那些人之一。我从讨厌科比到喜欢科比。8 年时光，是回不去的青春。
篮球教会我很多，科比也教会我很多。他的好胜心、野心、领袖气质，还有他的刻苦、坚韧以及对篮球的热爱，是我们永远所不能忘怀的。&lt;/p>
&lt;p>Farewell, Kobe. Thank you.&lt;/p></description><category domain="https://blog.imfing.com/categories/%E7%94%9F%E6%B4%BB/">生活</category><category domain="https://blog.imfing.com/categories/%E9%9A%8F%E7%AC%94/">随笔</category><category domain="https://blog.imfing.com/tags/%E9%9A%8F%E7%AC%94/">随笔</category></item><item><title>N 阶行列式算法</title><link>https://blog.imfing.com/2015/11/determinant-calculator/</link><guid isPermaLink="true">https://blog.imfing.com/2015/11/determinant-calculator/</guid><pubDate>Sun, 01 Nov 2015 09:46:23 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>那时，线性代数刚学到行列式。突然有种冲动想做个行列式计算器。于是就有了下面这个 N 阶行列式计算器。(●—●)&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/116758304-c0a77a80-a9dd-11eb-973e-c7191f441cb0.png" alt="运行效果">&lt;/p>
&lt;h2 id="思路">思路&lt;/h2>
&lt;p>其实用的是最简单的方式: 按行列式的第一行展开。
算法用的是递归。说的简单的点，类似数列的递推。如果: N 阶行列式的值是 &lt;code>a(N)&lt;/code>，那么按照第一行展开，会得到余子式，而余子式是 N-1 阶的行列式，那么可以记为 &lt;code>a(N-1)&lt;/code>，其计算方法是和计算 &lt;code>a(N)&lt;/code> 一样的。所以可以构造一个函数 &lt;code>f&lt;/code> ，计算 &lt;code>f(n)&lt;/code> 要用到 &lt;code>f(n-1)&lt;/code> ，同理一直推下去，直到 &lt;code>f(2)&lt;/code> 是一个固定的值。这个值会返回代入 &lt;code>f(3)&lt;/code> 并计算出 &lt;code>f(3)&lt;/code> 的值，如此直到计算出 &lt;code>f(n)&lt;/code> 的值。&lt;/p>
&lt;h2 id="使用工具">使用工具&lt;/h2>
&lt;ul>
&lt;li>开发工具: Visual Studio 2015&lt;/li>
&lt;li>开发语言: C#&lt;/li>
&lt;/ul>
&lt;p>下面贴出核心代码&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="k">public&lt;/span> &lt;span class="kt">double&lt;/span>&lt;span class="p">[,]&lt;/span> &lt;span class="n">cofactor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">double&lt;/span>&lt;span class="p">[,]&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="kt">double&lt;/span>&lt;span class="p">[,]&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">double&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">j&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">j&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">j&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">j&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">j&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">j&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">j&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">j&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">j&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">j&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>以上是求一个余子式的函数。最终返回 &lt;code>n-1&lt;/code> 阶数组的结果。
其中第一行的参数中：&lt;code>x&lt;/code> 为原矩阵，&lt;code>n&lt;/code> 为阶数，&lt;code>a&lt;/code> 为按第一行展开到了第几个数。
下面是求行列式的函数，调用到了 &lt;code>Pow&lt;/code> (求幂) 和上面的求余子式的函数。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="k">public&lt;/span> &lt;span class="kt">double&lt;/span> &lt;span class="n">det&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">double&lt;/span>&lt;span class="p">[,]&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="kt">double&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">*&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">*&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">];&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">else&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="c1">// 按第一行展开，降阶算
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">*&lt;/span> &lt;span class="n">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Pow&lt;/span>&lt;span class="p">(-&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">*&lt;/span> &lt;span class="n">det&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cofactor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>再加上输入和输出，就是一个完整的行列式计算器。
代码并不长，也没用到什么高级的东西。
仅供参考，欢迎指出问题。&lt;/p>
&lt;p>©Fing
2015-10-21&lt;/p>
&lt;p>原文最初发布于&lt;a href="https://www.jianshu.com/p/04da008cafea">简书&lt;/a>&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/math/">Math</category><category domain="https://blog.imfing.com/tags/programming/">Programming</category></item><item><title>UWP 自定义锁屏 &amp; 桌面壁纸</title><link>https://blog.imfing.com/2015/10/uwp-lock-screen-wallpaper/</link><guid isPermaLink="true">https://blog.imfing.com/2015/10/uwp-lock-screen-wallpaper/</guid><pubDate>Sat, 31 Oct 2015 12:16:55 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>调用通用的 API 来设置桌面壁纸，可以实现很多有趣的功能。
在 Windows 通用平台中，可以使 &lt;code>Windows.System.UserProfile&lt;/code> 命名空间下的类 &lt;code>UserProfilePersonalizationSettings&lt;/code> 来对系统的开始界面背景和锁屏壁纸进行操作。它可以修改锁屏壁纸和桌面壁纸，调用后会返回bool值，如果成功就是 true，否则返回 false。
在调用 &lt;code>UserProfilePersonalizationSettings&lt;/code> 类前，先访问一下 &lt;code>IsSupported&lt;/code> 方法，看看是否支持该操作。然后通过 Current 属性可以获取到一个 &lt;code>UserProfilePersonalizationSettings&lt;/code> 实例，之后你就可以设置壁纸了。
调用 &lt;code>TrySetLockScreenImageAsync&lt;/code> 方法设置锁屏壁纸，调用 &lt;code>TrySetWallpaperImageAsync&lt;/code> 方法可以设置桌面壁纸，参数都是用来作为背景的图片文件的 &lt;code>StorageFile&lt;/code>。&lt;/p>
&lt;p>&lt;img src="http://upload-images.jianshu.io/upload_images/717608-395c57b814407c2c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="应用界面">&lt;/p>
&lt;p>&lt;img src="http://upload-images.jianshu.io/upload_images/717608-b21f741494d1e5ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="实现效果">&lt;/p>
&lt;p>下面通过实例来演示一下如何实现这个功能。&lt;/p>
&lt;p>因为 &lt;code>UserProfilePersonalizationSettings&lt;/code> 被封装在 &lt;code>Windows.System.UserProfile&lt;/code> 中，先引用&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="k">using&lt;/span> &lt;span class="nn">Windows.System.UserProfile&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>设置壁纸&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="n">UserProfilePersonalizationSettings&lt;/span> &lt;span class="n">setting&lt;/span>
&lt;span class="p">=&lt;/span> &lt;span class="n">UserProfilePersonalizationSettings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Current&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="c1">// 实例化对象
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">setting&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TrySetLockScreenImageAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其中 &lt;code>TrySetLockScreenImageAsync(file)&lt;/code> 是尝试设置文件对象 &lt;code>file&lt;/code> 为锁屏壁纸。&lt;/p>
&lt;p>在测试中，我是直接在项目根目录下添加了一个 &lt;code>pic.jpg&lt;/code> 的 1280*720 的图片文件。（其实应该放 Assets 里面的）&lt;/p>
&lt;p>那么如何导入图片文件呢？
需要用到 &lt;code>StorageFile&lt;/code> 和 &lt;code>Uri&lt;/code>
在 Class 下定义：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="k">private&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="n">Uri&lt;/span> &lt;span class="n">imgUri&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Uri&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;ms-appx:///pic.jpg&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样就有个 &lt;code>Uri&lt;/code> 指向根目录下的 &lt;code>pic.jpg&lt;/code> 文件。
接下来要用到 &lt;code>StorageFile&lt;/code> 来导入文件。
还是要先引用&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="k">using&lt;/span> &lt;span class="nn">Windows.Storage&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>从 &lt;code>imgUri&lt;/code> 导入文件，注意要用到异步&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="n">StorageFile&lt;/span> &lt;span class="n">file&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">StorageFile&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetFileFromApplicationUriAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">imgUri&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>下面贴上主要源码。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="k">public&lt;/span> &lt;span class="k">sealed&lt;/span> &lt;span class="k">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MainPage&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">Page&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="k">public&lt;/span> &lt;span class="n">MainPage&lt;/span>&lt;span class="p">()&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">InitializeComponent&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">private&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="n">Uri&lt;/span> &lt;span class="n">imgUri&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Uri&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;ms-appx:///pic.jpg&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">private&lt;/span> &lt;span class="k">async&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">button_Click&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="n">sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">RoutedEventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="kt">var&lt;/span> &lt;span class="n">msg&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MessageDialog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">UserProfilePersonalizationSettings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsSupported&lt;/span>&lt;span class="p">())&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="kt">var&lt;/span> &lt;span class="n">mess&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MessageDialog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;人品太差，不支持哦！&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="k">await&lt;/span> &lt;span class="n">mess&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ShowAsync&lt;/span>&lt;span class="p">();&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="c1">// 获取文件
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">StorageFile&lt;/span> &lt;span class="n">file&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">StorageFile&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetFileFromApplicationUriAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">imgUri&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c1">// 设置背景
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">UserProfilePersonalizationSettings&lt;/span> &lt;span class="n">setting&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">UserProfilePersonalizationSettings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Current&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">bool&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">setting&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TrySetLockScreenImageAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>©Fing&lt;/p>
&lt;p>原文最初发布于 &lt;a href="https://www.jianshu.com/p/ee4ceeb8abda">https://www.jianshu.com/p/ee4ceeb8abda&lt;/a>&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/windows/">Windows</category><category domain="https://blog.imfing.com/tags/programming/">Programming</category></item><item><title>C 语言入门那些事</title><link>https://blog.imfing.com/2015/10/c-begin/</link><guid isPermaLink="true">https://blog.imfing.com/2015/10/c-begin/</guid><pubDate>Fri, 23 Oct 2015 14:49:49 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>刚开始上 C 语言课的同学都有哪些困惑呢？这里来为你解答，从开发环境的选择到学习资料等～&lt;/p>
&lt;!-- more -->
&lt;h2 id="c-语言开发环境选择">C 语言开发环境选择&lt;/h2>
&lt;h3 id="都有哪些开发环境">都有哪些开发环境？&lt;/h3>
&lt;p>很多很多： =￣ω￣=&lt;/p>
&lt;blockquote>
&lt;ul>
&lt;li>Visual C++（6.0；2010 Express）&lt;/li>
&lt;li>Visual Studio （2013；2015）&lt;/li>
&lt;li>Dev C++（5.11）&lt;/li>
&lt;li>C-Free&lt;/li>
&lt;li>Xcode（Mac 上的）&lt;/li>
&lt;li>Vim+GCC+GDB（Linux 上）&lt;/li>
&lt;li>......&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;hr>
&lt;h3 id="该用哪个">该用哪个？？&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Visual C++&lt;/strong>（简称 VC++），教学用的比较多的有 6.0 和 2010。
VC++6.0 是 1998 年发布的，古董级。
Visual C++2010 Express，即学习版，是免费的。教材上用的也是这个版本。VC++2010 包含在 Visual Studio 2010 中，安装包体积较大 (1.8G)，首选推荐的还是这个。&lt;/li>
&lt;li>&lt;strong>Visual Studio&lt;/strong>（简称 VS），2013 和 2015 版本非常的新，也是免费的，安装包体积更大 (3~4G)，对电脑性能要求也比较高。没事的话还是别装这个。（装了就算了，用起来和 VC++2010 差不多）&lt;/li>
&lt;li>&lt;strong>Dev C++&lt;/strong>，非常适合新手的开发环境。
安装包体积不到 50M，轻量级。功能上和 VC++2010 是差不多的。而且创建项目也不像 VC 那么繁琐。&lt;/li>
&lt;li>Xcode，买不起 Mac （债见）&lt;/li>
&lt;li>Vim+GCC+GDB，Linux 下的。虽然很好用，但非常不适合新手。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="vc-2010-express-学习版-下载及安装">VC++ 2010 Express (学习版) 下载及安装&lt;/h3>
&lt;p>&lt;a href="https://pan.baidu.com/s/1skXiRH7">官方中文版下载地址 (点我)&lt;/a>
下载好了之后是一个 1.76G 的 iso (光盘映像) 文件。Win8 及 Win10 的同学可以直接双击挂载，然后运行 setup 安装。
Win7 的同学需要一个映像挂载工具，具体私戳我吧。
运行安装程序后：
&lt;img src="https://user-images.githubusercontent.com/5097752/83670845-ca441a80-a5a1-11ea-860a-4bfc062b6bfd.jpg" alt="安装界面">
选择第三个。然后一直下一步，直到安装完成。
&lt;strong>完成后，到开始菜单里面去找这货！！&lt;/strong>
就可以运行啦！
然后参考实验与习题那本书，敲代码！&lt;/p>
&lt;hr>
&lt;h3 id="dev-c-下载与安装">Dev C++ 下载与安装&lt;/h3>
&lt;p>&lt;a href="http://xiazai.xiazaiba.com/Soft/D/devcpp_5.11.0_XiaZaiBa.zip">下载地址 (点我)&lt;/a>
这个的安装就快多了，下载完成后解压，运行 &lt;code>Dev-Cpp.5.11.exe&lt;/code>
首先选择 &lt;strong>安装过程中的语言&lt;/strong>，默认 English 就可以了，然后一直 Next，安装就完成了。
点开桌面的图标，第一次运行会有设置中文的选项。
&lt;img src="https://user-images.githubusercontent.com/5097752/83670847-cb754780-a5a1-11ea-845e-b94b8e02ca73.jpg" alt="选择中文">
然后 Next, 就可以新建文件了。F11 编译并运行。
&lt;img src="https://user-images.githubusercontent.com/5097752/83670865-d039fb80-a5a1-11ea-8614-4ba7501fb0eb.jpg" alt="编译 hello world">&lt;/p>
&lt;p>Dev C++ 真的挺好用的，没有 VS 那样臃肿，简单轻快。（我都不舍得卸载）&lt;/p>
&lt;p>不过鉴于教材，还是首推 &lt;strong>VC++2010&lt;/strong>。
有任何问题欢迎提问。&lt;/p>
&lt;h2 id="c-语言书籍推荐">C 语言书籍推荐&lt;/h2>
&lt;p>那么有哪些 C 语言的书籍呢？
公认的比较经典的是《C Primer Plus》，当然也有《C Programming Language》，中文名叫《C 程序设计语言》。
&lt;img src="https://img3.doubanio.com/lpic/s1106934.jpg" alt="C 程序设计语言">
&lt;img src="https://img3.doubanio.com/lpic/s1308874.jpg" alt="C Primer Plus">&lt;/p>
&lt;p>还有一些网上的视频课程也都很好。&lt;/p>
&lt;h2 id="linux-下-c-语言起步">Linux 下 C 语言起步&lt;/h2>
&lt;p>相比于 Windows 平台，Linux 下的开发环境搭建可以说是非常简单，或者说是 &lt;strong>根本不需要搭建&lt;/strong>！！
在众多的 Linux 发行版本中，都&lt;strong>自带&lt;/strong>了 gcc 编译器、被誉为&lt;strong>编辑器之神&lt;/strong>的 vim。
下面就已 Linux 的一个发行版：&lt;strong>Ubuntu&lt;/strong> 平台来举例如何轻松完成 helloworld！
首先，你得有 Ubuntu... 否则下面都是扯淡。
快捷键  &lt;code>Ctrl+Alt+T&lt;/code> 打开终端（就是给你输入指令的地方）。
黑底白字，看起来高大上。
&lt;img src="https://user-images.githubusercontent.com/5097752/83670925-e5af2580-a5a1-11ea-8829-ab344a8eca28.jpg" alt="">
输入 &lt;code>vim hello.c&lt;/code>
意思是用 vim 新建并打开 hello.c 
&lt;img src="https://user-images.githubusercontent.com/5097752/83670962-ef388d80-a5a1-11ea-8b02-bd559e6cd735.jpg" alt="">
从最下面可以看出，vim 打开了 hello.c 的新文件。
接下来按 &lt;code>i&lt;/code> 进入编辑模式。
&lt;img src="https://user-images.githubusercontent.com/5097752/83670970-f19ae780-a5a1-11ea-91f2-ff709370135e.jpg" alt="">
把熟悉的 helloworld 敲进去。
接下来按 &lt;code>Esc&lt;/code> 进入 vim 的命令模式，输入 &lt;code>:wq&lt;/code>，意为保存并退出。
&lt;img src="https://user-images.githubusercontent.com/5097752/83670975-f364ab00-a5a1-11ea-87b1-3a10e885d681.jpg" alt="">
返回之前的终端界面。
输入 &lt;code>gcc hello.c&lt;/code>
 编译 hello.c ，默认生成了 a.out 的可执行文件。
输入 &lt;code>./a.out&lt;/code>
运行 a.out
Bingo! 成功！&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/c/">C</category></item><item><title>C# 实现图片马赛克</title><link>https://blog.imfing.com/2015/09/csharp-mosaic/</link><guid isPermaLink="true">https://blog.imfing.com/2015/09/csharp-mosaic/</guid><pubDate>Fri, 04 Sep 2015 19:40:38 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>如何使用 C# 实现图片的马赛克算法呢？本文带你来一探究竟……&lt;/p>
&lt;p>&lt;img src="https://user-images.githubusercontent.com/5097752/83671152-36268300-a5a2-11ea-92bc-9708e3c00a94.jpg" alt="">&lt;/p>
&lt;!-- more -->
&lt;p>原文同步发布在&lt;a href="http://www.jianshu.com/p/f82bd0cedd36">简书&lt;/a>
本图片马赛克问题来自于 WHUMSC2015 年招新&lt;/p>
&lt;p>说到马赛克，大家一定不陌生。但是该如何利用编程语言实现呢？
使用工具：&lt;/p>
&lt;ul>
&lt;li>C#&lt;/li>
&lt;li>Visual Studio 2015 Community &lt;/li>
&lt;li>Windows 10&lt;/li>
&lt;/ul>
&lt;p>原理：
大家都知道，一张图片是由一个一个像素组成。这些像素呢逐次排列，就好像方格纸一样。(密集恐惧症慎入) 每个格子都是一个特定的颜色。
&lt;img src="https://user-images.githubusercontent.com/5097752/83671163-39ba0a00-a5a2-11ea-8803-8d23ab786ae0.jpg" alt="">
那么怎么实现马赛克呢？说白了呢，就是实现下面的一个转换（每个字母代表一个像素，假设马赛克的范围为 2）：
&lt;img src="https://user-images.githubusercontent.com/5097752/83671174-3fafeb00-a5a2-11ea-9c33-53116e9a3b0c.jpg" alt="">
间隔选取像素点（如 ACE...），接着填充至周围的格子中。
实战：
首先要用到 &lt;code>System.Drawing&lt;/code>
一般创建 Win 窗口程序时都已经引用了。我们要用到里面的 Bitmap 库
类来进行图片的读取和写入操作。
构造一个函数
 &lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="c1">// img为输入的图像，a为马赛克的范围
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="n">Bitmap&lt;/span> &lt;span class="n">imgMosaic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Bitmap&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="c1">// 两层循环，遍历每一个间隔的像素点
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Height&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Width&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="c1">// 获取颜色
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">Color&lt;/span> &lt;span class="n">xxx&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetPixel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="c1">// 另一层循环，填充颜色
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">w&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Width&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Height&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetPixel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">xxx&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p> 
至于效果么。。你们自己感受一下：
&lt;img src="https://user-images.githubusercontent.com/5097752/83671188-450d3580-a5a2-11ea-948a-fcfb278d3407.jpg" alt="">
其实挺好了，已经有马赛克的效果了。
我又想：怎么让马赛克的效果更好呢？问题是 ACE 这样的像素可能不能代表周围的一圈像素。
于是，我们可以取 ABGH 的 RGB 平均值再填入这些像素中。代码如下&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="n">Bitmap&lt;/span> &lt;span class="n">imgMosaic2&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Bitmap&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Height&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Width&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">avgR&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">avgG&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">avgB&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="kt">int&lt;/span> &lt;span class="n">count&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">w&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Width&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Height&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="n">Color&lt;/span> &lt;span class="n">pix&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetPixel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">avgR&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">pix&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">R&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">avgG&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">pix&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">G&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">avgB&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">pix&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">B&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">count&lt;/span>&lt;span class="p">++;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="n">avgR&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">avgR&lt;/span> &lt;span class="p">/&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">avgG&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">avgG&lt;/span> &lt;span class="p">/&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="n">avgB&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">avgB&lt;/span> &lt;span class="p">/&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">w&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">w&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Width&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Height&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="n">Color&lt;/span> &lt;span class="n">newColor&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Color&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FromArgb&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">avgR&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">avgG&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">avgB&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="n">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetPixel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">newColor&lt;/span>&lt;span class="p">);&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>  &lt;/p>
&lt;p>再来感受一下效果：
&lt;img src="https://user-images.githubusercontent.com/5097752/83671190-45a5cc00-a5a2-11ea-9fc9-540730ef039c.jpg" alt="">
瞬间感觉好多了。&lt;/p>
&lt;p>至于导入图片：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="n">image1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Bitmap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">@&amp;#34;C:\img.jpg&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">true&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// @后换成自己想要的路径就可以了
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p> &lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BC%96%E7%A8%8B/">编程</category><category domain="https://blog.imfing.com/tags/c/">C</category><category domain="https://blog.imfing.com/tags/%E7%AE%97%E6%B3%95/">算法</category></item><item><title>建站（折腾）日志</title><link>https://blog.imfing.com/2015/09/site-journal/</link><guid isPermaLink="true">https://blog.imfing.com/2015/09/site-journal/</guid><pubDate>Tue, 01 Sep 2015 13:09:56 +0000</pubDate><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh)</copyright><description>&lt;p>其实这是一篇最新的文章 23333
记录一下折腾的历史～&lt;/p>
&lt;!-- more -->
&lt;h2 id="写在前面">写在前面&lt;/h2>
&lt;p>生命不息，折腾不止。&lt;/p>
&lt;h2 id="折腾经历">折腾经历&lt;/h2>
&lt;h3 id="2020-年-6-月">2020 年 6 月&lt;/h3>
&lt;p>迁移到 &lt;a href="https://gohugo.io/">Hugo&lt;/a>，采用主题 &lt;a href="https://github.com/reuixiy/hugo-theme-meme">MemE&lt;/a>&lt;/p>
&lt;h3 id="2020-年-5-月">2020 年 5 月&lt;/h3>
&lt;p>迁移回 Hexo，更改主题为 &lt;a href="https://github.com/probberechts/hexo-theme-cactus">Cactus&lt;/a>。&lt;/p>
&lt;h3 id="2020-年-1-月">2020 年 1 月&lt;/h3>
&lt;p>从 Hexo 迁移到 &lt;a href="http://blogger.com/">Google Blogger (Blogspot)&lt;/a>&lt;/p>
&lt;h3 id="2019-年-1-月">2019 年 1 月&lt;/h3>
&lt;p>博客源文件托管从 Bitbucket 迁移至 &lt;a href="https://github.com/">GitHub&lt;/a> 私有仓库。&lt;/p>
&lt;h3 id="2018-年-4-月">2018 年 4 月&lt;/h3>
&lt;p>更改博客到子域名 &lt;a href="https://blog.imfing.com">blog.imfing.com&lt;/a>，采用 https。域名改用 &lt;a href="https://www.cloudflare.com/">Cloudflare&lt;/a> 解析和提供 CDN 加速（虽然在国内依旧很慢）。因为 SSL 问题放弃原服务器，改用 &lt;a href="https://www.netlify.com/">Netlify&lt;/a> 托管和自动构建静态网页，由 &lt;a href="https://bitbucket.org/">Bitbucket&lt;/a> 托管本站原始文件。&lt;/p>
&lt;h3 id="2017-年-12-月">2017 年 12 月&lt;/h3>
&lt;p>更换域名为顶级域名 &lt;a href="http://imfing.com">imfing.com&lt;/a>。服务器由国内腾讯云迁到国外 Vultr（US New York）。旧域名 &lt;a href="http://fingr8.cn">fingr8.cn&lt;/a> 将会自动跳转到新的域名。&lt;/p>
&lt;hr>
&lt;h3 id="2017-年-11-月">2017 年 11 月&lt;/h3>
&lt;p>从 Wordpress 转到 &lt;a href="https://hexo.io/zh-cn/">Hexo&lt;/a>，使用主题 &lt;a href="http://theme-next.iissnan.com/">NexT&lt;/a>，托管在 &lt;a href="https://pages.github.com/">Github Pages&lt;/a> 上。
因 Github Pages 有限制，速度不稳定，故考虑换自己的 VPS 托管。&lt;/p>
&lt;hr>
&lt;h3 id="2017-年-4-月">2017 年 4 月&lt;/h3>
&lt;p>因为 Grav 相对小众，便考虑使用更加成熟的 WordPress 来。
从 Grav 迁移到 WordPress，找到了一个挺不错的主题 &lt;a href="https://www.vtrois.com/theme-kratos.html">Kratos&lt;/a>。
完成了域名 fingr8.cn 的备案。&lt;/p>
&lt;hr>
&lt;h3 id="2017-年-1-月">2017 年 1 月&lt;/h3>
&lt;p>最初因看到一个开源的 CMS（内容管理系统）而萌生自己搭建（折腾）一个博客的想法。
故最开始使用 &lt;a href="https://getgrav.org/">Grav&lt;/a> 搭建。部署于腾讯云的 &lt;a href="https://cloud.tencent.com/act/campus">学生主机&lt;/a> 上，使用腾讯云注册的学生免费域名 &lt;a href="http://fingr8.cn">fingr8.cn&lt;/a>
Grav 是基于 PHP 的扁平 CMS，即不需要依赖数据库，部署非常便捷。当然，在此过程中也看到过很多其它的类似博客框架，比如 &lt;a href="http://typecho.org/">Typecho&lt;/a>，&lt;a href="http://www.gitblog.cn/">GitBlog&lt;/a>。
Grav 的官方文档非常清晰。在折腾 Grav 的过程中也学到了不少网站的知识，比如 Apache 的配置，PHP、模板引擎、前端等等。不满足 Grav 提供的主题，自己写了一个基于 Material Design 的主题，至今保留在 &lt;a href="http://grav.fingr8.cn">grav.fingr8.cn&lt;/a>。也一直希望把它贡献到官方主题库去，然而一直搁置了。&lt;/p>
&lt;h3 id="2016-及以前">2016 及以前&lt;/h3>
&lt;p>尝试过 &lt;a href="http://blog.csdn.net/">CSDN&lt;/a>、&lt;a href="https://www.cnblogs.com/">博客园&lt;/a>、&lt;a href="https://leanote.com/">Leanote&lt;/a>，到后来的 &lt;a href="https://www.jianshu.com/u/d8573851dc88">简书&lt;/a>(也是我发布文章最多的地方，现在太杂乱了)。
作为一个爱技术（折腾）的人，当然得搞一个自己的网站了。&lt;/p></description><category domain="https://blog.imfing.com/categories/%E6%8A%80%E6%9C%AF/">技术</category><category domain="https://blog.imfing.com/categories/%E7%BD%91%E7%AB%99/">网站</category><category domain="https://blog.imfing.com/tags/web/">Web</category></item></channel></rss>