<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>银河渡舟</title>
        <link>https://suborbit.net/</link>
        <description>浊以静之徐清，安以动之徐生。</description>
        <lastBuildDate>Fri, 28 Mar 2025 17:03:25 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-CN</language>
        <image>
            <title>银河渡舟</title>
            <url>https://suborbit.net/favicon.ico</url>
            <link>https://suborbit.net/</link>
        </image>
        <copyright>Copyright © 2025 Wider</copyright>
        <item>
            <title><![CDATA[循环变量与闭包]]></title>
            <link>https://suborbit.net/posts/loopvar-and-closure</link>
            <guid>https://suborbit.net/posts/loopvar-and-closure</guid>
            <pubDate>Tue, 20 Feb 2024 13:31:21 GMT</pubDate>
            <description><![CDATA[起因 在2023年9月19日，Go发布的1.22版本，修复了循环变量作用域的问题，最近在用其他语言写代码时，也遇到了类似的问题。于是意识到不同的编程语言对这种情况的处理有所不同，便有了这篇文章。 Go 首先回顾一下Go语言的循环变量问题，考虑下面的简单代码，生成多个打印数字的函数，然后调用这些函数。 package main import "fmt" var funcs []func() func main() { for i…]]></description>
            <content:encoded><![CDATA[<h2>起因</h2><p>在2023年9月19日，Go发布的<a href=https://go.dev/blog/loopvar-preview>1.22版本</a>，修复了循环变量作用域的问题，最近在用其他语言写代码时，也遇到了类似的问题。于是意识到不同的编程语言对这种情况的处理有所不同，便有了这篇文章。<h2>Go</h2><p>首先回顾一下Go语言的循环变量问题，考虑下面的简单代码，生成多个打印数字的函数，然后调用这些函数。<pre tabindex=0><code><span><span>package</span><span> main</span></span>
<span><span>import</span><span> "</span><span>fmt</span><span>"</span></span>
<span><span>var</span><span> funcs []</span><span>func</span><span>()</span></span>
<span></span>
<span><span>func</span><span> main</span><span>() {</span></span>
<span><span>    for</span><span> i </span><span>:=</span><span> 0</span><span>; i </span><span>&lt;</span><span> 3</span><span>; i</span><span>++</span><span> {</span></span>
<span><span>        funcs </span><span>=</span><span> append</span><span>(funcs, </span><span>func</span><span>() {</span></span>
<span><span>            fmt.</span><span>Println</span><span>(i)</span></span>
<span><span>        })</span></span>
<span><span>    }</span></span>
<span><span>    for</span><span> _, f </span><span>:=</span><span> range</span><span> funcs {</span></span>
<span><span>        f</span><span>()</span></span>
<span><span>    }</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>预期输出为<pre tabindex=0><code><span><span>0</span></span>
<span><span>1</span></span>
<span><span>2</span></span>
<span><span></span></span></code></pre><p>但实际输出为<pre tabindex=0><code><span><span>3</span></span>
<span><span>3</span></span>
<span><span>3</span></span>
<span><span></span></span></code></pre><p>见多识广的你可能已经猜到了：首先Go语言中循环变量在每次循环时会重复使用（当然，从1.22开始，<a href=https://go.dev/ref/spec#For_clause>每次迭代都有自己单独的循环变量</a>），而不是重新创建一个新的变量。其次上述示例中的闭包函数以引用的方式捕获了变量<sup><a href=#user-content-fn-variable_capture aria-describedby=user-content-footnote-label data-footnote-ref id=user-content-user-content-fnref-variable_capture>1</a></sup>（实际上是使用指针实现的）。这些因素共同造成了上述的输出不符合预期。<p>那么如何修复呢？只需要添加一个局部变量即可。<pre tabindex=0><code><span><span>for</span><span> i </span><span>:=</span><span> 0</span><span>; i </span><span>&lt;</span><span> 3</span><span>; i</span><span>++</span><span> {</span></span>
<span><span>    i </span><span>:=</span><span> i </span><span>// 给一个局部变量赋值</span></span>
<span><span>    funcs </span><span>=</span><span> append</span><span>(funcs, </span><span>func</span><span>() {</span></span>
<span><span>        fmt.</span><span>Println</span><span>(i)</span></span>
<span><span>    })</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>或者通过闭包保持循环变量的值，本质上都是一样的。<pre tabindex=0><code><span><span>for</span><span> i </span><span>:=</span><span> 0</span><span>; i </span><span>&lt;</span><span> 3</span><span>; i</span><span>++</span><span> {</span></span>
<span><span>    // 定义闭包函数</span></span>
<span><span>    var</span><span> make_func </span><span>=</span><span> func</span><span>(</span><span>x</span><span> int</span><span>) </span><span>func</span><span>() {</span></span>
<span><span>        var</span><span> func1 </span><span>=</span><span> func</span><span>() {</span></span>
<span><span>            fmt.</span><span>Println</span><span>(x)</span></span>
<span><span>        }</span></span>
<span><span>        return</span><span> func1</span></span>
<span><span>    }</span></span>
<span><span>    funcs </span><span>=</span><span> append</span><span>(funcs, </span><span>make_func</span><span>(i))</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>看上去不太优雅，不过从1.22开始，不需要这些额外的操作了。<h2>JavaScript</h2><p>看一看JavaScript中的情况。编写一个类似的代码。<pre tabindex=0><code><span><span>const</span><span> funcs</span><span> =</span><span> []</span></span>
<span><span>for</span><span> (</span><span>let</span><span> i </span><span>=</span><span> 0</span><span>; i </span><span>&lt;</span><span> 3</span><span>; i</span><span>++</span><span>) {</span></span>
<span><span>    funcs.</span><span>push</span><span>(() </span><span>=></span><span> console.</span><span>log</span><span>(i))</span></span>
<span><span>}</span></span>
<span><span>for</span><span> (</span><span>const</span><span> f</span><span> of</span><span> funcs) {</span></span>
<span><span>    f</span><span>()</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>执行后你会发现输出为<pre tabindex=0><code><span><span>0</span></span>
<span><span>1</span></span>
<span><span>2</span></span>
<span><span></span></span></code></pre><p>这是因为JavaScript的<code>for</code>中使用<code>let</code>或<code>const</code>声明的变量是语句的局部变量，属于其块级作用域，于是表现上和Go 1.22一致了，自然也就没有最开始循环变量的问题了。但是，如果你使用了古老的<code>var</code>，那么情况就和Go 1.22之前一样了<sup><a href=#user-content-fn-js_for aria-describedby=user-content-footnote-label data-footnote-ref id=user-content-user-content-fnref-js_for>2</a></sup><sup><a href=#user-content-fn-js_closure aria-describedby=user-content-footnote-label data-footnote-ref id=user-content-user-content-fnref-js_closure>3</a></sup><h2>Python</h2><p>那么Python的情况呢，首先我们要知道，Python中只有模块（module），类（class）以及函数（def、lambda）才会引入新的作用域，其他代码块（如<code>if</code>、<code>for</code>、<code>while</code>）都不会引入新的作用域。同时，Python中的闭包是以引用的方式捕获变量的，所以我们已经可以猜到最终输出是相同值了。<pre tabindex=0><code><span><span>funcs </span><span>=</span><span> []</span></span>
<span><span>for</span><span> i </span><span>in</span><span> range</span><span>(</span><span>3</span><span>):</span></span>
<span><span>    funcs.append(</span><span>lambda</span><span>: </span><span>print</span><span>(i))</span></span>
<span><span>for</span><span> f </span><span>in</span><span> funcs:</span></span>
<span><span>    f()</span></span>
<span></span></code></pre><p>输出为<pre tabindex=0><code><span><span>2</span></span>
<span><span>2</span></span>
<span><span>2</span></span>
<span><span></span></span></code></pre><p>由于<code>for</code>不创建块级作用域，创建局部变量的方法失效了，但我们仍然可以用闭包的方法解决这个问题。<pre tabindex=0><code><span><span>funcs </span><span>=</span><span> []</span></span>
<span><span>for</span><span> i </span><span>in</span><span> range</span><span>(</span><span>3</span><span>):</span></span>
<span><span>    funcs.append((</span><span>lambda</span><span> x: </span><span>lambda</span><span>: </span><span>print</span><span>(x))(i))</span></span>
<span><span>    # 或者使用默认参数</span></span>
<span><span>    # funcs.append(lambda i=i: print(i))</span></span>
<span><span>for</span><span> f </span><span>in</span><span> funcs:</span></span>
<span><span>    f()</span></span>
<span></span></code></pre><h2>C++</h2><p>C++的循环变量是重复使用的，但是C++的情况有些特殊，因为C++允许开发人员指定lambda使用值捕获还是引用捕获<sup><a href=#user-content-fn-lambda_capture aria-describedby=user-content-footnote-label data-footnote-ref id=user-content-user-content-fnref-lambda_capture>4</a></sup>。既然允许使用值捕获，那么自然不存在上述循环变量的问题了。<pre tabindex=0><code><span><span>#include</span><span>&#60functional></span></span>
<span><span>#include</span><span>&#60iostream></span></span>
<span><span>#include</span><span>&#60vector></span></span>
<span><span>std</span><span>::vector</span><span>&lt;</span><span>std</span><span>::function</span><span>&#60void</span><span>()</span><span>>></span><span> fList;</span></span>
<span><span>int</span><span> main</span><span>() {</span></span>
<span><span>    for</span><span> (</span><span>int</span><span> i </span><span>=</span><span> 0</span><span>; i </span><span>&lt;</span><span> 3</span><span>; i</span><span>++</span><span>) {</span></span>
<span><span>        fList.</span><span>push_back</span><span>([</span><span>i</span><span>]() { </span></span>
<span><span>            std</span><span>::cout </span><span>&#60&lt;</span><span> "i = "</span><span> &#60&lt;</span><span> i </span><span>&#60&lt;</span><span> std</span><span>::endl;</span></span>
<span><span>        });</span></span>
<span><span>    }</span></span>
<span><span>    for</span><span> (</span><span>auto</span><span> f: fList) {</span></span>
<span><span>        f</span><span>();</span></span>
<span><span>    }</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>执行后输出为<pre tabindex=0><code><span><span>i = 0</span></span>
<span><span>i = 1</span></span>
<span><span>i = 2</span></span>
<span><span></span></span></code></pre><p>注意你不能把lambda函数中的值捕获<code>[i](){...}</code>直接改为引用捕获<code>[&#38i](){...}</code>，这会导致悬垂引用，因为<code>i</code>在循环结束后就被销毁了。<h2>总结</h2><p>本文对4种语言循环变量的表现进行了解释，但是并没有深入讨论，如果深入，就需要考虑编译器和汇编层面的东西。如果对底层实现感兴趣，可以深入研究一下不同语言闭包捕获变量的机制。对于开发来说，注意循环变量的作用域，以及闭包的捕获方式，可以避免一些不必要的错误，这就足够了。<section class=footnotes data-footnotes><h2 class=sr-only id=user-content-footnote-label>Footnotes</h2><ol><li id=user-content-user-content-fn-variable_capture><p>关于变量是怎么被捕获的，可以看一下<a href=https://eli.thegreenplace.net/2019/go-internals-capturing-loop-variables-in-closures/>Go internals: capturing loop variables in closures</a>这篇文章。 <a href=#user-content-fnref-variable_capture aria-label="Back to reference 1"class=data-footnote-backref data-footnote-backref>↩</a><li id=user-content-user-content-fn-js_for><p>详细的解释可以看一下 <a href=https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for#initialization>MDN: for</a>。 <a href=#user-content-fnref-js_for aria-label="Back to reference 2"class=data-footnote-backref data-footnote-backref>↩</a><li id=user-content-user-content-fn-js_closure><p>如果你好奇JavaScript处理闭包的技术细节，可以参数 <a href=https://zh.javascript.info/closure>变量作用域，闭包</a>。 <a href=#user-content-fnref-js_closure aria-label="Back to reference 3"class=data-footnote-backref data-footnote-backref>↩</a><li id=user-content-user-content-fn-lambda_capture><p>详细用法可以看一下 <a href=https://zh.cppreference.com/w/cpp/language/lambda#lambda_.E6.8D.95.E8.8E.B7>C++ Reference: lambda 表达式</a>。 <a href=#user-content-fnref-lambda_capture aria-label="Back to reference 4"class=data-footnote-backref data-footnote-backref>↩</a></ol></section>]]></content:encoded>
            <enclosure url="https://suborbit.net/_astro/cover.BLxq91tJ.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[为 Astro 添加暗黑模式]]></title>
            <link>https://suborbit.net/posts/support-darkmode-for-astro</link>
            <guid>https://suborbit.net/posts/support-darkmode-for-astro</guid>
            <pubDate>Sun, 07 Jan 2024 03:34:46 GMT</pubDate>
            <description><![CDATA[现代操作系统普遍支持浅色和深色模式切换，为网站添加一个黑暗模式能显著提升用户体验。然而 Astro 作为站点生成器基本不关心 UI 层面的内容，因此在本文中，我们将基于 React 和 Tailwind CSS 为 Astro 添加暗黑模式支持，并动手制作一个精美的组件。虽然说是为 Astro 框架制作的功能，但是与 Astro 耦合并不深，你可以在任何其他框架上采用本文的逻辑。]]></description>
            <content:encoded><![CDATA[<p>现代操作系统普遍支持浅色和深色模式切换，为网站添加一个黑暗模式能显著提升用户体验。然而 Astro 作为站点生成器基本不关心 UI 层面的内容，因此在本文中，我们将基于 React 和 Tailwind CSS 为 Astro 添加暗黑模式支持，并动手制作一个精美的组件。虽然说是为 Astro 框架制作的功能，但是与 Astro 耦合并不深，你可以在任何其他框架上采用本文的逻辑。<p>过年之前小水一篇，以示我还活着…… 😂<h2>准备工作</h2><p>首先我们需要为 Astro 安装 react 和 tailwindcss 扩展：<pre tabindex=0><code><span><span>npm</span><span> install</span><span> -D</span><span> @astrojs/react</span><span> @astrojs/tailwindcss</span></span>
<span></span></code></pre><p>扩展的配置请参考官方教程，此处省略了。<p>然后我们需要将 tailwindcss 的黑暗模式切换方式更改为 <code>class</code> 模式，即通过在 html 标签上添加 <code>.dark</code> 类来开启暗黑模式。在 <code>tailwind.config.ts</code> 中添加如下配置：<pre tabindex=0><code><span><span>import</span><span> type</span><span> { Config } </span><span>from</span><span> 'tailwindcss'</span></span>
<span><span>export</span><span> default</span><span> {</span></span>
<span><span>  content: [</span><span>'./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'</span><span>],</span></span>
<span><span>  darkMode: </span><span>'class'</span><span>,</span></span>
<span><span>  // ...</span></span>
<span><span>} </span><span>satisfies</span><span> Config</span><span>;</span></span>
<span></span></code></pre><h2>颜色模式的切换逻辑</h2><p>为了让用户有自由选择权，我们需要提供三个选项：<strong>自动</strong>、<strong>浅色</strong>、<strong>深色</strong>。自动选项将根据用户操作系统的颜色模式自动切换，而浅色和深色选项则是固定的。我们将使用 localStorage 来缓存用户的选择，以便在下次用户访问网站时恢复用户的偏好。<p>一个基础的颜色模式切换逻辑如下：<p><img alt=颜色模式切换逻辑 src=./state-diagram.svg><p>在进入不同颜色模式的时候需要更改 html 标签上的 <code>.dark</code> 类和 localStorage 中的 <code>theme</code> 值。我们可以写出颜色模式切换的函数：<pre tabindex=0><code><span><span>function</span><span> switchColorMode</span><span>(</span><span>colorMode</span><span>:</span><span> 'light'</span><span> |</span><span> 'dark'</span><span> |</span><span> 'auto'</span><span>) {</span></span>
<span><span>  if</span><span> (colorMode </span><span>===</span><span> 'dark'</span><span>) {</span></span>
<span><span>    document.documentElement.classList.</span><span>add</span><span>(</span><span>'dark'</span><span>);</span></span>
<span><span>    document.documentElement.style.colorScheme </span><span>=</span><span> 'dark'</span><span>;</span></span>
<span><span>    localStorage.</span><span>setItem</span><span>(</span><span>'theme'</span><span>, </span><span>'dark'</span><span>);</span></span>
<span><span>  } </span><span>else</span><span> if</span><span> (colorMode </span><span>===</span><span> 'light'</span><span>) {</span></span>
<span><span>    document.documentElement.classList.</span><span>remove</span><span>(</span><span>'dark'</span><span>);</span></span>
<span><span>    document.documentElement.style.colorScheme </span><span>=</span><span> 'light'</span><span>;</span></span>
<span><span>    localStorage.</span><span>setItem</span><span>(</span><span>'theme'</span><span>, </span><span>'light'</span><span>);</span></span>
<span><span>  } </span><span>else</span><span> if</span><span> (colorMode </span><span>===</span><span> 'auto'</span><span>) {</span></span>
<span><span>    const</span><span> isMatchDark</span><span> =</span><span> window.</span><span>matchMedia</span><span>(</span><span>'(prefers-color-scheme: dark)'</span><span>).matches;</span></span>
<span><span>    if</span><span> (isMatchDark) {</span></span>
<span><span>      document.documentElement.classList.</span><span>add</span><span>(</span><span>'dark'</span><span>);</span></span>
<span><span>      document.documentElement.style.colorScheme </span><span>=</span><span> 'dark'</span><span>;</span></span>
<span><span>    } </span><span>else</span><span> {</span></span>
<span><span>      document.documentElement.classList.</span><span>remove</span><span>(</span><span>'dark'</span><span>);</span></span>
<span><span>      document.documentElement.style.colorScheme </span><span>=</span><span> 'light'</span><span>;</span></span>
<span><span>    }</span></span>
<span><span>    localStorage.</span><span>setItem</span><span>(</span><span>'theme'</span><span>, </span><span>'auto'</span><span>);</span></span>
<span><span>  }</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>和初始化颜色模式的函数：<pre tabindex=0><code><span><span>function</span><span> initColorMode</span><span>() {</span></span>
<span><span>  const</span><span> storedTheme</span><span> =</span><span> JSON</span><span>.</span><span>parse</span><span>(window.localStorage.</span><span>getItem</span><span>(</span><span>'theme'</span><span>) </span><span>??</span><span> '"auto"'</span><span>);</span></span>
<span><span>  let</span><span> presetTheme;</span></span>
<span><span>  if</span><span> (storedTheme </span><span>===</span><span> 'auto'</span><span>) {</span></span>
<span><span>    presetTheme </span><span>=</span><span> window.</span><span>matchMedia</span><span>(</span><span>'(prefers-color-scheme: dark)'</span><span>).matches </span><span>?</span><span> 'dark'</span><span> :</span><span> 'light'</span><span>;</span></span>
<span><span>  } </span><span>else</span><span> {</span></span>
<span><span>    presetTheme </span><span>=</span><span> storedTheme;</span></span>
<span><span>  }</span></span>
<span><span>  if</span><span> (presetTheme </span><span>===</span><span> 'dark'</span><span>) {</span></span>
<span><span>    document.documentElement.classList.</span><span>add</span><span>(</span><span>'dark'</span><span>);</span></span>
<span><span>    document.documentElement.style.colorScheme </span><span>=</span><span> 'dark'</span><span>;</span></span>
<span><span>  } </span><span>else</span><span> {</span></span>
<span><span>    document.documentElement.classList.</span><span>remove</span><span>(</span><span>'dark'</span><span>);</span></span>
<span><span>    document.documentElement.style.colorScheme </span><span>=</span><span> 'light'</span><span>;</span></span>
<span><span>  }</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>OK，到目前为止，我们写好了初始化和用于切换的工具函数，但是用户选择和OS颜色模式变更时如何触发<code>switchColorMode</code>呢？对于用户选择，处理逻辑与 UI 框架相关；对于 OS 变更，显然要监听 <code>prefers-color-scheme</code> 媒体查询的变化了。这写会在后面给出，接下来我们要考虑 React 框架上的实现了。<h2>包装为 Hook</h2><p>在React中，对于与处理 UI 无关的逻辑，我们可以将其包装为一个 React Hook，以便在组件中使用。上面提到了 localStorage 和媒体查询，这两个都可以包装为 Hook，我们可以自己写，也可以用现成的实现，例如 <a href=https://usehooks-ts.com/react-hook/use-local-storage>useLocalStorage</a> 和 <a href=https://usehooks-ts.com/react-hook/use-media-query>useMediaQuery</a>。<p>那么假设这两个 Hook 已经有了，现在我们可以写出颜色模式切换的 Hook：<pre tabindex=0><code><span><span>import</span><span> useLocalStorage </span><span>from</span><span> './useLocalStorage'</span><span>;</span></span>
<span><span>import</span><span> useMediaQuery </span><span>from</span><span> './useMediaQuery'</span><span>;</span></span>
<span><span>import</span><span> { useEffect } </span><span>from</span><span> 'react'</span><span>;</span></span>
<span></span>
<span><span>export</span><span> type</span><span> Theme</span><span> =</span><span> 'light'</span><span> |</span><span> 'dark'</span><span> |</span><span> 'auto'</span><span>;</span></span>
<span></span>
<span><span>export</span><span> default</span><span> function</span><span> useTheme</span><span>(</span><span>defaultTheme</span><span>:</span><span> Theme</span><span> =</span><span> 'auto'</span><span>) {</span></span>
<span><span>  const</span><span> isMatchDark</span><span> =</span><span> useMediaQuery</span><span>(</span><span>'(prefers-color-scheme: dark)'</span><span>);</span></span>
<span><span>  const</span><span> [</span><span>value</span><span>, </span><span>setValue</span><span>, </span><span>remove</span><span>] </span><span>=</span><span> useLocalStorage</span><span>&lt;</span><span>Theme</span><span>>(</span><span>'theme'</span><span>, defaultTheme);</span></span>
<span><span>  const</span><span> [</span><span>theme</span><span>, </span><span>setTheme</span><span>] </span><span>=</span><span> useState</span><span>&lt;</span><span>Theme</span><span>>(value);</span></span>
<span></span>
<span><span>  const</span><span> colorMode</span><span> =</span><span> theme </span><span>===</span><span> 'auto'</span><span> ?</span><span> (</span></span>
<span><span>    value </span><span>===</span><span> 'auto'</span><span> ?</span><span> (isMatchDark </span><span>?</span><span> 'dark'</span><span> :</span><span> 'light'</span><span>) </span><span>:</span><span> value</span></span>
<span><span>  ) </span><span>:</span><span> theme;</span></span>
<span><span>  useEffect</span><span>(() </span><span>=></span><span> {</span></span>
<span><span>    if</span><span> (value </span><span>&#38&amp</span><span> value </span><span>!==</span><span> theme) {</span></span>
<span><span>      setTheme</span><span>(value);</span></span>
<span><span>    }</span></span>
<span><span>  }, [])</span></span>
<span></span>
<span><span>  useEffect</span><span>(() </span><span>=></span><span> {</span></span>
<span><span>    if</span><span> (colorMode </span><span>===</span><span> 'dark'</span><span>) {</span></span>
<span><span>      document.documentElement.classList.</span><span>add</span><span>(</span><span>'dark'</span><span>);</span></span>
<span><span>      document.documentElement.style.colorScheme </span><span>=</span><span> 'dark'</span><span>;</span></span>
<span><span>    }</span></span>
<span><span>    if</span><span> (colorMode </span><span>===</span><span> 'light'</span><span>) {</span></span>
<span><span>      document.documentElement.classList.</span><span>remove</span><span>(</span><span>'dark'</span><span>);</span></span>
<span><span>      document.documentElement.style.colorScheme </span><span>=</span><span> 'light'</span><span>;</span></span>
<span><span>    }</span></span>
<span><span>  }, [colorMode]);</span></span>
<span></span>
<span><span>  const</span><span> setThemeAndStorage</span><span> =</span><span> (</span><span>theme</span><span>:</span><span> Theme</span><span>) </span><span>=></span><span> {</span></span>
<span><span>    setValue</span><span>(theme);</span></span>
<span><span>    setTheme</span><span>(theme);</span></span>
<span><span>  }</span></span>
<span></span>
<span><span>  return</span><span> {</span></span>
<span><span>    colorMode: colorMode, </span><span>// light or dark</span></span>
<span><span>    theme: theme, </span><span>// auto or light or dark</span></span>
<span><span>    setTheme: setThemeAndStorage, </span><span>// set theme and save to localStorage</span></span>
<span><span>  }</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>用法是：<pre tabindex=0><code><span><span>const</span><span> { </span><span>colorMode</span><span>, </span><span>theme</span><span>, </span><span>setTheme</span><span> } </span><span>=</span><span> useTheme</span><span>();</span></span>
<span></span></code></pre><p><code>setTheme</code>参数中我们的给用户提供了三种选择，思路是维持用户的选择直到用户重新选择颜色模式，而不是在系统切换颜色模式时放弃用户的设定。<h2>编写 React 组件</h2><p>我们来编写一个最朴素的组件，不附加任何样式。<pre tabindex=0><code><span><span>import</span><span> useTheme, { </span><span>type</span><span> Theme } </span><span>from</span><span> '@/hooks/useTheme'</span><span>;</span></span>
<span></span>
<span><span>function</span><span> ThemeToggle</span><span>() {</span></span>
<span><span>  const</span><span> { </span><span>colorMode</span><span>, </span><span>theme</span><span>, </span><span>setTheme</span><span> } </span><span>=</span><span> useTheme</span><span>();</span></span>
<span><span>  const</span><span> [</span><span>isMounted</span><span>, </span><span>setIsMounted</span><span>] </span><span>=</span><span> useState</span><span>(</span><span>false</span><span>);</span></span>
<span></span>
<span><span>  useEffect</span><span>(() </span><span>=></span><span> {</span></span>
<span><span>    setIsMounted</span><span>(</span><span>true</span><span>);</span></span>
<span><span>  }, []);</span></span>
<span><span>  if</span><span> (</span><span>!</span><span>isMounted) {</span></span>
<span><span>    return</span><span> null</span><span>;</span></span>
<span><span>  }</span></span>
<span></span>
<span><span>  const</span><span> handleChange</span><span> =</span><span> (</span><span>e</span><span>:</span><span> React</span><span>.</span><span>ChangeEvent</span><span>&lt;</span><span>HTMLSelectElement</span><span>>) </span><span>=></span><span> {</span></span>
<span><span>    setTheme</span><span>(e.target.value </span><span>as</span><span> Theme</span><span>);</span></span>
<span><span>  }</span></span>
<span><span>  return</span><span> (</span></span>
<span><span>    &lt;</span><span>select</span><span> value</span><span>=</span><span>{theme} </span><span>onChange</span><span>=</span><span>{handleChange}></span></span>
<span><span>      &lt;</span><span>option</span><span> value</span><span>=</span><span>"auto"</span><span>>自动&#60/</span><span>option</span><span>></span></span>
<span><span>      &lt;</span><span>option</span><span> value</span><span>=</span><span>"light"</span><span>>浅色&#60/</span><span>option</span><span>></span></span>
<span><span>      &lt;</span><span>option</span><span> value</span><span>=</span><span>"dark"</span><span>>深色&#60/</span><span>option</span><span>></span></span>
<span><span>    &#60/</span><span>select</span><span>></span></span>
<span><span>  )</span></span>
<span><span>}</span></span>
<span></span>
<span><span>export</span><span> default</span><span> ThemeToggle;</span></span>
<span></span></code></pre><p>注意这里在组件挂载后才显示，这是为了避免组件在客户端和服务器渲染时不一致的问题，尤其是当你需要根据不同颜色模式显示不同的内容时。<h2>解决 FOUC 问题</h2><p>FOUC（Flash of Unstyled Content）是指在页面加载完成前，页面上的内容没有样式，页面加载完成后，样式突然变化的现象。这是因为我们与颜色模式相关的 JS 的加载执行 晚于 html 首次渲染，浏览器提前因此我们需要在页面加载完成前将颜色模式初始化。<p><img alt="FOUC 现象演示"src=./fouc-demo.gif><p>Astro 提供了 <code>is:inline</code> 指令，允许我们为页面保留 <code>script</code> 和 <code>style</code> 标签不被移除。为了及时初始化颜色模式，我们需要在 <code>head</code> 标签中添加如下代码（即上述初始化代码）：<pre tabindex=0><code><span><span>&lt;</span><span>script</span><span> is:inline</span><span>></span></span>
<span><span>  (</span><span>function</span><span>() {</span></span>
<span><span>    const</span><span> storedTheme</span><span> =</span><span> JSON</span><span>.</span><span>parse</span><span>(window.localStorage.</span><span>getItem</span><span>(</span><span>'theme'</span><span>) </span><span>??</span><span> '"auto"'</span><span>);</span></span>
<span><span>    let</span><span> presetTheme;</span></span>
<span><span>    if</span><span> (storedTheme </span><span>===</span><span> 'auto'</span><span>) {</span></span>
<span><span>      presetTheme </span><span>=</span><span> window.</span><span>matchMedia</span><span>(</span><span>'(prefers-color-scheme: dark)'</span><span>).matches </span><span>?</span><span> 'dark'</span><span> :</span><span> 'light'</span><span>;</span></span>
<span><span>    } </span><span>else</span><span> {</span></span>
<span><span>      presetTheme </span><span>=</span><span> storedTheme;</span></span>
<span><span>    }</span></span>
<span><span>    if</span><span> (presetTheme </span><span>===</span><span> 'dark'</span><span>) {</span></span>
<span><span>      document.documentElement.classList.</span><span>add</span><span>(</span><span>'dark'</span><span>);</span></span>
<span><span>      document.documentElement.style.colorScheme </span><span>=</span><span> 'dark'</span><span>;</span></span>
<span><span>    } </span><span>else</span><span> {</span></span>
<span><span>      document.documentElement.classList.</span><span>remove</span><span>(</span><span>'dark'</span><span>);</span></span>
<span><span>      document.documentElement.style.colorScheme </span><span>=</span><span> 'light'</span><span>;</span></span>
<span><span>    }</span></span>
<span><span>  })()</span></span>
<span><span>&#60/</span><span>script</span><span>></span></span>
<span></span></code></pre><p>最好将这段代码置于 <code>head</code> 标签尽量靠前的位置，以便浏览器尽早执行。<p>最后我们还需要支持 Astro 的 ViewTransition 功能，否则会出现同 FOUC 一样的闪烁问题，由于 Astro 不会在页面切换时重新执行内联脚本（参考<a href=https://docs.astro.build/en/guides/view-transitions/#script-behavior-during-page-navigation>官方文档</a>），我们在页面的 astro 代码中添加：<pre tabindex=0><code><span><span>&lt;</span><span>script</span><span>></span></span>
<span><span>  // prevent flash when using view transitions in astro</span></span>
<span><span>  const</span><span> initColorMode</span><span> =</span><span> () </span><span>=></span><span> {</span></span>
<span><span>    const</span><span> storedTheme</span><span> =</span><span> JSON</span><span>.</span><span>parse</span><span>(window.localStorage.</span><span>getItem</span><span>(</span><span>'theme'</span><span>) </span><span>??</span><span> '"auto"'</span><span>);</span></span>
<span><span>    let</span><span> presetTheme;</span></span>
<span><span>    if</span><span> (storedTheme </span><span>===</span><span> 'auto'</span><span>) {</span></span>
<span><span>      presetTheme </span><span>=</span><span> window.</span><span>matchMedia</span><span>(</span><span>'(prefers-color-scheme: dark)'</span><span>).matches </span><span>?</span><span> 'dark'</span><span> :</span><span> 'light'</span><span>;</span></span>
<span><span>    } </span><span>else</span><span> {</span></span>
<span><span>      presetTheme </span><span>=</span><span> storedTheme;</span></span>
<span><span>    }</span></span>
<span><span>    if</span><span> (presetTheme </span><span>===</span><span> 'dark'</span><span>) {</span></span>
<span><span>      document.documentElement.classList.</span><span>add</span><span>(</span><span>'dark'</span><span>);</span></span>
<span><span>      document.documentElement.style.colorScheme </span><span>=</span><span> 'dark'</span><span>;</span></span>
<span><span>    } </span><span>else</span><span> {</span></span>
<span><span>      document.documentElement.classList.</span><span>remove</span><span>(</span><span>'dark'</span><span>);</span></span>
<span><span>      document.documentElement.style.colorScheme </span><span>=</span><span> 'light'</span><span>;</span></span>
<span><span>    }</span></span>
<span><span>  };</span></span>
<span></span>
<span><span>  // Runs on initial navigation</span></span>
<span><span>  initColorMode</span><span>();</span></span>
<span><span>  // Runs on view transitions navigation</span></span>
<span><span>  document.</span><span>addEventListener</span><span>(</span><span>'astro:after-swap'</span><span>, initColorMode);</span></span>
<span><span>&#60/</span><span>script</span><span>></span></span>
<span></span></code></pre><p>注意这个<code>script</code>标签没有<code>is:inline</code>指令，并且执行的时机是事件<code>astro:after-swap</code>，关于 ViewTransition 的生命周期，你可能会关心 <a href=https://github.com/withastro/astro/pull/9090>这个PR</a>。<p>至此我们就完成了 Astro 的暗黑模式支持，最后如果你要编写黑暗模式的样式，记得在选择器前加上 <code>.dark</code> 类，或者直接使用 Tailwind CSS 的 <code>dark:</code> 前缀。]]></content:encoded>
            <enclosure url="https://suborbit.net/_astro/cover.5kLwVkCL.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[装机记录：垃圾佬速成]]></title>
            <link>https://suborbit.net/posts/build-my-pc-in-2023</link>
            <guid>https://suborbit.net/posts/build-my-pc-in-2023</guid>
            <pubDate>Thu, 28 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[开个玩笑~ 事情的缘由是，最近笔记本坏了，鉴于手里已经有一台 Surface 了，所以考虑组装一个台式机，作为小白，人生第一次完整装机，记录一下过程。 预算 预算：7000~9000元，不包括显示器、键鼠、耳机； 生命周期：>=4年即可； 基本结论：这台主机不会用太久，所以配置不求最新，能省则省。 明确需求 性能需求：日常文档处理、浏览网页、音乐、视频、基本游戏需求需要满足，不常玩3A大作；有跑ai模型的学习需求…]]></description>
            <content:encoded><![CDATA[<p>开个玩笑~<p>事情的缘由是，最近笔记本坏了，鉴于手里已经有一台 Surface 了，所以考虑组装一个台式机，作为小白，人生第一次完整装机，记录一下过程。<h2>预算</h2><ul><li>预算：7000~9000元，不包括显示器、键鼠、耳机；<li>生命周期：>=4年即可；</ul><p>基本结论：这台主机不会用太久，所以配置不求最新，能省则省。<h2>明确需求</h2><ul><li>性能需求：日常文档处理、浏览网页、音乐、视频、基本游戏需求需要满足，不常玩3A大作；有跑ai模型的学习需求，常用开发工具不能出现卡顿；<li>稳定性需求：要求低故障率，不会爆炸；<li>外观需求：风格一致即可，不需要RGB灯光，无颜色偏好；</ul><p>基本结论：关键配件选择一线品牌，非关键配件尽量高性价比，按照 <strong>无光黑色</strong> 风格搭配即可。<h2>型号调研</h2><p>需要购置的配件有：CPU、主板、内存、硬盘、显卡、机箱、电源、散热器、风扇；<h3>CPU</h3><p>身为程序员，果然还是更倾向于Intel。关于Intel CPU的详细命名规则，在英特尔官网有写：<a href=https://www.intel.cn/content/www/cn/zh/processors/processor-numbers.html>英特尔® 处理器名称和编号</a>。对于桌面端消费级CPU，知道<code>13600</code>中<code>13</code>代表“代数”，<code>600</code>代表子型号（SKU），<code>F</code>后缀代表无核显，<code>K</code>后缀不锁倍频就够了。<p>目前最新一代CPU为2022年10月发布的13代，一般SKU越大性能越好。由于不求新，就同时比较12代和13代，但是我需要核显。热门的几款型号分别是：<p>i5 13400、i5 13490、i5 12600(k)、i5 12700(k)、i5 13600(k)、i5 13700(k)<p>它们的性能依次递增，价格也一样。其中我最关注的是12600(k)和13600(k)，看了参数和评测，发现13600除了核心频率提高了之外，相比12600主要是增加了4给小核，以及缓存变大了很多。还有一点需要提到的是，市面上有一种称为“散片”的CPU，没有正规包装，但是价格会便宜一点。<p><img alt="12600K 13600K 3DMark 对比"src=./12600k-13600k-3dmark.jpg><p>思考了很久，感觉这台主机不会用太长时间，所以最终选择了12600k，性能足够，价格更便宜。选择带k的CPU是因为，非k后缀的CPU锁SA电压，内存在Gear1模式下很难超过3200mhz的频率。<h3>主板</h3><p>不玩超频，倾向matx版型，可供选择的芯片组有b660、b760这两代。b760相比b660主要是增加了PCIe4.0接口的数量。需要注意不同ddr代数的主板不能混用，内存条接口都不一样。<p><img alt=DDR4与DDR5内存条接口对比 src=./ddr4-ddr5.jpg><p>为了贯彻能省则省的原则，直接考虑12代板U。本人暂时不认为现阶段ddr4和ddr5内存在工作的体验上有质的飞跃，所以主板就选了微星的b660m mortar ddr4 wifi，这样就可以省下一点钱。不过这款主板有个max版本，可以超外频，只需要加50RMB，后来就换了这个尝鲜。<h3>内存</h3><p>需要匹配主板的ddr4内存条，不要灯光，不超频，市面上可选产品很多，考虑到本人经常同时开很多网页和软件，最终选择的宏碁凌霜 3600 C18 16G *2。<h3>硬盘</h3><p>宏碁GM7 4TB只需要999！长存颗粒，看着是真香。不过我最终选择了另外一个，宏碁GM7000 2TB，这个带2G的DRAM缓存，京东价格599。<h3>显卡</h3><p>预算有限，不能上4080、4090这样的高端显卡了，我决定买一个显存大一点的，好能跑得动一些吃显存的模型。但是找来找去，只有RTX 4060ti 16G这种答辩，在显存频率没有太大提升的情况下，显存位宽砍半，仅有128bit，导致显存带宽大打折扣，便宜一点的和贵一点的显卡都没有大显存，二手3090怕矿卡不敢买，无奈呀。<p><img alt=饱受折磨的显卡娘 src=./gpu-meme.jpg><p>最后买了影驰金属大师RTX 4060ti 16G MAX OC，性价比不是很高，不过是跟无光黑色机箱倒是挺搭的。<p><img alt=影驰金属大师显卡 src=./gpu.jpg><h3>机箱</h3><p>海景房就算了，过一年还要搬家，怕碎，其他能装matx主板就行。于是跟风！选了乔思伯松果D31 mesh板。<h3>散热器</h3><p>跟风！酷里奥P60T 性能版 纯黑色，双塔风冷散热器。听说噪声小、性能很棒。<h3>风扇</h3><p>随便选了利民G12*3+利民S12，后者带一圈灯环，还是有一点点灯光的。实际上风扇是我最后买的，在此之前其他配件都到齐了，我发现即使只装一个风冷也能完美使用，所以风扇就没配全。<p>PS: 这套风扇真是一言难尽，静音确实是静音，但是没多少风量，早知道不如买更便宜的C12C。<h3>电源</h3><p>可选项很多，网上关于谁好谁坏的争论也很多，综合考虑下，我买了利民TG650 650w金牌全模组，五年质保，主要是价格便宜（汗）。<h2>配置总结</h2><p>发现拼多多上某些硬件好便宜，遂从拼多多入手大多数配件，最终配置单如下。<table><thead><tr><th>配件<th>型号<th>渠道<th>入手价（元）<tbody><tr><td>CPU<td>Intel Core i5 12600k 散片<td>拼多多<td>1405<tr><td>主板<td>微星 b660m mortar max wifi ddr4<td>拼多多<td>917<tr><td>内存<td>宏碁掠夺者凌霜 3600 C18 16G *2<td>拼多多<td>454<tr><td>硬盘<td>宏碁掠夺者 GM7000 2TB<td>拼多多<td>593<tr><td>显卡<td>影驰 RTX4060ti 金属大师 16G MAX OC<td>淘宝<td>3689<tr><td>散热器<td>酷里奥 P60T 性能版 黑色<td>拼多多<td>247<tr><td>机箱<td>乔思伯松果 D31 黑色 mesh版<td>拼多多<td>195<tr><td>风扇<td>利民 G12 *3 + 利民 S12 黑色 *1<td>拼多多<td>86<tr><td>电源<td>利民TG650 650w金牌全模组<td>京东<td>316<tr><td>总计<td><td><td>7902</table><p>这其中不少配件还能再缩水，恐怕还能挤出几百块来😂。<h2>成品展示</h2><p>参照b站教程： <a href=https://www.bilibili.com/video/BV1BG4y137mG/>【装机教程】全网最好的装机教程，没有之一</a><p>繁琐的过程就省略了，展示一下成果吧。<p><img alt=交作业咯 src=./result.jpg><p>主打一个硬朗风格。装好后，CPU和内存我都尝试了超频，不得不说，超不了多少，体质是真一般，不过无所谓了。Enjoying~<h2>后记</h2><p>2023.10.3 淦，4060ti 16g 最低价被干到了3099，亏麻了。<p>2023.10.6 SSD涨了200块，我貌似刚好赶在涨价钱入的手。]]></content:encoded>
            <enclosure url="https://suborbit.net/_astro/cover.CrinIBvY.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[给你的Markdown扩展新语法吧：下篇]]></title>
            <link>https://suborbit.net/posts/extend-markdown-syntax-practical</link>
            <guid>https://suborbit.net/posts/extend-markdown-syntax-practical</guid>
            <pubDate>Mon, 07 Aug 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[注意 这篇文章仅适用于基于 Remark.js 开发的 Markdown 应用，如 Astro.js、MDX 等等。 版本更新提醒 从 mdast-util-from-markdown 2.0.0 开始，this.exit() 方法删除了返回值，不再返回 AST 节点，因此在该版本里，我们需要自己使用其他方法获取节点。例如： function exitMyNodeType(token) { const node = this.stack[…]]></description>
            <content:encoded><![CDATA[<div><div><span>注意</span></div><div><p>这篇文章仅适用于基于 <a href=https://github.com/remarkjs/remark>Remark.js</a> 开发的 Markdown 应用，如 Astro.js、MDX 等等。</div></div><div><div><span>版本更新提醒</span></div><div><p>从 <a href=https://github.com/syntax-tree/mdast-util-from-markdown/releases/tag/2.0.0>mdast-util-from-markdown 2.0.0</a> 开始，this.exit() 方法删除了返回值，不再返回 AST 节点，因此在该版本里，我们需要自己使用其他方法获取节点。例如：<pre tabindex=0><code><span><span>function</span><span> exitMyNodeType</span><span>(</span><span>token</span><span>) {</span></span>
<span><span>  const</span><span> node</span><span> =</span><span> this</span><span>.stack[</span><span>this</span><span>.stack.</span><span>length</span><span> -</span><span> 1</span><span>] </span><span>as</span><span> MyNodeType</span><span>;</span></span>
<span><span>  this</span><span>.</span><span>exit</span><span>(token);</span></span>
<span><span>  // previous code will case error</span></span>
<span><span>  // const node = this.exit(token) as MyNodeType;</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>该变更影响 remark 15.0.0 及以上版本，请注意你所使用的框架的依赖版本。</div></div><p><a href=./%E6%89%A9%E5%B1%95markdown%E8%AF%AD%E6%B3%95-%E4%B8%8A%E7%AF%87>上一篇文章</a> 讲了 Remark 扩展新语法的方法，现在我们就来实践吧。<p>在这篇文章里，我们将会实现两个有趣的新语法，分别是：<ul><li>Spoiler（黑幕）：形成对文字的遮挡效果，只有在鼠标悬停时才会显示。<li>Admonition（警告框）：使用块级元素来强调一段文字，可以有不同的类型、颜色和图标。</ul><h2>Spoiler</h2><h3>语法定义</h3><p>先来介绍一下 Spoiler 的语法。使用 <code>!!</code> 双叹号包裹行内文本，文本将会具有 Spolier 效果。<pre tabindex=0><code><span><span>!!这是一条 Spoiler 测试文本。这是一条 Spoiler 测试文本。!!</span></span>
<span></span></code></pre><p>效果如下（鼠标悬停在文本上，以显示文字）：<span>这是一条 Spoiler 测试文本。这是一条 Spoiler 测试文本。</span><h3>文件结构</h3><pre tabindex=0><code><span><span>- remarkSpoiler</span></span>
<span><span>  - syntax.ts       // micromark 扩展</span></span>
<span><span>  - fromMarkdown.ts // mdast-util-from-markdown 扩展</span></span>
<span><span>  - index.ts        // 插件入口</span></span>
<span><span></span></span></code></pre><h3>micromark 扩展</h3><pre tabindex=0><code><span><span>import</span><span> { markdownLineEnding } </span><span>from</span><span> 'micromark-util-character'</span><span>;</span></span>
<span><span>import</span><span> { factorySpace } </span><span>from</span><span> 'micromark-factory-space'</span><span>;</span></span>
<span><span>import</span><span> { codes, types, constants } </span><span>from</span><span> 'micromark-util-symbol'</span><span>;</span></span>
<span><span>import</span><span> type</span><span> { Construct, Tokenizer, State, Extension </span><span>as</span><span> MicromarkExtension } </span><span>from</span><span> 'micromark-util-types'</span><span>;</span></span>
<span></span>
<span><span>// 定义 spoiler Tokenizer</span></span>
<span><span>const</span><span> tokenizeSpoiler</span><span>:</span><span> Tokenizer</span><span> =</span><span> function</span><span>(</span><span>effects</span><span>, </span><span>ok</span><span>, </span><span>nok</span><span>) {</span></span>
<span><span>  const</span><span> self</span><span> =</span><span> this</span><span>;</span></span>
<span><span>  const</span><span> start</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    effects.</span><span>enter</span><span>(</span><span>'spoiler'</span><span>);</span></span>
<span><span>    return</span><span> effects.</span><span>attempt</span><span>(</span></span>
<span><span>      marker,</span></span>
<span><span>      factorySpace</span><span>(effects, contentStart, types.whitespace),</span></span>
<span><span>      nok</span></span>
<span><span>    )</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> contentStart</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    effects.</span><span>enter</span><span>(types.chunkText, {</span></span>
<span><span>      contentType: constants.contentTypeText,</span></span>
<span><span>    })</span></span>
<span><span>    return</span><span> content;</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> content</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    return</span><span> effects.</span><span>check</span><span>(</span></span>
<span><span>      marker,</span></span>
<span><span>      factorySpace</span><span>(effects, contentAfter, types.whitespace),</span></span>
<span><span>      comsumeData</span></span>
<span><span>    )</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> comsumeData</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (</span><span>markdownLineEnding</span><span>(code) </span><span>||</span><span> code </span><span>===</span><span> codes.eof) {</span></span>
<span><span>      return</span><span> nok;</span></span>
<span><span>    }</span></span>
<span><span>    effects.</span><span>consume</span><span>(code);</span></span>
<span><span>    return</span><span> content;</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> contentAfter</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    effects.</span><span>exit</span><span>(types.chunkText);</span></span>
<span><span>    return</span><span> effects.</span><span>attempt</span><span>(marker, after, nok);</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> after</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    effects.</span><span>exit</span><span>(</span><span>'spoiler'</span><span>);</span></span>
<span><span>    return</span><span> ok;</span></span>
<span><span>  }</span></span>
<span><span>  return</span><span> start;</span></span>
<span><span>}</span></span>
<span></span>
<span><span>// 定义分界符 (!!) 的 Tokenizer</span></span>
<span><span>const</span><span> tokenizeMarker</span><span>:</span><span> Tokenizer</span><span> =</span><span> function</span><span>(</span><span>effects</span><span>, </span><span>ok</span><span>, </span><span>nok</span><span>) {</span></span>
<span><span>  let</span><span> markerSize </span><span>=</span><span> 0</span><span>;</span></span>
<span><span>  if</span><span> (</span><span>this</span><span>.previous </span><span>===</span><span> codes.exclamationMark) {</span></span>
<span><span>    return</span><span> nok;</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> start</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    effects.</span><span>enter</span><span>(</span><span>'spoilerMarker'</span><span>);</span></span>
<span><span>    return</span><span> marker;</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> marker</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (code </span><span>===</span><span> codes.exclamationMark) {</span></span>
<span><span>      effects.</span><span>consume</span><span>(code);</span></span>
<span><span>      markerSize</span><span>++</span><span>;</span></span>
<span><span>      return</span><span> marker;</span></span>
<span><span>    }</span></span>
<span><span>    if</span><span> (markerSize </span><span>==</span><span> 2</span><span>) {</span></span>
<span><span>      effects.</span><span>exit</span><span>(</span><span>'spoilerMarker'</span><span>);</span></span>
<span><span>      markerSize </span><span>=</span><span> 0</span><span>;</span></span>
<span><span>      return</span><span> ok;</span></span>
<span><span>    }</span></span>
<span><span>    return</span><span> nok;</span></span>
<span><span>  }</span></span>
<span><span>  return</span><span> factorySpace</span><span>(effects, start, types.whitespace);</span></span>
<span><span>}</span></span>
<span></span>
<span><span>const</span><span> marker</span><span>:</span><span> Construct</span><span> =</span><span> {</span></span>
<span><span>  tokenize: tokenizeMarker,</span></span>
<span><span>  partial: </span><span>true</span></span>
<span><span>}</span></span>
<span></span>
<span><span>const</span><span> spoiler</span><span>:</span><span> Construct</span><span> =</span><span> {</span></span>
<span><span>  name: </span><span>'spoiler'</span><span>,</span></span>
<span><span>  tokenize: tokenizeSpoiler</span></span>
<span><span>}</span></span>
<span></span>
<span><span>export</span><span> const</span><span> syntax</span><span> =</span><span> ()</span><span>:</span><span> MicromarkExtension</span><span> =></span><span> {</span></span>
<span><span>  return</span><span> {</span></span>
<span><span>    text: {</span></span>
<span><span>      [codes.exclamationMark]: spoiler,</span></span>
<span><span>    }</span></span>
<span><span>  }</span></span>
<span><span>}</span></span>
<span></span></code></pre><h3>mdast-util-from-markdown 扩展</h3><p>在 html 上实现spoiler效果比较简易，只需要添加一些 css 即可。<pre tabindex=0><code><span><span>import</span><span> type</span><span> { Parent } </span><span>from</span><span> 'mdast'</span><span>;</span></span>
<span><span>import</span><span> type</span><span> { Handle, Extension </span><span>as</span><span> FromMarkdownExtension } </span><span>from</span><span> 'mdast-util-from-markdown'</span><span>;</span></span>
<span></span>
<span><span>// 定义 spoiler 节点类型</span></span>
<span><span>export</span><span> interface</span><span> Spoiler</span><span> extends</span><span> Parent</span><span> {</span></span>
<span><span>  type</span><span>:</span><span> 'spoiler'</span><span>,</span></span>
<span><span>};</span></span>
<span></span>
<span><span>// 声明自定义 mdast 类型</span></span>
<span><span>declare</span><span> module</span><span> 'mdast'</span><span> {</span></span>
<span><span>  interface</span><span> StaticPhrasingContentMap</span><span> {</span></span>
<span><span>    spoiler</span><span>:</span><span> Spoiler</span><span>;</span></span>
<span><span>  }</span></span>
<span><span>}</span></span>
<span></span>
<span><span>export</span><span> const</span><span> fromMarkdown</span><span> =</span><span> ()</span><span>:</span><span> FromMarkdownExtension</span><span> =></span><span> {</span></span>
<span><span>  const</span><span> enterSpoiler</span><span>:</span><span> Handle</span><span> =</span><span> function</span><span>(</span><span>token</span><span>) {</span></span>
<span><span>    this</span><span>.</span><span>enter</span><span>&lt;</span><span>Spoiler</span><span>>({</span></span>
<span><span>      type: </span><span>'spoiler'</span><span>,</span></span>
<span><span>      children: [],</span></span>
<span><span>    }, token);</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> exitSpoiler</span><span>:</span><span> Handle</span><span> =</span><span> function</span><span>(</span><span>token</span><span>) {</span></span>
<span><span>    const</span><span> node</span><span> =</span><span> this</span><span>.</span><span>exit</span><span>(token) </span><span>as</span><span> Spoiler</span><span>;</span></span>
<span><span>    node.data </span><span>=</span><span> {</span></span>
<span><span>      ...</span><span>node.data,</span></span>
<span><span>      hName: </span><span>'span'</span><span>,</span></span>
<span><span>      hProperties: {</span></span>
<span><span>        className: </span><span>'bg-current hover:bg-transparent'</span><span>,</span></span>
<span><span>      },</span></span>
<span><span>    }</span></span>
<span><span>  }</span></span>
<span><span>  return</span><span> {</span></span>
<span><span>    enter: {</span></span>
<span><span>      spoiler: enterSpoiler,</span></span>
<span><span>    },</span></span>
<span><span>    exit: {</span></span>
<span><span>      spoiler: exitSpoiler,</span></span>
<span><span>    }</span></span>
<span><span>  }</span></span>
<span><span>}</span></span>
<span></span></code></pre><h3>完善插件</h3><p>这里编写了一个 <code>add</code> 工具函数，用来向对象中某列表属性添加元素，如果属性不存在则创建一个新的列表。<pre tabindex=0><code><span><span>import</span><span> type</span><span> { Plugin } </span><span>from</span><span> 'unified'</span><span>;</span></span>
<span><span>import</span><span> type</span><span> { Root } </span><span>from</span><span> 'mdast'</span><span>;</span></span>
<span><span>import</span><span> { syntax } </span><span>from</span><span> './syntax'</span><span>;</span></span>
<span><span>import</span><span> { fromMarkdown } </span><span>from</span><span> './fromMarkdown'</span><span>;</span></span>
<span></span>
<span><span>const</span><span> remarkSpoiler</span><span>:</span><span> Plugin</span><span>&#60[], </span><span>Root</span><span>> </span><span>=</span><span> function</span><span>() {</span></span>
<span><span>  const</span><span> data</span><span> =</span><span> this</span><span>.</span><span>data</span><span>();</span></span>
<span><span>  function</span><span> add</span><span>(</span><span>key</span><span>:</span><span> string</span><span>, </span><span>value</span><span>:</span><span> unknown</span><span>) {</span></span>
<span><span>    if</span><span> (Array.</span><span>isArray</span><span>(data[key])) {</span></span>
<span><span>      (data[key] </span><span>as</span><span> unknown</span><span>[]).</span><span>push</span><span>(value);</span></span>
<span><span>    } </span><span>else</span><span> {</span></span>
<span><span>      data[key] </span><span>=</span><span> [value];</span></span>
<span><span>    }</span></span>
<span><span>  }</span></span>
<span><span>  add</span><span>(</span><span>'micromarkExtensions'</span><span>, </span><span>syntax</span><span>());</span></span>
<span><span>  add</span><span>(</span><span>'fromMarkdownExtensions'</span><span>, </span><span>fromMarkdown</span><span>());</span></span>
<span><span>}</span></span>
<span><span>export</span><span> default</span><span> remarkSpoiler;</span></span>
<span></span></code></pre><h2>Admonition</h2><h3>语法定义</h3><p>使用三个叹号作为前缀，后面紧随 admonition 的类型，类型之后可以跟一个可选的标题，标题和类型之间用空格分隔。<p>从第二行开始为 admonition 的内容，需要有 4 个空格的缩进。如果下一行的内容仍然以 4 个空格缩进，则该内容块仍然属于 admonition，直到遇到非法缩进。<pre tabindex=0><code><span><span>!!! info 注意</span></span>
<span><span>    这是一条注意事项。</span></span>
<span><span>    !!! tip 提示：</span><span>_标题可以带有格式_</span></span>
<span><span>        Admonition 可以嵌套使用。</span></span>
<span></span></code></pre><div><div><span>注意</span></div><div><p>这是一条注意事项。<div><div><span>提示：<em>标题可以带有格式</em></span></div><div><p>Admonition 可以嵌套使用。</div></div></div></div><h3>文件结构</h3><pre tabindex=0><code><span><span>- remarkAdmonition</span></span>
<span><span>  - syntax.ts       // micromark 扩展</span></span>
<span><span>  - fromMarkdown.ts // mdast-util-from-markdown 扩展</span></span>
<span><span>  - index.ts        // 插件入口</span></span>
<span><span>  - styles.css      // 附加样式</span></span>
<span><span></span></span></code></pre><h3>micromark 扩展</h3><pre tabindex=0><code><span><span>import</span><span> { asciiAlpha, asciiAlphanumeric, markdownLineEnding, markdownSpace } </span><span>from</span><span> 'micromark-util-character'</span><span>;</span></span>
<span><span>import</span><span> { factorySpace } </span><span>from</span><span> 'micromark-factory-space'</span><span>;</span></span>
<span><span>import</span><span> { codes, types, constants } </span><span>from</span><span> 'micromark-util-symbol'</span><span>;</span></span>
<span><span>import</span><span> type</span><span> { TokenizeContext, Construct, Tokenizer, Effects, Exiter, State, Token, Extension </span><span>as</span><span> MicromarkExtension } </span><span>from</span><span> 'micromark-util-types'</span><span>;</span></span>
<span></span>
<span><span>// characters in name can be: a-z, A-Z, 0-9, -, _</span></span>
<span><span>// but the first character must be a-z or A-Z</span></span>
<span><span>function</span><span> factoryName</span><span>(</span></span>
<span><span>  this</span><span>:</span><span> TokenizeContext</span><span>, </span></span>
<span><span>  effects</span><span>:</span><span> Effects</span><span>, </span></span>
<span><span>  ok</span><span>:</span><span> State</span><span>, </span></span>
<span><span>  nok</span><span>:</span><span> State</span><span>, </span></span>
<span><span>  type</span><span>:</span><span> string</span></span>
<span><span>) {</span></span>
<span><span>  const</span><span> self</span><span> =</span><span> this</span><span>;</span></span>
<span><span>  const</span><span> start</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (</span><span>asciiAlpha</span><span>(code)) {</span></span>
<span><span>      effects.</span><span>enter</span><span>(type);</span></span>
<span><span>      effects.</span><span>consume</span><span>(code);</span></span>
<span><span>      return</span><span> name;</span></span>
<span><span>    }</span></span>
<span><span>    return</span><span> nok;</span></span>
<span><span>  }</span></span>
<span></span>
<span><span>  const</span><span> name</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (</span></span>
<span><span>      code </span><span>===</span><span> codes.dash </span><span>||</span></span>
<span><span>      code </span><span>===</span><span> codes.underscore </span><span>||</span></span>
<span><span>      asciiAlphanumeric</span><span>(code)</span></span>
<span><span>    ) {</span></span>
<span><span>      effects.</span><span>consume</span><span>(code);</span></span>
<span><span>      return</span><span> name;</span></span>
<span><span>    }</span></span>
<span><span>    effects.</span><span>exit</span><span>(type);</span></span>
<span><span>    return</span><span> self.previous </span><span>===</span><span> codes.dash </span><span>||</span><span> self.previous </span><span>===</span><span> codes.underscore</span></span>
<span><span>      ?</span><span> nok</span></span>
<span><span>      :</span><span> ok;</span></span>
<span><span>  }</span></span>
<span><span>  return</span><span> start;</span></span>
<span><span>}</span></span>
<span><span>// tokenizing title, the title can be any character except line ending</span></span>
<span><span>function</span><span> factoryTitle</span><span>(</span></span>
<span><span>  effects</span><span>:</span><span> Effects</span><span>,</span></span>
<span><span>  ok</span><span>:</span><span> State</span><span>,</span></span>
<span><span>  nok</span><span>:</span><span> State</span><span>,</span></span>
<span><span>  type</span><span>:</span><span> string</span><span>,</span></span>
<span><span>) {</span></span>
<span><span>  let</span><span> previous</span><span>:</span><span> Token</span><span>;</span></span>
<span><span>  const</span><span> start</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    effects.</span><span>enter</span><span>(type);</span></span>
<span><span>    return</span><span> titleStart;</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> titleStart</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (</span><span>markdownLineEnding</span><span>(code) </span><span>||</span><span> code </span><span>===</span><span> codes.eof) {</span></span>
<span><span>      effects.</span><span>exit</span><span>(type);</span></span>
<span><span>      return</span><span> ok;</span></span>
<span><span>    }</span></span>
<span><span>    const</span><span> token</span><span> =</span><span> effects.</span><span>enter</span><span>(types.chunkText, {</span></span>
<span><span>      contentType: constants.contentTypeText,</span></span>
<span><span>      previous</span></span>
<span><span>    })</span></span>
<span><span>    if</span><span> (previous) previous.next </span><span>=</span><span> token</span></span>
<span><span>    previous </span><span>=</span><span> token</span></span>
<span></span>
<span><span>    effects.</span><span>consume</span><span>(code);</span></span>
<span><span>    return</span><span> title;</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> title</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (</span><span>markdownLineEnding</span><span>(code) </span><span>||</span><span> code </span><span>===</span><span> codes.eof) {</span></span>
<span><span>      effects.</span><span>exit</span><span>(types.chunkText);</span></span>
<span><span>      effects.</span><span>exit</span><span>(type);</span></span>
<span><span>      return</span><span> ok;</span></span>
<span><span>    }</span></span>
<span><span>    effects.</span><span>consume</span><span>(code);</span></span>
<span><span>    return</span><span> title;</span></span>
<span><span>  }</span></span>
<span><span>  return</span><span> start;</span></span>
<span><span>}</span></span>
<span></span>
<span><span>const</span><span> tokenizeIndent</span><span>:</span><span> Tokenizer</span><span> =</span><span> function</span><span>(</span><span>effects</span><span>, </span><span>ok</span><span>, </span><span>nok</span><span>) {</span></span>
<span><span>  const</span><span> self</span><span> =</span><span> this</span><span>;</span></span>
<span><span>  const</span><span> prefix</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (</span><span>!</span><span>self.containerState) {</span></span>
<span><span>      throw</span><span> new</span><span> Error</span><span>(</span><span>'expected state'</span><span>)</span></span>
<span><span>    }</span></span>
<span><span>    if</span><span> (</span><span>typeof</span><span> self.containerState.indent </span><span>!==</span><span> 'number'</span><span>) {</span></span>
<span><span>      throw</span><span> new</span><span> Error</span><span>(</span><span>'expected indent'</span><span>)</span></span>
<span><span>    }</span></span>
<span><span>    return</span><span> factorySpace</span><span>(</span></span>
<span><span>      effects,</span></span>
<span><span>      afterPrefix,</span></span>
<span><span>      'admonitionIndent'</span><span>,</span></span>
<span><span>      constants.tabSize </span><span>+</span><span> 1</span><span>,</span></span>
<span><span>    )</span></span>
<span><span>  }</span></span>
<span></span>
<span><span>  const</span><span> afterPrefix</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (</span><span>!</span><span>self.containerState) {</span></span>
<span><span>      throw</span><span> new</span><span> Error</span><span>(</span><span>'expected state'</span><span>)</span></span>
<span><span>    }</span></span>
<span><span>    const</span><span> tail</span><span> =</span><span> self.events[self.events.</span><span>length</span><span> -</span><span> 1</span><span>]</span></span>
<span><span>    if</span><span> (tail) {</span></span>
<span><span>      const</span><span> [</span><span>type</span><span>, </span><span>token</span><span>, </span><span>context</span><span>] </span><span>=</span><span> tail;</span></span>
<span><span>      if</span><span> (token.type </span><span>===</span><span> 'admonitionIndent'</span></span>
<span><span>        &#38&amp</span><span> token.end.column </span><span>-</span><span> 1</span><span> ===</span><span> self.containerState.indent) {</span></span>
<span><span>        return</span><span> ok;</span></span>
<span><span>      }</span></span>
<span><span>    }</span></span>
<span><span>    return</span><span> nok;</span></span>
<span><span>  }</span></span>
<span><span>  return</span><span> prefix</span></span>
<span><span>}</span></span>
<span></span>
<span><span>const</span><span> tokenizeBlankLine</span><span>:</span><span> Tokenizer</span><span> =</span><span> function</span><span>(</span><span>effects</span><span>, </span><span>ok</span><span>, </span><span>nok</span><span>) {</span></span>
<span><span>  const</span><span> self</span><span> =</span><span> this</span><span>;</span></span>
<span><span>  const</span><span> start</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    return</span><span> markdownSpace</span><span>(code)</span></span>
<span><span>      ?</span><span> factorySpace</span><span>(effects, after, types.whitespace)</span></span>
<span><span>      :</span><span> after</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> after</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    return</span><span> code </span><span>===</span><span> codes.eof </span><span>||</span><span> markdownLineEnding</span><span>(code) </span><span>?</span><span> ok </span><span>:</span><span> nok</span></span>
<span><span>  }</span></span>
<span><span>  return</span><span> start</span></span>
<span><span>}</span></span>
<span></span>
<span><span>const</span><span> tokenizeAdmonitionStart</span><span>:</span><span> Tokenizer</span><span> =</span><span> function</span><span>(</span><span>effects</span><span>, </span><span>ok</span><span>, </span><span>nok</span><span>) {</span></span>
<span><span>  // used for factoryName</span></span>
<span><span>  const</span><span> self</span><span> =</span><span> this</span><span>;</span></span>
<span><span>  // define data</span></span>
<span><span>  let</span><span> markerSize </span><span>=</span><span> 0</span><span>;</span></span>
<span><span>  const</span><span> tail</span><span> =</span><span> self.events[self.events.</span><span>length</span><span> -</span><span> 1</span><span>];</span></span>
<span><span>  let</span><span> initialIndent </span><span>=</span><span> 0</span><span>;</span></span>
<span><span>  if</span><span> (tail) {</span></span>
<span><span>    const</span><span> [</span><span>tailType</span><span>, </span><span>tailToken</span><span>, </span><span>tailContext</span><span>] </span><span>=</span><span> tail;</span></span>
<span><span>    initialIndent </span><span>=</span><span> tailToken.type </span><span>===</span><span> 'admonitionIndent'</span></span>
<span><span>      ?</span><span> tailContext.</span><span>sliceSerialize</span><span>(tailToken, </span><span>true</span><span>).</span><span>length</span></span>
<span><span>      :</span><span> 0</span><span>;</span></span>
<span><span>  }</span></span>
<span><span>  if</span><span> (</span><span>!</span><span>self.containerState) {</span></span>
<span><span>    throw</span><span> new</span><span> Error</span><span>(</span><span>'expected state'</span><span>)</span></span>
<span><span>  }</span></span>
<span><span>  self.containerState.indent </span><span>=</span><span> initialIndent </span><span>+</span><span> constants.tabSize;</span></span>
<span><span>  // define states</span></span>
<span><span>  const</span><span> start</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    effects.</span><span>enter</span><span>(</span><span>'admonition'</span><span>);</span></span>
<span><span>    effects.</span><span>enter</span><span>(</span><span>'admonitionPrefix'</span><span>);</span></span>
<span><span>    if</span><span> (code </span><span>===</span><span> codes.exclamationMark) {</span></span>
<span><span>      effects.</span><span>enter</span><span>(</span><span>'admonitionMarker'</span><span>);</span></span>
<span><span>      return</span><span> marker;</span></span>
<span><span>    }</span></span>
<span><span>    return</span><span> nok;</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> marker</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (code </span><span>===</span><span> codes.exclamationMark) {</span></span>
<span><span>      effects.</span><span>consume</span><span>(code);</span></span>
<span><span>      markerSize</span><span>++</span><span>;</span></span>
<span><span>      return</span><span> marker;</span></span>
<span><span>    }</span></span>
<span><span>    if</span><span> (markerSize </span><span>!==</span><span> 3</span><span>) {</span></span>
<span><span>      return</span><span> nok;</span></span>
<span><span>    }</span></span>
<span><span>    effects.</span><span>exit</span><span>(</span><span>'admonitionMarker'</span><span>);</span></span>
<span><span>    return</span><span> afterMarker;</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> afterMarker</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (</span><span>markdownSpace</span><span>(code)) {</span></span>
<span><span>      return</span><span> factorySpace</span><span>(effects, afterMarker, types.whitespace);</span></span>
<span><span>    }</span></span>
<span><span>    return</span><span> name;</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> name</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    return</span><span> factoryName.</span><span>call</span><span>(self, effects, afterName, nok, </span><span>'admonitionName'</span><span>);</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> afterName</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (</span><span>markdownSpace</span><span>(code)) {</span></span>
<span><span>      return</span><span> factorySpace</span><span>(effects, afterName, types.whitespace);</span></span>
<span><span>    }</span></span>
<span><span>    if</span><span> (</span><span>markdownLineEnding</span><span>(code) </span><span>||</span><span> code </span><span>===</span><span> codes.eof) {</span></span>
<span><span>      return</span><span> after;</span></span>
<span><span>    }</span></span>
<span><span>    return</span><span> title;</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> title</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    return</span><span> factoryTitle</span><span>(effects, afterTitle, nok, </span><span>'admonitionTitle'</span><span>);</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> afterTitle</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (</span><span>!</span><span>markdownLineEnding</span><span>(code)) {</span></span>
<span><span>      effects.</span><span>consume</span><span>(code);</span></span>
<span><span>      // self.parser.lazy[t.start.line] = false</span></span>
<span><span>      return</span><span> afterTitle;</span></span>
<span><span>    }</span></span>
<span><span>    return</span><span> after;</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> after</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    effects.</span><span>exit</span><span>(</span><span>'admonitionPrefix'</span><span>);</span></span>
<span><span>    return</span><span> ok;</span></span>
<span><span>  }</span></span>
<span><span>  return</span><span> start;</span></span>
<span><span>}</span></span>
<span></span>
<span><span>const</span><span> tokenizeAdmonitionContinuation</span><span>:</span><span> Tokenizer</span><span> =</span><span> function</span><span>(</span><span>effects</span><span>, </span><span>ok</span><span>, </span><span>nok</span><span>) {</span></span>
<span><span>  return</span><span> effects.</span><span>check</span><span>(blankLine, ok, effects.</span><span>attempt</span><span>(indent, ok, nok))</span></span>
<span><span>}</span></span>
<span></span>
<span><span>const</span><span> exit</span><span>:</span><span> Exiter</span><span> =</span><span> function</span><span>(</span><span>effects</span><span>) {</span></span>
<span><span>  effects.</span><span>exit</span><span>(</span><span>'admonition'</span><span>);</span></span>
<span><span>}</span></span>
<span></span>
<span><span>const</span><span> indent</span><span>:</span><span> Construct</span><span> =</span><span> {tokenize: tokenizeIndent, partial: </span><span>true</span><span>}</span></span>
<span><span>const</span><span> blankLine</span><span>:</span><span> Construct</span><span> =</span><span> {tokenize: tokenizeBlankLine, partial: </span><span>true</span><span>}</span></span>
<span><span>const</span><span> admonition</span><span>:</span><span> Construct</span><span> =</span><span> {</span></span>
<span><span>  name: </span><span>'admonition'</span><span>,</span></span>
<span><span>  tokenize: tokenizeAdmonitionStart,</span></span>
<span><span>  continuation: {</span></span>
<span><span>    tokenize: tokenizeAdmonitionContinuation,</span></span>
<span><span>  },</span></span>
<span><span>  exit: exit,</span></span>
<span><span>}</span></span>
<span></span>
<span><span>export</span><span> const</span><span> syntax</span><span> =</span><span> ()</span><span>:</span><span> MicromarkExtension</span><span> =></span><span> {</span></span>
<span><span>  return</span><span> {</span></span>
<span><span>    document: {</span></span>
<span><span>      [codes.exclamationMark]: admonition,</span></span>
<span><span>    }</span></span>
<span><span>  }</span></span>
<span><span>}</span></span>
<span></span></code></pre><h3>mdast-util-from-markdown 扩展</h3><pre tabindex=0><code><span><span>import</span><span> type</span><span> { Paragraph, Parent, Text, Content } </span><span>from</span><span> 'mdast'</span><span>;</span></span>
<span><span>import</span><span> type</span><span> { Content </span><span>as</span><span> HastContent } </span><span>from</span><span> 'hast'</span><span>;</span></span>
<span><span>import</span><span> type</span><span> { Handle, Extension </span><span>as</span><span> FromMarkdownExtension } </span><span>from</span><span> 'mdast-util-from-markdown'</span><span>;</span></span>
<span><span>import</span><span> { fromHtml } </span><span>from</span><span> 'hast-util-from-html'</span><span>;</span></span>
<span></span>
<span><span>export</span><span> interface</span><span> Admonition</span><span> extends</span><span> Parent</span><span> {</span></span>
<span><span>  type</span><span>:</span><span> 'admonition'</span><span>,</span></span>
<span><span>  name</span><span>:</span><span> string</span><span>,</span></span>
<span><span>};</span></span>
<span><span>export</span><span> interface</span><span> admonitionHTML</span><span> extends</span><span> Parent</span><span> {</span></span>
<span><span>  type</span><span>:</span><span> 'admonitionHTML'</span><span>,</span></span>
<span><span>}</span></span>
<span><span>declare</span><span> module</span><span> 'mdast'</span><span> {</span></span>
<span><span>  interface</span><span> BlockContentMap</span><span> {</span></span>
<span><span>    admonitionHTML</span><span>:</span><span> admonitionHTML</span><span>;</span></span>
<span><span>    admonition</span><span>:</span><span> Admonition</span><span>;</span></span>
<span><span>  }</span></span>
<span><span>  interface</span><span> ParagraphData</span><span> {</span></span>
<span><span>    admonitionTitle</span><span>?:</span><span> boolean</span><span>;</span></span>
<span><span>  }</span></span>
<span><span>}</span></span>
<span></span>
<span><span>const</span><span> admonitionConfig</span><span>:</span><span> Record</span><span>&lt;</span><span>string</span><span>, {</span></span>
<span><span>  title</span><span>:</span><span> string</span><span>,</span></span>
<span><span>  icon</span><span>:</span><span> string</span><span>,</span></span>
<span><span>  class</span><span>:</span><span> string</span><span>,</span></span>
<span><span>}> </span><span>=</span><span> {</span></span>
<span><span>  note: {</span></span>
<span><span>    title: </span><span>'备注'</span><span>,</span></span>
<span><span>    icon: </span><span>'&#60svg>...&#60/svg>'</span><span>,</span></span>
<span><span>    class: </span><span>'admonition-note'</span><span>,</span></span>
<span><span>  },</span></span>
<span><span>  info: {</span></span>
<span><span>    title: </span><span>'信息'</span><span>,</span></span>
<span><span>    icon: </span><span>'...'</span><span>,</span></span>
<span><span>    class: </span><span>'admonition-info'</span><span>,</span></span>
<span><span>  },</span></span>
<span><span>  tip: {</span></span>
<span><span>    title: </span><span>'提示'</span><span>,</span></span>
<span><span>    icon: </span><span>'...'</span><span>,</span></span>
<span><span>    class: </span><span>'admonition-tip'</span><span>,</span></span>
<span><span>  },</span></span>
<span><span>  caution: {</span></span>
<span><span>    title: </span><span>'注意'</span><span>,</span></span>
<span><span>    icon: </span><span>'...'</span><span>,</span></span>
<span><span>    class: </span><span>'admonition-caution'</span><span>,</span></span>
<span><span>  },</span></span>
<span><span>  danger: {</span></span>
<span><span>    title: </span><span>'危险'</span><span>,</span></span>
<span><span>    icon: </span><span>'...'</span><span>,</span></span>
<span><span>    class: </span><span>'admonition-danger'</span><span>,</span></span>
<span><span>  },</span></span>
<span><span>}</span></span>
<span></span>
<span><span>function</span><span> h</span><span> (</span></span>
<span><span>  tagName</span><span>:</span><span> string</span><span>,</span></span>
<span><span>  properties</span><span>:</span><span> Record</span><span>&lt;</span><span>string</span><span>, </span><span>any</span><span>>,</span></span>
<span><span>  children</span><span>?:</span><span> (</span><span>Content</span><span> |</span><span> undefined</span><span> |</span><span> null</span><span> |</span><span> false</span><span>)[],</span></span>
<span><span>)</span><span>:</span><span> admonitionHTML</span><span> {</span></span>
<span><span>  const</span><span> filteredChildren</span><span> =</span><span> children?.</span><span>filter</span><span>&lt;</span><span>Content</span><span>>((</span><span>child</span><span>)</span><span>:</span><span> child</span><span> is</span><span> Content</span><span> =></span><span> {</span></span>
<span><span>    return</span><span> child </span><span>!==</span><span> undefined</span><span> &#38&amp</span><span> child </span><span>!==</span><span> null</span><span> &#38&amp</span><span> child </span><span>!==</span><span> false</span><span>;</span></span>
<span><span>  }) </span><span>??</span><span> [];</span></span>
<span><span>  return</span><span> {</span></span>
<span><span>    type: </span><span>'admonitionHTML'</span><span>,</span></span>
<span><span>    data: {</span></span>
<span><span>      hName: tagName,</span></span>
<span><span>      hProperties: properties,</span></span>
<span><span>    },</span></span>
<span><span>    children: filteredChildren,</span></span>
<span><span>  }</span></span>
<span><span>}</span></span>
<span></span>
<span><span>function</span><span> toMdast</span><span>(</span><span>node</span><span>:</span><span> HastContent</span><span>) {</span></span>
<span><span>  if</span><span> (node.type </span><span>===</span><span> 'text'</span><span>) {</span></span>
<span><span>    return</span><span> {</span></span>
<span><span>      type: </span><span>'text'</span><span>,</span></span>
<span><span>      value: node.value,</span></span>
<span><span>    } </span><span>as</span><span> Text</span><span>;</span></span>
<span><span>  }</span></span>
<span><span>  if</span><span> (node.type </span><span>===</span><span> 'element'</span><span>) {</span></span>
<span><span>    const</span><span> mdastNode</span><span> =</span><span> {</span></span>
<span><span>      type: </span><span>'admonitionHTML'</span><span>,</span></span>
<span><span>      data: {</span></span>
<span><span>        hName: node.tagName,</span></span>
<span><span>        hProperties: node.properties,</span></span>
<span><span>      },</span></span>
<span><span>      children: [],</span></span>
<span><span>    } </span><span>as</span><span> admonitionHTML</span><span>;</span></span>
<span><span>    node.children.</span><span>map</span><span>(toMdast).</span><span>forEach</span><span>((</span><span>child</span><span>) </span><span>=></span><span> {</span></span>
<span><span>      if</span><span> (child) {</span></span>
<span><span>        mdastNode.children.</span><span>push</span><span>(child);</span></span>
<span><span>      }</span></span>
<span><span>    });</span></span>
<span><span>    return</span><span> mdastNode;</span></span>
<span><span>  }</span></span>
<span><span>  return</span><span> null</span><span>;</span></span>
<span><span>}</span></span>
<span></span>
<span><span>function</span><span> htmlTemplate</span><span>(</span></span>
<span><span>  type</span><span>:</span><span> string</span><span>,</span></span>
<span><span>  title</span><span>?:</span><span> string</span><span> |</span><span> Content</span><span>,</span></span>
<span><span>  children</span><span>?:</span><span> (</span><span>Content</span><span> |</span><span> undefined</span><span> |</span><span> null</span><span> |</span><span> false</span><span>)[],</span></span>
<span><span>) {</span></span>
<span><span>  const</span><span> key</span><span> =</span><span> type </span><span>in</span><span> admonitionConfig </span><span>?</span><span> type </span><span>:</span><span> 'note'</span><span>;</span></span>
<span><span>  const</span><span> config</span><span> =</span><span> admonitionConfig[key];</span></span>
<span><span>  let</span><span> titleNode  </span><span>=</span><span> null</span><span>;</span></span>
<span><span>  if</span><span>(</span><span>typeof</span><span> title </span><span>===</span><span> 'string'</span><span>) {</span></span>
<span><span>    titleNode </span><span>=</span><span> {</span></span>
<span><span>      type: </span><span>'text'</span><span>,</span></span>
<span><span>      value: title,</span></span>
<span><span>    } </span><span>as</span><span> Text</span><span>;</span></span>
<span><span>  } </span><span>else</span><span> if</span><span> (</span><span>typeof</span><span> title </span><span>===</span><span> 'undefined'</span><span>) {</span></span>
<span><span>    titleNode </span><span>=</span><span> {</span></span>
<span><span>      type: </span><span>'text'</span><span>,</span></span>
<span><span>      value: config.title,</span></span>
<span><span>    } </span><span>as</span><span> Text</span><span>;</span></span>
<span><span>  } </span><span>else</span><span> {</span></span>
<span><span>    titleNode </span><span>=</span><span> title;</span></span>
<span><span>  }</span></span>
<span><span>  </span></span>
<span><span>  const</span><span> iconNode</span><span> =</span><span> config.icon </span><span>?</span><span> toMdast</span><span>(</span><span>fromHtml</span><span>(config.icon, {</span></span>
<span><span>    space: </span><span>'svg'</span><span>,</span></span>
<span><span>    fragment: </span><span>true</span><span>,</span></span>
<span><span>  }).children[</span><span>0</span><span>]) </span><span>:</span><span> null</span><span>;</span></span>
<span></span>
<span><span>  return</span><span> h</span><span>(</span><span>'div'</span><span>, {</span></span>
<span><span>    class: </span><span>'my-4 rounded-lg px-4 py-2 '</span><span> +</span><span> config.class,</span></span>
<span><span>  }, [</span></span>
<span><span>    h</span><span>(</span><span>'div'</span><span>, {</span></span>
<span><span>      class: </span><span>'flex items-center text-base font-bold'</span><span>,</span></span>
<span><span>    }, [iconNode, titleNode]),</span></span>
<span><span>    children </span><span>&#38&amp</span><span> children.</span><span>length</span><span> ></span><span> 0</span><span> &#38&amp</span><span> h</span><span>(</span><span>'div'</span><span>, {</span></span>
<span><span>      class: </span><span>'mt-2 prose-compact text-sm'</span><span>,</span></span>
<span><span>    }, children)</span></span>
<span><span>  ])</span></span>
<span><span>}</span></span>
<span></span>
<span><span>export</span><span> const</span><span> fromMarkdown</span><span> =</span><span> ()</span><span>:</span><span> FromMarkdownExtension</span><span> =></span><span> {</span></span>
<span><span>  const</span><span> enterAdmonition</span><span>:</span><span> Handle</span><span> =</span><span> function</span><span>(</span><span>token</span><span>) {</span></span>
<span><span>    this</span><span>.</span><span>enter</span><span>&lt;</span><span>Admonition</span><span>>({</span></span>
<span><span>      type: </span><span>'admonition'</span><span>,</span></span>
<span><span>      name: </span><span>''</span><span>,</span></span>
<span><span>      children: [],</span></span>
<span><span>    }, token);</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> enterAdmonitionTitle</span><span>:</span><span> Handle</span><span> =</span><span> function</span><span>(</span><span>token</span><span>) {</span></span>
<span><span>    this</span><span>.</span><span>enter</span><span>&lt;</span><span>Paragraph</span><span>>({</span></span>
<span><span>      type: </span><span>'paragraph'</span><span>,</span></span>
<span><span>      data: {</span></span>
<span><span>        admonitionTitle: </span><span>true</span><span>,</span></span>
<span><span>      },</span></span>
<span><span>      children: [],</span></span>
<span><span>    }, token);</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> exitAdmonition</span><span>:</span><span> Handle</span><span> =</span><span> function</span><span>(</span><span>token</span><span>) {    </span></span>
<span><span>    const</span><span> node</span><span> =</span><span> this</span><span>.</span><span>exit</span><span>(token) </span><span>as</span><span> Admonition</span><span>;</span></span>
<span><span>    const</span><span> type</span><span> =</span><span> node.name;</span></span>
<span><span>    const</span><span> titleNodeIndex</span><span> =</span><span> node.children.</span><span>findIndex</span><span>((</span><span>child</span><span>) </span><span>=></span><span> {</span></span>
<span><span>      return</span><span> child.type </span><span>===</span><span> 'paragraph'</span><span> &#38&amp</span><span> child.data?.admonitionTitle;</span></span>
<span><span>    });</span></span>
<span><span>    const</span><span> titleNode</span><span> =</span><span> titleNodeIndex </span><span>>=</span><span> 0</span></span>
<span><span>      ?</span><span> node.children.</span><span>splice</span><span>(titleNodeIndex, </span><span>1</span><span>)[</span><span>0</span><span>] </span><span>as</span><span> Paragraph</span></span>
<span><span>      :</span><span> undefined</span><span>;</span></span>
<span><span>    if</span><span> (titleNode) {</span></span>
<span><span>      titleNode.data </span><span>=</span><span> {</span></span>
<span><span>        ...</span><span>titleNode.data,</span></span>
<span><span>        hName: </span><span>'span'</span></span>
<span><span>      }</span></span>
<span><span>    }</span></span>
<span><span>    const</span><span> subtree</span><span> =</span><span> htmlTemplate</span><span>(type, titleNode, node.children);</span></span>
<span><span>    if</span><span> (subtree) {</span></span>
<span><span>      Object.</span><span>assign</span><span>(node, subtree);</span></span>
<span><span>    }</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> exitAdmonitionName</span><span>:</span><span> Handle</span><span> =</span><span> function</span><span>(</span><span>token</span><span>) {</span></span>
<span><span>    const</span><span> node</span><span> =</span><span> this</span><span>.stack[</span><span>this</span><span>.stack.</span><span>length</span><span> -</span><span> 1</span><span>] </span><span>as</span><span> Admonition</span><span>;</span></span>
<span><span>    if</span><span> (node.type </span><span>===</span><span> 'admonition'</span><span>) {</span></span>
<span><span>      node.name </span><span>=</span><span> this</span><span>.</span><span>sliceSerialize</span><span>(token);</span></span>
<span><span>    }</span></span>
<span><span>  }</span></span>
<span><span>  const</span><span> exitAdmonitionTitle</span><span>:</span><span> Handle</span><span> =</span><span> function</span><span>(</span><span>token</span><span>) {</span></span>
<span><span>    this</span><span>.</span><span>exit</span><span>(token);</span></span>
<span><span>  }</span></span>
<span><span>  return</span><span> {</span></span>
<span><span>    enter: {</span></span>
<span><span>      admonition: enterAdmonition,</span></span>
<span><span>      admonitionTitle: enterAdmonitionTitle,</span></span>
<span><span>    },</span></span>
<span><span>    exit: {</span></span>
<span><span>      admonition: exitAdmonition,</span></span>
<span><span>      admonitionName: exitAdmonitionName,</span></span>
<span><span>      admonitionTitle: exitAdmonitionTitle,</span></span>
<span><span>    }</span></span>
<span><span>  }</span></span>
<span><span>}</span></span>
<span></span></code></pre><h3>完善插件</h3><pre tabindex=0><code><span><span>import</span><span> type</span><span> { RemarkPlugin } </span><span>from</span><span> '@astrojs/markdown-remark'</span><span>;</span></span>
<span><span>import</span><span> { syntax } </span><span>from</span><span> './syntax'</span><span>;</span></span>
<span><span>import</span><span> { fromMarkdown } </span><span>from</span><span> './fromMarkdown'</span><span>;</span></span>
<span></span>
<span><span>const</span><span> remarkAdmonition</span><span>:</span><span> RemarkPlugin</span><span>&#60[]> </span><span>=</span><span> function</span><span>() {</span></span>
<span><span>  const</span><span> data</span><span> =</span><span> this</span><span>.</span><span>data</span><span>();</span></span>
<span></span>
<span><span>  function</span><span> add</span><span>(</span><span>key</span><span>:</span><span> string</span><span>, </span><span>value</span><span>:</span><span> unknown</span><span>) {</span></span>
<span><span>    if</span><span> (Array.</span><span>isArray</span><span>(data[key])) {</span></span>
<span><span>      (data[key] </span><span>as</span><span> unknown</span><span>[]).</span><span>push</span><span>(value);</span></span>
<span><span>    } </span><span>else</span><span> {</span></span>
<span><span>      data[key] </span><span>=</span><span> [value];</span></span>
<span><span>    }</span></span>
<span><span>  }</span></span>
<span><span>  add</span><span>(</span><span>'micromarkExtensions'</span><span>, </span><span>syntax</span><span>());</span></span>
<span><span>  add</span><span>(</span><span>'fromMarkdownExtensions'</span><span>, </span><span>fromMarkdown</span><span>());</span></span>
<span></span>
<span><span>}</span></span>
<span><span>export</span><span> default</span><span> remarkAdmonition;</span></span>
<span></span></code></pre>]]></content:encoded>
            <enclosure url="https://suborbit.net/_astro/cover.UVWWXqHs.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[给你的Markdown扩展新语法吧：上篇]]></title>
            <link>https://suborbit.net/posts/extend-markdown-syntax-theoretical</link>
            <guid>https://suborbit.net/posts/extend-markdown-syntax-theoretical</guid>
            <pubDate>Sat, 29 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[注意 这篇文章仅适用于基于 remark.js 的 Markdown ，如 Astro.js、Gatsby.js 等等。如果你正在使用 MDX 那么该文章可能也适用于你。 嗨，今天我们来聊一下怎么给 Markdown 扩展新语法吧。这篇文章是本教程的上篇，在这篇里你将会看到如何通过编写扩展的方法，给 remark.js 添加自定义的语法支持。 请注意，该文章中涉及到的内容将不限于JavaScript/TypeScript编程、编译原理、胡…]]></description>
            <content:encoded><![CDATA[<div><div><span>注意</span></div><div><p>这篇文章仅适用于基于 <a href=https://github.com/remarkjs/remark>remark.js</a> 的 Markdown ，如 Astro.js、Gatsby.js 等等。如果你正在使用 MDX 那么该文章可能也适用于你。</div></div><p>嗨，今天我们来聊一下怎么给 Markdown 扩展新语法吧。这篇文章是本教程的上篇，在这篇里你将会看到如何通过编写扩展的方法，给 remark.js 添加自定义的语法支持。<p>请注意，该文章中涉及到的内容将不限于<strong>JavaScript/TypeScript编程</strong>、<strong>编译原理</strong>、<strong>胡言乱语</strong>、<strong>灌水</strong>以及<strong>味同嚼蜡的流水账</strong>，如果您有任何不适，请及时停止阅读。<h2>起因</h2><p>Markdown 是一个非常实用便捷的富文本标记语言，但是 CommonMark 支持的语法毕竟是有限的，为了拓展 Markdown 的功能，不同平台通常会支持创建一些自定义标签，例如 <code>{% note %}...{% endnote %}</code>。这种标签功能极大程度上丰富了 Markdown 的生态，但是遗憾的是，这些拓展功能通常与平台绑定。你可以看到，这个站点目前使用了基于 remark 的 Markdown 解析器，通过编写 remark 插件，我们可以拓展 Markdown 语法，来实现一些自定义的功能。<p>remark 解析器常见于一些站点生成器上，如 Gatsby.js、Astro.js 等等。同时，它也是 MDX 的核心解析器。既然谈到了 MDX，你可能会问”为什么不直接用MDX呢？”。Well，我认为，在非动态内容上使用 MDX 是一种冗余，除非真的有交互需求。还有一点是，Vscode 下 MDX 语法高亮插件好像不太聪明的样子……<h2>remark 技术栈</h2><p>开始之前，我们先来了解一下，为了实现一个新语法，我们应该知道什么。<h3>remark 的执行机制</h3><p>remark 实际上是 unified.js 的一个插件，一串 Markdown 文本需要经过如下流程才能被解析为 HTML：·<p><img alt="remark workflow"src=./remark-workflow.jpg><p>MDAST 是 remark产生的语法树，在得到 MDAST 后需要使用 remark-rehype 插件将 MDAST 转换为 HAST，也就是 rehype 这个插件（用来解析HTML 的 unified 插件）的语法树。这样我们就能接驳 rehype 的生态，并输出 html。当然，文章不涉及 rehype 的生态。<p>我们把重点放在 remark 如何解析输入为语法树。有一些重点需要说明：<ol><li>remark 底层使用了 micromark 解析 markdown 语法为 token；<li>remark 使用了 mdast-util-from-markdown 插件辅助将 token 序列 转换为 MDAST;<li>micromark 和 mdast-util-from-markdown 均支持编写 extension；</ol><p>关于这套工具链的流程，<a href=https://github.com/orgs/remarkjs/discussions/869#discussioncomment-1602674>discussion</a> 中的回答可能会帮助你理解。<pre tabindex=0><code><span><span>1. Input            | .~~~~ ['#','F','o','o','EOL'] &#60~~~ file.md</span></span>
<span><span>                    | |</span></span>
<span><span>2. Tokenize         | `~~~~ micromark(string)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.</span></span>
<span><span>                    |                                                          o)-- micromarkExtension()</span></span>
<span><span>3. Token Stream     | .~~ ['enter:atxHeading','F','o','o','exit:atxHeading'] ~~'</span></span>
<span><span>                    | |</span></span>
<span><span>4. Grow Syntax Tree | `~~~> mdast-util-from-markdown(tokens) ~~~~~~~~~~~~~~~~~~.</span></span>
<span><span>                    |                                                 o        o)-- fromMarkdownExtension()</span></span>
<span><span>5. Transform Tree   | .~~~~~ transform(treeNode) => treeNode ~~~~~~~ / \  ~~~~~'</span></span>
<span><span>                    | |                                             o   o</span></span>
<span><span>6. Serialize        | `~~~~ mdast-util-to-markdown(tree) ~~~~~~~~~~~~~~~~~~~~~~.</span></span>
<span><span>                    |                                                          o)-- toMarkdownExtension()</span></span>
<span><span>7. Output           | &#60~~~~ ['#','F','o','o','EOL'] &#60~~~~~~~~~~~~~~~~~~~~~~~~~~'</span></span>
<span><span></span></span></code></pre><h3>如何编写一个 Rrmark 扩展</h3><p>为了编写一个完整的语法扩展，我们需要做的事情有<ol><li>编写一个 micromark 扩展；<li>编写一个 mdast-util-from-markdown 扩展；<li>编写一个 remark 扩展；</ol><p>嗯，也行你会注意到，micromark 似乎是用来做词法分析的，而 mdast-util-from-markdown 是用来做语法分析的。确实如此，不过之后的内容可能与你在课堂上学的内容有所不同。<h2>micromark 扩展</h2><h3>函数式状态机</h3><p>问：micromark 如何 tokenize markdown 语法的文本呢？<br>答：当然是<strong>状态机</strong>啦，不过这个状态机在我看来，应该是一个 NFA 而不是 DFA，而且写法上是函数式的。函数式状态机写法并不多见，但 <a href=https://go.dev/src/text/template/parse/lex.go#L228>golang 的词法分析器</a> 就用了这种模式。<p>函数式的状态机大概有如下方式编写：<pre tabindex=0><code><span><span>function</span><span> state_1</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (code </span><span>===</span><span> 'a'</span><span>) </span><span>return</span><span> state_1;</span></span>
<span><span>    return</span><span> state_2;</span></span>
<span><span>}</span></span>
<span><span>function</span><span> state_2</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>    if</span><span> (code </span><span>===</span><span> 'b'</span><span>) </span><span>return</span><span> state_2;</span></span>
<span><span>    return</span><span> state_1;</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>表示一个这样的状态机<p><img alt="state machine"src=./state-machine-example.jpg><p>函数表示一个状态，并且返回一个函数，表示下一个状态，状态的转换逻辑写在函数内部，也就是说，状态转换关系是隐式的（implicit）。 状态函数通过一个循环不断调用，直到遇到终止状态。大概长这个样子<pre tabindex=0><code><span><span>state </span><span>=</span><span> initial_state;</span></span>
<span><span>while</span><span> (</span><span>!</span><span>end_state) {</span></span>
<span><span>    state </span><span>=</span><span> state</span><span>(code);</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>在 micromark 中，状态的<a href=https://github.com/micromark/micromark/blob/4346e010d537b05f622e636d001bd25c1140f6a1/packages/micromark-util-types/index.d.ts#L353>typescript 类型定义</a>如下：<pre tabindex=0><code><span><span>/**</span></span>
<span><span> * A character code.</span></span>
<span><span> *</span></span>
<span><span> * This is often the same as what `String#charCodeAt()` yields but micromark</span></span>
<span><span> * adds meaning to certain other values.</span></span>
<span><span> *</span></span>
<span><span> * `null` represents the end of the input stream (called eof).</span></span>
<span><span> * Negative integers are used instead of certain sequences of characters (such</span></span>
<span><span> * as line endings and tabs).</span></span>
<span><span> */</span></span>
<span><span>export</span><span> type</span><span> Code</span><span> =</span><span> number</span><span> |</span><span> null</span></span>
<span><span>/**</span></span>
<span><span> * The main unit in the state machine: a function that gets a character code</span></span>
<span><span> * and has certain effects.</span></span>
<span><span> *</span></span>
<span><span> * A state function should return another function: the next</span></span>
<span><span> * state-as-a-function to go to.</span></span>
<span><span> *</span></span>
<span><span> * But there is one case where they return void: for the eof character code</span></span>
<span><span> * (at the end of a value).</span></span>
<span><span> * The reason being: well, there isn’t any state that makes sense, so void</span></span>
<span><span> * works well.</span></span>
<span><span> * Practically that has also helped: if for some reason it was a mistake, then</span></span>
<span><span> * an exception is throw because there is no next function, meaning it</span></span>
<span><span> * surfaces early.</span></span>
<span><span> */</span></span>
<span><span>export</span><span> type</span><span> State</span><span> =</span><span> (</span><span>code</span><span>:</span><span> Code</span><span>) </span><span>=></span><span> State</span><span> |</span><span> void</span></span>
<span></span></code></pre><h3>编写一个 tokenizer</h3><p>问：micromark 的 tokenizer 怎么编写？<br>答：tokenizer 是一个函数，你需要在 tokenizer 中编写需的状态函数，然后返回初始化状态函数。tokenizer 函数提供了一个 effects 参数，用来表示状态函数的副作用，例如创建 token、读取下一个字符，以及一个 ok 和 nok 参数，用来结束状态机。<p>问：micromark 中如何创建 token？<br>答：首先需要知道的是，micromark 中的 token 支持嵌套，具体通过维护一个 events 序列，包含 token 的 enter 和 exit 事件，来表示嵌套关系。然后通过 <code>effects.enter(type, fields)</code> 创建一个 token enter 事件，通过 <code>effects.exit(type)</code> 创建一个 token exit 事件。这也许就是 micromark 和一般编程语言编译器不一样的地方——它并不直接存储 token。<pre tabindex=0><code><span><span>const</span><span> tokenize</span><span>:</span><span> Tokenizer</span><span> =</span><span> function</span><span>(</span><span>effects</span><span>, </span><span>ok</span><span>, </span><span>nok</span><span>) {</span></span>
<span><span>    const</span><span> start</span><span>:</span><span> State</span><span> =</span><span> function</span><span>(</span><span>code</span><span>) {</span></span>
<span><span>        // ...</span></span>
<span><span>        return</span><span> ok</span><span>(code);</span></span>
<span><span>    };</span></span>
<span><span>    // ...</span></span>
<span><span>    return</span><span> start;</span></span>
<span><span>}</span></span>
<span></span>
<span></span></code></pre><p>effects 提供了诸多便捷的方法，常用的有<pre tabindex=0><code><span><span>// 用来表示打开一个 Token</span></span>
<span><span>effects.</span><span>enter</span><span>(type: string, fields: Record</span><span>&lt;</span><span>string, unknown</span><span>></span><span>): Token</span></span>
<span><span>// 用来表示关闭一个 Token</span></span>
<span><span>effects.</span><span>enter</span><span>(type: string): Token</span></span>
<span><span>// 用来消费一个字符，这会导致当前字符指向下一个字符</span></span>
<span><span>effects.</span><span>consume</span><span>(code: Code) </span><span>=></span><span> void</span></span>
<span><span>// 用来进行一次尝试解析， 若成功则保持解析结果并转到 returnState，若失败则回退解析结果，并跳到 bogusState</span></span>
<span><span>effects.</span><span>attempt</span><span>(</span></span>
<span><span>  construct: Construct </span><span>|</span><span> Construct[] </span><span>|</span><span> ConstructRecord,</span></span>
<span><span>  returnState: State,</span></span>
<span><span>  bogusState</span><span>?:</span><span> State </span><span>|</span><span> undefined</span></span>
<span><span>) </span><span>=></span><span> (</span><span>code</span><span>:</span><span> Code</span><span>) </span><span>=></span><span> void</span></span>
<span><span>// 与 appempt 相似，但是不会保留解析结果，根据成功与否跳到 returnState 或 bogusState</span></span>
<span><span>effects.</span><span>check</span><span>(</span></span>
<span><span>  construct: Construct </span><span>|</span><span> Construct[] </span><span>|</span><span> ConstructRecord,</span></span>
<span><span>  returnState: State,</span></span>
<span><span>  bogusState</span><span>?:</span><span> State </span><span>|</span><span> undefined</span></span>
<span><span>) </span><span>=></span><span> (</span><span>code</span><span>:</span><span> Code</span><span>) </span><span>=></span><span> void</span></span>
<span><span>// 与 check 相似，但是以打断当前块的方式尝试（tokenizer 中 this.interrupt = true）</span></span>
<span><span>effects.</span><span>interrupt</span><span>(</span></span>
<span><span>  construct: Construct </span><span>|</span><span> Construct[] </span><span>|</span><span> ConstructRecord,</span></span>
<span><span>  returnState: State,</span></span>
<span><span>  bogusState</span><span>?:</span><span> State </span><span>|</span><span> undefined</span></span>
<span><span>) </span><span>=></span><span> (</span><span>code</span><span>:</span><span> Code</span><span>) </span><span>=></span><span> void</span></span>
<span></span></code></pre><p>当然，你也可以看看源代码中，这些<a href=https://github.com/micromark/micromark/blob/4346e010d537b05f622e636d001bd25c1140f6a1/packages/micromark-util-types/index.d.ts#L384>类型是如何定义</a>的，以有一个更深的理解。<h3>完善 micromark 扩展</h3><p>我们还需要将 tokenizer 包装在一个 Construct 中，它表示 markdown 文本中一种语法结构。<pre tabindex=0><code><span><span>const</span><span> your_syntax_name</span><span>:</span><span> Construct</span><span> =</span><span> {</span></span>
<span><span>  name: </span><span>'your_syntax_name'</span><span>,</span></span>
<span><span>  tokenize: tokenizeSpoiler</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>然后导出扩展，并定义语法的头字符，micromark 用这个字符优化解析性能<pre tabindex=0><code><span><span>const</span><span> syntax</span><span> =</span><span> ()</span><span>:</span><span> MicromarkExtension</span><span> =></span><span> {</span></span>
<span><span>  return</span><span> {</span></span>
<span><span>    text: {</span></span>
<span><span>      [your_head_code]: your_syntax_name,</span></span>
<span><span>    }</span></span>
<span><span>  }</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>不过需要提醒的是，micromark 有两种 Construct。一种是非容器 Construct，另一种是容器 Construct。上面的例子里举的是非容器 Construct，例如 <code>**bold**</code> 这就是一个非容器 Construct，而 blockquote、list 这种就属于容器 Construct。对于容器 Construct，你还需要定义一个 continuation tokenizer，表示解析其连续块，比如 blockquote 中<pre tabindex=0><code><span><span>> first line will parsed by tokenizer.</span></span>
<span><span>> next line will parsed by continuation tokenizer.</span></span>
<span><span>> the content can include other fragments of markdown, so it's called container construct.</span></span>
<span><span>> > even multiple cascaded layer</span></span>
<span></span></code></pre><p>Container Construct 需要这样定义<pre tabindex=0><code><span><span>const</span><span> your_syntax_name</span><span>:</span><span> Construct</span><span> =</span><span> {</span></span>
<span><span>  name: </span><span>'your_syntax_name'</span><span>,</span></span>
<span><span>  tokenize: tokenize,</span></span>
<span><span>  continuation: {</span></span>
<span><span>    tokenize: tokenizeContinuation,</span></span>
<span><span>  },</span></span>
<span><span>  exit: exit, </span><span>// if your have a final hook</span></span>
<span><span>}</span></span>
<span></span></code></pre><h2>mdast-util-from-markdown 扩展</h2><p>mdast-util-from-markdown 用起来就容易多了，有了前面的 token events 序列，mdast-util-from-markdown 需要提供处理这些事件的函数，具体来说这么用<h3>自定义MDAST节点</h3><p>为了利用 TypeScript 的类型系统，我们先给这个新节点一个定义<pre tabindex=0><code><span><span>// define node type</span></span>
<span><span>export</span><span> interface</span><span> YourNodeType</span><span> extends</span><span> Parent</span><span> {</span></span>
<span><span>  type</span><span>:</span><span> 'your_syntax_name'</span><span>,</span></span>
<span><span>  // you can add other fields here</span></span>
<span><span>}</span></span>
<span><span>// modify mdast module declaration</span></span>
<span><span>declare</span><span> module</span><span> 'mdast'</span><span> {</span></span>
<span><span>  interface</span><span> BlockContentMap</span><span> {</span></span>
<span><span>    your_syntax_name</span><span>:</span><span> YourNodeType</span><span>;</span></span>
<span><span>  }</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>根据具体需求的不同，你可以选择是否要继承 <code>Parent</code> 接口，该接口要求节点存在一个 <code>children[]</code> 字段。<h3>构建 MDAST 语法树</h3><p>然后用如下方法构造语法树上的新节点<pre tabindex=0><code><span><span>const</span><span> enter</span><span>:</span><span> Handle</span><span> =</span><span> function</span><span>(</span><span>token</span><span>) {</span></span>
<span><span>  this</span><span>.</span><span>enter</span><span>&lt;</span><span>YourNodeType</span><span>>({</span></span>
<span><span>    type: </span><span>'your_syntax_name'</span><span>,</span></span>
<span><span>    // ... any other custom fields</span></span>
<span><span>    children: [],</span></span>
<span><span>  }, token);</span></span>
<span><span>  // ...</span></span>
<span><span>}</span></span>
<span><span>const</span><span> exit</span><span>:</span><span> Handle</span><span> =</span><span> function</span><span>(</span><span>token</span><span>) {</span></span>
<span><span>  this</span><span>.</span><span>exit</span><span>(token);</span></span>
<span><span>  // ...</span></span>
<span><span>}</span></span>
<span><span>const</span><span> config</span><span>:</span><span> Config</span><span> =</span><span> {</span></span>
<span><span>  enter: {</span></span>
<span><span>    your_syntax_name: enter,</span></span>
<span><span>    // ... any other token enter event handlers</span></span>
<span><span>  },</span></span>
<span><span>  exit: {</span></span>
<span><span>    your_syntax_name: exit,</span></span>
<span><span>    // ...</span></span>
<span><span>  }</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>enter handler 中的 <code>this.enter</code> 用来创建一个新的MDAST节点，此后所有解析出来的子节点都被添加到该节点的 children 中。直到 <code>this.exit</code>，也就是 exit handler 的执行，才退出这个新节点。<h3>完善 mdast-util-from-markdown 扩展</h3><p>最终，我们需要导出一个函数，该函数返回上文编写的 config 对象<pre tabindex=0><code><span><span>export</span><span> const</span><span> fromMarkdown</span><span> =</span><span> ()</span><span>:</span><span> FromMarkdownExtension</span><span> =></span><span> {</span></span>
<span><span>  return</span><span> config;</span></span>
<span><span>}</span></span>
<span></span></code></pre><h2>remark 扩展</h2><p>上面的流程已经完成了从字符串到 MDAST 的转换，接下来我们要完成从 MDAST 到 HAST 的转换<h3>从 MDAST 到 HAST</h3><p>从 MDAST 到 HAST 的转换规则由 remark-rehype 插件控制，更准确说，由 <a href=https://github.com/syntax-tree/mdast-util-to-hast>mdast-util-to-hast repo</a> 这个工具实现。这个工具预设了 MDAST 中默认节点到到 HAST 的转换规则，对于非默认节点，工具会检查 MDAST 节点中 data 属性中是否包含 <code>hName</code>、<code>hProperties</code>、<code>hChildren</code> 这三个属性，并将它们作为 HAST 节点的 <code>tagName</code>、<code>properties</code>、<code>children</code> 属性。这样，我们只需要在 MDAST 节点中添加这几个属性，就能实现自定义节点到 HAST 的转换规则。<p>我们可以在 MDAST 节点创建的时候，就添加这些属性<pre tabindex=0><code><span><span>const</span><span> enter</span><span>:</span><span> Handle</span><span> =</span><span> function</span><span>(</span><span>token</span><span>) {</span></span>
<span><span>  this</span><span>.</span><span>enter</span><span>&lt;</span><span>YourNodeType</span><span>>({</span></span>
<span><span>    type: </span><span>'your_syntax_name'</span><span>,</span></span>
<span><span>    data: {</span></span>
<span><span>      hName: </span><span>'div'</span><span>,</span></span>
<span><span>      hProperties: {</span></span>
<span><span>        // ... any other custom properties</span></span>
<span><span>      },</span></span>
<span><span>      hChildren: [],</span></span>
<span><span>    }</span></span>
<span><span>    children: [],</span></span>
<span><span>  }, token);</span></span>
<span><span>  // ...</span></span>
<span><span>}</span></span>
<span></span></code></pre><p>或者也可以在 <code>this.enter</code> 时，或构造完 MDAST 后，再遍历修改节点，这取决于你 HAST 节点中的属性是否依赖于后续的解析结果。<h3>完善 remark 扩展</h3><p>如果上述工作你都完成了，那么接下来，我们来完成这个 remark 扩展吧<pre tabindex=0><code><span><span>import</span><span> { syntax } </span><span>from</span><span> './syntax'</span><span>;</span></span>
<span><span>import</span><span> { fromMarkdown } </span><span>from</span><span> './fromMarkdown'</span><span>;</span></span>
<span></span>
<span><span>const</span><span> remarkAdmonition</span><span>:</span><span> Plugin</span><span>&#60[], </span><span>Root</span><span>> </span><span>=</span><span> function</span><span>() {</span></span>
<span><span>  const</span><span> data</span><span> =</span><span> this</span><span>.</span><span>data</span><span>();</span></span>
<span><span>  function</span><span> add</span><span>(</span><span>key</span><span>:</span><span> string</span><span>, </span><span>value</span><span>:</span><span> unknown</span><span>) {</span></span>
<span><span>    if</span><span> (Array.</span><span>isArray</span><span>(data[key])) {</span></span>
<span><span>      (data[key] </span><span>as</span><span> unknown</span><span>[]).</span><span>push</span><span>(value);</span></span>
<span><span>    } </span><span>else</span><span> {</span></span>
<span><span>      data[key] </span><span>=</span><span> [value];</span></span>
<span><span>    }</span></span>
<span><span>  }</span></span>
<span><span>  add</span><span>(</span><span>'micromarkExtensions'</span><span>, </span><span>syntax</span><span>());</span></span>
<span><span>  add</span><span>(</span><span>'fromMarkdownExtensions'</span><span>, </span><span>fromMarkdown</span><span>());</span></span>
<span></span>
<span><span>}</span></span>
<span></span></code></pre><p>上述代码中，<code>add</code> 函数用来向 processor 的 data 的某个属性中添加新的值，我们向 <code>micromarkExtensions</code>、<code>fromMarkdownExtensions</code> 添加了之前编写的扩展，这两个属性由 remark 定义，分别用来存储 micromark 和 mdast-util-from-markdown 的扩展。<p>好了，到此为止，我们的 remark 扩展就完成了，你可以在按照你所使用的平台提供的方式，为 remark 添加新的扩展了。<h2>结语</h2><p>呜呼，水完后才觉得，MDX 是真好用XD<p>实际上，remark 官方并不推荐编造自定义的语法，这样会导致迁移困难。他们更推荐使用<a href=https://github.com/remarkjs/remark-directive>remark-directive</a>，这玩意写起来就像是在写HTML的标签一样<pre tabindex=0><code><span><span>:::note{.warning title="note this"}</span></span>
<span><span>if you chose xxx, you should also use yyy somewhere…</span></span>
<span><span>:::</span></span>
<span></span></code></pre><p>emmm，这么看确实不错。<p>ok，本篇文章仅涉及理论知识，下一篇文章里，我们将实战两个有趣的语法扩展，今天就先聊到这里吧~<h2>Reference</h2><ol><li><a href=https://github.com/unifiedjs/unified>unified repo</a><li><a href=https://github.com/remarkjs/remark>remark repo</a><li><a href=https://github.com/rehypejs/rehype>rehype repo</a><li><a href=https://github.com/remarkjs/remark-rehype>remark-rehype repo</a><li><a href=https://github.com/micromark/micromark>micromark repo</a><li><a href=https://github.com/syntax-tree/mdast-util-from-markdown>mdast-util-from-markdown repo</a><li><a href=https://github.com/syntax-tree/mdast-util-to-hast>mdast-util-to-hast repo</a><li><a href=https://github.com/orgs/remarkjs/discussions/869>github discussion: Why do we both need fromMarkdownExtensions and micromarkExtensions?</a><li><a href=https://github.com/orgs/micromark/discussions/62>github discussion: How to tokenize multiline constructs? How to subtokenize their nested content?</a><li><a href=https://spec.commonmark.org/>CommonMark Spec</a></ol>]]></content:encoded>
            <enclosure url="https://suborbit.net/_astro/cover.Cw8mx_i5.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[TensorRT踩坑教程]]></title>
            <link>https://suborbit.net/posts/tensorrt-tutorial</link>
            <guid>https://suborbit.net/posts/tensorrt-tutorial</guid>
            <pubDate>Mon, 10 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[0. 前言 在部署深度学习模型时需要用到TensorRT，看了一遍文档后，我发现对于初学者来说，官方文档实在是太晦涩。文档里既没有接口的应用示例，也没有及时更新新接口的用法。在翻阅了部分博客和TensorRT仓库源码后，才有了本文。 TensorRT是NVIDIA推出的用于深度学习推理的高性能引擎，旨在优化深度神经网络模型的推理速度和性能。TensorRT可以通过层融合、混合精度、量化等技术显著提高深度神经网络的推理性能。 NVIDI…]]></description>
            <content:encoded><![CDATA[<h2>0. 前言</h2><p>在部署深度学习模型时需要用到TensorRT，看了一遍文档后，我发现对于初学者来说，官方文档实在是太晦涩。文档里既没有接口的应用示例，也没有及时更新新接口的用法。在翻阅了部分博客和TensorRT仓库源码后，才有了本文。<p>TensorRT是NVIDIA推出的用于深度学习推理的高性能引擎，旨在优化深度神经网络模型的推理速度和性能。TensorRT可以通过层融合、混合精度、量化等技术显著提高深度神经网络的推理性能。<p><img alt="TensorRT Logo"src=./tensorrt-logo.png><p>NVIDIA提供了TensorRT的C++和Python API，这里主要介绍Python API的一些用法。<h2>1. TensorRT的安装</h2><p>在安装TensorRT前，首先确保已经安装了CUDA和cuDNN。然后根据CUDA的版本在 <a href=https://developer.nvidia.com/nvidia-tensorrt-8x-download>下载页面</a> 下载对应的TensorRT安装包。<p>不过貌似最新版本的TensorRT（8.6.1）支持从PyPI直接安装<pre tabindex=0><code><span><span>pip</span><span> install</span><span> tensorrt</span></span>
<span></span></code></pre><p>但是我只在Linux系统中安装成功了，Windows系统中安装失败，原因暂时不明。<h3>1.1 Windows下的安装</h3><p>Windows下需要使用Zip文件安装，根据 <a href=https://docs.nvidia.com/deeplearning/tensorrt/install-guide/index.html#installing-zip>文档中 Zip File Installation 章节</a>，下载zip包后：<ol><li><p>将压缩包内文件解压后，复制到 <code>C:\Program Files\NVIDIA GPU Computing Toolkit\TensorRT\v8.x.x.x</code> 下。安装路径随意选择。<li><p>配置环境变量，将 <code>&#60安装路径>/lib</code> 和 <code>&#60安装路径>/bin</code> 添加到环境变量中。<li><p>安装python包，进入 <code>&#60安装路径>/python</code> 目录，执行如下命令：<pre tabindex=0><code><span><span>pip</span><span> install</span><span> tensorrt-8.x.x-cp3x-none-win_amd64.whl</span></span>
<span></span></code></pre></ol><h3>1.2 Linux下的安装</h3><p>Linux下的安装比较简单，根据 <a href=https://docs.nvidia.com/deeplearning/tensorrt/install-guide/index.html#installing-tar>文档中 Tar File Installation 章节</a>，下载tar包后：<ol><li><p>解压tar包，进入解压后的目录。<pre tabindex=0><code><span><span>tar</span><span> -xzvf</span><span> TensorRT-8.x.x.x.Linux.x86_64-gnu.cuda-1x.x.tar.gz</span></span>
<span><span>cd</span><span> TensorRT-8.x.x.x</span></span>
<span></span></code></pre><li><p>添加环境变量<pre tabindex=0><code><span><span>vim</span><span> ~/.bashrc</span></span>
<span><span>export</span><span> LD_LIBRARY_PATH</span><span>=</span><span>$LD_LIBRARY_PATH:</span><span>&lt;</span><span>安装路径</span><span>></span><span>/lib</span></span>
<span><span>export</span><span> PATH</span><span>=</span><span>$PATH:</span><span>&lt;</span><span>安装路径</span><span>></span><span>/bin</span></span>
<span><span>source</span><span> ~/.bashrc</span></span>
<span></span></code></pre><li><p>安装python包<pre tabindex=0><code><span><span>cd</span><span> TensorRT-8.x.x.x/python</span></span>
<span><span>pip</span><span> install</span><span> tensorrt-8.x.x.x-cp3x-none-linux_x86_64.whl</span></span>
<span></span></code></pre></ol><h2>2. 推理前的准备</h2><p>TensorRT有自己的一套推理流程，我们在使用PyTorch或TensorFlow导出模型权重后，需要进一步转换。<p>TensorRT最终需要的是一个TensorRT Engine，这个Engine是由TensorRT的Builder构建，而Builder需要一个TensorRT Network。TensorRT Network是由TensorRT Parser解析的ONNX模型构建的。<p><img alt="TensorRT 工作流"src=./tensorrt_workflow.jpg><p>当然，也有一些针对特定框架直接导出TensorRT Engine的工具，例如 <a href=https://github.com/NVIDIA-AI-IOT/torch2trt>torch2trt</a>、 <a href=https://github.com/tensorflow/tensorrt>TF-TRT</a> 等等，这里我用了更通用的方法，即先导出ONNX模型，再转换为TensorRT Engine。<p>我们还需要安装如下依赖，以备后续使用：<pre tabindex=0><code><span><span>pip</span><span> install</span><span> onnx</span><span> onnxruntime</span><span> pycuda</span></span>
<span></span></code></pre><h3>2.1 导出ONNX模型</h3><p>对于PyTorch模型，可以使用 <a href=https://pytorch.org/docs/stable/generated/torch.onnx.export.html>torch.onnx.export</a> 导出ONNX模型。<pre tabindex=0><code><span><span>import</span><span> torch</span></span>
<span><span>from</span><span> model </span><span>import</span><span> Model</span></span>
<span></span>
<span><span>model </span><span>=</span><span> Model()</span></span>
<span><span>model.load_state_dict(torch.load(</span><span>'model.pth'</span><span>))</span></span>
<span><span>model.eval()</span></span>
<span></span>
<span><span>dummy_input </span><span>=</span><span> torch.randn(</span><span>1</span><span>, </span><span>3</span><span>, </span><span>224</span><span>, </span><span>224</span><span>)</span></span>
<span><span>torch.onnx.export(model, dummy_input, </span><span>'model.onnx'</span><span>, </span><span>verbose</span><span>=</span><span>True</span><span>)</span></span>
<span></span></code></pre><p>对于TensorFlow模型，可以使用 <a href=https://www.tensorflow.org/api_docs/python/tf/saved_model/save>tf.saved_model.save</a> 导出SavedModel，再使用 <a href=https://github.com/onnx/tensorflow-onnx>tf2onnx</a> 将SavedModel转换为ONNX模型。<pre tabindex=0><code><span><span>import</span><span> tensorflow </span><span>as</span><span> tf</span></span>
<span><span>import</span><span> tf2onnx</span></span>
<span><span>import</span><span> onnx</span></span>
<span><span>from</span><span> model </span><span>import</span><span> Model</span></span>
<span></span>
<span><span>model </span><span>=</span><span> Model()</span></span>
<span><span>model.load_weights(</span><span>'model.h5'</span><span>)</span></span>
<span><span>model.compile()</span></span>
<span></span>
<span><span>input_signature </span><span>=</span><span> [tf.TensorSpec([</span><span>3</span><span>, </span><span>3</span><span>], tf.float32, </span><span>name</span><span>=</span><span>'x'</span><span>)]</span></span>
<span><span># Use from_function for tf functions</span></span>
<span><span>onnx_model, _ </span><span>=</span><span> tf2onnx.convert.from_keras(model, input_signature)</span></span>
<span><span>onnx.save(onnx_model, </span><span>"dst/path/model.onnx"</span><span>)</span></span>
<span></span></code></pre><h3>2.2 转换为TensorRT Engine</h3><p>为完成Engine的构建，我们需要完成的步骤有：<ol><li>转换并填充Network对象；<li>编写构建配置对象（Config）；<li>编写优化配置对象（Optimization Profile）；</ol><p>TensorRT 默认为我们提供了三种主流格式的转换器：<code>UffParser</code>、<code>CaffeParser</code>、<code>OnnxParser</code>，在本文中我们需要使用<code>OnnxParser</code>。<p>TensorRT 支持两种批处理模式：显式批处理（explicit batch）和隐式批处理（implicit batch）。假设网络的输入张量形状为<code>(n, c, h, w)</code>，在隐式批处理模式下，网络只需要指定输入形状为<code>(c, h, w)</code>，批次维度是隐式的，并且在运行时可动态指定；在显式批处理模式下，批量维度需要网络显示定义，甚至可以不用批量维度，对于动态批量大小的需求，可以使用Optimization Profile配置动态形状。隐式批处理由早期TensorRT使用，现在已经被弃用了，许多教程中你会发现代码中出现了<code>builder.max_batch_size = 32</code>这样的调用，这就隐式批处理的相关配置，现在已经不赞成使用。<p>为开启显示批处理，需要：<pre tabindex=0><code><span><span>builder </span><span>=</span><span> trt.Builder(</span><span>...</span><span>)</span></span>
<span><span>builder.create_network(</span><span>1</span><span> &#60&lt;</span><span> int</span><span>(trt.NetworkDefinitionCreationFlag.</span><span>EXPLICIT_BATCH</span><span>))</span></span>
<span></span></code></pre><p>Optimization Profile支持配置网络输入的动态形状，比如动态批次大小、动态图像输入大小。为实现动态形状输入，我们需要指定输入张量的三个形状配置：最小尺寸、最优尺寸、最大尺寸。<pre tabindex=0><code><span><span>min_shape </span><span>=</span><span> (</span><span>3</span><span>, </span><span>100</span><span>, </span><span>200</span><span>)</span></span>
<span><span>opt_shape </span><span>=</span><span> (</span><span>3</span><span>, </span><span>150</span><span>, </span><span>250</span><span>)</span></span>
<span><span>max_shape </span><span>=</span><span> (</span><span>3</span><span>, </span><span>200</span><span>, </span><span>300</span><span>)</span></span>
<span><span>profile </span><span>=</span><span> builder.create_optimization_profile()</span><span>;</span></span>
<span><span>profile.set_shape(</span><span>"input"</span><span>, min_shape, opt_shape, max_shape) </span></span>
<span><span>config.add_optimization_profile(profile)</span></span>
<span></span></code></pre><p>TensorRT支持设置多个优化配置文件，在运行时，需要设置优化配置文件，默认隐式选择配置文件0。你可以通过以下接口手动更改当前优化配置，需要注意的是，一个Engine可以有多个Context，但一个优化配置文件只能有一个Context。 优化配置文件的使用还有许多细节需要留意，更多请参考<a href=https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/index.html#work_dynamic_shapes>TensorRT文档 Working with Dynamic Shapes 章节</a><pre tabindex=0><code><span><span>context.set_optimization_profile_async(</span><span>0</span><span>, stream)</span></span>
<span></span></code></pre><p>有了以上基础，我们可以编写一个函数，将ONNX模型转换为TensorRT Engine。<pre tabindex=0><code><span><span>import</span><span> tensorrt </span><span>as</span><span> trt</span></span>
<span></span>
<span><span>TRT_LOGGER</span><span> =</span><span> trt.Logger(trt.Logger.</span><span>INFO</span><span>)</span></span>
<span></span>
<span><span>def</span><span> GiB</span><span>(val):</span></span>
<span><span>    return</span><span> val </span><span>*</span><span> 1</span><span> &#60&lt;</span><span> 30</span></span>
<span></span>
<span><span># This function builds an engine from a onnx model.</span></span>
<span><span>def</span><span> build_engine</span><span>(onnx_file_path, precision </span><span>=</span><span> 'fp32'</span><span>, dynamic_shapes </span><span>=</span><span> None</span><span>):</span></span>
<span><span>    """Takes an ONNX file and creates a TensorRT engine to run inference with"""</span></span>
<span></span>
<span><span>    EXPLICIT_BATCH_FLAG</span><span> =</span><span> 1</span><span> &#60&lt;</span><span> int</span><span>(trt.NetworkDefinitionCreationFlag.</span><span>EXPLICIT_BATCH</span><span>)</span></span>
<span></span>
<span><span>    builder </span><span>=</span><span> trt.Builder(</span><span>TRT_LOGGER</span><span>)</span></span>
<span><span>    network </span><span>=</span><span> builder.create_network(</span><span>EXPLICIT_BATCH_FLAG</span><span>)</span></span>
<span><span>    config </span><span>=</span><span> builder.create_builder_config()</span></span>
<span><span>    parser </span><span>=</span><span> trt.OnnxParser(network, </span><span>TRT_LOGGER</span><span>)</span></span>
<span></span>
<span><span>    # Parse model file</span></span>
<span><span>    TRT_LOGGER</span><span>.log(</span><span>TRT_LOGGER</span><span>.</span><span>INFO</span><span>, </span><span>f</span><span>'Loading ONNX file from path </span><span>{</span><span>onnx_file_path</span><span>}</span><span>...'</span><span>)</span></span>
<span><span>    with</span><span> open</span><span>(onnx_file_path, </span><span>'rb'</span><span>) </span><span>as</span><span> model:</span></span>
<span><span>        TRT_LOGGER</span><span>.log(</span><span>TRT_LOGGER</span><span>.</span><span>INFO</span><span>, </span><span>'Beginning ONNX file parsing'</span><span>)</span></span>
<span><span>        if</span><span> not</span><span> parser.parse(model.read()):</span></span>
<span><span>            for</span><span> error </span><span>in</span><span> range</span><span>(parser.num_errors):</span></span>
<span><span>                TRT_LOGGER</span><span>.log(</span><span>TRT_LOGGER</span><span>.</span><span>ERROR</span><span>, parser.get_error(error))</span></span>
<span><span>            raise</span><span> ValueError</span><span>(</span><span>'Failed to parse the ONNX file.'</span><span>)</span></span>
<span><span>    TRT_LOGGER</span><span>.log(</span><span>TRT_LOGGER</span><span>.</span><span>INFO</span><span>, </span><span>'Completed parsing of ONNX file'</span><span>)</span></span>
<span><span>    TRT_LOGGER</span><span>.log(</span><span>TRT_LOGGER</span><span>.</span><span>INFO</span><span>, </span><span>f</span><span>'Input number: </span><span>{</span><span>network.num_inputs</span><span>}</span><span>'</span><span>)</span></span>
<span><span>    TRT_LOGGER</span><span>.log(</span><span>TRT_LOGGER</span><span>.</span><span>INFO</span><span>, </span><span>f</span><span>'Output number: </span><span>{</span><span>network.num_outputs</span><span>}</span><span>'</span><span>)</span></span>
<span><span>    </span></span>
<span><span>    </span></span>
<span><span>    if</span><span> dynamic_shapes </span><span>is</span><span> not</span><span> None</span><span>:</span></span>
<span><span>        # set optimization profile for dynamic shape</span></span>
<span><span>        profile </span><span>=</span><span> builder.create_optimization_profile()</span></span>
<span><span>        for</span><span> i </span><span>in</span><span> range</span><span>(network.num_inputs):</span></span>
<span><span>            input</span><span> =</span><span> network.get_input(i)</span></span>
<span><span>            min_shape </span><span>=</span><span> dynamic_shapes[</span><span>'min_shape'</span><span>]</span></span>
<span><span>            opt_shape </span><span>=</span><span> dynamic_shapes[</span><span>'opt_shape'</span><span>]</span></span>
<span><span>            max_shape </span><span>=</span><span> dynamic_shapes[</span><span>'max_shape'</span><span>]</span></span>
<span><span>            profile.set_shape(</span><span>input</span><span>.name, min_shape, opt_shape, max_shape)</span></span>
<span><span>        config.add_optimization_profile(profile)</span></span>
<span></span>
<span><span>    # We set the builder batch size to be the same as the calibrator's, as we use the same batches</span></span>
<span><span>    # during inference. Note that this is not required in general, and inference batch size is</span></span>
<span><span>    # independent of calibration batch size.</span></span>
<span><span>    config.set_memory_pool_limit(trt.MemoryPoolType.</span><span>WORKSPACE</span><span>, GiB(</span><span>1</span><span>)) </span><span># 1G</span></span>
<span><span>    </span></span>
<span><span>    if</span><span> precision </span><span>==</span><span> 'fp32'</span><span>:</span></span>
<span><span>        pass</span></span>
<span><span>    elif</span><span> precision </span><span>==</span><span> 'fp16'</span><span>:</span></span>
<span><span>        config.set_flag(trt.BuilderFlag.</span><span>FP16</span><span>)</span></span>
<span><span>    elif</span><span> precision </span><span>==</span><span> 'int8'</span><span>:</span></span>
<span><span>        config.set_flag(trt.BuilderFlag.</span><span>INT8</span><span>)</span></span>
<span><span>    else</span><span>:</span></span>
<span><span>        raise</span><span> ValueError</span><span>(</span><span>'precision must be one of fp32, fp16, or int8'</span><span>)</span></span>
<span></span>
<span><span>    # Build engine.</span></span>
<span><span>    TRT_LOGGER</span><span>.log(</span><span>TRT_LOGGER</span><span>.</span><span>INFO</span><span>, </span><span>f</span><span>'Building an engine from file </span><span>{</span><span>onnx_file_path</span><span>}</span><span>; this may take a while...'</span><span>)</span></span>
<span><span>    serialized_engine </span><span>=</span><span> builder.build_serialized_network(network, config)</span></span>
<span><span>    TRT_LOGGER</span><span>.log(</span><span>TRT_LOGGER</span><span>.</span><span>INFO</span><span>, </span><span>'Completed creating Engine'</span><span>)</span></span>
<span><span>    return</span><span> serialized_engine</span></span>
<span></span></code></pre><h3>3.3 保存TensorRT Engine</h3><p>序列化和反序列化TensorRT Engine的代码如下：<pre tabindex=0><code><span><span>def</span><span> save_engine</span><span>(engine, path):</span></span>
<span><span>    TRT_LOGGER</span><span>.log(</span><span>TRT_LOGGER</span><span>.</span><span>INFO</span><span>, </span><span>f</span><span>'Saving engine to file </span><span>{</span><span>path</span><span>}</span><span>'</span><span>)</span></span>
<span><span>    path </span><span>=</span><span> Path(path)</span></span>
<span><span>    path.parent.mkdir(</span><span>parents</span><span>=</span><span>True</span><span>, </span><span>exist_ok</span><span>=</span><span>True</span><span>)</span></span>
<span><span>    with</span><span> open</span><span>(path, </span><span>'wb'</span><span>) </span><span>as</span><span> f:</span></span>
<span><span>        f.write(engine)</span></span>
<span><span>    TRT_LOGGER</span><span>.log(</span><span>TRT_LOGGER</span><span>.</span><span>INFO</span><span>, </span><span>'Completed saving engine'</span><span>)</span></span>
<span></span>
<span><span>def</span><span> load_engine</span><span>(path):</span></span>
<span><span>    logger.info(</span><span>f</span><span>'Loading engine from file </span><span>{</span><span>path</span><span>}</span><span>'</span><span>)</span></span>
<span><span>    runtime </span><span>=</span><span> trt.Runtime(logger.</span><span>TRT_LOGGER</span><span>)</span></span>
<span><span>    with</span><span> open</span><span>(path, </span><span>'rb'</span><span>) </span><span>as</span><span> f:</span></span>
<span><span>        engine </span><span>=</span><span> runtime.deserialize_cuda_engine(f.read())</span></span>
<span><span>    logger.info(</span><span>'Completed loading engine'</span><span>)</span></span>
<span><span>    return</span><span> engine</span></span>
<span></span></code></pre><h2>3. 使用TensorRT进行推理</h2><p>上文中提到了网络输入张量支持动态形状，同样的，输出张量也支持动态形状，比如网络输出形状由网络输入形状决定，为此，我们需要编写一些分配内存的策略。 在内存分配上，我们需要一点CUDA编程相关的知识，具体来说，使用pycuda分配内存（实际上更建议使用cudart）。在CUDA内存模型中，Host指电脑主机，Device指NVIDIA显卡。<p>我们需要派生<code>IOutputAllocator</code>类，然后实现自己的<code>reallocate_output</code>和<code>notify_shape</code>方法。<code>reallocate_output</code>中可以实现的内存分配方案很灵活，这里使用的是每次都重新申请内存，显然这样是不优的，这样做的好处是代码量少，另一些可选的方案有：<ol><li>预分配足够大的内存，可以使用<code>context.get_max_output_size(output_tensor_name)</code>来获取优化配置文件中的最大<strong>字节数</strong>；<li>重新利用上一次分配的内存空间，仅当空间不足时才重新分配；<li>…</ol><pre tabindex=0><code><span><span>from</span><span> collections </span><span>import</span><span> OrderedDict</span></span>
<span><span>import</span><span> tensorrt </span><span>as</span><span> trt</span></span>
<span><span>import</span><span> pycuda.driver </span><span>as</span><span> cuda</span></span>
<span><span>import</span><span> numpy </span><span>as</span><span> np</span></span>
<span></span>
<span><span>class</span><span> OutputAllocator</span><span>(</span><span>trt</span><span>.</span><span>IOutputAllocator</span><span>):</span></span>
<span><span>    def</span><span> __init__</span><span>(self):</span></span>
<span><span>        # print("[MyOutputAllocator::__init__]")</span></span>
<span><span>        super</span><span>().</span><span>__init__</span><span>()</span></span>
<span><span>        self</span><span>.buffers </span><span>=</span><span> {}</span></span>
<span><span>        self</span><span>.shapes </span><span>=</span><span> {}</span></span>
<span></span>
<span><span>    def</span><span> reallocate_output</span><span>(self, tensor_name: </span><span>str</span><span>, memory: </span><span>int</span><span>, size: </span><span>int</span><span>, alignment: </span><span>int</span><span>) -> </span><span>int</span><span>:</span></span>
<span><span>        # print("[MyOutputAllocator::reallocate_output] TensorName=%s, Memory=%s, Size=%d, Alignment=%d" % (tensor_name, memory, size, alignment))</span></span>
<span><span>        if</span><span> tensor_name </span><span>in</span><span> self</span><span>.buffers:</span></span>
<span><span>            del</span><span> self</span><span>.buffers[tensor_name]</span></span>
<span><span>        </span></span>
<span><span>        address </span><span>=</span><span> cuda.mem_alloc(size)</span></span>
<span><span>        self</span><span>.buffers[tensor_name] </span><span>=</span><span> address</span></span>
<span><span>        return</span><span> int</span><span>(address)</span></span>
<span><span>        </span></span>
<span><span>    def</span><span> notify_shape</span><span>(self, tensor_name: </span><span>str</span><span>, shape: trt.Dims):</span></span>
<span><span>        # print("[MyOutputAllocator::notify_shape] TensorName=%s, Shape=%s" % (tensor_name, shape))</span></span>
<span><span>        self</span><span>.shapes[tensor_name] </span><span>=</span><span> tuple</span><span>(shape)</span></span>
<span></span></code></pre><p>然后封装一个用于推理的类，该类提供一个 <code>infer</code> 接口，接收一个输入列表或字典，返回一个输出字典。列表和字典的值是numpy数组。当输入为列表时，列表中的元素将按顺序传入模型。为了顺利执行推理，我们需要在推理前将输入张量从host转移到device上，在推理后将输出张量从device转移到host上。基于index的张量方法已经被弃用，在新版本TensorRT中建议使用基于name的张量方法。<p>以下代码使用了异步方法，需要用到cuda stream，关于cuda stream的概念和用法，可以参考<a href=https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#streams>CUDA文档 Streams 章节</a><pre tabindex=0><code><span><span>from</span><span> collections </span><span>import</span><span> OrderedDict</span></span>
<span><span>from</span><span> typing </span><span>import</span><span> Dict, OrderedDict, List, Union</span></span>
<span><span>import</span><span> tensorrt </span><span>as</span><span> trt</span></span>
<span><span>import</span><span> numpy </span><span>as</span><span> np</span></span>
<span><span>import</span><span> pycuda.driver </span><span>as</span><span> cuda</span></span>
<span><span>import</span><span> pycuda.autoinit</span></span>
<span></span>
<span><span>def</span><span> get_input_tensor_names</span><span>(engine: trt.ICudaEngine) -> list[</span><span>str</span><span>]:</span></span>
<span><span>    input_tensor_names </span><span>=</span><span> []</span></span>
<span><span>    for</span><span> binding </span><span>in</span><span> engine:</span></span>
<span><span>        if</span><span> engine.get_tensor_mode(binding) </span><span>==</span><span> trt.TensorIOMode.</span><span>INPUT</span><span>:</span></span>
<span><span>            input_tensor_names.append(binding)</span></span>
<span><span>    return</span><span> input_tensor_names</span></span>
<span></span>
<span><span>def</span><span> get_output_tensor_names</span><span>(engine: trt.ICudaEngine) -> list[</span><span>str</span><span>]:</span></span>
<span><span>    output_tensor_names </span><span>=</span><span> []</span></span>
<span><span>    for</span><span> binding </span><span>in</span><span> engine:</span></span>
<span><span>        if</span><span> engine.get_tensor_mode(binding) </span><span>==</span><span> trt.TensorIOMode.</span><span>OUTPUT</span><span>:</span></span>
<span><span>            output_tensor_names.append(binding)</span></span>
<span><span>    return</span><span> output_tensor_names</span></span>
<span></span>
<span><span>class</span><span> ProcessorV3</span><span>:</span></span>
<span><span>    def</span><span> __init__</span><span>(self, engine: trt.ICudaEngine):</span></span>
<span><span>        # # 选择第一个可用的 GPU 设备</span></span>
<span><span>        # device = cuda.Device(0)</span></span>
<span><span>        # # 将所选设备设置为当前活动的上下文</span></span>
<span><span>        # self.cuda_context = device.make_context()</span></span>
<span><span>        # self.cuda_context.push()</span></span>
<span><span>        </span></span>
<span><span>        self</span><span>.engine </span><span>=</span><span> engine</span></span>
<span><span>        self</span><span>.output_allocator </span><span>=</span><span> OutputAllocator()</span></span>
<span><span>        # create execution context</span></span>
<span><span>        self</span><span>.context </span><span>=</span><span> engine.create_execution_context()</span></span>
<span><span>        # get input and output tensor names</span></span>
<span><span>        self</span><span>.input_tensor_names </span><span>=</span><span> get_input_tensor_names(engine)</span></span>
<span><span>        self</span><span>.output_tensor_names </span><span>=</span><span> get_output_tensor_names(engine)</span></span>
<span><span>        # create stream</span></span>
<span><span>        self</span><span>.stream </span><span>=</span><span> cuda.Stream()</span></span>
<span><span>        # Create a CUDA events</span></span>
<span><span>        self</span><span>.start_event </span><span>=</span><span> cuda.Event()</span></span>
<span><span>        self</span><span>.end_event </span><span>=</span><span> cuda.Event()</span></span>
<span><span>        </span></span>
<span><span>    # def __del__(self):</span></span>
<span><span>    #     self.cuda_context.pop()</span></span>
<span><span>        </span></span>
<span><span>    def</span><span> get_last_inference_time</span><span>(self):</span></span>
<span><span>        return</span><span> self</span><span>.start_event.time_till(</span><span>self</span><span>.end_event)</span></span>
<span><span>        </span></span>
<span><span>    def</span><span> infer</span><span>(self, inputs: Union[Dict[</span><span>str</span><span>, np.ndarray], List[np.ndarray], np.ndarray]) -> OrderedDict[</span><span>str</span><span>, np.ndarray]:</span></span>
<span><span>        """</span></span>
<span><span>        inference process:</span></span>
<span><span>        1. create execution context</span></span>
<span><span>        2. set input shapes</span></span>
<span><span>        3. allocate memory</span></span>
<span><span>        4. copy input data to device</span></span>
<span><span>        5. run inference on device</span></span>
<span><span>        6. copy output data to host and reshape</span></span>
<span><span>        """</span></span>
<span><span>        # set input shapes, the output shapes are inferred automatically</span></span>
<span></span>
<span><span>        if</span><span> isinstance</span><span>(inputs, np.ndarray):</span></span>
<span><span>            inputs </span><span>=</span><span> [inputs]</span></span>
<span><span>        if</span><span> isinstance</span><span>(inputs, </span><span>dict</span><span>):</span></span>
<span><span>            inputs </span><span>=</span><span> [inp </span><span>if</span><span> name </span><span>in</span><span> self</span><span>.input_tensor_names </span><span>else</span><span> None</span><span> for</span><span> (name, inp) </span><span>in</span><span> inputs.items()]</span></span>
<span><span>        if</span><span> isinstance</span><span>(inputs, </span><span>list</span><span>):</span></span>
<span><span>            for</span><span> name, arr </span><span>in</span><span> zip</span><span>(</span><span>self</span><span>.input_tensor_names, inputs):</span></span>
<span><span>                self</span><span>.context.set_input_shape(name, arr.shape)</span></span>
<span></span>
<span><span>        buffers_host </span><span>=</span><span> []</span></span>
<span><span>        buffers_device </span><span>=</span><span> []</span></span>
<span><span>        # copy input data to device</span></span>
<span><span>        for</span><span> name, arr </span><span>in</span><span> zip</span><span>(</span><span>self</span><span>.input_tensor_names, inputs):</span></span>
<span><span>            host </span><span>=</span><span> cuda.pagelocked_empty(arr.shape, </span><span>dtype</span><span>=</span><span>trt.nptype(</span><span>self</span><span>.engine.get_tensor_dtype(name)))</span></span>
<span><span>            device </span><span>=</span><span> cuda.mem_alloc(arr.nbytes)</span></span>
<span><span>            </span></span>
<span><span>            host[:] </span><span>=</span><span> arr</span></span>
<span><span>            cuda.memcpy_htod_async(device, host, </span><span>self</span><span>.stream)</span></span>
<span><span>            buffers_host.append(host)</span></span>
<span><span>            buffers_device.append(device)</span></span>
<span><span>        # set input tensor address</span></span>
<span><span>        for</span><span> name, buffer </span><span>in</span><span> zip</span><span>(</span><span>self</span><span>.input_tensor_names, buffers_device):</span></span>
<span><span>            self</span><span>.context.set_tensor_address(name, </span><span>int</span><span>(buffer))</span></span>
<span><span>        # set output tensor allocator</span></span>
<span><span>        for</span><span> name </span><span>in</span><span> self</span><span>.output_tensor_names:</span></span>
<span><span>            self</span><span>.context.set_tensor_address(name, </span><span>0</span><span>) </span><span># set nullptr</span></span>
<span><span>            self</span><span>.context.set_output_allocator(name, </span><span>self</span><span>.output_allocator)</span></span>
<span><span>        # The do_inference function will return a list of outputs</span></span>
<span><span>        </span></span>
<span><span>        # Record the start event</span></span>
<span><span>        self</span><span>.start_event.record(</span><span>self</span><span>.stream)</span></span>
<span><span>        # Run inference.</span></span>
<span><span>        self</span><span>.context.execute_async_v3(</span><span>stream_handle</span><span>=</span><span>self</span><span>.stream.handle)</span></span>
<span><span>        # Record the end event</span></span>
<span><span>        self</span><span>.end_event.record(</span><span>self</span><span>.stream)</span></span>
<span></span>
<span><span>        # self.memory.copy_to_host()</span></span>
<span><span>        </span></span>
<span><span>        output_buffers </span><span>=</span><span> OrderedDict()</span></span>
<span><span>        for</span><span> name </span><span>in</span><span> self</span><span>.output_tensor_names:</span></span>
<span><span>            arr </span><span>=</span><span> cuda.pagelocked_empty(</span><span>self</span><span>.output_allocator.shapes[name], </span><span>dtype</span><span>=</span><span>trt.nptype(</span><span>self</span><span>.engine.get_tensor_dtype(name)))</span></span>
<span><span>            cuda.memcpy_dtoh_async(arr, </span><span>self</span><span>.output_allocator.buffers[name], </span><span>stream</span><span>=</span><span>self</span><span>.stream)</span></span>
<span><span>            output_buffers[name] </span><span>=</span><span> arr</span></span>
<span><span>        </span></span>
<span><span>        # Synchronize the stream</span></span>
<span><span>        self</span><span>.stream.synchronize()</span></span>
<span><span>        </span></span>
<span><span>        return</span><span> output_buffers</span></span>
<span></span></code></pre><p>使用示例：<pre tabindex=0><code><span><span>inputs </span><span>=</span><span> {</span></span>
<span><span>  'image'</span><span>: np.array(</span><span>...</span><span>),</span></span>
<span><span>}</span></span>
<span></span>
<span><span>engine </span><span>=</span><span> load_engine(</span><span>...</span><span>)</span></span>
<span><span>processor </span><span>=</span><span> ProcessorV3(engine)</span></span>
<span></span>
<span><span>outputs </span><span>=</span><span> processor.infer(inputs)</span></span>
<span></span></code></pre><h2>4. 后记</h2><p>有一个需要注意的地方，TensorRT生成的Engine是和硬件相关的，不同型号的显卡不能通用这个Engine。<p>当然，如果只是按照前文这种用法的话，体现不出使用TensorRT的优势。TensorRT的另一个功能是低精度推理，支持模型量化，但这又是另一个体系庞大的故事了，这里先挖一个坑，以后慢慢填。<p>还有一点待优化的是，CUDA是支持多个Stream并行执行的，但是上述代码并没有体现这一点，这个作为CUDA编程的一个坑，先留一下。<h2>5. 参考资料</h2><ol><li><a href=https://docs.nvidia.com/deeplearning/tensorrt/index.html>NVIDIA 开发者手册</a><li><a href=https://github.com/HeKun-NVIDIA/TensorRT-Developer_Guide_in_Chinese>NVIDIA TensorRT 中文版开发手册</a><li><a href=https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/index.html>TensorRT Python API文档</a><li><a href=https://docs.nvidia.com/deeplearning/tensorrt/api/c_api/index.html>TensorRT C++ API文档</a><li><a href=https://dataxujing.github.io/TensorRT_CV/index.htm>TensorRT 的介绍与应用</a><li><a href=https://documen.tician.de/pycuda/>PyCUDA 文档</a><li><a href=https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html>CUDA C++ Programming Guide</a><li><a href=https://nvidia.github.io/cuda-python/>CUDA Python Manual</a><li><a href=https://leimao.github.io/blog/CUDA-Stream/>“CUDA Stream” from Lei Mao’s blog</a><li><a href=https://leimao.github.io/blog/Page-Locked-Host-Memory-Data-Transfer/>“Page-Locked Host Memory for Data Transfer” from Lei Mao’s blog</a></ol>]]></content:encoded>
            <enclosure url="https://suborbit.net/_astro/cover.vNACvdrZ.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[SQL简明手册进阶篇]]></title>
            <link>https://suborbit.net/posts/concise-sql-advanced</link>
            <guid>https://suborbit.net/posts/concise-sql-advanced</guid>
            <pubDate>Wed, 17 Feb 2021 20:15:08 GMT</pubDate>
            <description><![CDATA[SQL 简明手册 进阶篇 Toc SQL 数据库操作 创建数据库 CREATE DATABASE 删除数据库 DROP DATABASE 重命名数据库 RENAME DATABASE 选择数据库 USE 显示语句 SHOW SQL 表操作 创建表 CREATE TABLE 更改表结构 ALTER TABLE 添加新字段 修改字段属性 删除字段 删除表 DROP TABLE 清空表 TRUNCATE TABLE 重命名表…]]></description>
            <content:encoded><![CDATA[<h1>SQL 简明手册 进阶篇</h1><h2>Toc</h2><ol><li><a href=#sql-%E6%95%B0%E6%8D%AE%E5%BA%93%E6%93%8D%E4%BD%9C>SQL 数据库操作</a><ol><li><a href=#%E5%88%9B%E5%BB%BA%E6%95%B0%E6%8D%AE%E5%BA%93-create-database>创建数据库 CREATE DATABASE</a><li><a href=#%E5%88%A0%E9%99%A4%E6%95%B0%E6%8D%AE%E5%BA%93-drop-database>删除数据库 DROP DATABASE</a><li><a href=#%E9%87%8D%E5%91%BD%E5%90%8D%E6%95%B0%E6%8D%AE%E5%BA%93-rename-database>重命名数据库 RENAME DATABASE</a><li><a href=#%E9%80%89%E6%8B%A9%E6%95%B0%E6%8D%AE%E5%BA%93-use>选择数据库 USE</a><li><a href=#%E6%98%BE%E7%A4%BA%E8%AF%AD%E5%8F%A5-show>显示语句 SHOW</a></ol><li><a href=#sql-%E8%A1%A8%E6%93%8D%E4%BD%9C>SQL 表操作</a><ol><li><a href=#%E5%88%9B%E5%BB%BA%E8%A1%A8-create-table>创建表 CREATE TABLE</a><li><a href=#%E6%9B%B4%E6%94%B9%E8%A1%A8%E7%BB%93%E6%9E%84-alter-table>更改表结构 ALTER TABLE</a><ol><li><a href=#%E6%B7%BB%E5%8A%A0%E6%96%B0%E5%AD%97%E6%AE%B5>添加新字段</a><li><a href=#%E4%BF%AE%E6%94%B9%E5%AD%97%E6%AE%B5%E5%B1%9E%E6%80%A7>修改字段属性</a><li><a href=#%E5%88%A0%E9%99%A4%E5%AD%97%E6%AE%B5>删除字段</a></ol><li><a href=#%E5%88%A0%E9%99%A4%E8%A1%A8-drop-table>删除表 DROP TABLE</a><li><a href=#%E6%B8%85%E7%A9%BA%E8%A1%A8-truncate-table>清空表 TRUNCATE TABLE</a><li><a href=#%E9%87%8D%E5%91%BD%E5%90%8D%E8%A1%A8-rename-table>重命名表 RENAME TABLE</a></ol><li><a href=#sql-%E7%BA%A6%E6%9D%9F>SQL 约束</a><ol><li><a href=#%E4%B8%BB%E9%94%AE%E7%BA%A6%E6%9D%9F>主键约束</a><li><a href=#%E5%A4%96%E9%94%AE%E7%BA%A6%E6%9D%9F>外键约束</a><li><a href=#%E5%94%AF%E4%B8%80%E7%BA%A6%E6%9D%9F>唯一约束</a><li><a href=#%E9%9D%9E%E7%A9%BA%E7%BA%A6%E6%9D%9F>非空约束</a><li><a href=#%E9%BB%98%E8%AE%A4%E7%BA%A6%E6%9D%9F>默认约束</a><li><a href=#%E6%A3%80%E6%9F%A5%E7%BA%A6%E6%9D%9F>检查约束</a></ol><li><a href=#sql-%E7%B4%A2%E5%BC%95>SQL 索引</a><ol><li><a href=#%E5%88%9B%E5%BB%BA%E7%B4%A2%E5%BC%95>创建索引</a><li><a href=#%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95>删除索引</a></ol><li><a href=#sql-%E4%B8%AD%E7%9A%84%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B>SQL 中的数据类型</a><ol><li><a href=#%E6%95%B4%E6%95%B0>整数</a><li><a href=#%E6%B5%AE%E7%82%B9%E6%95%B0>浮点数</a><li><a href=#%E5%AE%9A%E7%82%B9%E6%95%B0>定点数</a><li><a href=#%E5%AD%97%E7%AC%A6%E4%B8%B2>字符串</a><li><a href=#%E6%97%A5%E6%9C%9F>日期</a><li><a href=#%E5%85%B6%E4%BB%96>其他</a></ol><li><a href=#%E5%8F%82%E8%80%83>参考</a></ol><h2>SQL 数据库操作</h2><h3>创建数据库 CREATE DATABASE</h3><pre tabindex=0><code><span><span>CREATE</span><span> DATABASE</span><span> [IF NOT EXISTS] </span><span>&lt;</span><span>数据库名称</span><span>></span><span>;</span></span>
<span></span></code></pre><p>数据库名称在RDBMS中必须是唯一的。<h3>删除数据库 DROP DATABASE</h3><pre tabindex=0><code><span><span>DROP</span><span> DATABASE</span><span> [IF EXISTS] </span><span>&lt;</span><span>数据库名称</span><span>></span><span>;</span></span>
<span></span></code></pre><h3>重命名数据库 RENAME DATABASE</h3><pre tabindex=0><code><span><span>RENAME </span><span>DATABASE</span><span> [IF EXISTS] </span><span>&lt;</span><span>旧名称</span><span>></span><span> TO</span><span> &lt;</span><span>新名称</span><span>></span><span>;</span></span>
<span><span>-- 这是MySQL的语法，不同数据库管理系统可能有不同的语法</span></span>
<span></span></code></pre><h3>选择数据库 USE</h3><p>选择当前要进行操作的数据库<pre tabindex=0><code><span><span>USE</span><span> &lt;</span><span>数据库名称</span><span>></span><span>;</span></span>
<span></span></code></pre><h3>显示语句 SHOW</h3><p>MySQL使用 <code>SHOW</code> 命令实际上就是对 <code>information_schema</code> 表执行 <code>SELECT</code> 。<pre tabindex=0><code><span><span>SHOW DATABASES; </span><span>-- 显示主机所有数据库</span></span>
<span><span>SHOW TABLES; </span><span>-- 显示当前数据库的所有表</span></span>
<span><span>SHOW COLUMNS; </span><span>-- 显示当前表的结构</span></span>
<span><span>DESCRIBE; </span><span>-- 和SHOW COLUMNS等价</span></span>
<span><span>SHOW </span><span>CREATE</span><span> DATABASE</span><span>|</span><span>SCHEMA</span><span> &lt;</span><span>数据库名</span><span>></span><span>\G </span><span>--显示创建某个数据库的命令</span></span>
<span><span>-- \G为按列打印 \g等同于;</span></span>
<span><span>SHOW </span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span><span>\G </span><span>-- 显示创建某个表的命令</span></span>
<span><span>SHOW </span><span>INDEX</span><span> FROM</span><span> &lt;</span><span>表名</span><span>></span><span>\G </span><span>-- 显示某个表的索引</span></span>
<span></span></code></pre><p>更多用法请参考官方手册：<a href=https://dev.mysql.com/doc/refman/8.0/en/show.html>https://dev.mysql.com/doc/refman/8.0/en/show.html</a><h2>SQL 表操作</h2><h3>创建表 CREATE TABLE</h3><pre tabindex=0><code><span><span>CREATE</span><span> TABLE</span><span> [IF NOT EXISTS] </span><span>&lt;</span><span>表名</span><span>></span><span>(</span></span>
<span><span>    &lt;</span><span>字段1</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span> &lt;</span><span>默认值</span><span>></span><span> &lt;</span><span>字段约束</span><span>></span><span>,</span></span>
<span><span>    &lt;</span><span>字段2</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span> &lt;</span><span>默认值</span><span>></span><span> &lt;</span><span>字段约束</span><span>></span><span>,</span></span>
<span><span>    ...,</span></span>
<span><span>    &lt;</span><span>表约束</span><span>></span></span>
<span><span>);</span></span>
<span></span></code></pre><h3>更改表结构 ALTER TABLE</h3><ul><li>使用 <code>ADD</code> 子句添加新字段。<li>使用 <code>MODIFY</code> 子句修改字段的属性，例如：约束，默认值等。<li>使用 <code>DROP</code> 子句删除字段。</ul><h4>添加新字段</h4><pre tabindex=0><code><span><span>ALTER</span><span> TABLE</span><span> [IF EXISTS] </span><span>&lt;</span><span>表名</span><span>></span></span>
<span><span>ADD</span><span> &lt;</span><span>新字段</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span> &lt;</span><span>字段约束</span><span>></span><span> [AFTER &#60已有字段>];</span></span>
<span></span></code></pre><p>如果要在表中指定新字段的顺序，可以使用可选子句 <code>AFTER &#60已有字段></code> ，使新字段添加在已有字段之后。默认添加在最后一列。<h4>修改字段属性</h4><pre tabindex=0><code><span><span>ALTER</span><span> TABLE</span><span> [IF EXISTS] </span><span>&lt;</span><span>表名</span><span>></span></span>
<span><span>MODIFY</span><span> &lt;</span><span>字段名</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span> &lt;</span><span>默认值</span><span>></span><span> &lt;</span><span>字段约束</span><span>></span><span>;</span></span>
<span></span>
<span><span>-- 在MySQL中还可以使用CHANGE语法，可以直接重命名</span></span>
<span><span>ALTER</span><span> TABLE</span><span> [IF EXISTS] </span><span>&lt;</span><span>表名</span><span>></span></span>
<span><span>CHANGE </span><span>&lt;</span><span>旧字段名</span><span>></span><span> &lt;</span><span>新段名</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span> &lt;</span><span>默认值</span><span>></span><span> &lt;</span><span>字段约束</span><span>></span><span>;</span></span>
<span></span></code></pre><p>应该修改没有数据的表的字段的属性。 因为更改已包含数据的表中字段的属性可能会导致永久性数据丢失。 例如，如果列的数据类型为VARCHAR，并且将其更改为 <code>INT</code> ，则数据库系统必须将数据从 <code>VARCHAR</code> 转换为 <code>INT</code> 。 如果转换失败，数据库系统可能会使用列的默认值，这可能与预期不符。<h4>删除字段</h4><pre tabindex=0><code><span><span>ALTER</span><span> TABLE</span><span> [IF EXISTS] </span><span>&lt;</span><span>表名</span><span>></span></span>
<span><span>DROP</span><span> &lt;</span><span>字段1</span><span>></span><span>,</span></span>
<span><span>DROP</span><span> &lt;</span><span>字段2</span><span>></span><span>,</span></span>
<span><span>...;</span></span>
<span></span></code></pre><h3>删除表 DROP TABLE</h3><pre tabindex=0><code><span><span>DROP</span><span> TABLE</span><span> [IF EXISTS] </span><span>&lt;</span><span>表名</span><span>></span><span>;</span></span>
<span></span></code></pre><p>为了防止删除不存在的表的错误，使用可选子句 <code>IF EXISTS</code> 。 如果使用 <code>IF EXISTS</code> 选项，如果删除一个不存在的表，数据库系统将不会抛出任何错误。 某些数据库系统会发出警告或通知。<h3>清空表 TRUNCATE TABLE</h3><p><code>DELETE</code> 对删除百万行的大表效率较低，使用 <code>TRUNCATE</code> 更高效。<pre tabindex=0><code><span><span>TRUNCATE</span><span> TABLE</span><span> [IF EXISTS] </span><span>&lt;</span><span>表名</span><span>></span><span>;</span></span>
<span><span>TRUNCATE</span><span> TABLE</span><span> [IF EXISTS] </span><span>&lt;</span><span>表1</span><span>></span><span>,</span><span>&lt;</span><span>表2</span><span>></span><span>,...;</span></span>
<span></span>
<span><span>TRUNCATE</span><span> [IF EXISTS] </span><span>&lt;</span><span>表名</span><span>></span><span>;</span></span>
<span><span>-- 有些数据库（如MySQL）允许省略TABLE</span></span>
<span></span></code></pre><h3>重命名表 RENAME TABLE</h3><pre tabindex=0><code><span><span>RENAME </span><span>TABLE</span><span> [IF EXISTS] </span><span>&lt;</span><span>旧表名</span><span>></span><span> TO</span><span> &lt;</span><span>新表名</span><span>></span><span>;</span></span>
<span><span>-- 下面是一个等价的写法</span></span>
<span><span>ALTER</span><span> TABLE</span><span> [IF EXISTS] </span><span>&lt;</span><span>旧表名</span><span>></span><span> RENAME </span><span>&lt;</span><span>新表名</span><span>></span><span>;</span></span>
<span></span></code></pre><h2>SQL 约束</h2><h3>主键约束</h3><p>对于关系表，通过主键字段可以唯一区分不同的记录，任意两条记录的主键值不能相同，主键设定好后一般不要再修改，作为主键最好是完全业务无关的字段，我们一般把这个字段命名为 id。常见的可作为 id 字段的类型有自增整数类型、全局唯一 GUID 类型。<ul><li>创建主键约束</ul><pre tabindex=0><code><span><span>-- 创建表时设置主键</span></span>
<span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span><span> (</span></span>
<span><span>    &lt;</span><span>字段1</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span> PRIMARY KEY</span><span>,</span></span>
<span><span>    &lt;</span><span>字段2</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    ...</span></span>
<span><span>);</span></span>
<span></span>
<span><span>-- 使用表级约束</span></span>
<span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span><span> (</span></span>
<span><span>    &lt;</span><span>字段1</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    &lt;</span><span>字段2</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    ...</span></span>
<span><span>    PRIMARY KEY</span><span>(字段1 [,字段2,...]) </span><span>-- 可以设置多个字段为联合主键</span></span>
<span><span>);</span></span>
<span></span>
<span><span>-- 使用表级约束并对约束起名</span></span>
<span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span><span> (</span></span>
<span><span>    &lt;</span><span>字段1</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    &lt;</span><span>字段2</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    ...</span></span>
<span><span>    CONSTRAINT</span><span> &lt;</span><span>约束名称</span><span>></span><span> PRIMARY KEY</span><span>(字段1 [,字段2,...])</span></span>
<span><span>);</span></span>
<span></span>
<span><span>-- 向已创的表添加主键约束</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>ADD</span><span> PRIMARY KEY</span><span> (字段1 [,字段2,...]);</span></span>
<span></span>
<span><span>-- 添加约束时也可以起名</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>ADD</span><span> CONSTRAINT</span><span> &lt;</span><span>约束名</span><span>></span><span> PRIMARY KEY</span><span> (字段1 [,字段2,...]);</span></span>
<span></span></code></pre><ul><li>删除主键约束</ul><pre tabindex=0><code><span><span>-- 有名称的约束都可以这样删除</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>DROP</span><span> CONSTRAINT</span><span> &lt;</span><span>约束名</span><span>></span><span>;</span></span>
<span></span>
<span><span>-- 或者直接这样</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>DROP</span><span> PRIMARY KEY</span><span>;</span></span>
<span></span></code></pre><h3>外键约束</h3><p>外键用来在两张关系表之间建立联系，将当前表一些值对应到另一张表的主键上，形成对应关系，这样就创建的两张表之间的链接，称第二张表的主键为当前表的外键。<ul><li>创建外键约束</ul><pre tabindex=0><code><span><span>-- 定义表时创建外键约束</span></span>
<span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表1</span><span>></span><span> (</span></span>
<span><span>    &lt;</span><span>字段</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    ...</span></span>
<span><span>    FOREIGN KEY</span><span> (字段)</span></span>
<span><span>        REFERENCES</span><span> &lt;</span><span>表2</span><span>></span><span> (外键字段)</span></span>
<span><span>);</span></span>
<span></span>
<span><span>-- 为约束起名</span></span>
<span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表1</span><span>></span><span> (</span></span>
<span><span>    &lt;</span><span>字段</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    ...</span></span>
<span><span>    CONSTRAINT</span><span> &lt;</span><span>约束名称</span><span>></span><span> FOREIGN KEY</span><span> (字段)</span></span>
<span><span>        REFERENCES</span><span> &lt;</span><span>表2</span><span>></span><span> (外键字段)</span></span>
<span><span>);</span></span>
<span></span>
<span><span>-- 向已创的表添加外键约束</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表1</span><span>></span></span>
<span><span>ADD</span><span> FOREIGN KEY</span><span> (字段)</span></span>
<span><span>    REFERENCES</span><span> &lt;</span><span>表2</span><span>></span><span> (外键字段)</span></span>
<span></span>
<span><span>-- 添加约束时也可以起名</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表1</span><span>></span></span>
<span><span>ADD</span><span> CONSTRAINT</span><span> &lt;</span><span>约束名</span><span>></span><span> FOREIGN KEY</span><span> (字段)</span></span>
<span><span>    REFERENCES</span><span> &lt;</span><span>表2</span><span>></span><span> (外键字段);</span></span>
<span></span></code></pre><ul><li>删除外键约束</ul><pre tabindex=0><code><span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>DROP</span><span> CONSTRAINT</span><span> &lt;</span><span>约束名</span><span>></span><span>;</span></span>
<span></span>
<span><span>-- 或者这样</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>DROP</span><span> FOREIGN KEY</span><span> &lt;</span><span>约束名</span><span>></span><span>; </span></span>
<span><span>-- 如果创建时没有定义约束名，数据库系统会自动生成一个，可以用其他命令查询</span></span>
<span></span></code></pre><h3>唯一约束</h3><p>唯一约束用来确保某个或某组字段在每个记录中都是唯一的。<ul><li>创建唯一约束</ul><pre tabindex=0><code><span><span>-- 创建表时设置唯一约束</span></span>
<span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span><span> (</span></span>
<span><span>    &lt;</span><span>字段1</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span> UNIQUE</span><span>,</span></span>
<span><span>    &lt;</span><span>字段2</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    ...</span></span>
<span><span>);</span></span>
<span></span>
<span><span>-- 使用表级约束</span></span>
<span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span><span> (</span></span>
<span><span>    &lt;</span><span>字段1</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    &lt;</span><span>字段2</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    ...</span></span>
<span><span>    UNIQUE</span><span>(字段1 [,字段2,...]) </span><span>-- 可以设置多个字段联合</span></span>
<span><span>);</span></span>
<span></span>
<span><span>-- 使用表级约束并对约束起名</span></span>
<span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span><span> (</span></span>
<span><span>    &lt;</span><span>字段1</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    &lt;</span><span>字段2</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    ...</span></span>
<span><span>    CONSTRAINT</span><span> &lt;</span><span>约束名称</span><span>></span><span> UNIQUE</span><span>(字段1 [,字段2,...])</span></span>
<span><span>);</span></span>
<span></span>
<span><span>-- 向已创的表添加唯一约束</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>ADD</span><span> UNIQUE</span><span> (字段1 [,字段2,...]);</span></span>
<span></span>
<span><span>-- 添加约束时也可以起名</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>ADD</span><span> CONSTRAINT</span><span> &lt;</span><span>约束名</span><span>></span><span> UNIQUE</span><span> (字段1 [,字段2,...]);</span></span>
<span></span></code></pre><ul><li>删除唯一约束</ul><pre tabindex=0><code><span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>DROP</span><span> CONSTRAINT</span><span> &lt;</span><span>约束名</span><span>></span><span>;</span></span>
<span></span>
<span><span>-- 或者这样</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>DROP</span><span> UNIQUE</span><span> &lt;</span><span>约束名</span><span>></span><span>; </span></span>
<span><span>-- 如果创建时没有定义约束名，数据库系统会自动生成一个，可以用其他命令查询</span></span>
<span></span></code></pre><h3>非空约束</h3><p>非空约束用来确保插入记录的某个字段不能为空（即 <code>NULL</code> ）<pre tabindex=0><code><span><span>-- 创建约束，非空约束没有表级定义</span></span>
<span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span><span> (</span></span>
<span><span>    &lt;</span><span>字段1</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span> NOT NULL</span><span>,</span></span>
<span><span>    &lt;</span><span>字段2</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    ...</span></span>
<span><span>);</span></span>
<span><span>-- 添加约束</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>MODIFY</span><span> &lt;</span><span>字段名</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span> NOT NULL</span><span>; </span><span>-- 先要确保字段没有NULL值</span></span>
<span><span>-- 删除约束</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>MODIFY</span><span> &lt;</span><span>字段名</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span> NULL</span><span>;</span></span>
<span></span></code></pre><h3>默认约束</h3><p>默认约束用来确定字段未指定时的默认值。<pre tabindex=0><code><span><span>-- 创建约束，默认约束没有表级定义</span></span>
<span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span><span> (</span></span>
<span><span>    &lt;</span><span>字段1</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span> DEFAULT</span><span> &lt;</span><span>值</span><span>></span><span>,</span></span>
<span><span>    &lt;</span><span>字段2</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    ...</span></span>
<span><span>);</span></span>
<span><span>-- 添加约束</span></span>
<span><span>-- MySQL用法:</span></span>
<span><span>-- ALTER COLUMN这种语法仅见于修改DEFAULT值和一些其他不常用地方</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>ALTER</span><span> &lt;</span><span>字段名</span><span>></span><span> SET</span><span> DEFAULT</span><span> &lt;</span><span>值</span><span>></span><span>;</span></span>
<span><span>-- 删除约束</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>ALTER</span><span> &lt;</span><span>字段名</span><span>></span><span> DROP</span><span> DEFAULT</span><span>;</span></span>
<span><span>-- 当然也可以通过MODIFY添加和删除</span></span>
<span></span></code></pre><h3>检查约束</h3><p>检查约束允许我们自定义新添加值是否合法。MySQL 8.0.16 之前的版本是默认忽略检查约束的。<ul><li>创建检查约束</ul><pre tabindex=0><code><span><span>-- 创建表时设置检查约束</span></span>
<span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span><span> (</span></span>
<span><span>    &lt;</span><span>字段1</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span> CHECK</span><span>(条件表达式),</span></span>
<span><span>    &lt;</span><span>字段2</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    ...</span></span>
<span><span>);</span></span>
<span></span>
<span><span>-- 使用表级约束</span></span>
<span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span><span> (</span></span>
<span><span>    &lt;</span><span>字段1</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    &lt;</span><span>字段2</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    ...</span></span>
<span><span>    CHECK</span><span>(条件表达式)</span></span>
<span><span>);</span></span>
<span></span>
<span><span>-- 使用表级约束并对约束起名</span></span>
<span><span>CREATE</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span><span> (</span></span>
<span><span>    &lt;</span><span>字段1</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    &lt;</span><span>字段2</span><span>></span><span> &lt;</span><span>类型</span><span>></span><span>,</span></span>
<span><span>    ...</span></span>
<span><span>    CONSTRAINT</span><span> &lt;</span><span>约束名称</span><span>></span><span> CHECK</span><span>(条件表达式)</span></span>
<span><span>);</span></span>
<span></span>
<span><span>-- 向已创的表添加检查约束</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>ADD</span><span> CHECK</span><span>(条件表达式);</span></span>
<span></span>
<span><span>-- 添加约束时也可以起名</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>ADD</span><span> CONSTRAINT</span><span> &lt;</span><span>约束名</span><span>></span><span> CHECK</span><span>(条件表达式);</span></span>
<span></span></code></pre><ul><li>删除检查约束</ul><pre tabindex=0><code><span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>DROP</span><span> CONSTRAINT</span><span> &lt;</span><span>约束名</span><span>></span><span>;</span></span>
<span></span>
<span><span>-- 或者这样</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>DROP</span><span> CHECK</span><span> &lt;</span><span>约束名</span><span>></span><span>; </span></span>
<span><span>-- 如果创建时没有定义约束名，数据库系统会自动生成一个，可以用其他命令查询</span></span>
<span></span></code></pre><h2>SQL 索引</h2><p>数据库索引或索引，有助于加速从表中检索数据。当从表查询数据时，首先MySQL会检查索引是否存在，然后MySQL使用索引来选择表的精确物理对应行，而不是扫描整个表。所有主键列都是表的主索引。对于数据量小的表不需要建立索引。<h3>创建索引</h3><ul><li>创建表时创建索引</ul><p>MySQL自动将声明为PRIMARY KEY，KEY，UNIQUE或INDEX的任何列添加到索引。<ul><li>向已有表添加索引</ul><pre tabindex=0><code><span><span>CREATE</span><span> [UNIQUE|FULLTEXT|SPATIAL] </span><span>INDEX</span><span> &lt;</span><span>索引名</span><span>></span></span>
<span><span>[USING BTREE|HASH|RTREE] </span></span>
<span><span>ON</span><span> &lt;</span><span>表</span><span>></span><span> (</span><span>&lt;</span><span>字段</span><span>></span><span> [(长度)] [ASC | DESC],...)</span></span>
<span></span></code></pre><p>UNIQUE索引会对字段创建唯一约束，FULLTEXT、SPATIAL索引仅由MyISAM存储引擎支持，其中FULLTEXT索引仅在数据类型为CHAR，VARCHAR或TEXT的列中接受，SPATIAL索引列值不能为NULL。<p>不同的存储引擎有不同的索引类型<table><thead><tr><th>存储引擎<th>允许的索引类型<tbody><tr><td>MyISAM<td>BTREE, RTREE<tr><td>InnoDB<td>BTREE<tr><td>MEMORY/HEAP<td>HASH, BTREE<tr><td>NDB<td>HASH</table><h3>删除索引</h3><pre tabindex=0><code><span><span>DROP</span><span> INDEX</span><span> &lt;</span><span>索引名</span><span>></span><span> ON</span><span> &lt;</span><span>表名</span><span>></span></span>
<span></span>
<span><span>-- 或者使用ALTER TABLE语法</span></span>
<span><span>ALTER</span><span> TABLE</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>DROP</span><span> INDEX</span><span> &lt;</span><span>索引名</span><span>></span></span>
<span></span></code></pre><h2>SQL 中的数据类型</h2><p>以MySQL为例<h3>整数</h3><table><thead><tr><th>数据类型<th>字节数<th>最小值<th>最大值<tbody><tr><td>TINYINT<td>1<td>-128<td>127<tr><td>SMALLINT<td>2<td>-32768<td>32767<tr><td>MEDIUMINT<td>3<td>-8388608<td>8388607<tr><td>INT<td>4<td>-2147483648<td>2147483647<tr><td>BIGINT<td>8<td>-9223372036854775808<td>9223372036854775807<tr><td>TINYINT UNSIGNED<td>1<td>0<td>255<tr><td>SMALLINT UNSIGNED<td>2<td>0<td>65535<tr><td>MEDIUMINT UNSIGNED<td>3<td>0<td>16777215<tr><td>INT UNSIGNED<td>4<td>0<td>4294967295<tr><td>BIGINT UNSIGNED<td>8<td>0<td>18446744073709551616</table><p>可以指定整数的显示宽度<code>INT(N) UNSIGNED ZEROFILL</code>，宽度不足用0补全，宽度超过N则无视规则。注意只有设定了<code>UNSIGNED ZEROFILL</code> 才有效。<h3>浮点数</h3><table><thead><tr><th>数据类型<th>字节数<th>描述<tbody><tr><td>FLOAT<td>4<td>单精度浮点型<tr><td>DOUBLE<td>8<td>双精度浮点型</table><p>可以设定浮点数小数位精度和有效数字精度 <code>FLOAT(P,D)</code> ：<ul><li>P是表示有效数字数的精度。 P范围为1〜65。<li>D是表示小数点后的位数。D的范围是0~30。MySQL要求D&#60=P。</ul><h3>定点数</h3><p><code>DECIMAL</code> 数据类型用于在数据库中存储精确的数值。有 <code>UNSIGNED</code> 和 <code>ZEROFILL</code> 属性，也可以和浮点数一样设置小数位精度和有效数字精度，用法同浮点数。不指定精度默认为 <code>DECIMAL(10, 0)</code> 。<h3>字符串</h3><table><thead><tr><th>数据类型<th>长度<th>描述<tbody><tr><td>CHAR(N)<td>0-255字节<td>定长字符串，用空格补全<tr><td>VARCHAR(N)<td>0-65536字节<td>可变长字符串，最大长N，N最大值与字符集有关<tr><td>TINYTEXT<td>0-255字节<td>可变长字符串，数据储存在磁盘中<tr><td>TEXT<td>0-65535字节<td>可变长文本，数据储存在磁盘中<tr><td>MEDIUMTEXT<td>0-16777215字节<td>可变长文本，数据储存在磁盘中<tr><td>LONGTEXT<td>0-4294967295字节<td>可变长文本，数据储存在磁盘中<tr><td>TINYBLOB<td>0-255字节<td>二进制字符串，数据储存在磁盘中<tr><td>BLOB<td>0-65535字节<td>二进制字符串，数据储存在磁盘中<tr><td>MEDIUMBLOB<td>0-16777215字节<td>二进制字符串，数据储存在磁盘中<tr><td>LONGBLOB<td>0-4294967295字节<td>二进制字符串，数据储存在磁盘中<tr><td>SET(&#60列表>)<td>最多64个成员<td>集合（多选）类型<tr><td>ENUM(&#60列表>)<td>最多65535个成员<td>枚举（单选）类型，列表成员从1开始编号</table><h3>日期</h3><table><thead><tr><th>数据类型<th>字节数<th>格式<th>描述<tbody><tr><td>DATE<td>3<td>yyyy-MM-dd<td>存储日期值<tr><td>TIME<td>3<td>HH:mm<div></div><td>存储时分秒<tr><td>YEAR<td>1<td>yyyy<td>存储年<tr><td>DATETIME<td>8<td>yyyy-MM-dd HH:mm<div></div><td>存储日期+时间<tr><td>TIMESTAMP<td>4<td>yyyy-MM-dd HH:mm<div></div><td>存储日期+时间，可作时间戳</table><h3>其他</h3><table><thead><tr><th>数据类型<th>描述<tbody><tr><td>GEOMETRY<td>任何类型的空间值<tr><td>POINT<td>一个点(一对X-Y坐标)<tr><td>LINESTRING<td>曲线(一个或多个POINT值)<tr><td>POLYGON<td>多边形<tr><td>GEOMETRYCOLLECTION<td>GEOMETRY值的集合<tr><td>MULTILINESTRING<td>LINESTRING值的集合<tr><td>MULTIPOINT<td>POINT值的集合<tr><td>MULTIPOLYGON<td>POLYGON值的集合<tr><td>JSON<td>JSON数据类型</table><h2>参考</h2><ol><li><a href=https://www.w3cschool.cn/sql/>https://www.w3cschool.cn/sql/</a><li><a href=https://www.runoob.com/sql/sql-tutorial.html>https://www.runoob.com/sql/sql-tutorial.html</a><li><a href=https://www.yiibai.com/sql/>https://www.yiibai.com/sql/</a><li><a href=https://www.yiibai.com/mysql/>https://www.yiibai.com/mysql/</a><li><a href=https://dev.mysql.com/doc/refman/8.0/en/>https://dev.mysql.com/doc/refman/8.0/en/</a></ol>]]></content:encoded>
            <enclosure url="https://suborbit.net/_astro/cover.Bwq71ey0.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[SQL简明手册入门篇]]></title>
            <link>https://suborbit.net/posts/concise-sql-introductory</link>
            <guid>https://suborbit.net/posts/concise-sql-introductory</guid>
            <pubDate>Wed, 17 Feb 2021 20:14:21 GMT</pubDate>
            <description><![CDATA[SQL 简明手册 入门篇 Toc 简介 SQL 数据检索 基本检索 SELECT 条件检索 WHERE 检索结果排序 ORDER BY 检索结果截取 LIMIT 检索结果连接 JOIN JOIN链接 自连接 检索结果组合 UNION 检索结果分组 GROUP BY SQL 中的别名 SQL 数据操作 插入记录 INSERT 修改记录 UPDATE 删除记录 DELETE SQL 函数 SQL 中的合计函数 SQL…]]></description>
            <content:encoded><![CDATA[<h1>SQL 简明手册 入门篇</h1><h2>Toc</h2><ol><li><a href=#%E7%AE%80%E4%BB%8B>简介</a><li><a href=#sql-%E6%95%B0%E6%8D%AE%E6%A3%80%E7%B4%A2>SQL 数据检索</a><ol><li><a href=#%E5%9F%BA%E6%9C%AC%E6%A3%80%E7%B4%A2-select>基本检索 SELECT</a><li><a href=#%E6%9D%A1%E4%BB%B6%E6%A3%80%E7%B4%A2-where>条件检索 WHERE</a><li><a href=#%E6%A3%80%E7%B4%A2%E7%BB%93%E6%9E%9C%E6%8E%92%E5%BA%8F-order-by>检索结果排序 ORDER BY</a><li><a href=#%E6%A3%80%E7%B4%A2%E7%BB%93%E6%9E%9C%E6%88%AA%E5%8F%96-limit>检索结果截取 LIMIT</a><li><a href=#%E6%A3%80%E7%B4%A2%E7%BB%93%E6%9E%9C%E8%BF%9E%E6%8E%A5-join>检索结果连接 JOIN</a><ol><li><a href=#join%E9%93%BE%E6%8E%A5>JOIN链接</a><li><a href=#%E8%87%AA%E8%BF%9E%E6%8E%A5>自连接</a></ol><li><a href=#%E6%A3%80%E7%B4%A2%E7%BB%93%E6%9E%9C%E7%BB%84%E5%90%88-union>检索结果组合 UNION</a><li><a href=#%E6%A3%80%E7%B4%A2%E7%BB%93%E6%9E%9C%E5%88%86%E7%BB%84-group-by>检索结果分组 GROUP BY</a><li><a href=#sql-%E4%B8%AD%E7%9A%84%E5%88%AB%E5%90%8D>SQL 中的别名</a></ol><li><a href=#sql-%E6%95%B0%E6%8D%AE%E6%93%8D%E4%BD%9C>SQL 数据操作</a><ol><li><a href=#%E6%8F%92%E5%85%A5%E8%AE%B0%E5%BD%95-insert>插入记录 INSERT</a><li><a href=#%E4%BF%AE%E6%94%B9%E8%AE%B0%E5%BD%95-update>修改记录 UPDATE</a><li><a href=#%E5%88%A0%E9%99%A4%E8%AE%B0%E5%BD%95-delete>删除记录 DELETE</a></ol><li><a href=#sql-%E5%87%BD%E6%95%B0>SQL 函数</a><ol><li><a href=#sql-%E4%B8%AD%E7%9A%84%E5%90%88%E8%AE%A1%E5%87%BD%E6%95%B0>SQL 中的合计函数</a><li><a href=#sql-%E4%B8%AD%E7%9A%84%E5%85%B6%E4%BB%96%E5%B8%B8%E7%94%A8%E5%87%BD%E6%95%B0>SQL 中的其他常用函数</a></ol><li><a href=#sql%E8%BF%90%E7%AE%97%E7%AC%A6>SQL运算符</a><ol><li><a href=#%E7%AE%97%E6%9C%AF%E8%BF%90%E7%AE%97%E7%AC%A6>算术运算符</a><li><a href=#%E6%AF%94%E8%BE%83%E8%BF%90%E7%AE%97%E7%AC%A6>比较运算符</a><li><a href=#%E9%80%BB%E8%BE%91%E8%BF%90%E7%AE%97%E7%AC%A6>逻辑运算符</a><ol><li><a href=#inallany%E7%94%A8%E6%B3%95>IN、ALL、ANY用法</a><li><a href=#existsbetweenlike%E7%94%A8%E6%B3%95>EXISTS、BETWEEN、LIKE用法</a><li><a href=#is-nullis-not-null%E7%94%A8%E6%B3%95>IS NULL、IS NOT NULL用法</a></ol></ol><li><a href=#sql%E8%A1%A8%E8%BE%BE%E5%BC%8F>SQL表达式</a><ol><li><a href=#%E5%B8%83%E5%B0%94%E8%A1%A8%E8%BE%BE%E5%BC%8F>布尔表达式</a><li><a href=#%E6%95%B0%E5%80%BC%E8%A1%A8%E8%BE%BE%E5%BC%8F>数值表达式</a><li><a href=#%E6%97%A5%E6%9C%9F%E8%A1%A8%E8%BE%BE%E5%BC%8F>日期表达式</a></ol><li><a href=#%E5%8F%82%E8%80%83>参考</a></ol><h2>简介</h2><p>数据库（DBMS）分为关系型数据库（RDBMS）和非关系型数据库（NoSQL）,SQL 全称 Structured Query Language 即结构化查询语言，用于存取数据以及查询、更新和管理关系数据库系统。<p>SQL标准命令 与关系数据库交互的标准SQL命令是创建、选择、插入、更新、删除和删除，简单分为以下几组：<ul><li>DDL（数据定义语言） 数据定义语言用于改变数据库结构，包括创建、更改和删除数据库对象。<li>DML（数据操纵语言） 数据操纵语言用于检索、插入和修改数据，数据操纵语言是最常见的SQL命令。<li>DCL（数据控制语言） 数据控制语言为用户提供权限控制命令。</ul><h2>SQL 数据检索</h2><h3>基本检索 SELECT</h3><pre tabindex=0><code><span><span>SELECT</span><span> *</span><span> FROM</span><span> &lt;</span><span>表名</span><span>></span><span>; </span><span>-- 查询所有字段</span></span>
<span><span>SELECT</span><span> &lt;</span><span>字段1</span><span>></span><span>, </span><span>&lt;</span><span>字段2</span><span>></span><span>, ... </span><span>FROM</span><span> &lt;</span><span>表名</span><span>></span><span>; </span><span>-- 查询指定字段</span></span>
<span><span>SELECT DISTINCT</span><span> &lt;</span><span>字段1</span><span>></span><span>, </span><span>&lt;</span><span>字段2</span><span>></span><span>, ... </span><span>FROM</span><span> &lt;</span><span>表名</span><span>></span><span>; </span><span>-- 列出不重复的查询结果</span></span>
<span><span>SELECT</span><span> 1</span><span>+</span><span>2</span><span>; </span><span>-- 使用SELECT进行运算</span></span>
<span></span></code></pre><p>许多检测工具会执行一条 <code>SELECT 1;</code> 来测试数据库连接。<p>FORM后可接多个表名：<pre tabindex=0><code><span><span>SELECT</span><span> *</span><span> FROM</span><span> &lt;</span><span>表1</span><span>></span><span>, </span><span>&lt;</span><span>表2</span><span>></span><span>;</span></span>
<span></span></code></pre><p>这种查询会返回二维表，其中行数是多个表的乘积，列数是多个表列数的和，查询结果包含所有表所有记录的排列组合，称为表的内连接。<h3>条件检索 WHERE</h3><pre tabindex=0><code><span><span>SELECT</span><span> &lt;</span><span>字段名</span><span>></span><span> FROM</span><span> &lt;</span><span>表名</span><span>></span><span> WHERE</span><span> &lt;</span><span>条件</span><span>></span><span>; </span><span>-- 查询指定条件下指定字段</span></span>
<span></span></code></pre><p>WHERE 子句不仅用于 SELECT 语法，还用于 UPDATE，DELETE 语法等。 可以使用 <code>AND OR NOT</code> 链接条件表达式。<p>注意： 文本字段使用单引号 <code>''</code> （部分数据库也接受双引号），数值字段不使用引号。<h3>检索结果排序 ORDER BY</h3><pre tabindex=0><code><span><span>SELECT</span><span> &lt;</span><span>字段1</span><span>></span><span>, </span><span>&lt;</span><span>字段2</span><span>></span><span>, ... </span><span>FROM</span><span> &lt;</span><span>表名</span><span>></span><span> ORDER BY</span><span> &lt;</span><span>字段1</span><span>></span><span>;</span></span>
<span><span> -- 按某一字段名排序查询结果，默认生序排序</span></span>
<span></span>
<span><span>SELECT</span><span> &lt;</span><span>字段1</span><span>></span><span>, </span><span>&lt;</span><span>字段2</span><span>></span><span>, ... </span><span>FROM</span><span> &lt;</span><span>表名</span><span>></span><span> ORDER BY</span><span> &lt;</span><span>字段1</span><span>></span><span> ASC</span><span>;  </span><span>-- 升序排序</span></span>
<span><span>SELECT</span><span> &lt;</span><span>字段1</span><span>></span><span>, </span><span>&lt;</span><span>字段2</span><span>></span><span>, ... </span><span>FROM</span><span> &lt;</span><span>表名</span><span>></span><span> ORDER BY</span><span> &lt;</span><span>字段1</span><span>></span><span> DESC</span><span>; </span><span>-- 降序排序</span></span>
<span></span>
<span></span>
<span><span>SELECT</span><span> &lt;</span><span>字段1</span><span>></span><span>, </span><span>&lt;</span><span>字段2</span><span>></span><span>, ... </span><span>FROM</span><span> &lt;</span><span>表名</span><span>></span><span> ORDER BY</span><span> &lt;</span><span>字段1</span><span>></span><span> ASC</span><span>,</span><span>&lt;</span><span>字段2</span><span>></span><span> DESC</span><span>, ... ; </span></span>
<span><span>-- 多重规则的排序，写在前面的字段规则更优先</span></span>
<span></span>
<span></span></code></pre><h3>检索结果截取 LIMIT</h3><p>LIMIT子句用于指定要返回的记录数量。<pre tabindex=0><code><span><span>SELECT</span><span> &lt;</span><span>字段名</span><span>></span><span> FROM</span><span> &lt;</span><span>表名</span><span>></span><span> LIMIT</span><span> &lt;</span><span>M</span><span>></span><span>;</span></span>
<span><span>-- 截取查询结果的前M行记录</span></span>
<span></span>
<span><span>SELECT</span><span> &lt;</span><span>字段名</span><span>></span><span> FROM</span><span> &lt;</span><span>表名</span><span>></span><span> LIMIT</span><span> &lt;</span><span>M</span><span>></span><span> OFFSET </span><span>&lt;</span><span>N</span><span>></span><span>; </span></span>
<span><span>-- 截取查询结果从第N行开始的M行记录</span></span>
<span></span></code></pre><p>注意，此处以MySQL为例，某些数据库系统写法不同，比如 SQL Server 使用的是TOP语法。<h3>检索结果连接 JOIN</h3><h4>JOIN链接</h4><p>连接（JOIN）子句用于将数据库中两个或者两个以上表中的记录组合起来。连接通过共有值将不同表中的字段组合在一起。<p><img alt="SQL JOINS"src=https://image.wider.ink/static/20210121192103.png><pre tabindex=0><code><span><span>SELECT</span><span> ... </span><span>FROM</span><span> &lt;</span><span>表1</span><span>></span><span> INNER JOIN</span><span> &lt;</span><span>表2</span><span>></span><span> ON</span><span> &lt;</span><span>条件</span><span>></span><span>;</span></span>
<span><span>-- INNER JOIN 可以简写为 JOIN</span></span>
<span><span>SELECT</span><span> ... </span><span>FROM</span><span> &lt;</span><span>表1</span><span>></span><span> LEFT OUTER JOIN</span><span> &lt;</span><span>表2</span><span>></span><span> ON</span><span> &lt;</span><span>条件</span><span>></span><span>;</span></span>
<span><span>--  LEFT OUTER JOIN 可以简写为 LEFT JOIN</span></span>
<span><span>SELECT</span><span> ... </span><span>FROM</span><span> &lt;</span><span>表1</span><span>></span><span> RIGHT OUTER JOIN</span><span> &lt;</span><span>表2</span><span>></span><span> ON</span><span> &lt;</span><span>条件</span><span>></span><span>;</span></span>
<span><span>--  RIGHT OUTER JOIN 可以简写为 RIGHT JOIN</span></span>
<span><span>SELECT</span><span> ... </span><span>FROM</span><span> &lt;</span><span>表1</span><span>></span><span> FULL OUTER JOIN</span><span> &lt;</span><span>表2</span><span>></span><span> ON</span><span> &lt;</span><span>条件</span><span>></span><span>;</span></span>
<span><span>--  FULL OUTER JOIN 可以简写为 FULL JOIN</span></span>
<span></span></code></pre><ul><li><code>INNER JOIN</code> 是选出两张表都存在的记录；<li><code>LEFT OUTER JOIN</code> 是选出左表存在的记录，仅右表存在的记录填充NULL；<li><code>RIGHT OUTER JOIN</code> 是选出右表存在的记录，仅左表存在的记录填充NULL；<li><code>FULL OUTER JOIN</code> 则是选出左右表都存在的记录，仅左表或右表存在的记录填充NULL。</ul><h4>自连接</h4><p>实际上使用了内连接的方法，把自身看做两张表。<pre tabindex=0><code><span><span>SELECT</span><span> &lt;</span><span>字段1</span><span>></span><span>, </span><span>&lt;</span><span>字段2</span><span>></span><span>, ... </span></span>
<span><span>FROM</span><span> &lt;</span><span>表</span><span>></span><span> T1, </span><span>&lt;</span><span>表</span><span>></span><span> T2</span></span>
<span><span>WHERE</span><span> &lt;</span><span>条件</span><span>></span><span>;</span></span>
<span></span></code></pre><h3>检索结果组合 UNION</h3><p>UNION 子句/运算符用于将两个或者更多的 SELECT 语句的运算结果组合起来。<ul><li>UNION中的每个SELECT语句必须具有相同的列数<li>这些列也必须具有相似的数据类型<li>每个SELECT语句中的列也必须以相同的顺序排列<li>每个SELECT语句必须有相同数目的列表达式<li>但是每个SELECT语句的长度不必相同</ul><pre tabindex=0><code><span><span>SELECT</span><span> &lt;</span><span>字段1</span><span>></span><span>, </span><span>&lt;</span><span>字段2</span><span>></span><span>, ... </span><span>FROM</span><span> &lt;</span><span>表1</span><span>></span></span>
<span><span>UNION</span></span>
<span><span>SELECT</span><span> &lt;</span><span>字段1</span><span>></span><span>, </span><span>&lt;</span><span>字段2</span><span>></span><span>, ... </span><span>FROM</span><span> &lt;</span><span>表2</span><span>></span></span>
<span><span>-- 默认UNION组合后无重复行</span></span>
<span></span>
<span><span>SELECT</span><span> &lt;</span><span>字段1</span><span>></span><span>, </span><span>&lt;</span><span>字段2</span><span>></span><span>, ... </span><span>FROM</span><span> &lt;</span><span>表1</span><span>></span></span>
<span><span>UNION ALL</span></span>
<span><span>SELECT</span><span> &lt;</span><span>字段1</span><span>></span><span>, </span><span>&lt;</span><span>字段2</span><span>></span><span>, ... </span><span>FROM</span><span> &lt;</span><span>表2</span><span>></span></span>
<span><span>--UNION ALL组合后不去除重复行</span></span>
<span></span></code></pre><h3>检索结果分组 GROUP BY</h3><p>GROUP BY 数据分组语法可以按某个字段名对数据进行分组，常与合计函数（SUM,MAX,MIN,AVG,…）结合使用<pre tabindex=0><code><span><span>SELECT</span><span> 合计函数(</span><span>&lt;</span><span>字段或表达式</span><span>></span><span>)</span></span>
<span><span>FROM</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>[WHERE &#60条件>]</span></span>
<span><span>GROUP BY</span><span> &lt;</span><span>字段名</span><span>></span><span>;</span></span>
<span></span></code></pre><p>合计函数无法与 <code>WHERE</code> 关键字一起使用，于是SQL中增加了 <code>HAVING</code> 子句，一般用来筛选 <code>GROUP BY</code> 后的数据：<pre tabindex=0><code><span><span>SELECT</span><span> 合计函数(</span><span>&lt;</span><span>字段或表达式</span><span>></span><span>)</span></span>
<span><span>FROM</span><span> &lt;</span><span>表名</span><span>></span></span>
<span><span>[WHERE &#60条件>]</span></span>
<span><span>[GROUP BY &#60字段名>]</span></span>
<span><span>HAVING</span><span> &lt;</span><span>带有合计函数的条件表达式</span><span>></span><span>;</span></span>
<span></span></code></pre><p>举例：<pre tabindex=0><code><span><span>SELECT</span><span> Customer,</span><span>SUM</span><span>(OrderPrice) </span><span>FROM</span><span> Orders</span></span>
<span><span>WHERE</span><span> Customer</span><span>=</span><span>'Bush'</span><span> OR</span><span> Customer</span><span>=</span><span>'Adams'</span></span>
<span><span>GROUP BY</span><span> Customer</span></span>
<span><span>HAVING</span><span> SUM</span><span>(OrderPrice)</span><span>></span><span>1500</span></span>
<span><span>-- 筛选客户 "Bush" 或 "Adams" 拥有超过 1500 的订单总金额。</span></span>
<span></span></code></pre><h3>SQL 中的别名</h3><p>通过使用 SQL，可以为表名称或列名称指定别名（Alias）。<pre tabindex=0><code><span><span>SELECT</span><span> &lt;</span><span>字段名</span><span>></span><span> AS</span><span> &lt;</span><span>别名</span><span>></span></span>
<span><span>FROM</span><span> &lt;</span><span>表名</span><span>></span><span>;</span></span>
<span><span>-- 字段的别名</span></span>
<span></span>
<span><span>SELECT</span><span> &lt;</span><span>字段名</span><span>></span></span>
<span><span>FROM</span><span> &lt;</span><span>表名</span><span>></span><span> AS</span><span> &lt;</span><span>别名</span><span>></span><span>;</span></span>
<span><span>-- 表的别名</span></span>
<span></span></code></pre><p><code>AS</code> 关键字是可以省略的，别名不能和表中已有的字段名重名。<h2>SQL 数据操作</h2><h3>插入记录 INSERT</h3><pre tabindex=0><code><span></span>
<span><span>INSERT INTO</span><span> &lt;</span><span>表名</span><span>></span><span> VALUES</span><span> (值1, 值2, ...);</span></span>
<span><span>-- 向表中插入新记录，值的顺序应与表字段顺序一致</span></span>
<span></span>
<span><span>INSERT INTO</span><span> &lt;</span><span>表名</span><span>></span><span> (字段1, 字段2, ...) </span><span>VALUES</span><span> (值1, 值2, ...);</span></span>
<span><span>-- 向表中插入新记录，值的顺序应与指定字段顺序一致</span></span>
<span><span>-- 如果字段是自增或自减、有默认值的，可以不在指定的字段中出现</span></span>
<span></span>
<span><span>INSERT INTO</span><span> &lt;</span><span>表名</span><span>></span><span> (字段1, 字段2, ...) </span><span>VALUES</span><span> </span></span>
<span><span>(值1, 值2, ...),</span></span>
<span><span>(值1, 值2, ...),</span></span>
<span><span>...;</span></span>
<span><span>-- 一次性插入多个记录</span></span>
<span></span>
<span><span>INSERT INTO</span><span> &lt;</span><span>表1</span><span>></span><span> [(字段1, 字段2, ...)] </span></span>
<span><span>SELECT</span><span> 字段1, 字段2, ...</span></span>
<span><span>FROM</span><span> &lt;</span><span>表2</span><span>></span></span>
<span><span>[WHERE &#60条件>];</span></span>
<span><span>-- 使用另一个表填充一个表 []表示可选</span></span>
<span></span>
<span><span>-- 上一条语句也可以这样写：</span></span>
<span><span>SELECT</span><span> (字段1, 字段2, ...) </span></span>
<span><span>INTO</span><span> &lt;</span><span>表1</span><span>></span><span> [IN &#60数据库文件>]</span></span>
<span><span>FROM</span><span> &lt;</span><span>表2</span><span>></span><span> </span></span>
<span><span>[WHERE &#60条件>];</span></span>
<span></span></code></pre><h3>修改记录 UPDATE</h3><pre tabindex=0><code><span><span>UPDATE</span><span> &lt;</span><span>表名</span><span>></span><span> SET</span><span> 字段1</span><span>=</span><span>值1, 字段2</span><span>=</span><span>值2, ... </span><span>WHERE</span><span> &lt;</span><span>条件</span><span>></span><span>;</span></span>
<span><span>-- 更新指定条件的记录的值</span></span>
<span><span>-- 注意如果不指定WHERE，则所有的记录都会更新</span></span>
<span></span></code></pre><p>UPDATE更新的值可以使用表达式，例如 <code>SET score=score+10</code> 就是给当前行的 <code>score</code> 字段的值加上了10。<h3>删除记录 DELETE</h3><pre tabindex=0><code><span><span>DELETE</span><span> FROM</span><span> &lt;</span><span>表名</span><span>></span><span> WHERE</span><span> &lt;</span><span>条件</span><span>></span><span>;</span></span>
<span><span>-- 删除指定条件的记录</span></span>
<span><span>-- 注意如果不指定WHERE则表中的全部记录都将被删除！</span></span>
<span></span></code></pre><h2>SQL 函数</h2><p>不同的数据库管理系统有不同的函数功能，这里仅仅列了一点常用函数，更详细的介绍需要参考官方文档或自行搜索。 MySQL手册函数部分：<a href=https://dev.mysql.com/doc/refman/8.0/en/functions.html>https://dev.mysql.com/doc/refman/8.0/en/functions.html</a><h3>SQL 中的合计函数</h3><p>又叫聚合（Aggregate）函数，合计函数计算一组值并返回单个值，通常与 <code>SELECT</code> 语句的 <code>GROUP BY</code> 联用。用法见 <code>GROUP BY</code> 条目。<table><thead><tr><th>函数<th>描述<tbody><tr><td>`AVG([ALL<td>DISTINCT] expression)`<tr><td>`COUNT([ALL<td>DISTINCT] expression)`<tr><td><code>COUNT(*)</code><td>返回表中的记录数<tr><td><code>MAX(expression)</code><td>取最大值<tr><td><code>MIN(expression)</code><td>取最小值<tr><td>`SUM([ALL<td>DISTINCT] expression)`<tr><td><code>STD( expression)</code><td>求总体标准差</table><p>使用 <code>ALL</code> 关键字将会对数据进行操作，使用 <code>DISTINCT</code> 关键字将会对数据去重后进行操作。默认使用 <code>ALL</code> 。<h3>SQL 中的其他常用函数</h3><table><thead><tr><th>函数<th>描述<tbody><tr><td><code>UCASE(str)</code><td>大写转换<tr><td><code>LCASE(str)</code><td>小写转换<tr><td><code>UPPER(str)</code><td>大写转换<tr><td><code>LOWER(str)</code><td>小写转换<tr><td><code>MID(&#60字段>,&#60起始位置,从1开始>[,长度])</code><td>提取子串<tr><td><code>REPLACE(&#60字段>,&#60模式串>,&#60目标串>)</code><td>替换子串<tr><td><code>LEN(str)</code><td>获取文本中值的长度<tr><td><code>TRIM(str)</code><td>移除始末空白<tr><td><code>CONCAT(str1,str2,...)</code><td>连接字符串<tr><td><code>SQRT(expression)</code><td>开平方<tr><td><code>ROUND(&#60字段>,&#60小数位>)</code><td>四舍五入到指定小数位<tr><td><code>RAND(seed)</code><td>产生0~1之间的随机数<tr><td><code>NOW()</code><td>返回当前系统的日期和时间</table><h2>SQL运算符</h2><p>运算符用于表达式中，常见运算符有以下几种：<ul><li>算术运算符<li>比较运算符<li>逻辑运算符</ul><h3>算术运算符</h3><p>a=10,b=20<table><thead><tr><th>运算符<th>描述<th>例子<tbody><tr><td>+<td>加法，执行加法运算。<td>a + b = 30<tr><td>-<td>减法，执行减法运算。<td>a - b = -10<tr><td>*<td>乘法，执行乘法运算。<td>a * b = 200<tr><td>/<td>用左操作数除以右操作数。<td>b / a = 2<tr><td>%<td>用左操作数除以右操作数并返回余数。<td>b % a = 0</table><h3>比较运算符</h3><p>a=10,b=20<table><thead><tr><th>符号<th>描述<th>例子<tbody><tr><td>=<td>等于<td>(a = b) = false.<tr><td>&#60><td>不等于<td>(a &#60> b) = true.<tr><td>><td>大于<td>(a > b) = false.<tr><td>&lt;<td>小于<td>(a &#60 b) = true.<tr><td>>=<td>大于等于<td>(a >= b) = false<tr><td>&#60=<td>小于等于<td>(a &#60= b) = true.</table><h3>逻辑运算符</h3><table><thead><tr><th>符号<th>描述<tbody><tr><td>IN<td>为列指定多个可能的值<tr><td>ALL<td>将值与另一个值集中的所有值进行比较<tr><td>ANY<td>根据条件将值与列表中的任何适用值进行比较<tr><td>SOME<td>ANY的别称<tr><td>AND<td>逻辑与运算<tr><td>OR<td>逻辑或运算<tr><td>NOT<td>逻辑非运算<tr><td>EXISTS<td>检查子查询是否至少会返回一行数据<tr><td>BETWEEN<td>在某个范围内<tr><td>LIKE<td>匹配某种模式<tr><td>IS NULL<td>字段值是否为NULL<tr><td>IS NOT NULL<td>字段值是否不为NULL</table><h4>IN、ALL、ANY用法</h4><pre tabindex=0><code><span><span>-- 这里只是条件表达式，一般跟在WHERE后</span></span>
<span><span>&lt;</span><span>比较对象</span><span>></span><span> &lt;</span><span>比较运算符</span><span>></span><span> ANY (</span><span>&lt;</span><span>子查询</span><span>></span><span>)</span></span>
<span><span>&lt;</span><span>比较对象</span><span>></span><span> IN</span><span> (</span><span>&lt;</span><span>子查询</span><span>></span><span>);</span></span>
<span><span>&lt;</span><span>比较对象</span><span>></span><span> &lt;</span><span>比较运算符</span><span>></span><span> SOME (</span><span>&lt;</span><span>子查询</span><span>></span><span>)</span></span>
<span><span>&lt;</span><span>比较对象</span><span>></span><span> &lt;</span><span>比较运算符</span><span>></span><span> ALL (</span><span>&lt;</span><span>子查询</span><span>></span><span>)</span></span>
<span></span></code></pre><p><code>&#60子查询></code> 是一个受限的 SELECT 语句 (不允许有 COMPUTE 子句和 INTO 关键字)。 <code>&#60子查询></code> 可以是值的序列 <code>(值1, 值2, ...)</code> ，也可以是子查询 <code>(SELECT column FROM table)</code>。<p>举例：<pre tabindex=0><code><span><span>age </span><span>&lt;</span><span> ANY (</span><span>SELECT</span><span> age </span><span>FROM</span><span> student)</span></span>
<span><span>age </span><span>IN</span><span> (</span><span>SELECT</span><span> age </span><span>FROM</span><span> student)</span></span>
<span><span>age </span><span>IN</span><span> (</span><span>16</span><span>,</span><span>17</span><span>,</span><span>18</span><span>,</span><span>19</span><span>,</span><span>20</span><span>)</span></span>
<span><span>age </span><span>></span><span> ALL (</span><span>SELECT</span><span> age </span><span>FROM</span><span> student)</span></span>
<span></span></code></pre><h4>EXISTS、BETWEEN、LIKE用法</h4><pre tabindex=0><code><span><span>-- 这里只是条件表达式，一般跟在WHERE后</span></span>
<span><span>EXISTS</span><span> (</span><span>&lt;</span><span>子查询</span><span>></span><span>)</span></span>
<span><span>BETWEEN</span><span> &lt;</span><span>值1</span><span>></span><span> AND</span><span> &lt;</span><span>值2</span><span>></span></span>
<span><span>LIKE</span><span> &lt;</span><span>模式</span><span>></span></span>
<span></span></code></pre><p>使用 <code>LIKE</code> 时的模式有两个通配符（MySQL中）： <code>%</code> ：表示零个，一个或多个字符 <code>_</code> ：表示单个字符<p>举例：<pre tabindex=0><code><span><span>EXISTS</span><span> (</span><span>SELECT</span><span> *</span><span> FROM</span><span> student </span><span>WHERE</span><span> class_id</span><span>=</span><span>class</span><span>.</span><span>id</span><span>)</span></span>
<span><span>BETWEEN</span><span> 1</span><span> AND</span><span> 100</span></span>
<span><span>LIKE</span><span> 'a__d%'</span></span>
<span></span></code></pre><h4>IS NULL、IS NOT NULL用法</h4><pre tabindex=0><code><span><span>-- 这里只是条件表达式，一般跟在WHERE后</span></span>
<span><span>&lt;</span><span>字段</span><span>></span><span> IS</span><span> NULL</span></span>
<span><span>&lt;</span><span>字段</span><span>></span><span> IS NOT NULL</span></span>
<span></span></code></pre><h2>SQL表达式</h2><p>SQL的表达式有以下几种：<ul><li>布尔表达式<li>数值表达式<li>日期表达式</ul><h3>布尔表达式</h3><p>布尔表达式主要跟随在WHERE之后用作条件表达式。<h3>数值表达式</h3><p>数值表达式用来执行数学计算，可以替代&#60值>的位置。 SQL内置部分函数如 <code>AVG() SUM() COUNT()</code> 用于对表或特定表列执行所谓的聚合数据计算。<h3>日期表达式</h3><p>日期表达式返回当前系统日期和时间值：<pre tabindex=0><code><span><span>SELECT</span><span> CURRENT_TIMESTAMP;</span></span>
<span><span>SELECT</span><span> GETDATE</span><span>();</span></span>
<span></span></code></pre><h2>参考</h2><ol><li><a href=https://www.w3cschool.cn/sql/>https://www.w3cschool.cn/sql/</a><li><a href=https://www.liaoxuefeng.com/wiki/1177760294764384>https://www.liaoxuefeng.com/wiki/1177760294764384</a><li><a href=https://www.runoob.com/sql/sql-tutorial.html>https://www.runoob.com/sql/sql-tutorial.html</a><li><a href=http://xuesql.cn/>http://xuesql.cn/</a><li><a href=https://www.yiibai.com/sql/>https://www.yiibai.com/sql/</a><li><a href=https://dev.mysql.com/doc/refman/8.0/en/>https://dev.mysql.com/doc/refman/8.0/en/</a></ol>]]></content:encoded>
            <enclosure url="https://suborbit.net/_astro/cover.BWy95Xnn.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Hello World]]></title>
            <link>https://suborbit.net/posts/hello-world</link>
            <guid>https://suborbit.net/posts/hello-world</guid>
            <pubDate>Fri, 06 Mar 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[This post is originated from here and is used for testing markdown style. This post contains nearly every markdown usage. Make sure all the markdown elements below show up correctly.]]></description>
            <content:encoded><![CDATA[<p>This post is originated from <a href=https://gist.github.com/apackeer/4159268>here</a> and is used for testing markdown style. This post contains nearly every markdown usage. Make sure all the markdown elements below show up correctly.<hr><h2>Headers</h2><pre tabindex=0><code><span><span># H1</span></span>
<span><span>## H2</span></span>
<span><span>### H3</span></span>
<span><span>#### H4</span></span>
<span><span>##### H5</span></span>
<span><span>###### H6</span></span>
<span></span>
<span><span>Alternatively, for H1 and H2, an underline-ish style:</span></span>
<span></span>
<span><span>Alt-H1</span></span>
<span><span>======</span></span>
<span></span>
<span><span>Alt-H2</span></span>
<span><span>------</span></span>
<span></span></code></pre><h1>H1</h1><h2>H2</h2><h3>H3</h3><h4>H4</h4><h5>H5</h5><h6>H6</h6><p>Alternatively, for H1 and H2, an underline-ish style:<h1>Alt-H1</h1><h2>Alt-H2</h2><h2>Emphasis</h2><pre tabindex=0><code><span><span>Emphasis, aka italics, with </span><span>*asterisks*</span><span> or </span><span>_underscores_</span><span>.</span></span>
<span></span>
<span><span>Strong emphasis, aka bold, with </span><span>**asterisks**</span><span> or </span><span>__underscores__</span><span>.</span></span>
<span></span>
<span><span>Combined emphasis with </span><span>**asterisks and </span><span>_underscores_</span><span>**</span><span>.</span></span>
<span></span>
<span><span>Strikethrough uses two tildes. </span><span>~~Scratch this.~~</span></span>
<span></span></code></pre><p>Emphasis, aka italics, with <em>asterisks</em> or <em>underscores</em>.<p>Strong emphasis, aka bold, with <strong>asterisks</strong> or <strong>underscores</strong>.<p>Combined emphasis with <strong>asterisks and <em>underscores</em></strong>.<p>Strikethrough uses two tildes. <del>Scratch this.</del><h2>Lists</h2><pre tabindex=0><code><span><span>1.</span><span> First ordered list item</span></span>
<span><span>2.</span><span> Another item</span></span>
<span><span>  *</span><span> Unordered sub-list.</span></span>
<span><span>1.</span><span> Actual numbers don't matter, just that it's a number</span></span>
<span><span>  1.</span><span> Ordered sub-list</span></span>
<span><span>4.</span><span> And another item.</span></span>
<span></span>
<span><span>   You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown).</span></span>
<span></span>
<span><span>   To have a line break without a paragraph, you will need to use two trailing spaces.  </span></span>
<span><span>   Note that this line is separate, but within the same paragraph.  </span></span>
<span><span>   (This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.)</span></span>
<span></span>
<span><span>*</span><span> Unordered list can use asterisks</span></span>
<span><span>-</span><span> Or minuses</span></span>
<span><span>+</span><span> Or pluses</span></span>
<span><span>-</span><span> Paragraph In unordered list</span></span>
<span></span>
<span><span>  For example like this.</span></span>
<span></span>
<span><span>Common Paragraph with some text.</span></span>
<span><span>And more text.</span></span>
<span></span></code></pre><ol><li>First ordered list item<li>Another item</ol><ul><li>Unordered sub-list.</ul><ol><li><p>Actual numbers don’t matter, just that it’s a number<li><p>Ordered sub-list<li><p>And another item.<p>You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we’ll use three here to also align the raw Markdown).<p>To have a line break without a paragraph, you will need to use two trailing spaces.<br>Note that this line is separate, but within the same paragraph.<br>(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.)</ol><ul><li>Unordered list can use asterisks</ul><ul><li>Or minuses</ul><ul><li>Or pluses</ul><ul><li><p>Paragraph In unordered list<p>For example like this.</ul><p>Common Paragraph with some text. And more text.<h2>Inline HTML</h2><pre tabindex=0><code><span><span>&#60p>To reboot your computer, press &#60kbd>ctrl&#60/kbd>+&#60kbd>alt&#60/kbd>+&#60kbd>del&#60/kbd>.&#60/p></span></span>
<span></span></code></pre><p>To reboot your computer, press <kbd>ctrl</kbd>+<kbd>alt</kbd>+<kbd>del</kbd>.<pre tabindex=0><code><span><span>&#60dl></span></span>
<span><span>    &#60dt>Definition list&#60/dt></span></span>
<span><span>    &#60dd>Is something people use sometimes.&#60/dd></span></span>
<span></span>
<span><span>    &#60dt>Markdown in HTML&#60/dt></span></span>
<span><span>    &#60dd>Does </span><span>*not*</span><span> work </span><span>**very**</span><span> well. Use HTML &#60em>tags&#60/em>.&#60/dd></span></span>
<span><span>&#60/dl></span></span>
<span></span></code></pre><dl><dt>Definition list<dd>Is something people use sometimes.</dd><pre tabindex=0><code><span><span>&#60dt>Markdown in HTML&#60/dt></span></span>
<span><span>&#60dd>Does *not* work **very** well. Use HTML &#60em>tags&#60/em>.&#60/dd></span></span>
<span><span></span></span></code></pre></dl><h2>Links</h2><pre tabindex=0><code><span><span>[</span><span>I'm an inline-style link</span><span>](</span><span>https://www.google.com</span><span>)</span></span>
<span></span>
<span><span>[</span><span>I'm an inline-style link with title</span><span>](</span><span>https://www.google.com</span><span> </span><span>"</span><span>Google's Homepage</span><span>"</span><span>)</span></span>
<span></span>
<span><span>[</span><span>I'm a reference-style link</span><span>][</span><span>Arbitrary case-insensitive reference text</span><span>]</span></span>
<span></span>
<span><span>[</span><span>I'm a relative reference to a repository file</span><span>](</span><span>../blob/master/LICENSE</span><span>)</span></span>
<span></span>
<span><span>[</span><span>You can use numbers for reference-style link definitions</span><span>][</span><span>1</span><span>]</span></span>
<span></span>
<span><span>Or leave it empty and use the [link text itself]</span></span>
<span></span>
<span><span>Some text to show that the reference links can follow later.</span></span>
<span></span>
<span><span>[</span><span>arbitrary case-insensitive reference text</span><span>]: </span><span>https://www.google.com</span></span>
<span><span>[</span><span>1</span><span>]: </span><span>https://www.google.com</span></span>
<span><span>[</span><span>link text itself</span><span>]: </span><span>https://www.google.com</span></span>
<span></span></code></pre><p><a href=https://www.google.com>I’m an inline-style link</a><p><a href=https://www.google.com title="Google&#39s Homepage">I’m an inline-style link with title</a><p><a href=https://www.google.com>I’m a reference-style link</a><p><a href=../blob/master/LICENSE>I’m a relative reference to a repository file</a><p><a href=https://www.google.com>You can use numbers for reference-style link definitions</a><p>Or leave it empty and use the <a href=https://www.google.com>link text itself</a><p>Some text to show that the reference links can follow later.<h2>Images</h2><pre tabindex=0><code><span><span>hover to see the title text:</span></span>
<span></span>
<span><span>Inline-style:</span></span>
<span></span>
<span><span>![</span><span>alt text</span><span>](</span><span>https://picsum.photos/536/354</span><span> </span><span>"</span><span>Logo Title Text 1</span><span>"</span><span>)</span></span>
<span></span>
<span><span>Reference-style:</span></span>
<span><span>![</span><span>alt text</span><span>][</span><span>logo</span><span>]</span></span>
<span></span>
<span><span>[</span><span>logo</span><span>]: </span><span>https://picsum.photos/536/354</span><span> </span><span>"</span><span>Logo Title Text 2</span><span>"</span></span>
<span></span></code></pre><p>hover to see the title text:<p>Inline-style:<p><img alt="alt text"src=https://picsum.photos/536/354 title="Logo Title Text 1"><p>Reference-style: <img alt="alt text"src=https://picsum.photos/536/354 title="Logo Title Text 2"><h2>Code and Syntax Highlighting</h2><p>Inline <code>code</code> has <code>back-ticks around</code> it.<pre tabindex=0><code><span><span>var</span><span> s </span><span>=</span><span> "JavaScript syntax highlighting"</span><span>;</span></span>
<span><span>alert</span><span>(s);</span></span>
<span></span></code></pre><pre tabindex=0><code><span><span>s </span><span>=</span><span> "Python syntax highlighting"</span></span>
<span><span>print</span><span> s</span></span>
<span></span></code></pre><pre tabindex=0><code><span><span>No language indicated, so no syntax highlighting.</span></span>
<span><span>But let's throw in a &#60b>tag&#60/b>.</span></span>
<span><span></span></span></code></pre><h2>Tables</h2><pre tabindex=0><code><span><span>|                |ASCII                          |HTML                         |</span></span>
<span><span>|----------------|-------------------------------|-----------------------------|</span></span>
<span><span>|Single backticks|</span><span>`'Isn't this fun?'`</span><span>            |'Isn't this fun?'            |</span></span>
<span><span>|Quotes          |</span><span>`"Isn't this fun?"`</span><span>            |"Isn't this fun?"            |</span></span>
<span><span>|Dashes          |</span><span>`-- is en-dash, --- is em-dash`</span><span>|-- is en-dash, --- is em-dash|</span></span>
<span></span></code></pre><table><thead><tr><th><th>ASCII<th>HTML<tbody><tr><td>Single backticks<td><code>'Isn't this fun?'</code><td>’Isn’t this fun?‘<tr><td>Quotes<td><code>"Isn't this fun?"</code><td>”Isn’t this fun?”<tr><td>Dashes<td><code>-- is en-dash, --- is em-dash</code><td>— is en-dash, --- is em-dash</table><p>Colons can be used to align columns.<pre tabindex=0><code><span><span>| Tables        | Are           | Cool  |</span></span>
<span><span>| ------------- |:-------------:| -----:|</span></span>
<span><span>| col 3 is      | right-aligned |  |</span></span>
<span><span>| col 2 is      | centered      |    |</span></span>
<span><span>| zebra stripes | are neat      |   </span></span>
<span></span></code></pre><table><thead><tr><th>Tables<th align=center>Are<th align=right>Cool<tbody><tr><td>col 3 is<td align=center>right-aligned<td align=right><tr><td>col 2 is<td align=center>centered<td align=right><tr><td>zebra stripes<td align=center>are neat<td align=right></table><p>The outer pipes (|) are optional, and you don’t need to make the raw Markdown line up prettily. You can also use inline Markdown.<pre tabindex=0><code><span><span>Markdown | Less | Pretty</span></span>
<span><span>--- | --- | ---</span></span>
<span><span>*Still*</span><span> | </span><span>`renders`</span><span> | </span><span>**nicely**</span></span>
<span><span>1 | 2 | 3</span></span>
<span></span></code></pre><table><thead><tr><th>Markdown<th>Less<th>Pretty<tbody><tr><td><em>Still</em><td><code>renders</code><td><strong>nicely</strong><tr><td>1<td>2<td>3</table><blockquote><p>You can find more information about <strong>LaTeX</strong> mathematical expressions <a href=https://math.meta.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference>here</a>.</blockquote><h2>Blockquotes</h2><blockquote><p>Blockquotes are very handy in email to emulate reply text. This line is part of the same quote.</blockquote><p>Quote break.<blockquote><p>This is a very long line that will still be quoted properly when it wraps. Oh boy let’s keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can <em>put</em> <strong>Markdown</strong> into a blockquote.</blockquote><h2>Horizontal Rule</h2><p>Three or more…<pre tabindex=0><code><span><span>---</span></span>
<span></span>
<span><span>Hyphens</span></span>
<span></span>
<span><span>***</span></span>
<span></span>
<span><span>Asterisks</span></span>
<span></span>
<span><span>___</span></span>
<span></span>
<span><span>Underscores</span></span>
<span></span></code></pre><hr><p>Hyphens<hr><p>Asterisks<hr><p>Underscores<h2>Line Breaks</h2><pre tabindex=0><code><span><span>Here's a line for us to start with.</span></span>
<span></span>
<span><span>This line is separated from the one above by two newlines, so it will be a </span><span>*separate paragraph*</span><span>.</span></span>
<span></span>
<span><span>This line is also a separate paragraph, but...</span></span>
<span><span>This line is only separated by a single newline, so it's a separate line in the </span><span>*same paragraph*</span><span>.</span></span>
<span></span></code></pre><p>Here’s a line for us to start with.<p>This line is separated from the one above by two newlines, so it will be a <em>separate paragraph</em>.<p>This line is also a separate paragraph, but… This line is only separated by a single newline, so it’s a separate line in the <em>same paragraph</em>.<hr><pre tabindex=0><code><span><span>This is a regular paragraph.</span></span>
<span></span>
<span><span>&#60table></span></span>
<span><span>    &#60tr></span></span>
<span><span>        &#60td>Foo&#60/td></span></span>
<span><span>    &#60/tr></span></span>
<span><span>&#60/table></span></span>
<span></span>
<span><span>This is another regular paragraph.</span></span>
<span></span></code></pre><p>This is a regular paragraph.<table><tr><td>Foo</table><p>This is another regular paragraph.]]></content:encoded>
        </item>
    </channel>
</rss>